pax_global_header00006660000000000000000000000064140251474340014516gustar00rootroot0000000000000052 comment=7256e6a63bbb5890c8c32051707854e3ace13d03 jgrapht-jgrapht-1.5.1/000077500000000000000000000000001402514743400146165ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/.editorconfig000066400000000000000000000002321402514743400172700ustar00rootroot00000000000000root = true [*] charset = utf-8 ident_style = space insert_final_newline = true trim_trailing_whitespace = true [*.md] trim_trailing_whitespace = false jgrapht-jgrapht-1.5.1/.github/000077500000000000000000000000001402514743400161565ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/.github/ISSUE_TEMPLATE.md000066400000000000000000000002701402514743400206620ustar00rootroot00000000000000 ``` * JGraphT version: * Java version (java -version)/platform: ``` **Issue** **Steps to reproduce (small coding example)** **Expected behaviour** **Other information** jgrapht-jgrapht-1.5.1/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000017151402514743400217630ustar00rootroot00000000000000 ---- - [ ] I read and understood - [ ] I read and understood - [ ] I added [unit tests](https://github.com/jgrapht/jgrapht/wiki/Unit-testing) - [ ] I added [documentation](https://github.com/jgrapht/jgrapht/wiki/How-to-write-documentation) - [ ] I followed the [Coding and Style Conventions](https://github.com/jgrapht/jgrapht/wiki/Coding-and-Style-Conventions) - [ ] I **have not** modified `HISTORY.md` or `CONTRIBUTORS.md` - [ ] I ensured that [the git commit message is a good one](https://github.com/joelparkerhenderson/git_commit_message) jgrapht-jgrapht-1.5.1/.github/workflows/000077500000000000000000000000001402514743400202135ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/.github/workflows/PR-workflow.yaml000066400000000000000000000014241402514743400232710ustar00rootroot00000000000000name: JGrapht Pull Request build on: pull_request: types: [opened, synchronize, reopened] branches: - master jobs: build: name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up JDK 11 uses: actions/setup-java@v1 with: java-version: 11 architecture: x64 - name: Cache Maven packages uses: actions/cache@v2 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - name: Build with Maven run: | set -e mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V && mvn verify -B && mvn javadoc:aggregate && mvn checkstyle:check -P checkstyle jgrapht-jgrapht-1.5.1/.github/workflows/master-workflow.yaml000066400000000000000000000027371402514743400242530ustar00rootroot00000000000000name: JGrapht Master build on: push: branches: - master jobs: build: name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: persist-credentials: false - uses: actions/setup-java@v1 with: java-version: 11 architecture: x64 - name: Cache Maven packages uses: actions/cache@v2 with: path: ~/.m2 key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} restore-keys: ${{ runner.os }}-m2 - name: Build with Maven run: | set -e mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V && mvn verify -B && mvn javadoc:aggregate && mvn checkstyle:check -P checkstyle - uses: actions/setup-node@v2 with: node-version: 14 architecture: x64 - run: npm install -g hercule@5.0.0 - name: Run prepareDocs script run: ./etc/prepareDocs.sh shell: bash - name: Deploy snapshot to Sonatype env: SONATYPE_USER: ${{ secrets.CI_DEPLOY_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.CI_DEPLOY_PASSWORD }} run: mvn deploy -DskipTests=true --settings etc/snapshot-settings.xml shell: bash - name: Publish Github Pages uses: JamesIves/github-pages-deploy-action@3.7.1 with: ACCESS_TOKEN: ${{ secrets.PAGES_TOKEN }} BRANCH: gh-pages FOLDER: docs CLEAN: true jgrapht-jgrapht-1.5.1/.gitignore000066400000000000000000000005661402514743400166150ustar00rootroot00000000000000# default crapfile-ignores *~ *.*bak* *.old* *.log* *_log_* *.bz2 *.gz *.xz *.tar *.zip *.nbm *.orig *.swp* *.*-swp *.db *.jar *Kopie* nbproject/private one-jar target build .cache .settings .project .kdev4 .buildpath .svn .idea/* *.iml out/* **/out/* **/bin/* pom.xml.releaseBackup **/pom.xml.releaseBackup **/dependency-reduced-pom.xml .classpath docs/javadoc* docs/_site jgrapht-jgrapht-1.5.1/.travis.yml.backup000066400000000000000000000012231402514743400201710ustar00rootroot00000000000000language: java dist: trusty jdk: - oraclejdk11 node_js: - "node" before_install: - nvm install 14.4.0 - npm install -g hercule@5.0.0 script: - set -e - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V && mvn verify -B && mvn javadoc:aggregate && mvn checkstyle:check -P checkstyle - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then etc/prepareDocs.sh; fi after_success: - set +e - if [ "$TRAVIS_BRANCH" = "master" ]; then mvn deploy -DskipTests=true --settings etc/snapshot-settings.xml; fi deploy: provider: pages skip-cleanup: true github-token: $GITHUB_TOKEN keep-history: true local-dir: docs on: branch: master jgrapht-jgrapht-1.5.1/CODE_OF_CONDUCT.md000066400000000000000000000076661402514743400174340ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and committers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members * Proactively reading, following, and improving community guidelines Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project committers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project committers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Contribution Model In the JGraphT project, we use the term "committers" rather than "maintainers". This is because other than release management, most project maintenance is done through contributions, rather than through work carried out by a dedicated team. Committers help contributors carry this out through processes such as issue discussions and code review. Committers generally do not even commit their own work directly; instead, they wait for another committer to review and merge their contributions. Understanding and respecting these roles is an important aspect of our code of conduct. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project committers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at admin@jgrapht.org. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project committers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq jgrapht-jgrapht-1.5.1/CONTRIBUTING.md000066400000000000000000000014321402514743400170470ustar00rootroot00000000000000Thanks for your interest in improving JGraphT.:+1::tada: Before preparing your first pull request, please take a look at our [developer guidelines wiki](https://github.com/jgrapht/jgrapht/wiki#developer-pages) page, and in particular the [how to make your first contribution page](https://github.com/jgrapht/jgrapht/wiki/How-to-make-your-first-%28code%29-contribution). The probability that your PR gets accepted increases exponentially when your submission complies with these guidelines. Please do **not** submit PRs that are partially finished: each time you push new commits we get e-mail notifications. If you want feedback or have a dev question, please post an a message to [jgrapht-dev](https://groups.google.com/forum/#!forum/jgrapht-dev) containing a URL to your branch/repository. jgrapht-jgrapht-1.5.1/CONTRIBUTORS.md000066400000000000000000000130731402514743400171010ustar00rootroot00000000000000## Contributors ## Copyright Notice: The JGraphT project source code is a composite of contributions by multiple authors. The copyright for each contribution is owned by the corresponding author. For details, please see [HISTORY.md](HISTORY.md) as well as the complete git history. All authors have agreed to license their contributions to the public as open source under the specific terms noted in [README.md](README.md). JGraphT wouldn't be the library it is today without the source contributions and suggestions made by the authors: - [Barak Naveh](https://github.com/baraknaveh) (project founder) - [John V Sichi](https://github.com/jsichi) (current project administrator) - [Joris Kinable](https://github.com/jkinable) (JGraphtT Project Reviewer/Committer and Release Manager) - [Dimitrios Michail](https://github.com/d-michail) (JGraphT Project Reviewer/Committer) - [Timofey Chudakov](https://github.com/Toptachamann) (JGraphT Project Reviewer/Committer) - [Semen Chudakov](https://github.com/SChudakov) (JGraphT Project Reviewer/Committer) - [Liviu Rau](http://sourceforge.net/users/liviu_aurelian/) - [Nathan Fiedler](http://www.bluemarsh.com/personal/index.html) - [Michael Behrisch](http://sourceforge.net/users/behrisch/) - [Linda Buisman](http://sourceforge.net/users/linda_buisman/) - Erik Postma - Mikael Hansen - Avner Linder - Marden Neubert - [Christian Soltenborn](http://sourceforge.net/users/csoltenborn/) - [Christian Hammer](http://sourceforge.net/users/hammerc/) - Ewgenij Proschak - [Hartmut Benz](http://sourceforge.net/users/ivins/) - [Charles Fry](http://frogcircus.org/) - Guillaume Boulmier - Carl Anderson - Khanh Vu - Aaron Harnly - Welson Sun - Trevor Harmon - David Black-Schaffer - Vinayak Borkar - Andrew Berman - Lucas Scharenbroich - Hookahey - Tim Shearouse - Holger Brandl - Ilya Razenshteyn - Peter Giles - Andrew Newell - Tim Engler - Tom Larkworthy - Soren Davidsen - Andrea Pagani - Tom Conerly - Michele Mancioppi - Adrian Marte - [Assaf Mizrachi](https://github.com/assimiz) - Harshal Vora - Matt Sarjent - Robby McKilliam - Yuriy Nakonechnyy - Andreas Schnaiter - Owen Jacobson - Alejandro R. Lopez del Huerto - Vladimir Kostyukov - Ernst de Ridder - Michal Pasieka - Alexey Kudinkin - Adam Gouge - Nikolay Ognyanov - Graham Hill (AzrgExplorers) - Leo Crawford - Isaac Kleinman - Sebastian Hubenschmid - JeanYves Tinevez - [Oliver Kopp](https://github.com/koppor) - Javier Gutierrez (javierj) - Nicolas Fortin - Peter Goldstein - Rodrigo López Dato - Anders Wallgren - Siarhei - Jan Altenbernd - Andrew Chen - Florian Buenzli - Thomas Tschager - Tomas Hruz - Philipp Hoppen - Chris Wensel - Wil Selwood - Mihhail Verhovtsov - Fabian Späh - Rita Dobler - [Szabolcs Besenyei](https://github.com/besza) - Luiz Kill - Christophe Thiebaud - Jon Robinson - Thomas Breitbart - Sarah Komla-Ebri - Graeme Ahokas - Christoph Zauner - [Andrew Gainer-Dewar](https://github.com/agdphd) - Benedikt Waldvogel - Victor Mikhaylov - Nils Olberg - [Daniel Gomez-Sanchez](https://github.com/magicDGS) - [Skuratovich Sergey](https://github.com/SSNikolaevich) - [Martin Sturm](https://github.com/WorstCase00) - [Patrick Sharp](https://github.com/sharpTrick) - [Piotr Turski](https://github.com/piotrturski) - [Alexandru Văleanu](https://github.com/AlexandruValeanu) - [Davide Cavestro](https://github.com/davidecavestro) - [Mark Raynsford](https://github.com/io7m) - [Mariusz Smykuła](https://github.com/mariuszs) - [Pratik Tibrewal](https://github.com/tibrewalpratik17) - [Chen Kui](https://github.com/Yimismi) - [Konstantinos Karatsenidis](https://github.com/gate2k1) - [Kirill Vishnyakov](https://github.com/LightnessOfBeing) - [Emilio Cruciani](https://github.com/ioemilio) - [Vivek Talreja](https://github.com/Vivek1012) - [Gilles Gosuin](https://github.com/gilles-gosuin) - [Viktor Volkov](https://github.com/chupacabra007) - [Philipp Kaesgen](https://github.com/PhilippKaesgen) - [Lukas Harzenetter](https://github.com/lharzenetter) - [Christoph Grüne](https://github.com/christophgruene) - [Daniel Mock](https://github.com/danielmock) - [Oliver Feith](https://github.com/Watercrystal) - [Abdallah Atouani](https://github.com/AbdallahAt) - [Peter Harman](https://github.com/harmanpa) - [Nikhil Sharma](https://github.com/nks1558) - [Dennis Fischer](https://github.com/pdelvo) - [PHaroZ](https://github.com/PHaroZ) - [simlu](https://github.com/simlu) - [Karri Sai Satish Kumar Reddy](https://github.com/ksskreddy) - [Stephan Schroevers](https://github.com/Stephan202) - [Ned Twigg](https://github.com/nedtwigg) - [Karri Sai Satish Kumar Reddy](https://github.com/ksskreddy) - [Lavish Kothari](https://github.com/LavishKothari) - [Andre Immig](https://github.com/Aimmig) - [Charul Bhanawat](https://github.com/CharulBhanawat13) - [Benjamin Krogh](https://github.com/bkrogh) - [Reynaldo Gil Pons](https://github.com/gilcu3) - [Sean Hudson](https://github.com/shduke) - [Edwin Ouwehand](https://github.com/EdwinOuwehand) - [Amr Alhossary](https://github.com/aalhossary) - [Volkov Viktor](https://github.com/bingo-soft) - [Hannes Wellmann](https://github.com/HannesWell) - [Shevek](https://github.com/shevek) - [Ritik Goyal](https://github.com/rtkg12) - [Johannes M Dieterich](https://github.com/iotamudelta) - [Milan Szoszkiewicz](https://github.com/szoszk) - [Baljit Singh](https://github.com/singhbaljit) - [Sebastiano Vigna](https://github.com/vigna) - [Florentin Dörre](https://github.com/FlorentinD) (If we have missed your name on this list, please email us to get it fixed.) Other people have also helped in different ways: reporting bugs, requesting features, commenting, and by merely asking very good questions. Many thanks to all of you. jgrapht-jgrapht-1.5.1/HISTORY.md000066400000000000000000001601171402514743400163070ustar00rootroot00000000000000# HISTORY # Changes to JGraphT in each version: - **version 1.5.1** (18-Mar-2021) - Prepared release cycle 1.5.1: removed deprecated code, updated dependencies (contributed by Joris Kinable) - Fix non-determinism in `BaseKDisjointShortestPathsAlgorithm` (reported by andreamarotta, contributed by Assaf Mizrachi) - Avoid package self-import in MANIFEST.MF (contributed by Hannes Wellmann) - Fixes issue with reverse path weights in `DijkstraManyToManyShortestPath` (contributed by Semen Chudakov) - Added `RescaleLayoutAlgorithm2D` - layout model rescaling algorithm (contributed by Dimitrios Michail) - Bug fix by rewriting algorithmic part of `NaiveLCAFinder` (contributed by Timofey Chudakov) - Added Boykov-Kolmogorov maximum flow algorithm for computer-vision related flow networks (contributed by Timofey Chudakov) - Added `TransitNodeRoutingShortestPathAlgorithm` (contributed by Semen Chudakov) - Added Zachary's karate club named graph (contributed by Dimitrios Michail) - Simplified graph creation in tests (contributed by Timofey Chudakov) - Add `RandomWalkVertexIterator`, replacing `RandomWalkIterator` (contributed by Dimitrios Michail) - Fixed identically-positioned and isolated vertices in`FRLayoutAlgorithm2D` (reported by rlbns, contributed by Dimitrios Michail) - Added Zhang-Shasha tree edit distance (contributed by Semen Chudakov) - `GraphMetrics.naiveCountTriangles` now returns the correct number of triangles when multiple edges are present (reported by FlorentinD, contributed by Dimitrios Michail) - Fixed JSON importer issue with negative integer weights (see #982) (reported by xianfuzheng, contributed by Dimitrios Michail) - Enabled checkstyle for test files (contributed by Szabolcs Besenyei) - Fixed hashCode/equals on weighted graphs (reported by Sebastiano Vigna, contributed by Dimitrios Michail) - Changed hashCode/equals to ignore edge direction on undirected graphs, and made source/target assignment harmonious for EndpointPair in Guava adapter (reported by Sebastiano Vigna, contributed by Dimitrios Michail) - Bring back importer support for supplying attributes at the point where vertex/edge instantiation occurs (reported by Sebastian Goeb, contributed by Dimitrios Michail) - Added `GraphIterables` interface extension for big graph support (suggested by Sebastiano Vigna, contributed by Dimitrios Michail) - Fixed label propagation clustering bug with isolated vertices (contributed by Dimitrios Michail) - Added Bipartite layout drawing algorithm (contributed by Dimitrios Michail) - Fixed addEdge in `AbstractGraphBuilder` (contributed by Baljit Singh) - Added code of conduct (contributed by John Sichi) - Added documentation for graph thread safety and updated graph equality (contributed by John Sichi) - Enhanced and refactored AlphaCentrality to Katz- and Eigenvector-Centrality (contributed by Sebastiano Vigna) - Added overflow strategy in `BetweennessCentrality` (contributed by Dimitrios Michail) - Replaced `VertexDegreeComparator` and `GeneralVertexDegreeComparator` objects with lambda (contributed by Hannes Wellmann) - Improved performance of the weighted `PageRank` algorithm by caching graph adjacency lists (contributed by Florentin Dörre) - Optimized integer to vertex mappings in several algorithms (contributed by Hannes Wellmann) - Added a collection of local algorithms for link prediction (contributed by Dimitrios Michail) - Fixed some linty Integer comparisons (contributed by Dimitrios Michail) - Added `ThreadPoolExecutor` parameter to all parallel algorithms (contributed by Semen Chudakov) - Fixed bug in `DeltaSteppingShortestPath` (see #994) (reported by Andreas Hartung, contributed by Semen Chudakov) - Added NETGEN-style problems generator (contributed by Timofey Chudakov) - Added algorithm for minimum cycle mean (contributed by Semen Chudakov) - Replace Travis CI with Github Actions (contributed by Szabolcs Besenyei) - Added WebGraph adapter (contributed by Sebastiano Vigna with assistance from Dimitrios Michail) - Added support for vertex provider with attributes in `JSONImporter` (contributed by Dimitrios Michail) - Added edge betweenness centrality algorithm (contributed by Dimitrios Michail) - Added Girvan-Newman community detection algorithm (contributed by Dimitrios Michail) - Refactored sparse graphs to allow different backend implementations (contributed by Dimitrios Michail) - Improved `SupplierUtil` and added tests (contributed by Hannes Wellmann) - Added succinct graph implementations using sux4j (contributed by Sebastiano Vigna) - Ensured predictable vertex order in `MaximumCardinalityIterator` (contributed by Dimitrios Michail) - Used -noimport to simplify package self-import exclusion (contributed by Hannes Wellmann) - Reduced intrusive edge map lookups and prevented invalid reuse (contributed by Hannes Wellmann) - Moved exceptions to be public at top level (contributed by Hannes Wellmann) - Improvements to Hamiltonian Cycle algorithms (contributed by Hannes Wellmann) - Improvements to strong connectivity algorithms (contributed by Hannes Wellmann) - **version 1.5.0** (14-Jun-2020) - Prepared release cycle 1.4.1: removed deprecated code, updated dependencies, upgraded java to version 11 (contributed by Joris Kinable) - Bring back vertex factory in importers (contributed by Dimitrios Michail) - Added `LabelPropagationClustering` algorithm (contributed by Dimitrios Michail) - Make sure `addVertex()` fires listener event (spotted by akirschbaum, contributed by Dimitrios Michail) - Change queue implementation from LinkedList to ArrayDeque (spotted by shevek, contributed by Ritik Goyal) - Added TSPLIB95 graph and tour importer (contributed by Hannes Wellmann) - Fixed issue with inconsistent graph after duplicate edge addition (spotted by Greg Gibeling, contributed by Dimitrios Michail) - Fixed issue with failing `DirectedScaleFreeGraphGenerator` by fixing the tests seed (contributed by Timofey Chudakov) - Fixed corner case issue with `BoyerMyrvoldPlanarityInspector` (spotted by Malcolm Deck, contributed by Timofey Chudakov) - Added tree dynamic connectivity using the Euler tour data structure (contributed by Timofey Chudakov) - `GmlExporter` support for custom attributes (contributed by Dimitrios Michail) - Invoking `addVertex()` on an `AsUnmodifiableGraph` now throws an `UnsupportedOperationException` (contributed by Dimitrios Michail) - Added `PathValidator` functionality to `YenKShortestPath` (contributed by Semen Chudakov) - Optimize debugging in VF2 (contributed by Johannes M Dieterich) - Fix calculation of matching weight in `MaximumWeightBipartiteMatching` (contributed by Dimitrios Michail) - Add GEXF import/export (contributed by Dimitrios Michail) - Change cache array entries from Boolean to byte in `GraphOrdering` (contributed by Johannes M Dieterich) - Cache edge references in `GraphOrdering` (contributed by Johannes M Dieterich) - Fix to make JSON import/export symmetric (contributed by Dimitrios Michail) - Added support for exporting identifiers in DotExporter (contributed by Milan Szoszkiewicz) - Fixed cycle order in `HawickJamesSimpleCycles` (contributed by Dimitrios Michail) - Fixed unreported edge attributes in `SimpleGraphMLImporter` (contributed by Dimitrios Michail) - Optimized `isFeasiblePair` and finalized fields (contributed by Johannes M Dieterich) - Unified TSP algorithms internal implementation (contributed by Hannes Wellmann) - Deprecated `KShortestSimplePaths` due to bug reported in #892 (contributed by Semen Chudakov) - Clean up some docs and tests (contributed by Oliver Kopp) - Add .editorconfig (contributed by Oliver Kopp) - Change assert to mandatory enforcement in `AsWeightedGraph` (contributed by Dimitrios Michail) - Streamline constructors in `VertexToIntegerMapping` (contributed by Hannes Wellmann) - Add explicit module definitions (contributed by Dimitrios Michail) - Enforce warnings (contributed by John Sichi after nudge from Oliver Kopp) - Prevent tabs in XML files (contributed by John Sichi) - **version 1.4.0** (21-Feb-2020): - Prepared release cycle 1.3.2: removed deprecated code, updated dependencies, etc (contributed by Joris Kinable) - Format code in parallel (contributed by Joris Kinable) - Fix edge reuse bug in KDisjointShortestPaths implementations (contributed by Benjamin Krogh) - Updated Yen's algorithm to operate on pseudo graphs correctly (contributed by Semen Chudakov) - Updated Guava to version 28.0 (contributed by John Sichi) - Fixed bug in `BiconnectivityInspector` which would occasionally return an incorrect set of biconnected components (contributed by Reynaldo Gil Pons) - Allow edge selection to be overridden in `CrossComponentIterator` (contributed by Sean Hudson) - Reuse traversal listener implementation in tests (contributed by Timofey Chudakov) - Fixed bug in `BetweennessCentrality` which occasionally returned in incorrect centrality score for vertices in weighted graphs (contributed by Gil Pons) - Added path length limit to `HawickJamesSimpleCycles` (contributed by Edwin Ouwehand) - Added links in Guava adapater package-info (contributed by John Sichi) - Added Boyer-Myrvold planarity testing algorithm (contributed by Timofey Chudakov) - Added contraction hierarchy precomputation algorithm (contributed by Semen Chudakov) - Enhanced `IntegerComponentNameProvider` to take arbitrary base (contributed by Amr Alhossary) - Fixed a bug in the `GraphWalk.equals()` method which caused a NullpointerException when invoked on an empty walk (contributed by Volkov Viktor) - Added k-spanning-tree clustering algorithm (contributed by Dimitrios Michail) - Added directed scale-free graph generator (contributed by Amr Alhossary) - Enhanced `CompleteGraphGenerator` and `CompleteBipartiteGraphGenerator` to generate edges between existing vertices (contributed by Joris Kinable) - Added efficient bidirectional Dijkstra implementation based on contraction hierarchy (contributed by Semen Chudakov) - Added sparse graphs and event-based importers, and improved numerous algorithms (contributed by Dimitrios Michail) - Added `CollectionUtil` for preallocating maps and sets correctly (contributed by Hannes Wellmann) - Added efficient many-to-many shortest path algorithm based on contraction hierarchy (contributed by Semen Chudakov) - Added greedy, nearest-insertion, nearest-neighbor heuristic for TSP and improved stability in two-opt heuristic (contributed by Peter Harman) - Enhanced `DoublyLinkedList` by implementing the `List` and `Deque` interfaces (contributed by Hannes Wellmann) - Added dedicated class for `ContractionHierarchy` (contributed by Semen Chudakov) - Use try with resources in default implementations of GraphImporter and GraphExporter (contributed by Hannes Wellmann) - Fixed naive lca bug and code cleanup (reported by Shinpei Hayashi, contributed by Timofey Chudakov) - Enhanced Javadoc generation by adding compatibility with newer JDKs (contributed by Dimitrios Michail) - Reflection speedup (suggested by shevek, contributed by Dimitrios Michail) - Added a graph drawing component, including various layout algorithms incl., random, circular, and tree layouts, the Fruchterman and Reingold Force-Directed Placement algorithm, and a variation of the latter augmented with the Barnes-Hut indexing technique (contributed by Dimitrios Michail) - **version 1.3.1** (3-Jun-2019): - Prepared release cycle 1.3.1: removed deprecated code, updated dependencies, etc (contributed by Joris Kinable) - Added new logo (from 99designs, with site additions by John Sichi and Joris Kinable) - Added new website (contributed by John Sichi) - Converted all methods and fields to protected in `HierholzerEulerianCycle` (contributed by simlu) - Optimized specifics hash lookups (contributed by Dimitrios Michail) - Fixed mobile website menu (contributed by Karri Sai Satish Kumar Reddy) - Upgraded Antlr version to 4.7.2 and jheaps to 0.10 (contributed by Dimitrios Michail) - Replaced URL with URI in HelloJGraphT example (contributed by Stephan Schroevers) - Deprecated `FibonacciHeap` (contributed by Timofey Chudakov) - Replaced usage of `Graph.{vertex,edge}Set().contains()` by `Graph.contains{Vertex,Edge}()` (contributed by Ned Twigg) - Updated examples with Guava adapter example (contributed by John Sichi) - Added Warnsdorff rule heuristic and Parberry's algorithm for closed knight's tour problem to demo package (contributed by Kirill Vishnyakov) - Added min weight, max weight and max weight perfect matching algorithms (contributed by Timofey Chudakov) - Fixed bug where DOTImporter throws NullPointerException when trying to parse a vertex without attributes (contributed by Dimitrios Michail) - Added BFS as a shortest path algorithm (contributed by Karri Sai Satish Kumar Reddy) - Added concurrent implementation of the delta-stepping shortest path algorithm (contributed by Semen Chudakov) - Added support for the capacitated minimum spanning tree (CMST) problem (contributed by Christoph Grüne) - Made `GraphMLImporter` ordering deterministic (contributed by Dimitrios Michail) - Added bidirectional A-star (contributed by Semen Chudakov) - Added Yen's k shortest loopless paths algorithm (contributed by Semen Chudakov) - Defer graph vertex iterator initialization in CrossComponentIterator (contributed by John Sichi) - Updated jgraphx version to 3.9.8.1 (contributed by John Sichi) - Added JSON exporter and importer (contributed by Dimitrios Michail) - Enhanced `DirectedAcyclicGraph` to support multiple edges (contributed by Dimitrios Michail based on a suggestion by Sarat Chandra Balla) - Refactored `SerializationTestUtils` and made it generic (contributed by Lavish Kothari) - Added more serialization test coverage (contributed by Lavish Kothari) - Added Goldberg's algorithms for the calculation of maximum density subgraphs (contributed by Andre Immig) - Optimize UnmodifiableUnionSet to lazily read live sizes from underlying sets (contributed by John Sichi) - Added Eppsteins k-shortest paths algorithm (contributed by Semen Chudakov) - Added serialization test for `AsGraphUnion` and made `WeightCombiner` default implementations serializable (contributed by Charul Bhanawat) - **version 1.3.0** (12-Nov-2018): - Prepared release cycle 1.2.1: removed deprecated code, updated dependencies, etc (contributed by Joris Kinable) - Restored optional tests for `BergeGraphInspector` (contributed by Philipp Kaesgen) - Use POSIX tar format for assembly (contributed by Mark Raynsford) - Moved BrownBacktrackingColoring out of experimental, fixed bugs and wrote tests (contributed by Joris Kinable) - Removed code not dual licensed under EPL-1.0 and LGPL-2.1-or-later: `AsUnweightedGraph` and `AsWeightedGraph` are gone. (supported by Robert Höttger and Oliver Kopp) - Added forest generator based on the Barabasi-Albert model (contributed by Alexandru Văleanu) - Added new implementation of `AsUnweightedGraph` and `AsWeightedGraph` (contributed by Lukas Harzenetter) - Added pull request template (contributed by Oliver Kopp) - Added `GraphSpecificsStrategy` and use the same edge set factory consistently (contributed by Dimitrios Michail) - Added user overview doc (contributed by John Sichi) - Clarified definition of `SimpleGraph` (contributed by Joris Kinable) - Added O(m^1.5) algorithm for counting triangles in undirected graphs (contributed by Alexandru Văleanu) - Calculate actual path weight in `AllDirectedPaths` (contributed by Andrew Gainer-Dewar) - Refactored vertex cover tests (contributed by Alexandru Văleanu) - Added `SimpleGraphMLImporter` for faster parsing (contributed by Dimitrios Michail) - Increased numeric precision in PushRelabelMFImpl (contributed by Alexandru Văleanu) - Added ColorRefinement and ColorRefinementIsomorphismInspector (contributed by Christoph Grüne, Daniel Mock, Oliver Feith and Abdallah Atouani) - Improved efficiency of `BhandariKDisjointShortestPaths` (contributed by Assaf Mizrachi) - Added `UnmodifiableUnionSet` to optimize `AsGraphUnion` (contributed by Dimitrios Michail) - Made `GraphWalk` serializable (contributed by Alexandru Văleanu) - Optimize `JohnsonShortestPaths` space usage (contributed by Dimitrios Michail) - Heuristics for `FloydWarshallShortestPaths` (suggested by shevek, contributed by Dimitrios Michail) - Added new `TreeToPathDecompositionAlgorithm` interface and implementation `HeavyPathDecomposition` (contributed by Alexandru Văleanu) - Removed recursion from `FibonacciHeap` (contributed by Timofey Chudakov) - Added `PruferTreeGenerator` for generating trees based on Prüfer sequences (contributed by Alexandru Văleanu) - Added `VertexToIntegerMapping` utility class (contributed by Alexandru Văleanu) - Handle maxLength=0 case in AllDirectedPaths (reported by Nikolas Havrikov, contributed by Andrew Gainer-Dewar) - Added `SuurballeKDisjointShortestPaths` (contributed by Assaf Mizrachi) - Make AsWeightedGraph propagate weight changes by default when backing graph is weighted (contributed by John Sichi) - Fix assumptions about SAX `characters()` method calls in GraphML importers (contributed by Dimitrios Michail) - Throw exception from no-arg `addVertex` when duplicate vertex generated (contributed by Dimitrios Michail) - Replace `GenericFibonacciHeap` with dependency on jheaps library (contributed by Dimitrios Michail) - Added `DulmageMendelsohnDecomposition` (contributed by Peter Harman) - Package one bundle jar instead of multiple uber jars (contributed by Dimitrios Michail) - Removed touchgraph support and corresponding module jgrapht-touchgraph (contributed by John Sichi) - Added `ClusteringCoefficient` to compute the local and global clustering coefficient of a graph (contributed by Alexandru Văleanu) - Refactored LCA interface, reimplemented Tarjan's algorithm and added HeavyPathLCAFinder, BinaryLiftingLCAFinder, EulerTourRMQLCAFinder (contributed by Alexandru Văleanu) - Added jgrapht-opt module with fastutil graph implementation (contributed by Dimitrios Michail) - Added negative weight cycle reporting in Bellman-Ford (contributed by Dimitrios Michail in response to proposal from Miron Balcerzak) - Added `KolmogorovMinimumWeightPerfectMatching` (contributed by Timofey Chudakov) - Added graph implementation specific for integer vertices and fastutil map to jgrapht-opt module (contributed by Dimitrios Michail) - Added Christofides algorithm for computing 3/2 approximate TSP solutions (contributed by Timofey Chudakov) - Fixed bug in HeldKarpTSP (reported by Timofey Chudako, contributed by Alexandru Văleanu) - Addded `PartitioningAlgorithm` interface and `BipartitePartitioning` implementation for recognizing bipartite graphs (contributed by Alexandru Văleanu) - Fixed bug in `GraphTests.isStronglyConnected`: undirected graphs are now correctly identified as strongly connected whenever the graph is connected (reported by Joris Kinable, contributed by Dimitrios Michail) - Upgraded EPL to v2.0, copyright header cleanup, removed @since tag (contributed by John Sichi) - Use checkstyle to enforce correct file headers (contributed by John Sichi) - Added `ChinesePostman` (contributed by Joris Kinable) - Added `LineGraphConverter` (contributed by Joris Kinable and Nikhil Sharma) - Refactoring for color refinement (contributed by Dimitrios Michail) - Added implementation for minimum cost flow problems through the Successive Shortest Path algorithm with capacity scaling (contributed by Timofey Chudakov) - Updated library dependencies (contributed by Joris Kinable) - Added exporter for the Lemon (LGF) format (contributed by Dimitrios Michail) - Added support for using an edge function in AsWeightedGraph (contributed by Joris Kinable) - Fixed bug in ColorRefinementIsomorphismInspector which required a disjoint graph union (contributed by Christoph Grüne and Dennis Fischer) - Bug fix in the Watts-Strogatz generator which caused a null pointer exception when the graph vertices were any type except integers (contributed by Dimitrios Michail) - Added support for edge weights in CSV export/import (contributed by Dimitrios Michail) - Added support for html attributes and labels in DOTExporter (contributed by PHaroZ) - Unified flow interfaces (contributed by Joris Kinable) - Optimizations for Edmonds maximum cardinality matchings (contributed by Dimitrios Michail) - **version 1.2.0** (16-May-2018): - Prepared release cycle 1.1.1: removed deprecated code, updated dependencies, etc (contributed by Joris Kinable) - Updated demos (contributed by Dimitrios Michail) - Added assertions to NeighborCache (contributed by Joris Kinable) - Upgraded Antlr version to 4.7 (contributed by Dimitrios Michail) - Rewrote `MaximumWeightBipartiteMatching` with exact arithmetic, introducing a `GenericFibonacciHeap` (contributed by Dimitrios Michail) - Updated jmh to jdk9 compatible version; updated xmlunit to 2.x (contributed by Dimitrios Michail) - Fixed FastLookup specifics to init edge container capacity to 1 (suggested by shevek, contributed by Joris Kinable) - Made `IntrusiveEdgesSpecifics` interface public and optimized add call sequence (suggested by shevek, contributed by Dimitrios Michail) - Fixed deprecation of Class.newInstance for Java 9; cleaned up GraphTests (contributed by Dimitrios Michail) - Fixed PathValidator interface to use GraphPath (contributed by Assaf Mizrachi) - Added Held-Karp dynamic programming algorithm for TSP (contributed by Alexandru Văleanu) - Added fundamental cycle basis implementations (contributed by Dimitrios Michail) - Added `BetweennessCentrality` scoring algorithm (contributed by Assaf Mizrachi) - Automatically publish snapshots after successful Travis CI builds (contributed by Davide Cavestro) - Implemented method findLcas() in NaiveLcaFinder (contributed by Alexandru Văleanu) - Added `MultiObjectiveShortestPathAlgorithm` interface and first implementation `MartinShortestPath` (contributed by Dimitrios Michail) - Added `TwoOptHeuristicTSP` (contributed by Dimitrios Michail) - Fixed bug in `JohnsonSimpleCycles` with custom edge type (spotted by fredshevek, fix contributed by Dimitrios Michail) - Added Automatic-Module Names to the various jgrapht modules to support modularization in JDK 9 (contributed by Mark Raynsford) - Added `GraphTypeBuilder` (contributed by Dimitrios Michail) - Reimplemented BiconnectivityInspector with additional functionality such as computing bridges and articulation points. BiconnectivityInspector now works on multiple graph types (contributed by Joris Kinable) - Revised BlockCutpointGraph and added additional tests (contributed by Joris Kinable) - Removed old JUnit 3 dependencies (contributed by Joris Kinable) - Fixed bug with maxPathLength equal to 1 in AllDirectedPaths (contributed by Andrew Gainer-Dewar) - Allow digits as non-leading character in DOTUtils#isValidID (contributed by Mariusz Smykuła) - Escape quotes in identifiers in `DOTExporter` (contributed by Mariusz Smykuła) - Added `AlphaCentrality` (contributed by Pratik Tibrewal) - Fixed bug in AbstractBaseGraph where degreeOf() method would create vertex instead of throwing an exception (spotted and fixed by Chen Kui) - Added Folkman, Diamond, Tietze, Pappus and Tutte named graphs (contributed by Pratik Tibrewal) - Added automorphism count verification to NamedGraphGeneratorTest (contributed by Pratik Tibrewal) - Removed old JGraph dependency, updated JGraphX to version 3.4.1.3, minor revision of `JGraphXAdapterDemo` (contributed by John Sichi) - Added `ChordalityInspector`, `LexBreadthFirstIterator`, and `MaximumCardinalityIterator` (contributed by Timofey Chudakov) - Removed redundant parameter from `TypeUtil.uncheckedCast` method (contributed by Konstantinos Karatsenidis) - Removed recursion from `NaiveLcaFinder.findLca` method (contributed by Konstantinos Karatsenidis) - Added `AsSynchronizedGraph` in new package `org.jgrapht.graph.concurrent` (contributed by Chen Kui) - Fixed bug with `ClosestFirstIterator` when given multiple start vertices (repro by shevek, fixed by John Sichi) - Added method `GraphMeasurer.getGraphPseudoPeriphery` to compute the Pseudo-Periphery of a graph (contributed by Alexandru Văleanu) - Added `PalmerHamiltonianCycle` which implements Palmer's exact algorithm to find Hamiltonian Cycles in undirected graphs. Added new interface `HamiltonianCycleAlgorithm`. (contributed by Alexandru Văleanu) - Removed lazy instantiation of containers in edge specifics (contributed by Dimitrios Michail) - Added `DinicMFImpl` which implements Dinic's maximum flow algorithm (contributed by Kirill Vishnyakov) - Optimized `PushRelabelMFImpl` implementation which drastically improves max flow computations on certain graphs. (contributed by Alexandru Văleanu) - Added `RandomRegularGraphGenerator` (contributed by Emilio Cruciani) - Reimplemented significantly faster version of Prim's minimum spanning tree algorithm using a FibonacciHeap (suggested by Joris Kinable, contributed by Alexandru Văleanu) - Added new jgrapht-guava module containing adapters for package com.google.common.graph (contributed by Dimitrios Michail) - Demo classes listed on our wiki page are now also included in our demo package (contributed by Vivek Talreja) - Added missing math markup to javadoc in all classes (contributed by Kirill Vishnyakov) - Expose lock as public for `AsSynchronizedGraph`, and add copyless access mode (contributed by John Sichi) - Handle nested structures in `GmlImporter` (suggested by Philippe Marchesseault, contributed by Dimitrios Michail) - Replaced StringBuffer with StringBuilder (contributed by John Sichi) - Added search tree query methods to `BreadthFirstIterator` (contributed by Joris Kinable) - Improved documentation of VF2 subgraph isomorphism algorithm (contributed by John Sichi) - Removed recursion from DAG forward and backwards DFS methods (contributed by Gilles Gosuin) - Implemented performance improvements for `GnmRandomGraphGenerator` and `GnpRandomGraphGenerator` (suggested by @Shevek, contributed by Dimitrios Michail) - Added `WeakChordalityInspector` to test whether a graph is weakly chordal (contributed by Timofey Chudakov) - Fixed typo in `TreeSingleSourcePathsImpl` (contributed by Viktor Volkov) - Deprecated `EdgeFactory` in favor of `Supplier`, and added supplier support for vertices as well (contributed by Dimitrios Michail) - Separate fast and slow tests, via mvn test vs verify (contributed by Dimitrios Michail) - Added `ChordalGraphMinimalVertexSeparatorFinder` for the detection of minimal vertex separators in chordal graphs (contributed by Timofey Chudakov) - Added suites for fast tests, integration tests and performance tests (contributed by John Sichi) - Refactored `ChordalityInspector` and revised several interfaces (vertex cover, independent set, clique, etc) (contributed by Joris Kinable) - Minor improvements to `DOTExporter` (contributed by Dimitrios Michail) - Added graph listener event for edge weight update (contributed by Dimitrios Michail) - Added Planted Partition Graph Generator `PlantedPartitionGraphGenerator` (contributed by Emilio Cruciani) - Added `BergeGraphInspector` which checks whether a graph is perfect (contributed by Philipp Kaesgen) - Added Bhandari K-disjoint shortest paths implementation `BhandariKDisjointShortestPaths` (contributed by Assaf Mizrachi) - **version 1.1.0** (13-Nov-2017): - Added ID descriptor to maven-assembly-plugin configuration to prevent a 'Assembly is incorrectly configured' error being thrown (contributed by Joris Kinable) - Deleted all previously deprecated methods and general cleanup (contributed by Joris Kinable) - Moved all importers/exporters from org.jgrapht.ext to org.jgrapht.io. This change allows users to use importers/exporters without the dependency on the various visualization libraries. (contributed by Dimitrios Michail) - Added vertex coloring interface `VertexColoringAlgorithm`, as well as several greedy graph coloring algorithms (`LargestDegreeFirstColoring`, `RandomGreedyColoring`, `SaturationDegreeColoring`, `SmallestDegreeLastColoring`). The former `ChromaticNumber` class is now deprecated, as well as some related classes in the experimental package. (contributed by Dimitrios Michail) - Extended the network analysis algorithms by adding closeness centrality computation (contributed by Dimitrios Michail) - Added interface `StrongConnectivityAlgorithm`; Extracted common code of strong connectivity inspectors to abstract base class; added method which computes the graph condensation. (contributed by Dimitrios Michail) - Added interface `TSPAlgorithm`, Added 2-approximation algorithm for metric TSP `TwoApproxMetricTSP`. The old implementation `HamiltonianCycle` is now deprecated due to its reduced efficiency. (contributed by Dimitrios Michail) - Added a new, scalable `DOTImporter` which handles the full DOT specification including XML string identifiers. A dependency on the commons-lang3 package has been added to handle escaping/unescaping of strings efficiently; Updated antlr version to 4.6. (contributed by Dimitrios Michail) - Added Johnson's all-pairs-shortest paths algorithm `JohnsonShortestPaths`; Simplified Bellman-Ford implementation and added support for negative cycle detection; added `AsWeightedUndirectedGraph` and `AsUnweightedUndirectedGraph`; Constructors of `UndirectedGraphUnion` are now public. (contributed by Dimitrios Michail) - Refactored `DirectedAcyclicGraph`, moved it out of the experimental package, and refactored its perfomance tests using jmh; Deleted `GraphSquare` from experimental (inefficient implementation) (contributed by Dimitrios Michail) - Added Watts-Strogatz small-world graph generator, Kleinberg's small-world graph generator, Barabasi-Albert graph generator, Linearized Chord Diagram GraphGenerator, AliasMethodSampler utility class with Vose's implementation; Refactored ScaleFreeGraphGenerator (constructor with random number generator added); Refactored CompleteGraphGenerator to not assume a specific graph type (simple, etc). (contributed by Dimitrios Michail) - Deleted JUnit test suites. (contributed by Joris Kinable) - Added interface `MaximalCliqueEnumerationAlgorithm`; Refactored and added timeout in BronKerbosch clique enumeration; Added pivot variant of Bron-Kerbosch; Added Bron-Kerbosch variant with pivoting and degeneracy ordering; Added Coreness vertex scorer; Added Degeneracy graph iterator; Added performance test for maximal clique enumeration algorithms (contributed by Dimitrios Michail) - Deprecated `DirectedGraph`, `UndirectedGraph`, and `WeightedGraph`, moving their methods up to the base `Graph` interface; added `GraphType` metadata; made usage of DefaultWeightedEdge optional for weighted graphs (contributed by Dimitrios Michail) - Refactored traversal iterators (contributed by Dimitrios Michail) - Backport to support build using Android SDK 24 (contributed by Dimitrios Michail) - Added support for attributes in GmlImporter; extracted common code from importers and exporters into abstract base classes to avoid code duplication. (contributed by Dimitrios Michail) - Fixed issue where HierholzerEulerianCycle would sometimes set the wrong startVertex (reported by Frank Gevaerts, contributed by Dimitrios Michail) - Revised `GraphWalk`; added additional input checks, additional functionality such as reverse and concat, added verification and factory methods. (contributed by Joris Kinable) - Made several algorithm return objects iterable. (contributed by Joris Kinable) - Support latex math notation in Javadoc (contributed by Joris Kinable) - Allow CrossComponentIterator to start from a collection of startVertices (contributed by Patrick Sharp) - New implementation of Edmonds' maximum cardinality matching algorithm for general graphs. Added certifier for maximum cardinality by computing a Edmonds-Gallai decomposition. Reimplemented Hopcroft-Karp's algorithm for bipartite graphs. Added greedy algorithm for maximum cardinality case and enhanced greedy algorithm for the weighted case. Extended UnionFind with additional functionality. (contributed by Joris Kinable) - Optimize map operations in FastLookupSpecifics (suggested by shevek, fix contributed by Joris Kinable) - Added importers and exporters for the graph6 (g6) and sparse6(s6) graph format (contributed by Joris Kinable) - Added graph generator for Generalized Petersen graphs (contributed by Joris Kinable) - Added graph generator for Windmill, Dutch Windmill and Friendship graphs (contributed by Joris Kinable) - Added collection of 31 commonly used named graphs (contributed by Joris Kinable) - Added GraphMeasurer class to compute graph diameter, radius, vertex eccentricity, graph center and graph periphery (contributed by Joris Kinable) - Added GraphMetrics which computes general graph metrics; added method to compute the girth of a graph (contributed by Joris Kinable) - Added GraphTests: isCubic, isOverfull, isForest, isSplit (contributed by Joris Kinable) - Added basic support for graph attributes in DOTExporter (contributed by Dimitrios Michail) - Added a generator which generates the complement graph of a given input graph (contributed by Joris Kinable) - Performance improvement for Johnson's algorithm when a directed graph has no negative edge weights (contributed by Joris Kinable) - Fix `BellmanFordShortestPath` to return null instead of empty path (contributed by Dimitrios Michail) - Added type information in attributes for graph importers/exporters (contributed by Dimitrios Michail, per suggestion from Dimitrij Drus) - Changed `UnionFind` from recursive to iterative (contributed by Piotr Turski) - New `NeighborCache` replaces both `NeighborIndex` and `DirectedNeighborIndex`; this new class supports both directed and undirected graphs (contributed by Szabolcs Besenyei) - Fixed bug in `PushRelabelMFImpl`: passing an object as parameter to `calculateMaxFlow` which is equal but not identical to the corresponding node in the graph would cause a Nullpointer exception. - **version 1.0.1** (16-Jan-2017): - Deleted all previously deprecated methods (cleanup contributed by Joris Kinable and Dimitrios Michail) - Cleanup of main pom.xml; Added `CONTRIBUTING.md` (contributed by John Sichi) - Added Checkstyle plugin and rules; they are automatically executed by Travis (contributed by Dimitrios Michail) - Unified graph export name providers using a common interface (contributed by Dimitrios Michail) - Fix `GnmRandomGraphGenerator` bug in computation of maximum number of edges (contributed by Dimitrios Michail) - Added new demo class `GraphMLDemo` to demonstrate importing and exporting graphs in the GraphML format (contributed by Dimitrios Michail) - Added project badges (contributed by Daniel Gómez-Sánchez) - Hide autogenerated classes of antlr (contributed by Dimitrios Michail) - DOT-language import/export changes (contributed by Daniel Gómez-Sánchez) - Clean up tests and warnings (contributed by Dimitrios Michail) - Fixed GraphML importer xsd resolution bug (contributed by Dimitrios Michail, spotted by Peter Manning Jr.) - Replaced VertexPair/UnorderedVertexPair with Pair/UnorderedPair (contributed by Dimitrios Michail) - Clean up AbstractBaseGraph/Specifics dependencies (contributed by Dimitrios Michail) - `GraphTests` has been reworked, extended with additional functionality, and moved from experimental to the core package (contributed by Dimitrios Michail and Barak Naveh) - a `IntegerVertexFactory` has been added to the test package to reduce code duplication (contributed by Dimitrios Michail) - 2 new 1/2-approximation algorithms (greedy algorithm and Drake and Hougardy path growing algorithm) have been added; matching algorithms have been moved to dedicated package (contributed by Dimitrios Michail) - Added `HierholzerEulerianCycle`, a Linear time implementation of Hierholzer's algorithm to find a Eulerian Circuit in the graph. This class replaces the old `EulerianCircuit` implementation since it is significantly faster. (contributed by Dimitrios Michail) - Added methods for adding/deletion of specified edge in graph builders (contributed by Skuratovich Sergey) - Fixed a NullPointerException caused by `GraphMLImport` when an attributed was associated with the graph itself (i.e. at the level); this could occur for instance with yED graphml instances. (reported by Tim Schultze, contributed by Dimitrios Michail) - Minor updates and fixes to demo (contributed by Dimitrios Michail) - Removed underscore identifiers and refactored tests in `KuhnMunkresMinimalWeightBipartitePerfectMatchingTest`. (contributed by Szabolcs Besenyei) - All matching algorithms are now unified under a new `MatchingAlgorithm` interface; the old `WeightedMatchingAlgorithm` interface is now deprecated. (contributed by Dimitrios Michail) - Added Borůvka's algorithm for the computation of a minimum spanning tree (contributed by Dimitrios Michail) - Revised spanning tree and spanner interfaces and bundled them under the alg.spanning package (contributed by Dimitrios Michail) - Small improvements in `StopWatch` class (contributed by Dimitrios Michail) - Revised `Subgraph`, `MaskSubgraph` and subclasses to use Java 8 features; incorporated a number of performance improvements. Revised `MaskSubgraph` uses Java 8 predicates instead of the `MaskFunctor` interface. The latter interface is now deprecated. (contributed by Dimitrios Michail) - Added `GusfieldGomoryHuCutTree`, `GusfieldEquivalentFlowTree`, and `PadbergRaoOddMinimumCutset` (contributed by Joris Kinable, following up on a Gomory-Hu proposal from Mads Jensen) - Added support for inconsistent admissible heuristics in A* search (contributed by Joris Kinable) - Added a `DIMACSExporter` which supports exporting graphs in various DIMACS formats (contributed by Dimitrios Michail) - Enforce valid nodes in `FibonacciHeap`; Fixed a bug which could cause an infinite loop in the Fibonacci heap consolidate() method. (contributed by Dimitrios Michail) - Revision of shortest path algorithms. Added interfaces `ShortestPathAlgorithm` and `KShortestPathAlgorithm`, and bundled shortest path algorithms in dedicated package. Adjusted KShortestPaths returns an empty list instead of null if no path exists. (contributed by Dimitrios Michail) - `FlowdWarshall` now has support for multigraphs. Fixed diameter method now returns POSITIVE_INFINITY when a graph is disconnected. (contributed by Dimitrios Michail) - `GraphPath` supports zero edge paths (path consisting of 1 vertex). (contributed by Dimitrios Michail) - `DijkstraShortestPath` as well as several other shortest path classes can now return single-source shortest paths, using the traditional representation of a shortest path tree (contributed by Dimitrios Michail) - Added an implementation of Goldberg and Harrelson's ALT admissible heuristic for A* (contributed by Dimitrios Michail) - Replaced the `ClosestFirstIterator` in `DijkstraShortestPath` by a light-weight version of the iterator which significantly speeds up the shortest path computations. (contributed by Dimitrios Michail) - Added implementation of Larry Page's `PageRank` algorithm. (contributed by Dimitrios Michail) - Added faster transitive closure calculation for DAGs. (contributed by Martin Sturm) - K-shortest paths interface now accepts k as a parameter (contributed by Dimitrios Michail) - **version 1.0.0** (19-Sept-2016): - Moved to JDK 1.8 (cleanup contributed by Joris Kinable) - Fixes for `MaskSubgraph`, contributed by Andrew Gainer-Dewar - Optimized edge lookups (contributed by Joris Kinable) - Use LinkedHashSet in `CycleDetector` (contributed by Benedikt Waldvogel) - Use unique OSGi bundle symbolic name for uber artifact (contributed by Christoph Zauner) - Replace experimental GraphReader with `DIMACSImporter` (contributed by Joris Kinable) - Implement various graph utility methods (contributed by Christoph Zauner) - Add getFirstHop and getLastHop to `FloydWarshallShortestPaths` (contributed by Joris Kinable) - Allow paths to be expressed in terms of vertices instead of edges; deprecate `GraphPathImpl` in favor of new `GraphWalk` (contributed by Joris Kinable) - Weighted graph support in `GmlExporter` (contributed by Dimitrios Michail) - Add `RandomWalkIterator` (contributed by Assaf Mizrachi) - Add `GreedyMultiplicativeSpanner` (contributed by Dimitrios Michail) - Support undirected graphs in max flow algorithms (contributed by Joris Kinable) - Fix for reading escaped quotes in `DOTImporter` (contributed by Victor Mikhaylov) - Add `BidirectionalDijkstraShortestPath` (contributed by Dimitrios Michail) - Add external path validator for `KShortestPaths` (contributed by Assaf Mizrachi) - Add `GmlImporter` (contributed by Dimitrios Michail) - Fixed bug in HopcroftKarpBipartiteMatching which caused the algorithm to occasionally throw a NullPointerException (contributed by Dimitrios Michail, bug reported by Nils Olberg) - Fixed `AStarShortestPath` to use Object.equals (contributed by Joris Kinable, bug reported by Zgce) - Enhance `DirectedAcyclicGraph` to extend Iterable (contributed by Joris Kinable, suggested by Andrew Pennebaker) - Enhance `MinSourceSinkCut` to make max-flow implementation configurable (contributed by Joris Kinable, suggested by Roman Pearah) - Add new vertex cover algorithms and package (contributed by Joris Kinable and Nils Olberg) - Improved GraphML support (contributed by Dimitrios Michail) - Add common `GraphExporter` and `GraphImporter` interfaces (contributed by Dimitrios Michail) - DOT importer id fix (contributed by Dimitrios Michail) - Add `CSVExporter` and `CSVImporter` (contributed by Dimitrios Michail) - Add `MinimumSTCutAlgorithm` (contributed by Joris Kinable) - Capture trivial paths in `AllDirectedPaths` (contributed by Andrew Gainer-Dewar) - Switch from Jalopy source code formatter to Eclipse, since Jalopy does not support java 8 (contributed by John Sichi) - Fixed a bug in `RandomGraphGenerator`, reported by (@ckapop), which could cause an overflow when calculating the maximum number of edges allowed in a graph (contributed by Dimitrios Michail) - Added generics to code in test package (contributed by Dimitrios Michail) - Fixed a bug in `PushRelabelMFImpl`, reported by Joris Kinable, which caused a NullPointerException whenever the network contained multiple components (contributed by Dimitrios Michail) - Fixed a bug in `MaximumFlowAlgorithmBase`: some vertices ended up with a null prototype vertex (contributed by Dimitrios Michail) - Use equals for compare to fix bug in `EdmondsBlossomShrinking` (contributed by Szabolcs Besenyei) - Reformat all file headers (contributed by Joris Kinable) - Refactor and enhance random graph generators (contributed by Dimitrios Michail) - Fix DirectedAcyclicGraph.removeAllVertices (contributed by Szabolcs Besenyei) - Use Travis to enforce Javadoc correctness (contributed by Joris Kinable) - Corrected all javadoc warnings (contributed by Dimitrios Michail) - **version 0.9.2** (3-Apr-2016): - Add `HawickJamesSimpleCycles`, contributed by Luiz Kill - Add `DOTImporter`, contributed by Wil Selwood - Optimize `FloydWarshallShortestPaths`, contributed by Mihhail Verhovtsov - Add VF2 isomorphism and subgraph isomorphism detection, contributed by Fabian Späh - Remove old experimental isomorphism implementation - Fix for empty graph input to `KuhnMunkresMinimalWeightBipartitePerfectMatching`, contributed by Szabolcs Besenyei - Fix for `EdmondsBlossomShrinking`, contributed by Alexey Kudinkin - Add `TransitiveReduction`, contributed by Christophe Thiebaud - Add `AStarShortestPath`, contributed by Joris Kinable, Jon Robinson, and Thomas Breitbart - More `FloydWarshallShortestPaths` optimizations, contributed by Joris Kinable - Add `MixedGraphUnion` and `AsWeightedDirectedGraph`; fix UndirectedGraphUnion constructors; contributed by Joris Kinable - Add `GabowStrongConnectivityInspector` and `KosarajuStrongConnectivityInspector`, contributed by Joris Kinable and Sarah Komla-Ebri - Add `PushRelabelMaximumFlow`; boost `EdmondsKarpMaximumFlow`; add `MaximumFlowAlgorithm` interface; add `Pair` and `Extension` utility classes; optional seed parameter to `RandomGraphGenerator`, contributed by Alexey Kudinkin - Add `MaximumWeightBipartiteMatching`, contributed by Graeme Ahokas - Osgify jgrapht-ext, contributed by Christoph Zauner - Add `AllDirectedPaths`, contributed by Andrew Gainer-Dewar - **version 0.9.1** (5-Apr-2015): - Auto-generation of bundle manifest, contributed by Nicolas Fortin - Travis CI configuration, contributed by Peter Goldstein - TarjanLCA bugfix, contributed by Leo Crawford - Add `SimpleGraphPath`, contributed by Rodrigo Lopez Dato - Add `NaiveLcaFinder`, contributed by Leo Crawford - Make getEdgeWeight throw NPE on null edge, suggested by Joris Kinable - Clarify that shortest path length is weighted, per gjafachini - Add DAG constructor that takes an edge factory, and make TarjanLCA constructor public, contributed by Anders Wallgren - Fixed rounding error in graph generation, contributed by Siarhei - Fixed Javadoc for `DirectedWeightedMultigraph`, noticed by Martin Lowinski - Use annotations to simplify test suites, contributed by Jan Altenbernd - Add missing Override/Deprecated annotations, contributed by Jan Altenbernd - Update maven-compiler-plugin version, contributed by Andrew Chen - Add builder package, contributed by Andrew Chen - Add CliqueMinimalSeparatorDecomposition, contributed by Florian Buenzli, Thomas Tschager, Tomas Hruz, and Philipp Hoppen - Include vertex #toString value in excn message, contributed by Chris Wensel - Open up Specifics to allow for custom backing containers, contributed by Chris Wensel - Make abstract graph constructors protected, contributed by John Sichi - Moved to JDK 1.7 - **version 0.9.0** (06-Dec-2013): - Move to github for source control, and Apache Maven for build, contributed by Andreas Schnaiter, Owen Jacobson, and Isaac Kleinman. - Add source/target vertices to edge events to fix sf.net bug 3486775, spotted by Frank Mori Hess. - Add `EdmondsBlossomShrinking` algorithm, contributed by Alejandro R. Lopez del Huerto. - Fix empty diameter calculation in `FloydWarshallShortestPaths`, contributed by Ernst de Ridder (bug spotted by Jens Lehmann) - Add `HopcroftKarpBipartiteMatching` and `MinSourceSinkCut`, contributed by Joris Kinable - Fix multiple bugs in `StoerWagnerMinimumCut`, contributed by Ernst de Ridder - Fix path weight bug in `FloydWarshallShortestPaths`, contributed by Michal Pasieka - Add `PrimMinimumSpanningTree`, contributed by Alexey Kudinkin - Add `DirectedWeightedPseudograph`, and fix 'DirectedMultigraph' contributed by Adam Gouge - More KSP bugfixes (spotted by Sebastian Mueller, fixed by Guillaume Boulmier) - Add `KuhnMunkresMinimalWeightBipartitePerfectMatching` and associated generators+interfaces (contributed by Alexey Kudinkin) - Add cycle enumeration (contributed by Nikolay Ognyanov, originally from http://code.google.com/p/niographs/ ) - Update `removeAllEdges` to match specification (contributed by Graham Hill) - Add `TarjanLowestCommonAncestor`, contributed by Leo Crawford - Add `JGraphXAdapter`, contributed by Sebastian Hubenschmid and JeanYves Tinevez - Add LGPL/EPL dual licensing, coordinated by Oliver Kopp - Refactoring for `DirectedAcyclicGraph`, contributed by Javier Gutierrez - **version 0.8.3** (20-Jan-2012): - fix regression in `DOTExporter` inadvertently introduced by `0.8.2` changes. - Add `GridGraphGenerator`, contributed by Assaf Mizrachi. - Return coloring from ChromaticNumber, contributed by Harshal Vora. - Fix bugs in KSP, contributed by Guillaume Boulmier; note that these bugfixes worsen the running time. - Fix an object identity bug in CycleDetector, contributed by Matt Sarjent. -Add StoerWagnerMinimumCut, contributed by Robby McKilliam. - Fix `MANIFEST.MF`, spotted by Olly. - Make `FloydWarshallShortestPaths.getShortestPaths` unidirectional, contributed by Yuriy Nakonechnyy. - **version 0.8.2** (27-Nov-2010): - Clean up `FibonacciHeapNode` constructor, as suggested by Johan Henriksson. - Optimize and enhance `FloydWarshallShortestPaths`, contributed by Soren Davidsen. - Optimize `ChromaticNumber`,pointed out by gpaschos@netscape.net. - Add unit test for `FloydWarshallShortestPaths` for bug noticed by Andrea Pagani. - Add vertex factory validation to `RandomGraphGenerator` to prevent a confusing problem encountered by Andrea Pagani. - Add `KruskalMinimumSpanningTree` and `UnionFind`, contributed by Tom Conerly. - Add attributes to `DOTExporter`, based on suggestion from Chris Lott. - Fix inefficient assertion in `TopologicalOrderIterator`, spotted by Peter Lawrey. - Fix induced subgraph bug with addition of edge to underlying graph, contributed by Michele Mancioppi. - Make `getEdgeWeight` delegate to `DefaultWeightedEdge.getWeight`, spotted by Michael Lindig. - Add maven support, contributed by Adrian Marte. - **version 0.8.1** (3-Jul-2009): - Enhanced `GmlExporter` with customized labels and ID's, contributed by Trevor Harmon. - Added new algorithms `HamiltonianCycle`, `ChromaticNumber` and `EulerianCircuit`, plus new generators `HyperCubeGraphGenerator`, `StarGraphGenerator`, and `CompleteBipartiteGraphGenerator`, all contributed by Andrew Newell. - Fix bug with vertices which are equals but not identity-same in graphs allowing loops, spotted by Michael Michaud. - Fix bug in `EquivalenceIsomorphismInspector`, reported by Tim Engler. - Add `toString` for shortest paths wrapper, spotted by Achim Beutel. - Add `FloydWarshallShortestPaths`, contributed by Tom Larkworthy. - Enhance `DijskstraShortestPath` to support `GraphPath` interface. - Add `GraphUnion` (with directed and undirected variants), contributed by Ilya Razenshteyn. - **version 0.8.0** (Sept-2008): - Moved to JDK 1.6. - Fixed problem with `RandomGraphGenerator` reported by Mario Rossi. - Added `CompleteGraphGenerator`, contributed by Tim Shearouse. - Fixed `FibonacciHeap` performance problem reported by Jason Lenderman. - Made `DotExporter` reject illegal vertex ID's, contributed by Holger Brandl. - Fixed bogus assertion for topological sort over empty graph, spotted by Harris Lin. - Added scale-free graph generator and `EdmondsKarpMaximumFlow`, contributed by Ilya Razenshteyn. - Added `DirectedAcyclicGraph`, contributed by Peter Giles. - Added protected `getWeight` accessor to `DefaultWeightedEdge`, likewise `getSource` and `getTarget` on `DefaultEdge`. - Optimized iterators to skip calling event firing routines when there are no listeners, and used `ArrayDeque` in a number of places, per suggestion from Ross Judson. - Improvements to `StrongConnectivityInspector` and OSGi bundle support contributed by Christian Soltenborn. - **version 0.7.3** (Jan-2008): - Patch to `JGraphModelAdapter.removeVertex` provided by Hookahey. - Added `ParanoidGraph`. - Removed obsolete `ArrayUtil` (spotted by Boente). - Added `GraphPath`, and used it to fix mistake in `0.7.2` (k-shortest-paths was returning a private data structure, as discovered by numerous users). - Fixed `EdgeReversedGraph.getAllEdges` (spotted by neumanns@users.sf.net). - Fixed incorrect assertion in `TopologicalOrderIterator` constructor. - Enabled assertions in JUnit tests. - Fixed NPE in `BellmanFordShortestPath.getCost`. - Fixed a few problems spotted by findbugs. - **version 0.7.2** (Sept-2007): - Added `TransitiveClosure`, contributed by Vinayak Borkar. - Added biconnectivity/cutpoint inspection, k-shortest-paths, and masked subgraphs, all contributed by Guillaume Boulmier. - Made some Graphs helper methods even more generic, as suggested by JongSoo. - Test and fixes for (Directed)NeighborIndex submitted by Andrew Berman. - Added `AsUnweighted(Directed)Graph` and `AsWeightedGraph`, contributed by Lucas Scharenbroich. - Dropped support for retroweaver. - **version 0.7.1** (March-2007): - Fixed some bugs in `CycleDetector` reported by Khanh Vu, and added more testcases for it. - Fixed bugs in `DepthFirstIterator` reported by Welson Sun, and added WHITE/GRAY/BLACK states and `vertexFinished` listener event. - Exposed `Subgraph.getBase()`, and parameterized `Subgraph` on graph type (per suggestion from Aaron Harnly). - Added `EdgeReversedView`. - Added `GmlExporter` (contributed by Dimitrios Michail), plus `DOTExporter` and `GraphMLExporter` (both contributed by Trevor Harmon). - Enhanced `TopologicalOrderIterator` to take an optional Queue parameter for tie-breaking (per suggestion from JongSoo Park). - Fixed some documentation errors reported by Guillaume Boulmier. - **version 0.7.0** (July-2006) : - Upgraded to JDK 1.5 (generics support added by Christian Hammer with help from Hartmut Benz and John Sichi). - Added `(Directed)NeighborIndex` and `MatrixExporter`, contributed by Charles Fry. - Added BellmanFord, contributed by Guillaume Boulmier of France Telecom. - Removed never-used `LabeledElement`. - Renamed package from `org._3pq.jgrapht` to `org.jgrapht`. - Made various breaking change to interfaces; edge collections are now Sets, not Lists. - Added Touchgraph converter, contributed by Carl Anderson - **version 0.6.0** (July-2005) : - Upgraded to JDK 1.4, taking advantage of its new linked hash set/map containers to make edge/vertex set order-deterministic - Added support for custom edge lists. - Fixed various serialization and Subgraph issues. - Added to `JGraphModelAdapter` support for JGraph's "dangling" edges; its constructors have slightly changed and now forbid `null` values. - Improved interface to `DijskstraShortestPath`, and added radius support to `ClosestFirstIterator`. - Added new `StrongConnectivityInspector` algorithm (contributed by Christian Soltenborn) and `TopologicalOrderIterator` (contributed by Marden Neubert). - Deleted deprecated `TraverseUtils`. - Upgraded to JGraph `5.6.1.1`. - **version 0.5.3** (June-2004) : - Removed Subgraph verification of element's identity to base graph, upgraded to JGraph 4.0 - Added the `VisioExporter` which was contributed by Avner Linder - minor bug fixes and improvements. - **version 0.5.2** (March-2004) : - Serialization improvements, fixes to subgraphs and listenable graphs - added support for JGraph > JGraphT change propagation for JGraph adapter (contributed by Erik Postma) - upgraded to JGraph 3.1, various bug fixes and improvements. - **version 0.5.1** (November-2003) : - Semantics of `Graph.clone()` has changed, please check the documentation if you're using it. - Added Dijkstra's shortest path, vertex cover approximations, new graph generation framework - upgraded to JGraph 3.0 - various bug fixes and API improvements. - **version 0.5.0** (14-Aug-2003) : - a new connectivity inspector added - edge API refactored to be simpler - improved ant build - improved event model - all known bugs were fixed, documentation clarifications, other small improvements. - API of 0.5.0 is not 100% backward compatible with 0.4.1 but upgrade is simple and straightforward. - **version 0.4.1** (05-Aug-2003) : - A new adapter to JGraph that provides graph visualizations, new depth-first and breadth-first iteration algorithms - various bug fixes and refactoring - moved unit-tests to a separate folder hierarchy and added more unit-tests. - **version 0.4.0** (July-2003) : Initial public release. jgrapht-jgrapht-1.5.1/README.md000066400000000000000000000254321402514743400161030ustar00rootroot00000000000000![Build Status](https://github.com/jgrapht/jgrapht/workflows/JGrapht%20Master%20build/badge.svg) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.jgrapht/jgrapht/badge.svg)](http://search.maven.org/#search%7Cga%7C1%7Ca%3A%22jgrapht%22) [![Snapshot](https://img.shields.io/nexus/s/https/oss.sonatype.org/org.jgrapht/jgrapht.svg)](https://oss.sonatype.org/content/repositories/snapshots/org/jgrapht/jgrapht-core/) [![License](https://img.shields.io/badge/license-LGPL%202.1-blue.svg)](http://www.gnu.org/licenses/lgpl-2.1.html) [![License](https://img.shields.io/badge/license-EPL%202.0-blue.svg)](http://www.eclipse.org/legal/epl-2.0) [![Language](http://img.shields.io/badge/language-java-brightgreen.svg)](https://www.java.com/) # JGraphT Released: March 18, 2021

Written by [Barak Naveh](mailto:barak_naveh@users.sourceforge.net) and Contributors (C) Copyright 2003-2021, by Barak Naveh and Contributors. All rights reserved. Please address all contributions, suggestions, and inquiries to the [user mailing list](https://lists.sourceforge.net/lists/listinfo/jgrapht-users) ## Introduction JGraphT is a free Java class library that provides mathematical graph-theory objects and algorithms. It runs on Java 2 Platform (requires JDK 11 or later starting with JGraphT 1.5.0). JGraphT may be used under the terms of either the * GNU Lesser General Public License (LGPL) 2.1 or the * Eclipse Public License (EPL) As a recipient of JGraphT, you may choose which license to receive the code under. For detailed information on the dual license approach, see . A copy of the [EPL license](license-EPL.txt) and the [LPGL license](license-LGPL.txt) is included in the download. Please note that JGraphT is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Please refer to the license for details. SPDX-License-Identifier: LGPL-2.1-or-later OR EPL-2.0 ## Release Contents The files below make up the table of contents for a release distribution archive (produced by `mvn package`): - `README.md` this file - `CONTRIBUTORS.md` list of contributors - `HISTORY.md` changelog - `license-EPL.txt` Eclipse Public License 2.0 - `license-LGPL.txt` GNU Lesser General Public License 2.1 - `javadoc/` Javadoc documentation - `lib/` JGraphT libraries and dependencies: - `jgrapht-core-x.y.z.jar` core library - `jgrapht-demo-x.y.z.jar` demo classes - `jgrapht-opt-x.y.z.jar` optimized graph implementations - `jgrapht-ext-x.y.z.jar` extensions - `jgrapht-io-x.y.z.jar` Importers/Exporters for various graph formats - `jgrapht-guava-x.y.z.jar` Adapter classes for the Guava library - `jgrapht-unimi-dsi-x.y.z.jar` Webgraph adapter and succinct graph implementations - `jgraphx-a.b.c.jar` JGraphX dependency library - `jheaps-x.y.jar` JHeaps library - `antlr4-runtime-x.y.jar` ANTLR parser runtime - `commons-lang3-x.y.z.jar` Apache Commons Lang library - `commons-text-x.y.jar` Apache Commons Text library - `fastutil-x.y.z.jar` Fastutil library - `guava-x.y-jre.jar` Guava library - `jsap-x.y.jar` Jsap library - `logback-classic-x.y.z.jar` Logger - `logback-core-x.y.z.jar` Logger - `slf4j-api-x.y.z.jar` Logger api - `sux4j-x.y.z.jar` Sux4j library - `webgraph-x.y.z.jar` Webgraph library - `webgraph-big-z.y.z.jar` Webgraph big library - `source/` complete source tree used to build this release ## Getting Started ## The JGraphT [wiki](https://github.com/jgrapht/jgrapht/wiki) provides various helpful pages for new users, including a [How to use JGraphT in your projects](https://github.com/jgrapht/jgrapht/wiki/Users%3A-How-to-use-JGraphT-as-a-dependency-in-your-projects) page. The package `org.jgrapht.demo` includes small demo applications to help you get started. If you spawn your own demo app and think others can use it, please send it to us and we will add it to that package. To run the graph visualization demo from the downloaded release, try executing this command in the lib directory: java -jar jgrapht-demo-x.y.z.jar More information can be found on the [user pages](https://github.com/jgrapht/jgrapht/wiki#user-pages) of our wiki. Finally, all classes come with corresponding test classes. These test classes contain many examples. To help us understand how you use JGraphT, and which features are important to you, [tell](https://github.com/jgrapht/jgrapht/wiki/Projects-Using-JGraphT) us how you are using JGraphT, and [cite](https://github.com/jgrapht/jgrapht/wiki/How-to-cite-JGraphT) the usage of JGraphT in your book, paper, website, or technical report. ## Using via Maven Starting from 0.9.0, every JGraphT release is published to the Maven Central Repository. You can add a dependency from your project as follows: ```xml org.jgrapht jgrapht-core 1.5.1 ``` We have also started auto-publishing SNAPSHOT builds for every successful commit to master. To use the bleeding edge: ```xml org.jgrapht jgrapht-core 1.5.2-SNAPSHOT ``` and make sure the snapshot repository is enabled: ```xml maven-snapshots http://oss.sonatype.org/content/repositories/snapshots default false true ``` ## Upgrading Versions To help upgrading, JGraphT maintains a one-version-backwards compatibility. While this compatibility is not a hard promise, it is generally respected. (This policy was not followed for the jump from `0.6.0` to `0.7.0` due to the pervasive changes required for generics.) You can upgrade via: - **The safe way**: compile your app with the JGraphT version that immediately follows your existing version and follow the deprecation notes, if they exist, and modify your application accordingly. Then move to the next version, and on, until you're current. - **The fast way**: go to the latest JGraphT right away - if it works, you're done. Reading the [change history](HISTORY.md) is always recommended. ## Documentation A local copy of the Javadoc HTML files is included in the distribution. The latest version of these files is also available [on-line](http://www.jgrapht.org/javadoc). ## Dependencies - JGraphT requires JDK 11 or later to build starting with version 1.5.0. - [JHeaps](http://www.jheaps.org/) is a library with priority queues. JHeaps is licensed under the terms of the Apache License, Version 2.0. - [JUnit](http://www.junit.org) is a unit testing framework. You need JUnit only if you want to run the unit tests. JUnit is licensed under the terms of the IBM Common Public License. The JUnit tests included with JGraphT have been created using JUnit 4. - [XMLUnit](http://xmlunit.sourceforge.net) extends JUnit with XML capabilities. You need XMLUnit only if you want to run the unit tests. XMLUnit is licensed under the terms of the BSD License. - [JGraphX](https://github.com/jgraph/jgraphx) is a graph visualizations and editing component (the successor to the older JGraph library). You need JGraphX only if you want to use the JGraphXAdapter to visualize the JGraphT graph interactively via JGraphX. JGraphX is licensed under the terms of the BSD license. - [ANTLR](http://www.antlr.org) is a parser generator. It is used for reading text files containing graph representations, and is only required by the jgrapht-io module. ANTLR v4 is licensed under the terms of the [BSD license](http://www.antlr.org/license.html). - [Guava](https://github.com/google/guava) is Google's core libraries for Java. You need Guava only if you are already using Guava's graph data-structures and wish to use our adapter classes in order to execute JGraphT's algorithms. Only required by the jgrapht-guava module. - [Apache Commons Proper](http://commons.apache.org/components.html) is an Apache project containing reusable Java components. The packages [commons-text](https://commons.apache.org/proper/commons-text/) and [commons-lang3.](http://commons.apache.org/proper/commons-lang/) which provide additional utilities for String manipulation are only required by the jgrapht-io module. The package [commons-math](https://commons.apache.org/proper/commons-text/) is only required by the jgrapht-unimi-dsi module. - [fastutil](http://fastutil.di.unimi.it/) provides a collection of type-specific maps, sets, lists and queues with a small memory footprint and fast access and insertion. Fastutil is only required by the jgrapht-opt module. - [webgraph](http://webgraph.di.unimi.it/) provides a framework for graph compression enabling management of very large graphs. Webgraph is only required by the jgrapht-unimi-dsi module. - [sux4j](http://sux.di.unimi.it/) provides implementations of basic succinct data structures. Sux4j is only required by the jgrapht-unimi-dsi module. - [jsap](http://www.martiansoftware.com/jsap/) provides a simple argument parser. Jsap is only required by the jgrapht-unimi-dsi module. ## Online Resources The JGraphT website is at . You can use this site to: - **Obtain the latest version**: latest version and all previous versions of JGraphT are available online. - **Report bugs**: if you have any comments, suggestions or bugs you want to report. - **Get support**: if you have questions or need help with JGraphT. There is also a [wiki](https://github.com/jgrapht/jgrapht/wiki) set up for everyone in the JGraphT community to share information about the project. For support, refer to our [support page](https://github.com/jgrapht/jgrapht/wiki/Getting-Support) Source code is hosted on [github](https://github.com/jgrapht/jgrapht). You can send contributions as pull requests there. ## Your Improvements If you add improvements to JGraphT please send them to us as [pull requests on github](https://github.com/jgrapht/jgrapht/wiki/How-to-make-your-first-%28code%29-contribution). We will add them to the next release so that everyone can enjoy them. You might also benefit from it: others may fix bugs in your source files or may continue to enhance them. ## Thanks With regards from [Barak Naveh](mailto:barak_naveh@users.sourceforge.net), JGraphT Project Creator [John Sichi](mailto:perfecthash@users.sourceforge.net), JGraphT Project Administrator [Joris Kinable](https://github.com/jkinable), JGraphtT Project Reviewer/Committer and Release Manager [Dimitrios Michail](https://github.com/d-michail), JGraphT Project Reviewer/Committer jgrapht-jgrapht-1.5.1/docs/000077500000000000000000000000001402514743400155465ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/docs/CNAME000066400000000000000000000000141402514743400163070ustar00rootroot00000000000000jgrapht.org jgrapht-jgrapht-1.5.1/docs/Gemfile000066400000000000000000000001741402514743400170430ustar00rootroot00000000000000source 'https://rubygems.org' gem 'github-pages', group: :jekyll_plugins gem 'jekyll-redirect-from', group: :jekyll_plugins jgrapht-jgrapht-1.5.1/docs/Gemfile.lock000066400000000000000000000156001402514743400177720ustar00rootroot00000000000000GEM remote: https://rubygems.org/ specs: activesupport (5.2.4.3) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) coffee-script (2.4.1) coffee-script-source execjs coffee-script-source (1.11.1) colorator (1.1.0) commonmarker (0.17.13) ruby-enum (~> 0.5) concurrent-ruby (1.1.7) dnsruby (1.61.4) simpleidn (~> 0.1) em-websocket (0.5.1) eventmachine (>= 0.12.9) http_parser.rb (~> 0.6.0) ethon (0.12.0) ffi (>= 1.3.0) eventmachine (1.2.7) execjs (2.7.0) faraday (1.0.1) multipart-post (>= 1.2, < 3) ffi (1.13.1) forwardable-extended (2.6.0) gemoji (3.0.1) github-pages (207) github-pages-health-check (= 1.16.1) jekyll (= 3.9.0) jekyll-avatar (= 0.7.0) jekyll-coffeescript (= 1.1.1) jekyll-commonmark-ghpages (= 0.1.6) jekyll-default-layout (= 0.1.4) jekyll-feed (= 0.13.0) jekyll-gist (= 1.5.0) jekyll-github-metadata (= 2.13.0) jekyll-mentions (= 1.5.1) jekyll-optional-front-matter (= 0.3.2) jekyll-paginate (= 1.1.0) jekyll-readme-index (= 0.3.0) jekyll-redirect-from (= 0.15.0) jekyll-relative-links (= 0.6.1) jekyll-remote-theme (= 0.4.1) jekyll-sass-converter (= 1.5.2) jekyll-seo-tag (= 2.6.1) jekyll-sitemap (= 1.4.0) jekyll-swiss (= 1.0.0) jekyll-theme-architect (= 0.1.1) jekyll-theme-cayman (= 0.1.1) jekyll-theme-dinky (= 0.1.1) jekyll-theme-hacker (= 0.1.1) jekyll-theme-leap-day (= 0.1.1) jekyll-theme-merlot (= 0.1.1) jekyll-theme-midnight (= 0.1.1) jekyll-theme-minimal (= 0.1.1) jekyll-theme-modernist (= 0.1.1) jekyll-theme-primer (= 0.5.4) jekyll-theme-slate (= 0.1.1) jekyll-theme-tactile (= 0.1.1) jekyll-theme-time-machine (= 0.1.1) jekyll-titles-from-headings (= 0.5.3) jemoji (= 0.11.1) kramdown (= 2.3.0) kramdown-parser-gfm (= 1.1.0) liquid (= 4.0.3) mercenary (~> 0.3) minima (= 2.5.1) nokogiri (>= 1.11.0.rc4, < 2.0) rouge (= 3.19.0) terminal-table (~> 1.4) github-pages-health-check (1.16.1) addressable (~> 2.3) dnsruby (~> 1.60) octokit (~> 4.0) public_suffix (~> 3.0) typhoeus (~> 1.3) html-pipeline (2.13.0) activesupport (>= 2) nokogiri (>= 1.4) http_parser.rb (0.6.0) i18n (0.9.5) concurrent-ruby (~> 1.0) jekyll (3.9.0) addressable (~> 2.4) colorator (~> 1.0) em-websocket (~> 0.5) i18n (~> 0.7) jekyll-sass-converter (~> 1.0) jekyll-watch (~> 2.0) kramdown (>= 1.17, < 3) liquid (~> 4.0) mercenary (~> 0.3.3) pathutil (~> 0.9) rouge (>= 1.7, < 4) safe_yaml (~> 1.0) jekyll-avatar (0.7.0) jekyll (>= 3.0, < 5.0) jekyll-coffeescript (1.1.1) coffee-script (~> 2.2) coffee-script-source (~> 1.11.1) jekyll-commonmark (1.3.1) commonmarker (~> 0.14) jekyll (>= 3.7, < 5.0) jekyll-commonmark-ghpages (0.1.6) commonmarker (~> 0.17.6) jekyll-commonmark (~> 1.2) rouge (>= 2.0, < 4.0) jekyll-default-layout (0.1.4) jekyll (~> 3.0) jekyll-feed (0.13.0) jekyll (>= 3.7, < 5.0) jekyll-gist (1.5.0) octokit (~> 4.2) jekyll-github-metadata (2.13.0) jekyll (>= 3.4, < 5.0) octokit (~> 4.0, != 4.4.0) jekyll-mentions (1.5.1) html-pipeline (~> 2.3) jekyll (>= 3.7, < 5.0) jekyll-optional-front-matter (0.3.2) jekyll (>= 3.0, < 5.0) jekyll-paginate (1.1.0) jekyll-readme-index (0.3.0) jekyll (>= 3.0, < 5.0) jekyll-redirect-from (0.15.0) jekyll (>= 3.3, < 5.0) jekyll-relative-links (0.6.1) jekyll (>= 3.3, < 5.0) jekyll-remote-theme (0.4.1) addressable (~> 2.0) jekyll (>= 3.5, < 5.0) rubyzip (>= 1.3.0) jekyll-sass-converter (1.5.2) sass (~> 3.4) jekyll-seo-tag (2.6.1) jekyll (>= 3.3, < 5.0) jekyll-sitemap (1.4.0) jekyll (>= 3.7, < 5.0) jekyll-swiss (1.0.0) jekyll-theme-architect (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-cayman (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-dinky (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-hacker (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-leap-day (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-merlot (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-midnight (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-minimal (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-modernist (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-primer (0.5.4) jekyll (> 3.5, < 5.0) jekyll-github-metadata (~> 2.9) jekyll-seo-tag (~> 2.0) jekyll-theme-slate (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-tactile (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-theme-time-machine (0.1.1) jekyll (~> 3.5) jekyll-seo-tag (~> 2.0) jekyll-titles-from-headings (0.5.3) jekyll (>= 3.3, < 5.0) jekyll-watch (2.2.1) listen (~> 3.0) jemoji (0.11.1) gemoji (~> 3.0) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) kramdown (2.3.0) rexml kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) liquid (4.0.3) listen (3.2.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) mercenary (0.3.6) mini_portile2 (2.4.0) minima (2.5.1) jekyll (>= 3.5, < 5.0) jekyll-feed (~> 0.9) jekyll-seo-tag (~> 2.1) minitest (5.14.1) multipart-post (2.1.1) nokogiri (1.10.10) mini_portile2 (~> 2.4.0) octokit (4.18.0) faraday (>= 0.9) sawyer (~> 0.8.0, >= 0.5.3) pathutil (0.16.2) forwardable-extended (~> 2.6) public_suffix (3.1.1) rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) rexml (3.2.4) rouge (3.19.0) ruby-enum (0.8.0) i18n rubyzip (2.3.0) safe_yaml (1.0.5) sass (3.7.4) sass-listen (~> 4.0.0) sass-listen (4.0.0) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) sawyer (0.8.2) addressable (>= 2.3.5) faraday (> 0.8, < 2.0) simpleidn (0.1.1) unf (~> 0.1.4) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) typhoeus (1.4.0) ethon (>= 0.9.0) tzinfo (1.2.7) thread_safe (~> 0.1) unf (0.1.4) unf_ext unf_ext (0.0.7.7) unicode-display_width (1.7.0) PLATFORMS ruby DEPENDENCIES github-pages jekyll-redirect-from BUNDLED WITH 2.1.4 jgrapht-jgrapht-1.5.1/docs/LGPL.html000066400000000000000000000726351402514743400172070ustar00rootroot00000000000000 LGPL

GNU Lesser General Public License
Version 2.1, February 1999

 

Copyright (C) 1991, 1999 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

[This is the first released version of the Lesser GPL.  It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.]

Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users.

This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below.

When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things.

To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it.

For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights.

We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library.

To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others.

Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license.

Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs.

When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library.

We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances.

For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License.

In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. 

Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library.

The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run.

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you".

A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables.

The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".)

"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library.

Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does.

1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library.

You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

* a) The modified work must itself be a software library.

* b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change.

* c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License.

* d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful.

(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.)

These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library.

In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices.

Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy.

This option is useful when you wish to copy part of the code of the Library into a program that is not a library.

4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange.

If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code.

5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License.

However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables.

When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law.

If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.)

Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself.

6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications.

You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things:

* a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.)

* b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with.

* c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution.

* d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place.

* e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy.

For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute.

7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things:

* a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above.

* b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work.

8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it.

10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License.

11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library.

If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation.

14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY

15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

How to Apply These Terms to Your New Libraries

If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License).

To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.

< one line to give the library's name and a brief idea of what it does. >
Copyright (C) < year > < name of author >
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Also add information on how to contact you by electronic and paper mail.

You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker.

< signature of Ty Coon >, 1 April 1990
Ty Coon, President of Vice

That's all there is to it!

 

 

 


Valid HTML 4.01! © Website copyright 2003, by Barak Naveh and Contributors. All rights reserved. Get JGraphT at SourceForge.net. Fast, secure and Free Open Source software downloads

jgrapht-jgrapht-1.5.1/docs/SinglePaged-LICENSE.txt000066400000000000000000000020751402514743400215550ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2014 Tim O'Brien (t413) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jgrapht-jgrapht-1.5.1/docs/_config.yml000066400000000000000000000011271402514743400176760ustar00rootroot00000000000000permalink: /:title timezone: null lsi: false plugins: - jekyll-seo-tag - jekyll-sitemap - jekyll-redirect-from theme: jekyll-theme-primer title: JGraphT description: "a Java library of graph theory data structures and algorithms" source_link: "https://github.com/jgrapht/jgrapht" favicon: "img/favicon.ico" touch_icon: "img/apple-touch-icon.png" google_analytics_key: UA-129653402-1 github: is_project_page: false colors: black: '#111111' white: '#f8f8f8' blue: '#49a7e9' green: '#9bcf2f' purple: '#c869bf' orange: '#fab125' turquoise: '#0fbfcf' jgrapht-jgrapht-1.5.1/docs/_includes/000077500000000000000000000000001402514743400175135ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/docs/_includes/analytics.html000077500000000000000000000010341402514743400223710ustar00rootroot00000000000000{% if site.google_analytics_key %} {% endif %} jgrapht-jgrapht-1.5.1/docs/_includes/css/000077500000000000000000000000001402514743400203035ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/docs/_includes/css/base.css000066400000000000000000000303011402514743400217240ustar00rootroot00000000000000/* * Skeleton V1.2 * Copyright 2011, Dave Gamache * www.getskeleton.com * Free to use under the MIT license. * http://www.opensource.org/licenses/mit-license.php * 6/20/2012 */ /* Table of Content ================================================== #Reset & Basics #Basic Styles #Site Styles #Typography #Links #Lists #Images #Buttons #Forms #Misc */ /* #Reset & Basics (Inspired by E. Meyers) ================================================== */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } body { line-height: 1; } ol, ul { list-style: none; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } table { border-collapse: collapse; border-spacing: 0; } /* #Basic Styles ================================================== */ body { background: #fff; font: 14px/21px "Raleway", "HelveticaNeue-Light", Arial, sans-serif; color: #444; -webkit-font-smoothing: antialiased; /* Fix for webkit rendering */ -webkit-text-size-adjust: 100%; } /* #Typography ================================================== */ h1, h2, h3, h4, h5, h6 { font-weight: 300; } h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { font-weight: inherit; } h1 { font-size: 46px; line-height: 50px; margin-bottom: 14px;} h2 { font-size: 35px; line-height: 40px; margin-bottom: 10px; } h3 { font-size: 28px; line-height: 34px; margin-bottom: 8px; } h4 { font-size: 21px; line-height: 30px; margin-bottom: 4px; } h5 { font-size: 17px; line-height: 24px; } h6 { font-size: 14px; line-height: 21px; } .subheader { color: #777; } p { margin: 0 0 20px 0; } p img { margin: 0; } p.lead { font-size: 21px; line-height: 27px; color: #777; } em { font-style: italic; } strong { font-weight: bold; } small { font-size: 80%; } /* Blockquotes */ blockquote, blockquote p { font-size: 17px; line-height: 24px; color: #777; font-style: italic; } blockquote { margin: 0 0 20px; padding: 9px 20px 0 19px; border-left: 1px solid #ddd; } blockquote cite { display: block; font-size: 12px; color: #555; } blockquote cite:before { content: "\2014 \0020"; } blockquote cite a, blockquote cite a:visited, blockquote cite a:visited { color: #555; } hr { border: solid #ddd; border-width: 1px 0 0; clear: both; margin: 10px 0 30px; height: 0; } /* #Links ================================================== */ a, a:visited { text-decoration: underline; outline: 0; } a:hover, a:focus { } p a, p a:visited { line-height: inherit; } /* #Lists ================================================== */ ul, ol { margin-bottom: 20px; } ul { list-style: none outside; } ol { list-style: decimal; } ul, ul.square { list-style: square outside; } ul ul, ul.circle { list-style: circle outside; } ul ul ul, ul.disc { list-style: disc outside; } ul ul li, ul ol li, ol ol li, ol ul li { margin-bottom: 6px; } li { line-height: 18px; margin-bottom: 12px; } ul.large li { line-height: 21px; } li p { line-height: 21px; } /* #Images ================================================== */ img.scale-with-grid { max-width: 100%; height: auto; } /* #Buttons ================================================== */ .button, button, input[type="submit"], input[type="reset"], input[type="button"] { background: #eee; /* Old browsers */ background: #eee -moz-linear-gradient(top, rgba(255,255,255,.2) 0%, rgba(0,0,0,.2) 100%); /* FF3.6+ */ background: #eee -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.2)), color-stop(100%,rgba(0,0,0,.2))); /* Chrome,Safari4+ */ background: #eee -webkit-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Chrome10+,Safari5.1+ */ background: #eee -o-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* Opera11.10+ */ background: #eee -ms-linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* IE10+ */ background: #eee linear-gradient(top, rgba(255,255,255,.2) 0%,rgba(0,0,0,.2) 100%); /* W3C */ border: 1px solid #aaa; border-top: 1px solid #ccc; border-left: 1px solid #ccc; -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; color: #444; display: inline-block; font-size: 11px; font-weight: bold; text-decoration: none; text-shadow: 0 1px rgba(255, 255, 255, .75); cursor: pointer; margin-bottom: 20px; line-height: normal; padding: 8px 10px; font-family: "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; } .button:hover, button:hover, input[type="submit"]:hover, input[type="reset"]:hover, input[type="button"]:hover { color: #222; background: #ddd; /* Old browsers */ background: #ddd -moz-linear-gradient(top, rgba(255,255,255,.3) 0%, rgba(0,0,0,.3) 100%); /* FF3.6+ */ background: #ddd -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.3)), color-stop(100%,rgba(0,0,0,.3))); /* Chrome,Safari4+ */ background: #ddd -webkit-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Chrome10+,Safari5.1+ */ background: #ddd -o-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* Opera11.10+ */ background: #ddd -ms-linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* IE10+ */ background: #ddd linear-gradient(top, rgba(255,255,255,.3) 0%,rgba(0,0,0,.3) 100%); /* W3C */ border: 1px solid #888; border-top: 1px solid #aaa; border-left: 1px solid #aaa; } .button:active, button:active, input[type="submit"]:active, input[type="reset"]:active, input[type="button"]:active { border: 1px solid #666; background: #ccc; /* Old browsers */ background: #ccc -moz-linear-gradient(top, rgba(255,255,255,.35) 0%, rgba(10,10,10,.4) 100%); /* FF3.6+ */ background: #ccc -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(255,255,255,.35)), color-stop(100%,rgba(10,10,10,.4))); /* Chrome,Safari4+ */ background: #ccc -webkit-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Chrome10+,Safari5.1+ */ background: #ccc -o-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* Opera11.10+ */ background: #ccc -ms-linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* IE10+ */ background: #ccc linear-gradient(top, rgba(255,255,255,.35) 0%,rgba(10,10,10,.4) 100%); /* W3C */ } .button.full-width, button.full-width, input[type="submit"].full-width, input[type="reset"].full-width, input[type="button"].full-width { width: 100%; padding-left: 0 !important; padding-right: 0 !important; text-align: center; } /* Fix for odd Mozilla border & padding issues */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /* #Forms ================================================== */ form { margin-bottom: 20px; } fieldset { margin-bottom: 20px; } input[type="text"], input[type="password"], input[type="email"], textarea, select { border: 1px solid #ccc; padding: 6px 4px; outline: none; -moz-border-radius: 2px; -webkit-border-radius: 2px; border-radius: 2px; font: 13px "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; color: #777; margin: 0; width: 210px; max-width: 100%; display: block; margin-bottom: 20px; background: #fff; } select { padding: 0; } input[type="text"]:focus, input[type="password"]:focus, input[type="email"]:focus, textarea:focus { border: 1px solid #aaa; color: #444; -moz-box-shadow: 0 0 3px rgba(0,0,0,.2); -webkit-box-shadow: 0 0 3px rgba(0,0,0,.2); box-shadow: 0 0 3px rgba(0,0,0,.2); } textarea { min-height: 60px; } label, legend { display: block; font-weight: bold; font-size: 13px; } select { width: 220px; } input[type="checkbox"] { display: inline; } label span, legend span { font-weight: normal; font-size: 13px; color: #444; } /* #Misc ================================================== */ .remove-bottom { margin-bottom: 0 !important; } .half-bottom { margin-bottom: 10px !important; } .add-bottom { margin-bottom: 20px !important; } /* #Syntax highlighting ================================================== */ .highlight { color: #f8f8f2; table-layout: fixed; white-space: nowrap; width:90%; } .highlight pre, .highlight code { display:block; margin:0; padding:0; background: none; overflow:auto; word-wrap: normal; } .highlight, .linenodiv { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIHWPQ1dU1BgABzQC7XXMTYQAAAABJRU5ErkJggg==); display:block; padding: 10px; margin-bottom:20px; } .gutter, .lineno { color: #ccc; } td.gl { width: 40px; } .gutter { border-right: none; padding: 10px; text-align: right; } span.lineno { display: block; float: left; width: 40px; padding-right: 8px; text-align: right; } .hll { background-color: #49483e } .c { color: #75715e } /* Comment */ .err { color: #960050; background-color: #1e0010 } /* Error */ .k { color: #66d9ef } /* Keyword */ .l { color: #ae81ff } /* Literal */ .n { color: #f8f8f2 } /* Name */ .o { color: #f92672 } /* Operator */ .p { color: #f8f8f2 } /* Punctuation */ .cm { color: #75715e } /* Comment.Multiline */ .cp { color: #75715e } /* Comment.Preproc */ .c1 { color: #75715e } /* Comment.Single */ .cs { color: #75715e } /* Comment.Special */ .ge { font-style: italic } /* Generic.Emph */ .gs { font-weight: bold } /* Generic.Strong */ .kc { color: #66d9ef } /* Keyword.Constant */ .kd { color: #66d9ef } /* Keyword.Declaration */ .kn { color: #f92672 } /* Keyword.Namespace */ .kp { color: #66d9ef } /* Keyword.Pseudo */ .kr { color: #66d9ef } /* Keyword.Reserved */ .kt { color: #66d9ef } /* Keyword.Type */ .ld { color: #e6db74 } /* Literal.Date */ .m { color: #ae81ff } /* Literal.Number */ .s { color: #e6db74 } /* Literal.String */ .na { color: #a6e22e } /* Name.Attribute */ .nb { color: #f8f8f2 } /* Name.Builtin */ .nc { color: #a6e22e } /* Name.Class */ .no { color: #66d9ef } /* Name.Constant */ .nd { color: #a6e22e } /* Name.Decorator */ .ni { color: #f8f8f2 } /* Name.Entity */ .ne { color: #a6e22e } /* Name.Exception */ .nf { color: #a6e22e } /* Name.Function */ .nl { color: #f8f8f2 } /* Name.Label */ .nn { color: #f8f8f2 } /* Name.Namespace */ .nx { color: #a6e22e } /* Name.Other */ .py { color: #f8f8f2 } /* Name.Property */ .nt { color: #f92672 } /* Name.Tag */ .nv { color: #f8f8f2 } /* Name.Variable */ .ow { color: #f92672 } /* Operator.Word */ .w { color: #f8f8f2 } /* Text.Whitespace */ .mf { color: #ae81ff } /* Literal.Number.Float */ .mh { color: #ae81ff } /* Literal.Number.Hex */ .mi { color: #ae81ff } /* Literal.Number.Integer */ .mo { color: #ae81ff } /* Literal.Number.Oct */ .sb { color: #e6db74 } /* Literal.String.Backtick */ .sc { color: #e6db74 } /* Literal.String.Char */ .sd { color: #e6db74 } /* Literal.String.Doc */ .s2 { color: #e6db74 } /* Literal.String.Double */ .se { color: #ae81ff } /* Literal.String.Escape */ .sh { color: #e6db74 } /* Literal.String.Heredoc */ .si { color: #e6db74 } /* Literal.String.Interpol */ .sx { color: #e6db74 } /* Literal.String.Other */ .sr { color: #e6db74 } /* Literal.String.Regex */ .s1 { color: #e6db74 } /* Literal.String.Single */ .ss { color: #e6db74 } /* Literal.String.Symbol */ .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ .vc { color: #f8f8f2 } /* Name.Variable.Class */ .vg { color: #f8f8f2 } /* Name.Variable.Global */ .vi { color: #f8f8f2 } /* Name.Variable.Instance */ .il { color: #ae81ff } /* Literal.Number.Integer.Long */ .gh { } /* Generic Heading & Diff Header */ .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ jgrapht-jgrapht-1.5.1/docs/_includes/css/main.css000066400000000000000000000163141402514743400217460ustar00rootroot00000000000000html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } /* ---------------------------*/ /* ----- Special Styles ----- */ /* ---------------------------*/ /* ----- colors (autogenerated from _config.yml)----- */ {% for c in site.colors %} .border-{{c[0]}} { border-color: {{ c[1] }} !important; } .text-{{c[0]}} { color: {{ c[1] }}; } .text-{{c[0]}} a { color: {{ c[1] }}; } .bg-{{c[0]}} { background-color: {{ c[1] }} !important; } {% endfor %} /* ----- per-post colors! ----- */ {% for node in site.posts %} {% capture id %}{{ node.id | remove:'/' | downcase }}{% endcapture %} {% capture bg %}{% if site.colors[node.bg] %}{{ site.colors[node.bg] }}{% else %}{{ node.bg }}{% endif %}{% endcapture %} {% capture fg %}{% if site.colors[node.color] %}{{ site.colors[node.color] }}{% else %}{{ node.color }}{% endif %}{% endcapture %} nav .p-{{id}} { border-color: {{ bg }}; } #{{id}} { background-color: {{ bg }} !important; color: {{ fg }}; } #{{id}} a { color: {{ fg }}; } #{{id}} .sectiondivider { color: {{ bg }}; } {% endfor %} /* ----- code, syntax highlighting, etc ----- */ code, pre { font-family: Monaco, Menlo, Consolas, "Courier New", monospace; } /* spesifically inline code */ code, pre { background: rgba(255,255,255,0.2); display: inline; word-wrap: break-word; } /* block code */ pre code { background: none; display: block; } pre { display: block; margin: 20px 5%; padding: 4px 8px; background: rgba(255,255,255,0.1); word-wrap: break-word; } .highlight { margin:20px 5%; } /* ----- base elements ----- */ img { max-width:100%!important; height:auto; vertical-align:middle; } hr { margin:60px auto; width:50%; border-color: {{ site.colors.black }}; } .container { word-wrap: break-word; } .center { text-align: center; } .left, .container .left { text-align: left; } .container h1, .container h2, .container h3, .container h4 { margin-bottom: 20px; text-align: center; padding: 0 4%; } .container p, .container ol, .container ul { font-size: 17px; padding: 0 5%; } .container ol, .container ul { padding: 0 8%; } .container p:first-of-type { margin-top: 40px; } /* keep embedded videos fluid! */ .icontain { position: relative; height: 0; overflow: hidden; padding-bottom: 56.25%; /* keep 16x9 Aspect Ratio */ } .i4x3 { padding-bottom: 75.00%; } /* keep 4x3 Aspect Ratio */ .icontain iframe { position: absolute; top:0; left: 0; width: 100%; height: 100%; } .inlineblock { display:-moz-inline-stack; display:inline-block; zoom:1; *display:inline; } /* ---------------------------*/ /* ----- Main Structure ----- */ /* ---------------------------*/ /* ----- top menu ----- */ {% assign navborder = 3 %} {% assign navborder_active = 6 %} nav { font-size:15px; width:100%; position:fixed; z-index:100; top:0; left:0; background:#2e2e2e; } nav ul { list-style:none; text-align:center; padding:0; margin:0; letter-spacing:-4px; } nav ul li { display:inline-block; border-top:{{navborder}}px solid; padding: {{navborder}}px; *display:inline; zoom:1; line-height:normal; letter-spacing:normal; text-transform:uppercase; min-width:110px; line-height:60px; margin:0; } nav ul li a, nav ul li a:visited { display:block; color:#fff; text-decoration:none; font-weight:600; opacity:.75; } nav ul li a:hover { opacity:1 } nav ul li:hover, nav ul li.active { border-top-width: {{navborder_active}}px; padding-top: 0; } /* ----- sections/articles ----- */ .section { position:relative; display:block; width:100%; min-height:300px; padding:210px 0; background:url(img/bgnoise.png); /* generated noise from noisetexturegenerator.com */ } .section:first-of-type { padding-top: 140px; } #footer { padding: 8px 0; min-height:0; text-align:center; background-color:#2e2e2e; background-image:none; } #footer .container p { font-size:13px; margin:0; } .subtlecircle { text-align:center; z-index:3; border-radius:50%; -moz-border-radius:50%; -webkit-border-radius:50%; box-shadow: 0px 1px 15px rgba(0,0,0,0.05); background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAF0lEQVQIHWP8//+/MQMaYELjg7kUCgIASm8DOqGzfp8AAAAASUVORK5CYII=); } .sectiondivider { width:270px; height:270px; padding:15px; position:absolute; top:-135px; left:50%; margin-left:-135px; } .sectiondivider img { width:200px; height:240px; position: static; margin-top: -20px; } .sectiondivider .fa-stack { font-size: 130px; position: static; margin-top: -8px; } .sectiondivider .fa-circle { color: #fff; } .sectiondivider h5 { font-size:15px; font-weight:700; text-transform:uppercase; position:absolute; bottom:50px; left:auto; text-align:center; display:block; z-index:6; width:240px; } .sectiondivider.imaged { text-shadow: 1px 1px 3px #333; } .columned { -webkit-column-count: 3; -moz-column-count: 3; column-count: 3; -webkit-column-gap: 40px; -moz-column-gap: 40px; column-gap: 40px; -webkit-column-rule: 1px outset rgba(255,255,255,0.5); -moz-column-rule: 1px outset rgba(255,255,255,0.5); column-rule: 1px outset rgba(255,255,255,0.5); } .longlist { font-size: 14px !important; } .longlist li { margin-bottom: 3px; } /* ----- fork on github banner ----- */ #forkongithub a { color:#fff; text-decoration:none; font-family:arial,sans-serif; text-align:center; font-weight:700; font-size:1rem; line-height:2rem; position:relative; transition:.5s; padding:5px 40px; } #forkongithub a::before, #forkongithub a::after { content:""; width:100%; display:block; position:absolute; top:1px; left:0; height:1px; background:#fff; } #forkongithub a::after { bottom:1px; top:auto; } @media screen and (min-width:800px) { #forkongithub { position:fixed; display:block; top:0; right:0; width:200px; overflow:hidden; height:200px; z-index:9999; } #forkongithub a { width:200px; position:absolute; top:60px; right:-60px; transform:rotate(45deg); -webkit-transform:rotate(45deg); -ms-transform:rotate(45deg); -moz-transform:rotate(45deg); -o-transform:rotate(45deg); box-shadow:4px 4px 10px rgba(0,0,0,0.8); box-sizing: content-box; } } /* mid size (tablets, landscapes) */ @media only screen and (max-width: 679px) { nav { font-size:11px; } nav ul li { min-width:60px; line-height:40px; } } /* tiny size (phones) */ @media only screen and (max-width: 380px) { nav ul li { min-width:90px; line-height:20px; } } /* anything not desktop */ @media only screen and (max-width: 767px) { .container h1 { font-size: 30px; } .container h2 { font-size: 24px; } .container h3 { font-size: 20px; } .container h4 { font-size: 18px; } .section { padding:130px 0; } .sectiondivider { width:200px; height:200px; padding:15px; top:-100px; margin-left:-100px; } .sectiondivider img { width:150px; height:180px; } .sectiondivider .fa-stack { font-size: 100px; margin-top: -14px; } .sectiondivider h5 { font-size:15px; bottom:30px; width:170px } .columned { -webkit-column-count: 2; -moz-column-count: 2; column-count: 2; } } jgrapht-jgrapht-1.5.1/docs/_includes/css/skeleton.css000066400000000000000000000046741402514743400226540ustar00rootroot00000000000000 /* -----------------------------------*/ /* ----- 960px wide fancy grid! ----- */ /* -----------------------------------*/ /* by tim o'brien, t413.com * based on getskeleton.com */ /* ----- base grid----- */ .container { position: relative; width: 960px; margin: 0 auto; padding: 0; } .container .column { float: left; display: inline; margin-left: 10px; margin-right: 10px; } .row { margin-bottom: 20px; } .container .small.column { width: 300px; } .container .half.column { width: 460px; } .container .big.column { width: 620px; } .container .full.column { width: 940px; } /* ----- Tablet (Portrait) -- 768px ----- */ @media only screen and (min-width: 768px) and (max-width: 959px) { .container { width: 768px; } .container .small.column { width: 236px; } .container .half.column { width: 364px; } .container .big.column { width: 488px; } .container .full.column { width: 748px; } } /* ----- Mobile (Portrait) ----- */ @media only screen and (max-width: 767px) { .container { width: 96%; } .container .column { margin: 1%; } .container .small.column { width: 48%; } .container .half.column { width: 48%; } .container .big.column { width: 98%; } .container .full.column { width: 98%; } } /* ----- Mobile (Landscape) -- 480px ----- */ @media only screen and (min-width: 480px) and (max-width: 767px) { .container { width: 92%; } .container .column { margin: 2%; } .container .small.column { width: 46%; } .container .half.column { width: 46%; } .container .big.column { width: 96%; } .container .full.column { width: 96%; } } /* ----- Clearing ----- */ /* Self Clearing Goodness */ .container:after { content: "\0020"; display: block; height: 0; clear: both; visibility: hidden; } /* Use clearfix class on parent to clear nested columns, or wrap each row of columns in a
*/ .clearfix:before, .clearfix:after, .row:before, .row:after { content: '\0020'; display: block; overflow: hidden; visibility: hidden; width: 0; height: 0; } .row:after, .clearfix:after { clear: both; } .row, .clearfix { zoom: 1; } /* You can also use a
to clear columns */ .clear { clear: both; display: block; overflow: hidden; visibility: hidden; width: 0; height: 0; } jgrapht-jgrapht-1.5.1/docs/_includes/footer.md000077500000000000000000000003151402514743400213350ustar00rootroot00000000000000 Design by Tim O'Brien [t413.com](http://t413.com/) — [SinglePaged theme](https://github.com/t413/SinglePaged) Website © copyright 2003-2018, by Barak Naveh and Contributors. All rights reserved. jgrapht-jgrapht-1.5.1/docs/_layouts/000077500000000000000000000000001402514743400174055ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/docs/_layouts/default.html000066400000000000000000000017561402514743400217300ustar00rootroot00000000000000 < {% seo %}
JGraphT Logo {{ content }}
{% include analytics.html %} jgrapht-jgrapht-1.5.1/docs/_posts/000077500000000000000000000000001402514743400170555ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/docs/_posts/2000-01-01-intro.md000077500000000000000000000034371402514743400215570ustar00rootroot00000000000000--- title: "Home" bg: white color: black style: center --- JGraphT Logo ### a Java library of graph theory data structures and algorithms {: .text-blue} #### now with [Python bindings](https://pypi.org/project/jgrapht) too! {: .text-blue}
### *flexible* ##### **any object** can be used for vertex and edge types, with full **type safety** via generics ##### edges can be **directed** or **undirected**, **weighted** or **unweighted** ##### **simple graphs**, **multigraphs**, and **pseudographs** ##### **unmodifiable** graphs allow modules to provide "read-only" access to internal graphs ##### **listenable** graphs allow external listeners to track modification events ##### live **subgraph** views on other graphs ##### **compositions** and **converter views** for combining and adapting graphs ##### **customizable** incidence and adjacency representations
### *powerful* ##### specialized **iterators** for graph traversal (**DFS**, **BFS**, etc) ##### **algorithms** for path finding, clique detection, isomorphism detection, coloring, common ancestors, tours, connectivity, matching, cycle detection, partitions, cuts, flows, centrality, spanning, **and the list goes on** ##### **exporters** and **importers** for popular external representations such as GraphViz ##### **live adapters** to other graph libraries such as **JGraphX visualization** and **Guava Graphs** ##### **generators** and **transforms**
### *efficient* ##### designed for performance, with **near-native** speed in many cases ##### adapters for memory-optimized **fastutil** representation ##### **sparse** representations for immutable graphs Fork me on GitHub jgrapht-jgrapht-1.5.1/docs/_posts/2000-01-02-jumpstart.md000077500000000000000000000016441402514743400224540ustar00rootroot00000000000000--- title: "Jumpstart" bg: green color: black fa-icon: bolt --- ## Maven JGraphT releases are published to the **Maven Central Repository**, so you can easily add us as a dependency to your project:
<groupId>org.jgrapht</groupId>
<artifactId>jgrapht-core</artifactId>
<version>1.5.0</version>
(There are also [instructions](https://github.com/jgrapht/jgrapht#using-via-maven) for how to use the latest **SNAPSHOT** build instead.)
## Development Environment First, find out how to set up [your favorite IDE (or the command line)](https://github.com/jgrapht/jgrapht/wiki/Users:-How-to-use-JGraphT-as-a-dependency-in-your-projects) to work with JGraphT.
## Hello JGraphT Next, try compiling and running the [hello world](guide/HelloJGraphT) example. Once you get that working, dig into the [user guide](guide/UserOverview) to learn more about JGraphT! jgrapht-jgrapht-1.5.1/docs/_posts/2000-01-03-docs.md000077500000000000000000000015621402514743400213530ustar00rootroot00000000000000--- title: "Docs" bg: blue color: black fa-icon: book --- ##  [README](https://github.com/jgrapht/jgrapht/blob/master/README.md) ##  [User Guide](guide/UserOverview) ##  [Wiki](https://github.com/jgrapht/jgrapht/wiki) ##  [Javadoc for latest release](javadoc) ##   [Javadoc for latest SNAPSHOT build](javadoc-SNAPSHOT) ##   Javadoc for older releases, e.g. [javadoc-1.0.0](javadoc-1.0.0) ##  [Documentation for python bindings](https://python-jgrapht.readthedocs.io) ##  [Research Paper in ACM TOMS](https://dl.acm.org/doi/10.1145/3381449) jgrapht-jgrapht-1.5.1/docs/_posts/2000-01-04-download.md000077500000000000000000000022521402514743400222300ustar00rootroot00000000000000--- title: "Download" bg: turquoise color: white fa-icon: cloud-download style: center --- # Latest Release For development without Maven, or for [running demos from the command line](https://github.com/jgrapht/jgrapht/wiki/Users:-Running-JGraphT-demos), you can download a full archive of the release: Regardless of which archive format you download, you'll have the same [release contents](https://github.com/jgrapht/jgrapht#release-contents) after unpacking.
# Historical Releases [Older releases](http://sourceforge.net/project/showfiles.php?group_id=86459&package_id=89784) are also available. They have less functionality, but may be useful with obsolete JDK's or JRE's. jgrapht-jgrapht-1.5.1/docs/_posts/2000-01-05-community.md000066400000000000000000000021131402514743400224370ustar00rootroot00000000000000--- title: "Community" bg: purple color: white fa-icon: users --- ##  Get answers on [StackOverflow](https://stackoverflow.com/questions/tagged/jgrapht) ##  Browse [known issues](https://github.com/jgrapht/jgrapht/issues) or report [a new issue](https://github.com/jgrapht/jgrapht/wiki/Users:-Getting-Support) ##  Join [the user mailing list](https://sourceforge.net/projects/jgrapht/lists/jgrapht-users) ##  Check out [open pull requests](https://github.com/jgrapht/jgrapht/pulls) ##  Learn how to [contribute your first improvement](https://github.com/jgrapht/jgrapht/wiki/Dev-guide:-Become-a-Contributor) ##  Tell us your success story: [how are you are using JGraphT?](https://github.com/jgrapht/jgrapht/wiki/Users:-Projects-Using-JGraphT) ##  Cite JGraphT [in your research](https://github.com/jgrapht/jgrapht/wiki/Users:-How-to-cite-JGraphT) jgrapht-jgrapht-1.5.1/docs/_posts/2000-01-06-news.md000066400000000000000000000016331402514743400213760ustar00rootroot00000000000000--- title: "News" bg: orange color: white fa-icon: exclamation style: center --- ## **29-Jun-2020:** First Release of Python Bindings! ### Read the [announcement](https://medium.com/@dimitrios.michail/announcing-the-python-bindings-of-jgrapht-918d0cf386de) here. ## **14-Jun-2020:** Release 1.5.0 is now available! ### Read the [release announcement](https://sourceforge.net/p/jgrapht/news/2020/06/jgrapht-version-150-released/) for more info. ## **21-May-2020:** JGraphT Research Paper Published! ### [Our paper](https://dl.acm.org/doi/10.1145/3381449), published in the ACM Transactions on Mathematical Software, provides an in-depth look at the design of JGraphT, and also includes performance comparisons against other libraries. ## **21-Feb-2020:** Release 1.4.0 is now available! ### Read the [release announcement](https://sourceforge.net/p/jgrapht/news/2020/02/jgrapht-version-140-released/) for more info. jgrapht-jgrapht-1.5.1/docs/_posts/2000-01-07-ack.md000066400000000000000000000034121402514743400211560ustar00rootroot00000000000000--- title: "Thanks" bg: lavender fa-icon: heart color: black style: center --- The JGraphT team is grateful to all of our [contributors](https://github.com/jgrapht/jgrapht/blob/master/CONTRIBUTORS.md) over the years for making the project what it is today!
JGraphT is dual-licensed under [LGPL 2.1](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html) and [EPL 2.0](https://www.eclipse.org/legal/epl-2.0/). As a recipient of JGraphT, you may choose which license to receive the code under. Licensing information for libraries on which the project depends is available in the [README](https://github.com/jgrapht/jgrapht#dependencies). Project development takes place on [github](https://github.com/jgrapht/jgrapht), but we still make use of [sourceforge](https://sourceforge.net/projects/jgrapht) for some resources as well. This website is built using [Jekyll](https://github.com/jekyll/jekyll), with help from the [SinglePaged theme](https://github.com/t413/SinglePaged) and the [Primer theme](https://github.com/pages-themes/primer).
If you enjoy using JGraphT, show us by clicking the **Like** button for our [Facebook page](https://www.facebook.com/jgrapht)!

jgrapht-jgrapht-1.5.1/docs/combo.css000066400000000000000000000001351402514743400173560ustar00rootroot00000000000000--- --- {% include css/base.css %} {% include css/skeleton.css %} {% include css/main.css %} jgrapht-jgrapht-1.5.1/docs/guide-templates/000077500000000000000000000000001402514743400206375ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/docs/guide-templates/GuavaAdapter.md000066400000000000000000000032561402514743400235330ustar00rootroot00000000000000--- title: Guava Graph Adapter --- # {{ page.title }} If you are using [Guava's common.graph data structure](https://github.com/google/guava/wiki/GraphsExplained), and would like to take advantage of an algorithm implemented by JGraphT, it's quite straightforward to do this via the adapters supplied by JGraphT. For example, suppose you've created a Guava graph as follows: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableGraphAdapterTest.java?example=createGuavaGraph) ``` The graph does not have any information associated with the edges, so we can use JGraphT's [MutableGraphAdapter](https://jgrapht.org/javadoc/org.jgrapht.guava/org/jgrapht/graph/guava/MutableGraphAdapter.html) to view it in JGraphT: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableGraphAdapterTest.java?example=adaptGuavaGraph) ``` Now suppose we want to find a [minimum vertex cover](https://brilliant.org/wiki/vertex-cover) for this graph. JGraphT supplies [several algorithms](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/alg/vertexcover/package-summary.html) for this purpose: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/MutableGraphAdapterTest.java?example=findVertexCover) ``` Since the result is just a set of strings, it can be used to directly reference the JGraphT view as well as the underlying Guava graph. For more information on the available adapters, please see the [org.jgrapht.graph.guava javadoc](https://jgrapht.org/javadoc/org.jgrapht.guava/org/jgrapht/graph/guava/package-summary.html). jgrapht-jgrapht-1.5.1/docs/guide-templates/HelloJGraphT.md000066400000000000000000000003001402514743400234350ustar00rootroot00000000000000--- title: Hello JGraphT Complete Example --- # {{ page.title }} ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java) ``` jgrapht-jgrapht-1.5.1/docs/guide-templates/LabeledEdges.md000066400000000000000000000042711402514743400234650ustar00rootroot00000000000000--- title: Labeled Edges --- # {{ page.title }} A common requirement for JGraphT applications is the need to associate a label with each edge. This can be accomplished efficiently via a custom edge class: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/LabeledEdges.java?example=edgeclass) ``` JGraphT's default graph and edge implementations take care of maintaining the connectivity information between vertices, so the custom edge subclass only needs to store the label. Since the custom edge class does not override `equals`/`hashCode`, each edge object is distinct from every other edge object (regardless of whether they share the same label). Consequently, labels do not have to be unique within the same graph. As defined above, `RelationshipEdge` could be used in either a directed or undirected graph. In the example below, we apply it to a a backstabby form of non-symmetric friendship via a directed graph: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/LabeledEdges.java?example=create) ``` Since the `RelationshipEdge` class does not have a default constructor, edges must be explicitly instantiated and added via [addEdge(V,V,E)](http://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#addEdge-V-V-E-) rather than implicitly instantiated via [addEdge(V,V)](http://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#addEdge-V-V-). Once the graph has been populated, label information can be accessed during traversal: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/LabeledEdges.java?example=print) ``` Given two vertices, an application can check the label on the edge between them by using [getEdge(V,V)](http://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#getEdge-V-V-): ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/LabeledEdges.java?example=isEnemyOf) ``` You can find the complete source code for this example at [LabeledEdge.java](https://github.com/jgrapht/jgrapht/blob/master/jgrapht-demo/src/main/java/org/jgrapht/demo/LabeledEdges.java) jgrapht-jgrapht-1.5.1/docs/guide-templates/UserOverview.md000066400000000000000000000575251402514743400236440ustar00rootroot00000000000000--- title: Overview for Application Developers --- # {{ page.title }} {:.no_toc} This overview will help get you started with using the JGraphT library in your own applications. We'll cover the following topics: 1. Table of contents {:toc} ## Development Setup First, [set up your development environment](https://github.com/jgrapht/jgrapht/wiki/Users:-How-to-use-JGraphT-as-a-dependency-in-your-projects) with JGraphT as a dependency. ## Hello JGraphT In JGraphT, a graph is defined as a set of vertices connected by a set of edges. Many possible variations on this fundamental definition are supported, as we'll explain further on; but for now, let's take a look at a simple example of creating a directed graph: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java?example=uriCreate) ``` Notice how the vertex objects are instances of the [java.net.URI](https://docs.oracle.com/javase/8/docs/api/java/net/URI.html) class. JGraphT does not supply a vertex class itself; instead, you're free to choose your own based on whatever works best for your application, subject to certain restrictions mentioned below. You are also free to choose your own edge class. If you don't need to associate any application-specific information with your edges, you can just use the library-supplied [DefaultEdge](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/DefaultEdge.html) as in this example. The graph constructor takes the edge class as a parameter so that it can create new edge objects implicitly whenever `addEdge` is called to connect two vertices. ## Choosing Vertex and Edge Types There are a number of restrictions to be aware of when choosing custom vertex and edge types, mostly regarding override of the `equals`/`hashCode` methods; be sure to read through [this overview](VertexAndEdgeTypes). ## Graph Accessors Once a graph has been created, an application can access its vertices and edges directly via live set views: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java?example=findVertex) ``` Here we iterate over all vertices of the graph via the [vertexSet](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#vertexSet--) method, filtering for only those whose URL has `www.jgrapht.org` for its hostname; in our example, we can expect to find exactly one match, which we obtain via `findAny().get()`. Given a reference to a vertex or edge, we can find connections via `Graph` methods such as `getEdgeSource`, `getEdgeTarget`, `edgesOf`, `incomingEdgesOf`, and `outgoingEdgesOf`. Given a pair of vertices, we can find the edge(s) connecting them via `getEdge` and `getAllEdges`. Here, collection-returning methods should not to be assumed to be live views (although they may be for some graph implementations). In some cases, the returned collections may be unmodifiable, while in others they may consist of transient results. In no case should an application expect modifications to the returned collection to result in modifications to the underyling graph. The [Graphs](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graphs.html) utility class has additional convenience methods such as [successorListOf](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graphs.html#successorListOf-org.jgrapht.Graph-V-) and [getOppositeVertex](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graphs.html#getOppositeVertex-org.jgrapht.Graph-E-V-) for easing common access patterns. Note that the default graph implementations guarantee predictable ordering for the collections that they maintain; so, for example, if you add vertices in the order `[B, A, C]`, you can expect to see them in that order when iterating over the vertex set. However, this is not a requirement of the `Graph` interface, so other graph implementations are not guaranteed to honor it. ## Graph Structures Besides choosing your vertex and edge classes, JGraphT also allows you to choose a graph structure. One way to do so is by instantiating a concrete class which implements the [Graph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html) interface, as with `DefaultDirectedGraph` in the example above. When doing so, you can make your selection from the table below (or from your own subclasses of any of these). | Class Name | Edges | Self-loops | Multiple edges | Weighted | |:----------------------------:|:--------:|:----------:|:--------------:|:--------:| |SimpleGraph |undirected|no | no |no | |Multigraph |undirected|no | yes |no | |Pseudograph |undirected|yes | yes |no | |DefaultUndirectedGraph |undirected|yes | no |no | |SimpleWeightedGraph |undirected|no | no |yes | |WeightedMultigraph |undirected|no | yes |yes | |WeightedPseudograph |undirected|yes | yes |yes | |DefaultUndirectedWeightedGraph|undirected|yes | no |yes | |SimpleDirectedGraph |directed |no | no |no | |DirectedMultigraph |directed |no | yes |no | |DirectedPseudograph |directed |yes | yes |no | |DefaultDirectedGraph |directed |yes | no |no | |SimpleDirectedWeightedGraph |directed |no | no |yes | |DirectedWeightedMultigraph |directed |no | yes |yes | |DirectedWeightedPseudograph |directed |yes | yes |yes | |DefaultDirectedWeightedGraph |directed |yes | no |yes | The structural properties are as follows: * undirected edges: an edge simply connects a vertex pair, without imposing a direction * directed edges: an edge has a source and a target * self-loops: whether to allow edges which connect a vertex to itself * multiple edges: whether to allow more than one edge between the same vertex pair (note that in a directed graph, two edges between the same vertex pair but with opposite direction do not count as multiple edges) * weighted: whether a double weight is associated with each edge (for these graph types, you'll usually want to use [DefaultWeightedEdge](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/DefaultWeightedEdge.html) as your edge class); unweighted graphs are treated as if they have a uniform edge weight of 1.0, which allows them to be used in algorithms such as finding a shortest path The [GraphType](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/GraphType.html) interface allows you to access this metadata for an existing graph instance (using the [getType](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#getType--) accessor). You can also use [GraphTypeBuilder](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/builder/GraphTypeBuilder.html) to instantiate a new graph without directly constructing a concrete class: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphBuilderDemo.java?example=buildType) ``` `GraphTypeBuilder` uses the property values you supply in order to automatically choose the correct concrete class for you. This is generally a cleaner pattern to follow, but it's not applicable if you end up needing to subclass one of the provided graph classes. ## Graph Modification Earlier, we saw how to add vertices and edges to a new graph by calling the [addVertex](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#addVertex-V-) and [addEdge](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#addEdge-V-V-) methods on the `Graph` interface. Likewise, there are corresponding methods for removing graph components. All of these methods are modeled on the `java.util` collections framework, so: * adding a duplicate object to a set (e.g. when adding a vertex) is not an error, but the duplicate is discarded * adding a duplicate object to a non-unique collection (e.g. when adding an edge to a multigraph) inserts the new instance * attempting to remove an object which was not part of the graph is not an error * but _attempting to access attributes of an object which is not part of the graph_ is strictly forbidden, and results in an `IllegalArgumentException` (e.g. when you ask for the edges of a vertex, but the vertex is not currently part of the graph) The strictness enforcement mentioned above is the default in order to help catch application errors. There are two convenience helpers available to assist with this when adding components to a graph; both of them take care of automatically adding vertices whenever edges are added: * The [Graphs](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graphs.html) utility class provides methods such as [addEdgeWithVertices](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graphs.html#addEdgeWithVertices-org.jgrapht.Graph-V-V-) * The [GraphBuilder](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/builder/AbstractGraphBuilder.html) framework also allows you to use [method chaining](https://en.wikipedia.org/wiki/Method_chaining) when populating data in a new graph. Here's an example using `GraphBuilder` to construct a [kite graph](http://mathworld.wolfram.com/KiteGraph.html): ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphBuilderDemo.java?example=buildEdges) ``` The integer vertex objects are added to the graph implicitly as the referencing edges are added. Note that building the graph proceeds in two phases; first `buildEmptySimpleGraph` builds an empty graph instance for the specified graph type, then `GraphBuilder` takes over for populating the vertices and edges. ### Vertex and Edge Suppliers JGraphT optionally allows you to provide a graph with vertex and edge suppliers. When these are available, the graph will automatically construct a new object instance whenever one is not explicitly supplied by the corresponding `add` method. ### Modification Listeners JGrapht provides a framework for reacting to graph modifications via the [ListenableGraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/ListenableGraph.html) interface. By default, graph instances are not listenable for efficiency; here's how to use the framework: * wrap your graph instance via [DefaultListenableGraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/DefaultListenableGraph.html) * perform all modifications on the wrapper (not the underlying graph instance) * register one or more [GraphListener](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/event/GraphListener.html) to react to modification events This can be a convenient way to keep other data structures or visualizations in sync with graph changes. For example, suppose your graph represents a CAD model being visualized; then every time the graph is edited, all affected views can be automatically refreshed from listener events. ### Concurrency The default graph implementations are not safe for concurrent reads and writes from different threads. If an application attempts to modify a graph in one thread while another thread is reading or writing the same graph, undefined behavior will result. However, concurrent reads against the same graph from different threads are safe. (Note that the Graph interface itself makes no such guarantee, so for non-default implementations, different rules may apply.) If you need support for concurrent reads and writes, consider using the [AsSynchronizedGraph wrapper](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/concurrent/AsSynchronizedGraph.html). ## Graph Generation Besides constructing vertices and edges individually, applications can also generate graph instances according to predefined patterns. This is often useful for generating test cases or default topologies. JGraphT provides a number of different generators for this purpose in the [org.jgrapht.generate](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/generate/package-summary.html) package. Here's an example of generating a [complete graph](http://mathworld.wolfram.com/CompleteGraph.html): ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/CompleteGraphDemo.java?example=class) ``` The `SIZE` parameter controls the number of vertices added to the graph (which in turn dictates the number of edges added). ## Graph Traversal Once you've created a graph, you can traverse it using an ordering such as depth-first, breadth-first, or topological. JGraphT provides for this via package [org.jgrapht.traverse](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/traverse/package-summary.html). The common interface is [GraphIterator](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/traverse/GraphIterator.html), which specializes the generic Java `Iterator` interface with JGraphT specifics. A graph iterator produces vertices in the requested order; as the iteration proceeds, additional information (such as when a particular edge is traversed) can be obtained by registering a [TraversalListener](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/event/TraversalListener.html). (The specific meaning of traversal events varies with the iterator type.) Here's an example using depth-first ordering on our HelloJGraphT example: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java?example=traverse) ``` with expected output ``` http://www.jgrapht.org http://www.wikipedia.org http://www.google.com ``` In this example, no extra information is required during the traversal, so it is treated as a standard Java `Iterator`. ## Graph Algorithms Beyond basic traversals, you'll often want to run more complex algorithms on a graph. JGraphT provides quite a few of these, so they are subcategorized under the [org.jgrapht.alg parent package](https://jgrapht.org/javadoc/org.jgrapht.core/module-summary.html). For example, various shortest path algorithms are implemented in [org.jgrapht.alg.shortestpath](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/alg/shortestpath/package-summary.html). In cases where there are alternative algorithms available for the same problem, the commonality is abstracted via an interface in [org.jgrapht.alg.interfaces](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/alg/interfaces/package-summary.html). This makes it easier to write application code which selects an optimal algorithm implementation for a given graph instance. Here's an example of running [strongly connected components](http://mathworld.wolfram.com/StronglyConnectedComponent.html) and shortest path algorithms on a directed graph: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/DirectedGraphDemo.java?example=main) ``` with expected output ``` Strongly connected components: ([i], []) ([h], []) ([e, f, g], [(e,f), (f,g), (g,e)]) ([a, b, c, d], [(a,b), (b,d), (d,c), (c,a)]) Shortest path from i to c: [(i : h), (h : e), (e : d), (d : c)] Shortest path from c to i: null ``` ## Graph Serialization and Export/Import The default graph implementations provided by JGraphT are [serializable](https://docs.oracle.com/javase/8/docs/api/java/io/Serializable.html) as long as you choose vertex and edge types which are themselves serializable. Serialization is a convenient way to store a graph instance as binary data, but the format is not human-readable, and we don't make any guarantee of serialization compatibility across JGraphT versions. (In other words, if you serialize a graph with version X, and then attempt to deserialize it with version X+1, an exception may be thrown.) To address this, JGraphT provides module [org.jgrapht.io](https://jgrapht.org/javadoc/org.jgrapht.io/module-summary.html) for exporting and importing graphs in a variety of standard formats. These can also be used for data interchange with other applications. Continuing our HelloJGraphT example, here's how to export a graph in [GraphViz .dot](https://www.graphviz.org/) format: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java?example=render) ``` with expected output ``` strict digraph G { www_google_com [ label="http://www.google.com" ]; www_wikipedia_org [ label="http://www.wikipedia.org" ]; www_jgrapht_org [ label="http://www.jgrapht.org" ]; www_jgrapht_org -> www_wikipedia_org; www_google_com -> www_jgrapht_org; www_google_com -> www_wikipedia_org; www_wikipedia_org -> www_google_com; } ``` which GraphViz renders as: ![example graph rendering](hello.png "Hello GraphViz!") If you just want a quick dump of the structure of a small graph, you can also use the `toString` method; here's another example from the HelloJGraphT demo: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java?example=toString) ``` which produces ``` ([v1, v2, v3, v4], [{v1,v2}, {v2,v3}, {v3,v4}, {v4,v1}]) ``` First comes the vertex set, followed by the edge set. Directed edges are rendered with round brackets, whereas undirected edges are rendered with curly brackets. Custom edge attributes are not rendered. If you want a nicer rendering, you can override [toStringFromSets](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/AbstractGraph.html#toStringFromSets-java.util.Collection-java.util.Collection-boolean-) in your graph implementation, but you're probably better off using one of the exporters instead. ## Graph Cloning The `Graph` interface does not expose a public `clone` method, because we do not require all implementations to be cloneable. However, all subclasses of [AbstractBaseGraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/AbstractBaseGraph.html) are cloneable. The clone semantics are shallow in that the same vertex and edge objects are shared between the original graph and the clone; however, the vertex and edge sets and all associated connectivity structures are copied, not shared, so that the two graphs are otherwise independent. ## Graph Comparisons The default JGraphT implementations of the `Graph` interface override `equals`/`hashCode`, so it's possible to use them to compare two graph instances. However, it's important to note that the definition of equality used may not be the one you are expecting. Here are the rules used: * the two graph instances must be of identical concrete class (e.g. `DefaultDirectedGraph`) * the vertex sets of the two graph instances must be equal (using the definition from [java.util.Set](https://docs.oracle.com/javase/7/docs/api/java/util/Set.html#equals(java.lang.Object)), and taking into account the `equals` implementation of the vertex type you've chosen) * the edges sets of the two graph instances must be equal (again using the `java.util.Set` definition, and taking into account the `equals` implementation of the edge type you've chosen) * for a given edge, the source/target/weight must be equal in both graph instances (for undirected graphs, the source/target distinction is ignored) In general, an exact copy of a graph object via `Graphs.addGraph` or `clone` will be equal to the original according to this definition (assuming the same concrete class is chosen for the copy). However, for copy via serialization followed by deserialization, this won't hold unless both the vertex and edge classes override `equals`/`hashCode`. If you were expecting a structural comparison instead, then you might want to investigate the [isomorphism](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/alg/isomorphism/package-summary.html) package. In the unrestricted case, isomorphism detection can take exponential time, but it can be speeded up significantly if you're able to guide it with a labeling. For example, suppose you have two graphs with anonymous edges, but the vertex set is the same, and you want to decide whether the graphs are effectively equal. In that case, you can run an isomorphism inspector with a comparator specified for the vertices. Then JGraphT can tell you whether the two graphs are structurally equivalent (and if so, provide a mapping between the edge objects). ## Graph Wrappers Besides core graph data structures, JGraphT also provides a number of useful wrappers which allow you to define live _transformed_ views into other graphs: * [AsGraphUnion](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/AsGraphUnion.html): a union of two underlying graphs * [AsSubgraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/AsSubgraph.html): a subgraph (possibly induced) of an underlying graph * [AsUndirectedGraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/AsUndirectedGraph.html): an undirected view of an underlying directed graph (with edge directions ignored) * [AsUnmodifiableGraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/AsUnmodifiableGraph.html): an unmodifiable view of an underlying graph * [AsUnweightedGraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/AsUnweightedGraph.html): an unweighted view of a underlying weighted graph (ignoring all edge weights and treating them as 1.0 instead) * [AsWeightedGraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/AsWeightedGraph.html): a weighted view of an underlying unweighted graph (with edge-specific weights imposed via a map) * [EdgeReversedGraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/graph/EdgeReversedGraph.html): an edge-reversed view of a directed graph Wrappers add some access cost, so if you don't need a live view, and you will be accessing the transformed results heavily, then you can copy the view to a snapshot using [Graphs.addGraph](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graphs.html#addGraph-org.jgrapht.Graph-org.jgrapht.Graph-). ## Graph Adapters ### Guava Graph Adapter If you are already using [com.google.common.graph](https://google.github.io/guava/releases/snapshot/api/docs/com/google/common/graph/package-summary.html) for representing graphs, it's easy to interoperate with JGraphT by using our [adapter package](https://jgrapht.org/javadoc/org.jgrapht.guava/org/jgrapht/graph/guava/package-summary.html). Simply instantiate the correct adapter on top of your Guava graph, and you'll have an implementation of JGraphT's `Graph` interface which stays in sync with the Guava graph automatically, at no extra memory cost. Now you can [run JGraphT algorithms](GuavaAdapter) on top of your Guava graph, or run our importers or exporters against it. ### JGraphX Adapter JGraphT also provides an adapter that lets you use a JGraphT graph instance as the data model for a [JGraphX](https://jgraph.github.io/mxgraph/docs/manual_javavis.html) visualization. All you need to do is wrap your JGraphT graph with [org.jgrapht.ext.JGraphXAdapter](https://jgrapht.org/javadoc/org.jgrapht.ext/org/jgrapht/ext/JGraphXAdapter.html) as in the following example: ```java :[source code](http://code.jgrapht.org/raw/master/jgrapht-demo/src/main/java/org/jgrapht/demo/JGraphXAdapterDemo.java?example=full) ``` ## Running Demos If you want to run the demo programs excerpted throughout this overview, see [these instructions](https://github.com/jgrapht/jgrapht/wiki/Users:-Running-JGraphT-demos). You can also find the full source code in [github](https://github.com/jgrapht/jgrapht/tree/master/jgrapht-demo/src/main/java/org/jgrapht/demo). ## Browsing Unit Tests Another good way to learn how to use the various classes provided by JGraphT is to study their usage in unit tests. Here's the source code of [tests for the core classes](https://github.com/jgrapht/jgrapht/tree/master/jgrapht-core/src/test/java/org/jgrapht). jgrapht-jgrapht-1.5.1/docs/guide-templates/VertexAndEdgeTypes.md000066400000000000000000000311031402514743400246710ustar00rootroot00000000000000--- title: Vertex and Edge Types --- # {{ page.title }} {:.no_toc} When constructing a JGraphT graph, it's important to select the vertex and edge types carefully in order to ensure correct behavior while satisfying application requirements. This page walks through a number of variations based on common application use cases: 1. Table of contents {:toc} ## equals and hashCode Vertex and edge objects are used as keys inside of the default graph implementation, so when choosing their types, you must follow these rules: * You must follow the contract defined in `java.lang.Object` for both [equals](https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object)) and [hashCode](https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#hashCode()). * In particular, if you override either `equals` or `hashCode`, you must override them both * Your implementation for `hashCode` must produce a value which does not change over the lifetime of the object [This article](https://www.ibm.com/developerworks/java/library/j-jtp05273/index.html) explains some of the nuances. Additional guidelines are provided in the scenario-specific sections below. ## Anonymous Vertices Applications interested only in graph structure (e.g. graph theory research) may want to save memory by keeping vertices as minimalist as possible. In this case, just use [java.lang.Object](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html) as the vertex type. In this case, the vertex `hashCode` may serve as a "nearly" unique identifier during debugging. ## Vertices as Key Values More common is for each vertex to represent a key value, as in the [HelloJGraphT](UserOverview#hello-jgrapht) example (where each vertex corresponds to a website identified by a URL). For this use case, a `String`, `Integer`, or similar class is a good choice for vertex type. In this case, it's important to note that the value must be unique within the graph. In other words, for a vertex of type `String`, the `String` value is an _identifier_, not a mere _label_. ## Vertices with Attributes More sophisticated applications may need to associate non-key attributes (possibly multiple) with each vertex. The obvious way to do this is with a class that stores the attributes, but there are a few different cases to consider. ### No keys If all attributes on the vertex are non-key, then the approach is straightforward. For example, suppose you are modeling molecular structures, where the vertices are atoms and the edges are the bonds between them. Then you might define a class ```java class AtomVertex { Element element; // using enum of the periodic table int formalCharge; // for bookkeeping purposes ... other atomic properties ... } ``` In this case, you should **not** override `equals` and `hashCode`, since there may be many distinct atoms with the exact same properties. ### All keys Conversely, if all attributes are components of a key, then the approach is also simple. Suppose your application is a software package manager, and each vertex in your graph corresponds to a package, with edges representing package dependencies. Then you might define a class like ```java class SoftwarePackageVertex { final String orgName; final String packageName; final String packageVersion; ... constructor etc ... public String toString() { return orgName + "-" + packageName + "-" + packageVersion; } public int hashCode() { return toString().hashCode(); } public boolean equals(Object o) { return (o instanceof SoftwarePackageVertex) && (toString().equals(o.toString())); } } ``` Here, you almost certainly **do** want to override `equals` and `hashCode`, since there should not be more than one vertex object representing the same package version. And you'll be able to access an existing vertex in a graph just by constructing it, without having to iterate over all vertices in the graph to find it. Note that the fields are declared final; this is important since vertices and edges are used as hash keys, meaning their hash codes must never change after construction. ### Key subset Now we come to the problematic case. Continuing the previous example, suppose you want to add a `releaseDate` field to `SoftwarePackageVertex` in order to track when the package version was released. This new field is not part of the key; it's just data. But what do we do about `equals`/`hashCode`? * It's not a good idea to incorporate `releaseDate` into them for a number of reasons. For example, if we want to reference the vertex for a package by its identifier, but we don't know its release date, how do we find the vertex? And what if the release date changes for an unreleased package? How do we avoid two distinct vertex objects representing the same package version? * However, if we don't incorporate `releaseDate` into `equals`/`hashCode`, then we could end up with inconsistencies due to two vertex objects with different `releaseDate` values being treated as equivalent. So if you try to implement this case, beware that you're likely to run into unforeseen pitfalls. ## Vertices as Pointers A more flexible way to handle the situation above is to make the vertex refer to an external object rather than containing data itself. For the example above, the vertex type could be `SoftwarePackageVertex` as originally defined, without the release date. Then additional information such as the release date would be stored via a separate `SoftwarePackageVersion` class, with a map keyed on `SoftwarePackageVertex` for lookups. This keeps the graph representation clear, but adds some lookup cost. An optimization is to implement the vertex as a direct reference: ```java class SoftwarePackageVertex { final SoftwarePackageVersion version; public String toString() { return version.keyString(); } public int hashCode() { return toString().hashCode(); } public boolean equals(Object o) { return (o instanceof SoftwarePackageVertex) && (toString().equals(o.toString())); } } class SoftwarePackageVersion { final String orgName; final String packageName; final String packageVersion; Date releaseDate; public String keyString() { return orgName + "-" + packageName + "-" + packageVersion; } } ``` This way, we can construct a vertex from a package version at any time, and given a vertex, we can directly access package version information without any map lookup required. The application is still responsible for avoiding inconsistencies due to the existence of multiple SoftwarePackageVersion objects with the same key, but now that responsibility is separate from the graph representation. ## Anonymous Edges Now let's move on to edge types. The most common case is that there is no information associated with an edge other than its connectivity. Generally, you can use the `DefaultEdge` class provided by JGraphT for this and not think about it any further. However, there is one point you should be aware of: * JGraphT optimizes `DefaultEdge`-based graphs by using an intrusive technique in which the connectivity information is stored inside the edge object itself (rather than inside the graph). As a result, if you need to add the same edge object to two different graphs, then those graphs must have the same vertex connections for that edge, otherwise undefined behavior will result. If this (rare) case applies to your application, then instead of using `DefaultEdge`, just use `java.lang.Object` as your edge type. Note that this adds a map lookup penalty to connectivity accessor methods. It's common in JGraphT for edge objects to be reused across graphs; for example, an algorithm may return a subgraph of the input graph as its result, and the subgraph will reuse subsets of the input graph's vertex and edge sets. In these cases, the connectivity equivalence is valid (or if it's not, the algorithm avoids reuse). ## Weighted Edges Another common case is for each edge to bear a double-valued weight as its only attribute (e.g. a physical network with latency measured for each link). For this case, JGraphT supplies the `DefaultWeightedEdge` class, which extends the optimization mentioned in the previous section by storing the weight directly on the edge object. The same caveats apply, with the additional restriction that if a `DefaultWeightedEdge` is reused in another graph, it will have the same weight in both graphs. Again, if this presents a problem, then use `java.lang.Object` as your edge class instead. ## Edges as Key Values Sometimes, applications may be able to associate a unique key value with each edge. For example, consider a graph representing money transfers between bank accounts, where the vertices represent the accounts and the edges represent the transfers. In this case, the application could use a `String` containing the transfer transaction ID as the edge type. * _NOTE:_ Although correct, this implementation may not be optimal, since it loses the connectivity optimization described for `DefaultEdge` above. However, it would definitely be **incorrect** to use the transaction amount as the edge type, since this is not unique across the entire graph. (A weighted edge could instead be used for this purpose.) ## Edges with Attributes For edges with multiple attributes or non-key attributes, the same considerations as those [given previously for vertices](#vertices-with-attributes) apply. In addition, when defining a class which will be used as an edge type, applications will typically want to subclass either `DefaultEdge` or `DefaultWeightedEdge` (subject to the caveats already mentioned). Those base classes do not override `equals`/`hashCode`, but applications are free to do so in subclasses as appropriate. Note that when overriding `equals`/`hashCode` for an edge class, it is incorrect to incorporate the edge source/target into the operations; the edge identity is always independent of its connectivity. For an example of how to apply a `String` attribute as a non-key label on each edge, see [the LabeledEdges demo](LabeledEdges.md). JGraphT does not provide a labeled edge class since there are many different ways to implement this pattern. ## Vertices and Edges as External Objects In some cases, an application may want to use existing complex objects as vertex and/or edge types directly. For example, consider an application in which the graph is used in a manager thread to optimize concurrent dataflow, with each vertex representing a worker thread and each edge representing a dataflow producer/consumer queue. In this case, it would be OK to use [java.lang.Thread](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html) for the vertex type and [LinkedBlockingDeque](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/LinkedBlockingDeque.html) for the edge type (since these classes do no override `equals`/`hashCode`). However, if the queue implementation were such that it allowed two queue instances to be compared for value-equality via `equals`, then this would **not** be a good choice for edge type. In this case, it would be necessary to wrap the queue in a custom edge class which references it, [similar to what was described for vertices above](#vertices-as-pointers). ## Labeled Edges in a Multigraph This is one case for which JGraphT does not currently support a 100% efficient implementation. Suppose we want to represent a [finite state machine](https://en.wikipedia.org/wiki/Finite-state_machine) using a pseudograph (allowing both self-loops and multiple edges between vertices). Vertices will represent states, and edges will represent transitions. For the vertex type, we might choose `String`, but for the edge type, we can't use `String` since transition names are not unique across the entire graph; they are only unique within the subset of edges between a given pair of vertices. Instead, we can use a labeled edge class as [described above](#edges-with-attributes). However, suppose we want to find an existing edge given a pair of vertices and a transition name. This requires invoking [getAllEdges](https://jgrapht.org/javadoc/org.jgrapht.core/org/jgrapht/Graph.html#getAllEdges-V-V-) for the vertex pair and then searching through the result, filtering by transition name. If many transitions are defined, this may become slow. It would be nice if there were a faster solution for this problem, especially since the graph's edge set already provides an index into all edges in the graph. There are kludgy ways to accomplish a constant time lookup, but we don't recommend them, so we won't go into them here. jgrapht-jgrapht-1.5.1/docs/guide/000077500000000000000000000000001402514743400166435ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/docs/guide/.gitignore000066400000000000000000000002611402514743400206320ustar00rootroot00000000000000# Markdown files get expanded from ../guide-templates, so ignore # generated output here. Do NOT try to git add .md files here; # put them in ../guide-templates instead. *.md jgrapht-jgrapht-1.5.1/docs/guide/hello.png000066400000000000000000000576201402514743400204660ustar00rootroot00000000000000PNG  IHDRQbKGD IDATxwXa.]A)+"⍹4S1j!1)j4M^c-b ]QRv)[8?ĕ~gݳ3.9s 1@!L :ڎBH!h!hztj0 H J!J!HN]]n>C p͡6{B4]S__*TVV555H$DŐH$H$/Q]] DF.1d2bmoRYXXqC ‚{mjj sss|033`ii }}}mo! QˎЀ2A(r2UVV$J7[!~333|&&&r`ff]]]zzz055>qjez`UU@,C&q "PSSBZH$5K"{O܃LMMħL~k[[[ё$PkΝ;TVV A_XK[}֢œ B444px-888 pvv#%DBJtTTT ''Kfo]ro߾-((auA555(..P(lvRZZ22 _~ ggg 0|>_[F7UUU!//ٔbn>KKKpssSy (3EEE(..~3׷nBSS7>>>^'ܸqHKKCJJ fECCCa_~DEZnBvv6rrrΝ;P(;;;.>>>TBFD؈k׮*I`ccT %3ggg'Jdee!##HOOGmm-E% 6 ^^^$}gS(y&T`w QHKKCRRӑz <  .G8<␘$bcС>|80|pxzzAt{2 t._˗/#== 1bF`P;!5]}}=8\t 2 5jFÇחI!H%gϢ;v,!C@WWWUtDcǎ!** hll1~x^aҥdff"66;) 0m4L2mnKJJq1 O?4&M`W[0Ɛ_8}4r9ƍӧcpuuvtOt999pӧO3b͚5X`4m"hx^gΜAvv6{=mӭB|ؾ};3gvXhBaÆ1ccc~z&56ۺu+;v,v(1233+..n_|qqqD\addGΗ_|eÁ /XFNM=ɤIpU`̘1HNNvHY݅ kwΝ;:ݻw1eX^W^ŭ[T{+#L26is?>z)l޼Y1uPYf >>>|2`Ϟ=HOOGII ^ue1$$$߆+JJJuȑ#Ġo߾8s8p'Oڲ@ ٳܹs0~x(99e1۷cܹXt) hժUGXX&M0TUUqi<,ue,X8q"RSS[~qF̝;o&_G+H~ 5j0tP3<>} W\i1m222ѣG0mCH۴Ǚ2p@},?? {'yerEEE1###-_9sOL 01Ǝ;ƌoƭKP0'''bҥK!f1VWWlmmK/#رcYSSS.W_}ttt | c0n:nRϪZ5Ok>Kc .dPfkkD"mJ,((͝;) cwfo֪x fYZZ\\\֭[Yuu5KNNfXPPCqW~OHwDoM!NyR(ʊs<*)yxx0*=Jm{+d*1>$Ieʃ_͕M:|VSSwwu2L:x< d1vZ,w^ZU泔STTmvML&ݻwV7?ljjb}a-.#Gh;BW{]VVVGW|JlllP__uߣtڴil:8ӧĤ2ooocΝ`!?? R?3/Ν˨ N8 &Lp.,7n8@|||igsex{{1l2ee@e@q===̛7W]sx?c lѢE*1+ƍc{rȦLe6m,,,X~~>WavOvD|||57|5IVӚR~c׳3llce&&& f;v`>[x1S(`}bfbbbbbՕ=[*ewf Z[/`Ν;\ɓ'm}[MM [j eaaalժU죏>b zZ3ϣ>+77X+߶mdlԩҒٱEۮ6i$faaʕ+[ 300`Nbe؊+Xyy9ꫯxl]i˵kט5:uJ n$݃:#88zzz8~8|||ڳ. h.cQVVիWwݫxw  gxW(z43sii);v,322b[liv-.S:ç~!Ct"UUU^{`K.c 43-bcc~z|šC4.E"7rFowyw}24Trr2ñnY\.Ν;?G7|-#DSgVV={6xl~kqtY2o<-_ߟ#Gg}ܹS=:z#T<==>[xq^HHcӦM?E_eG|!1~? kXf \]]!'o5k-Zq цF;v =bbby_W Rmm-"##k.\t 9s&O#Gr#NB4N±coA$駟… O!RRRO?رcʂMgy!!!044Q*++q ;v P__@̘1/7t!vqq미r |>0~xcȐ!tI##>>qqqŋ &gi`oo0 'GLL  aaaq!88fN566ŋEll,?'NSO=333mJuD>>󃭭v&unܸ4"-- JŠA#FϏF)!uDNuuuGjj*ddx{{c*#4J㪨@vv6rrrTy&jkk...񁯯/wR=E.=/=LII nܸTdff"''999s GGG줇jhh@AAܹw"77m䠪 ```ZO}*9!]DIt؈LMMڢ)`ddcccr7|a``3yPCCԔ{ kT Der9BmoUU$ 긎( ͅ>Ff' u D]pHԬ6#rK "MMMCcc#d2XeG j(r *AĩLP'D}}}n^===.ZXXq*dnnZ|Ԕ/-- 1qD۵ !%:ަMo>deej/o6OcǎuptիWڵk5knJ7X$РDǺxBnnnFz'Nĕ+Wd 8+W|h3Q#j V':Bgς1]]]l۶k=ٳg###۷oGdd$ݱe뇄QK(ήU^r'ɰk.n$B__/Fnn.VX 6;wBvxD851 vQ@ 둝ɓ'cٲeV^\̝;o&&! %:Ҍ>ڜRW-͛7#33Çs=#Gܹs H3孺 &&K:::000F Μ9av&۷/^1ƍPܸqCۡBZ@4SUU=A%VVVDHH^|E,[ Xd ~7$$$ //u>|8bbbp)aСXd !D zziW_Eee%{Ykkk|'XdID455ȑ#xwQVV˗c͚5077vh{6؞DGGgFff&>cDDDpF766j;ؼy3233#Gٳg!]%:%₈\x|>AAA EjjC#ˡDGThFGM# OƩSP^^!CW^AqqC#ˠDGT7@GGjtLyydd$Ο?ФqG DGDB`ٸy&mۆݻwsF766j;o%Ds(t5 ΝCdd$^ ///,Yeee s,ccc=6|_]]]}1nƖjb2=C"pɻ w^ߟձTI|CGp4F֢nVqq1JKKQZZ"B>lmmamm kkkbРA*b}}}pe011>ǃCo=MH$Bee%7\YAA***PQQP%`^ܤl(>%:-Bvv6Oj浴#8::V[3).er9B!JJJP\\2reׯ_P(DAAI WWWã= JtlnloK]]]JtHKKCzz:T&%KKK AAAptt^ś{Ӄ#9oUUP\\HLLį[nqooopAuZy[P#j:"%%(**po 8;;;$ݗ%,--z!;;[eAaa!{''x 薽N鯁p(iGII ._.eggC.Ð!CSSS-lIQ#JtO&!%%ϟGRRP^^^ tO|>C !CTe24$%%aǎ\sF cĈ000&Ep(i^yy9bccB.#F/@ >Zٳ"\x~Wb|cԨQ0aƌՎ/]I$={1118}4_?WF`` \]]*!3f`ƌ :K.!11ǎ͛ahhQF!$$&L;:2%:¡D6nѣGqq$$$@&!!!X~=Fz]]]ʂ 8}4N>o>0a̘1SN)%ѣGqQ$''SLŋ{{{mHHW^y ͛8}4,^MMM?~7}vJKKo>Ki-['N{쁙䄠 ߿_ϴDG8ƩFܹsx饗Я_?|ǘ0aΞ="DDD`ҤI՟6077Nj/Ç  ϟ}wAVVV?Pߚ7n_;vرciyB43fÇqm[8|0sεyJ8a{c>.** C +WX`ƟFiƍHIIyQ#ʇojj}ŕ+W0fL>HMMŞ={ﯕxD"V>h}'q)B :skz:0FBBuaȑCbb"d }ƍkkk|˺@ZJazwW5qD\t ?3燃jYJtWԩSg/Dll,F՘VXV5޹sUe?c„ *IՕu'7x3g_{?<֮]#JtתO?W̙3Xlƚm666/??/#ڃ1^hƍ}߽7|`۶mXlY ך7o233q9 8PC~w=<UL.(fdd˗3gΰ~ ?1رcǘ1߸u) "##[,[t)344dՌ1꘭-{饗yd2;v,kjje$JYLLL}W_1V^^cO>aXXXkhh`.\` OةS؂ Xmm-0`sssonn|||k~PMM `֭JKK߿?+))iq[Z]Ycc#e7nTv<%:MN ]vi;R c^bˆ ƤR)c(c^`-RI]6nx<KMMeݻ6p@6e-i&faaU 60777k. .L}⢲vI~fccDVPPЪH$LJ999\{7٨Q{ضf?t52M0r@QzScx#bq~s \^}UYRRfDGcΈy<[v-㭧Qh=r`Æ cݫl*{2v}^w4=ؘ-OH{K.ٳggk;,_3+--–-[fs_~3f ogN!&&χT*Ell,[z]^o޼yHJJD"7,Y {DZDx lڴ .];|rr2ñv}nw4!33sĉヤG&9ݠ>M:MolT*e zjMmm-[f we޼y,>>^aiG}lmm۶m7zJyy9[|9߿?344d#Gd>,۹s:7Jw4ݻlLOOy{{><ƺ1I9{,P\\ܮǯ9r>,r9tuu5aضmŋ1`mFHrDDDСCƍ/r R5]^܇\-|śoHxzz"44GL&vX";v쀟ƌt|Wܹs7kt>Jt27v}||?~3g7xW{@_0g8::"<<t\Ek`Jt&Btuu1uT8qx7Pc޼yBCCC%ۨĞ={0m4bܹBڵ Çgun1Lt=F+>C|ѣGqQL>|>ǏGHHBBBeEHgdHLLӧ"44| MkklB8Ajj8y{Б{BoF\\*++agg`.=lYBz&\~KlgϞD" BBB0i$L<fffJB<&mkt=5tR,]MMM ~555G@@1cRۡոr Ο?$ǣ}App0>c3%:¡Kс|||xbHR\xHLL? 6@WW1b1d<덐)))z*۾}6x<0blڴ G֛{ш4|oOt200رc1vX/^_~b:::pwwO<???bpsskSjBC*"==7n@jj*_7n`kk#F`… Ĉ#;m 3g̙3ܻƍ駟B>A& -o JJJlny&!`hhooo!$$/ٮFCh pꐞ#++ ?3!J666* ŅDsq*I'P3f̀٭O^oD4 EJLLLr\۷os*3gpY__pvv+ggg8;;o߾֯d (..Faa!n߾۷o$bn~ccc 믿xzzv㢣Pewww㩧RyOPntڵk(((H$RY-`oo;;;899Y[[ VVVvTVV(,,DYYPRRRA(B(,kggǝ̌9/666Z*_,PeϠ '''899ajD(,,P(-..FII ޽K.=`mm KKKd2p̌|>Z Ȩw։bbH$TWWb""PW9թNP+DG8t{Aannsssx{{8YpEE***p99s 娭Eee%jkkUjm abb½xׇ(QG̃:T[MM  r[F&A,1jeLl{ɂ ĝ(_{VNԼ>t4"j$jJopu̚5 [nkekjjMM D"ŐJjI~~>B:466rH$\UWWCݠOj111!Yx߿J611@ @ %WOHjGhunܸp8q'NիWOJ2d.\)S 777o$GQ#*LMMKp/^DPP&M///#""vvvQHҞKѩ9s`ȑ8w<h;4Bz4JtsssD6-K+++ʕ+담4/pF(ёf̨Rb1lwww=z_5RRR0{lmFHB.I3fffa7]d2!J{7ߤ? Jtzs%c ڵkQPP^{ 6mC#WDG177UV!11fɓ'ѿmE]#j_+++CFFΟ?=%F9s`011ARRrsszjj;z:ʺ|o}qݾ} Q__3gԩSV DG{jAXXXDI&VVV֥;[=Wc!<<wExx8K._~A||<ƍ !@p^{fd0wAMk$@P@UmݢjVxknAvծvjgj]u^LK Z54 7l0b빦F455y]]w $gggH$F@`O0t:@YYQQQ*0rP`С6`DDJ\媦ⱡ4닑#G"::GFdd$"## Vq~TTTeeeh#BCCExx8$ ;X0vGΝ;\ y&WQa_ȑ#YS(<:^[nڵkV嚀=FFFZ_a2N*{y& <<<(!..Ǐ0>hDII pe͛0Lq)66FbggsKբ(**Bqq1JJJhₘ!>>  uƖlZ-\<* &MBll,bcc1nܸGPag(..Fqq1^Bt:"11 HLL /*BzH$$&&bȮ90ӡjqU!??/^B70sL$''$3=@T*qi"''RAAAx1uT$&&"$$Ya+L&nܸ<8}4T*"##d$$$`:JG";;yyy0L8q";ݹaDgg'򐛛'N^^^9s&RSS1c Qf0h4·~ ɄYfaܹ>}:|}}Vayjjj#G //xxbںx <544`Ϟ=8pR)㑚 fAvv6p-DGGÒ%KAfx@WQQ-[ȑ#ûヒT1Yat+dgg+WDFFM(=566bݺuBDD?a…vqZT1^+W !!E( ݻ{b׮]t@fffxb;p}{Add$Ο?/%%%xwlv؁)SM͛pppT*3m{嗑c͚5Abb#{VZ-lقI&}:!a4q9DHH0uT 6 rDݻw#%%+W x<7ٻ)SСCؿ?;f0xr<<;v[h :|pi+W$R(DDVߟ~qD2Lǚ'Yݻw{|] ݻG)))tuuu8cT^^-'))ITRgg'K|r|UUU`ti#G0""Mѽ~W*iƍ\Zss3Shh(IR*(( >Oh֭tiZl?O2d֭[ N{bc;pG;y${٢@|5D @gNK?Ah\ZggEsΑ;uttVZZJ>fϞMRᆪ?X$1L۷oҥKǺ{|2:?~vMb];wo*,,{֮]K"233hԨQVٳg#NGDD%%%^~^i;F<l]1أ鲵yjgtȐ! h4O|M[>>\Soi]J$9s&я?HhԩtmyA֮]K<N8X̺nÇڰa{JKKi׮]h"Уr;(({mnzMqF@,[YYIO>ue}TYYc@(::/^l0LW==zx=l{h4 %tRΦuQRR)J*..&\R?'^EԩSMB˗AdqsÇ[66mDaaarss*++V>UZZyzzRUU0]YtDDD=2H۷o:|F۶mIի) r;v LF[N:Eb~joo'777:{,7ZZw2>#t%OhذaTWWiooիWsuhժU-[P}}=AӓnJvIJGMM ͞=|||H$ъ+,:?Ã?Dz̙3T*{Ϟ=E&LK.Ѯ]H ЛoIV/JED错I7o&VKi&n=+VN"j,~h4Rzz:9887|c0Lw:DV"G}i,M3=L&߿ߢ`0Pmm-8pB K7TJIII2 ZR^f<;v@vv6ۇcٳ} a|r.HHHˎ& {Edd$n߾,Xb::EPYY7ARR^ew{6. |ǔ)Sxb̝;^^^O :|BYYF,].2C@gى\dee2d^Ga>\~999ŋヷ~)))8q0 t]r=z=Μ9pA/>>X v9s999E}}=D"1g̜9ζ.&< O?upĉuM&L˳*0  PPP<&L*qqqw m֭[8y$򐟟FbHLLɓ?0((( PZZ ɄHL<SNŌ3  m]Ty7u؈n,**A,#..&L[aZcc#]f1555HHHkW^y6yd@םL&˗QTTb H0n8b̘1FXXl\jj(//Gqq1( 888`ԨQŸq Ba1 tִr;9VUU䄑#G"** G#"" XCyy9QQQ2TTTݻ>:: j;v,3u1l+++QZZr0|p9aaa Chh(ݝ?P]]M555Fee%d2GFdd$"##k`20]o n߾Հo߾D`4pwwZFRDGG4 T*ۡhR B\nNl___X=ZD:t >=0gI@&yv0\.s򂇇|><==<8d  >=u^w<<Jk9,΂ ,uvvrST0\EA.C@@P8::V+}U2B!0 t]VCV8sikkZ ZDZ NRiAsp{2d;35mAOC'''.`o>~>}:afPd0 0 t 0̠0 39օ`agJV"xIENDB`jgrapht-jgrapht-1.5.1/docs/img/000077500000000000000000000000001402514743400163225ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/docs/img/apple-touch-icon.png000066400000000000000000000146331402514743400222060ustar00rootroot00000000000000PNG  IHDR99gAMA asRGB cHRMz&u0`:pQ<bKGD pHYs.#.#x?vIDATh޽{w\ՑWugzfFiFH"$,@&#<2" "w5~c@a@`^de1D YI=n8UH@ݿSuNU53E`„PV9Ďiߊ{Tug7=E-XsBf{T#=[sUgЗX$J(b 0%W @pv}ـ{}d&X[=Cs!1%Y~a0>H+vEai󝰤`J& SŪF˧[>{NX85'1F$jBѪClk E$\8 1V=qӑ7 =--A6.l L0SLy+<ڞ}baI P H)Gz9jX& =ԊgE1qCgYykl+|?G@% +_1m'Svt4gVܪƤA鰊Ẹ)>[}~"0* .p dm@Z"ѴM O-#̩8qINU:hu~]u@ 6\_1 #qHѪ38Lo9L][\eV q:42\#fPˆ2U LXGK1C.>a-~kyrj- ņ* z&@SPxguW}DǬqќ4:VVoJ*v(U>bV^E1=(=ClCh zLKapCpCAer7EDX9ioREss`PT/6~Ӆ>^@>5.3ثl,aH{eEODꭁj a_n-H(Ug{5Ĩ.nqX%Wtަya$T!lEBѷ.}_C@#l,D9`u 3#*Y9Ɇi(Ψss!$~P;}Hoy1US5[~٪{KYs: .F +Pq&c"ET@HHՇntB(}5/^uw``f:LQ%)F? ȸLHP8 qΎ;# ki1:0rGфƙZ| k}tD`CX^v`N]b4QhNbZ ͜/VO(xcX^Ueśq.#.LXKSlrXKUIôAɄ6&<)~|l70->]iuq.7vYh!z6nN߻.nnƋ[F㤵Q#/@}陁wGdWwq-}YRq3޴tu!NgEӭSO=/}'M -&7lTU7+>CQm}! ~Y|Q ^j$M3aeB)E jFe u@s/cU?Wtj_."Q#@ ^PfB>'R]S@eaU33m*jU"مP~355%kkDd36Gn3$1󈈌N7LJCVq():ms@T! c%C[GmNtHfYD$ՀVL2故" }D\cSԕfի?t !P*owcol|Jy&Cz"r !U* 1Y"vvM hQ=a<2+=?;3pnCPt]҂W-c|K?zAF XUP{o\&~t*w iE')[7L?S;nT" U-@KK ^'9swt]lRaK߼(Zt}}T:*jA @iB=+K8a:io"鶵:pQU ēi|cȕхo8ˣtݳRX*X;|IBQv Q]ܩtt z*=8ac݅..1̃ ŋ/֞byq_W&qTA8_gibohu. nQ`r[K९wh6ǧhKfR\e1' EZ;mù|࿷T-j===ez*VPC@HD'WvfW}1/Zk!"C@<w(̽tzq?+ !dD bP)SD+`F@%X eH%ѾV+[ "sL&eǵPnT*Yk)6tHU51~%hu=LhȺAv*?T ;$nsATBuUzzXOE vV"_MIsQ`5]dYo*rQ&#3d&21dL<."KpX{8fkkojj:KZ ܵT"/ѸJ kOzq2a*1 f @(];o'MXuM9N& u7VSItxCCx3X&zA8:f(X/s6DT$0[:@'P>?zB6FQUy߼W[Mu[P)0AoтPJ$Q=:T [03;C1fA34rD0f~*΋1Y9I$ge{zz#ɓNѹ1f><'hy2NyW_?Y1S\&j,Qd,J$IUTDAuV[Lly?ۜ27SoApdւ7:sw:]1 *4D;p&8  FU 2E.Sˣ0uнVhÚ#my (pl3ƔDcpQGO/KC]Je6D$ L8X,Z+a[3n׭bnL~47Njkk*OAP;6BKjc|ox4NLN w1a_C{HɅmp M(z28mk~r<ox 3TX.=i8L[.no늅RtB9_zcƣLf؋㥲UbH(Jỹ̵*^@̷Ճn FP䤄\h%+Df߮|/xhxlBF7ByeuP!tֻ A!zy"D q)ٴ}#DqJKL;]vk23d/GzUcz8௘G6'#f(QrQ4P`@c4ٲt=fz¦`?ouv6ظ Ffun$C1-ᕘw_t> C\Ɗy4΃s6'O'aZ-|ZG/n*O . E 74XљO:軴eD 4*5.ӃkgfV+FzO%қ*Vo)G: [Jh@Q %Nc|^7!ɦ] rˉFtf!R$zydm`B/#Qٟ_]'ge↮g(0HR *:* kɸx{ 1C[t6-@a)E[Şb"׷T^J|* 3tvhux!fk1C+4=;.7'"2\ ӯ -YYo(\&w-.ÿT9 Ahn_*juI+^51*auE/1V%tEXtdate:create2018-11-21T03:08:49+00:00$%tEXtdate:modify2018-11-21T03:08:49+00:00y(FtEXtsoftwareImageMagick 6.7.8-9 2014-05-12 Q16 http://www.imagemagick.org܆tEXtThumb::Document::Pages1/tEXtThumb::Image::height192rtEXtThumb::Image::Width192Ӭ!tEXtThumb::Mimetypeimage/png?VNtEXtThumb::MTime1542769729DtEXtThumb::Size0BB>VtEXtThumb::URIfile:///mntlog/favicons/2018-11-21/de7fe015ea5f3a60e5837079760a657d.ico.png'L@fIENDB`jgrapht-jgrapht-1.5.1/docs/img/bgnoise.png000066400000000000000000001102771402514743400204660ustar00rootroot00000000000000PNG  IHDRX@IDATxlᑜIaj#\ss\p\\p#5Pʪofv%~ӟ~9ӟOv~Oooob?կ~闿~ǎ?-嬿<uY?g`0Li*?.[ճ wzqkV; L]zJ֔#|?=dgHW5V>z>;㎃YM< qeg[ ~\iT] ŋY_8H0`$Lg5)kMs>5`pÉq3O:i,+o'.\rWK7=e ipU[q52xs3 _6w{YjnZb¿'Y-F`ƃb5 .%!g3_y\ bgah?8ɗ.; g=Տ\~8\ZaT:|z HQV}ڸㅳ̀n#948ᩆxɩ; zqwI/pup~a|W~`¥zYL3Sg^/ˑR_-;Դ]@"PEk p"5x~\my><_; vΜV8X\sOGCgճOuq}E_`Z0qOVܮ,|dn:fWvCn w\,θ56.=6815瓗vՠE~[Mg0Y@%[ o+ʑoɳ`ĽʼnNj'&GLNKDZPU}gXuipS?vĘ!Fra,!#&>Z ~δvۮf`yUOPxʩ/4ebZ{Xwyq9[zӮ>ng;#)fy}vIg>~|~ܥ/~+oۧ~|Κo q3Oz׿py|i_ E԰#:ПxνZ%F84Cob\SSo, 8Gw>gH y*9!Wq,jU0}Bķ7i'6~wqӍ ^joN׋|V|ʣqSwI|K~jWO._K/~1Xc|i#_jӃONc_.#;u]n{w~>+sGMCXŇ7.4:OsՅkCxx0jO;,^{\qC;xU3;w/oqj3{>X9;g+)'a㭞z8W:p\=]/ϻλ:wVCryN;/m0ΧWVxr'< a)Y {&U[\r/.>{yQa,y}/ ,[iK;+у퍮j;=θO.~>{=cUG; ռƱb=:?A$HRV|Vfa DNT~{qZL~KqhRO_vy   xY;\ {̮6wg977;/gLso8|=/94i|rՌc=u {=tF{\LśI}>燂x.Yߴ!C7H #_!9wXipW3@x1 gäS 7ZnC*&Rz0z!oXg1(靯ɫogfz7KڬbxU9УZ4ԃ]')V-_aѫZ|5,çG ^n=NNO,ϋX89':rVy飚ӣ_bbxL|4|)j|"70iI_\mיTSߚa77v\3c1x_f 33ǁ^{vv~x:b 0ּaer #xK_pXbt4иh(8ɘO/LN@=LxqzU/833usqw oWO] osg|ͨs6;)Òg.>gu~(֧ †`B4h$r%$QDx-5Vfvn V~慧FK7/x5$?+f陿}C#}p-s~pt'd03k}o~-X94϶.Mv>A/ɩ/8bikvxh8nrzQV8V$@I 7v~!vgD>9Yk (}|jПw8< lrqzLFMw5w֏V]aլܭbv15i>=IZi;]j..7S<>հ[r3y3{`Y"pfӳ + f⸭zVc0{z/NLa3L6y8iՃQ?=x>pLm Fϴ~\4߃$V+oы0+bi×D㗧F #\ˇ]spT[_".'8xa)pe0`[43<ͼ]>חsW3;%Ǻ<ZLl`a=ק:|teDp;<18ᄻ(B GΧ3(D 4Xj#-v0j/CWSfJ4sciwcXq9ՄI'1w1&.cǙVwӔ3 [OԏO=x:9[<4؞% ą{c K"!( \(0~{s┛1U㺤$<{ݳ|b8<(>\q4C![ϋuO h zo3T[W~9iT %^G9@.S#鮏tKS9Ѱ9jNdD3qd X&‹\;㊹[̎s?\W.WΙ/.SmMgypf"oc&N>9CN}:3V?ǵ:QX8WL0i9ǃ_:{88'RO>[djѯ/fpx\:ѝ?QYЪ"bhoj <1N<|u Nhz+ǒccxsNy/G= Egf> :VϦ?|;^ &1;,[l<[}!ţGrŖ/,u%ΰ=G]Nsǥ8>?V !~ϨX X϶8+Y$iMwXB4/Xg>?b~gx=kp'l ^|K9S]8qU۹:!}ų=JxqSo8覷(>przN|<Ř83ً,曋S?zz7g/RwppOQsov|ra䨃ƯZrqg ΐ( z%b{pIyb8թx>|}e.('F3<=[OGwXj诇|1Xu-fۛ^>3j_tӳgUSilfM/:b=;[͇a.9jNZL\^)V$V!7x1j"Du/,N>`#ra@芫!jŋMkңOC=3zW=XGX-).U9{W7GK痛y8e;E^>x=2\0jWmYڝ3kxzmO}5X}yƗpb+˒X"}e/Էr}!K ӌ\v_[زpMi`qrIg2ȥe ^DKu&.+? <ΰ|2uK; |EZM>--tm3sȇw/k'wlqDi^XK7.5iW5,q/N/WLj83X=r>$9si;R8b1gooqgk>]iKzL'Ӏ)}S/Џ3v9qY-}Y^3Q[rҝ>x>.g}Jzrz^0]=V_0vZznu>lK>lg%̋W=O>5awV{ e/0 b;aԀrXo( Y)|z`.'磑ebt'l:ˁ_nfʋ7щ;A`XpjE쵶)3w|;_~9) |; '^ߴp}1w[xiYp眮%]<}|i[tNF{9 4 | uVy)η|8 3:Ղ+י>ڼipbIX`O  wIu V35K[6n{K-╫v8`ƹWo)F9ahů6̝_E.:%+L._~=^ɓI MW.3 ?'N.^4H|ik;.7\\{g9ΤV|W7^LzWy17Gqo&vMu̧GtU7}3f;7β9vW.YU JC aXjuKz|+5ͬq1K1+]8ӗZ83RCX|-Yz9?_5EgQ W瓷^~yjg|kaK;s;l5%z;// 2! נAKsg`1bTSqՔՐ< ~8ewq\Ò/^o/Y4.tn0^&W]ϙ>,ԆŠ}%%=OGyt:W;ʅe8[)0=8^jS& ^]<)Ѝ^qa '?ng8vä)>; =.jK_jS}5`Ě{u$|sp|aԓW{zKë<8iwzq[r´)^_ŊN#n-w!<>ռ)p}b{4;D'_wS]-zz}v>}W³q*IGBW3iVxqQ_|OjӉ<KOP^yx~♳_Ev=M+zC\o_ú33.qZq2gu31gPo^,ćcu:W_ D0au[*dܗib9`ƜX>=H~x_̮V3-˩%>, +s[}g9u/r`V~kӢ5FzҁN>i}Fp,z'^9ΰIk5`ƑZ4s9:U F=|>?ᇶ,?ص?燷Лrg~_!?`Ɛ`wy_EP/FFヅqgKSjktT083~8O.^u]o8ƇzK0tU~b]<|ռ7 rMfGݳG-~;y09p~ ~tj6uǭ'3WZԅ^4oVԬ􅯺jUW\$>=U8 _SW guճV7<~hSO;n{v>8xL~aUV /fL3zrOg=A's=¨ӷ~pig|84Y}8?^qKiY,kÊuv7'or : cg=tɣ͢A8 @H+zp5D%~CLd5 6q5\35ha3[߹br,F;_ 5,.N|96/178v|gŏcbiopbrh3vyfc&V3E; wNsvussu{O^d$× ӘU!nABC>u7]-&ªAgEoӧiq{O@d},wga4㰘;oȽ Wo8Ë韕ï'0<:qjͭ8㒗F8o yzW21:qtqO fߎj}O r+ė&Q5m[|5%W,sGMº;3Û]>mO< &\kE`x`[igpg8iH[awrO FK/,wzkb| WtWVf+-,-oɵ<31v_;| V"r;KP;f,Cwggɳg +A$n9p4V: `r_1Z3>Z,p]VI'.? l\Z_w\¬9_աUG._9sV뙩UhuV;3e%!!po%WQ'%/O|j-1&3>Ezhgos 9ˣM͆koK}b>\ZquW\Ĝt5߸׃3с3w+tpz͔p` +__Š?,>>rn/>j0w&.*1a0%X .Ҋ*d7 #L5qeeb5ҭ|qfWj+&_||vVON}ӆكxϢ;nXya|z6#ZˣCܹW:ԯZ^t=Ysz^08iNgv9wu-Vr!';8Ί9ǹNxuXr;!gu#|,0MB,QHk^ہ%^sglx^| 苛ziVq>Y_Kr3W?^|ruu.>73c/W:Y?8O>a=gҞ&|Ԃc|)ƪ2OwAra}oK@{`B$vD[S05ߠ;nuh7^;j[gzE竖;Í/y3/lwZxX}]UC=" k6r~Y.>wz2zgvxᚅ|\38_>x#{a7DD ֜;&rKCO|wy șO3 y8r ǽlG38G/zo˪!l nMrha[|6ּӨU|crҍ^>\0쌟 +j#í?~>tO񸫑Vxg+y;/GDD 0 > x v/B\▸aq.OVgpʵPp1L`L=V}3q7C9͵:\gYϲZ,ԑcFpqevX90Ց`ղչww%?GO%a_r/i$AaU-/ &]ql0ir;դ8~qI+0\3ÇA+?LJ'|v:w.F^;3t[>8\5}r7wX\qfiK+>o x>Ͻ>G|Okȗza0^:瓋y|aiC|Q )?q??(/f9vxmr`wp+ˇc9/]Ŭ\;NcoT?zVW+=r`?i~TWLNj+>~9ͨ|֍>{h/f?5pߑ'xuT&~:ە||gnvpqIϹ<}xpZrΠ`!8h K;4ɃC|aX,.xT߾gX'~ᛓs|0|VϽ|>-nuԝÈ9ZfG|儗a}U|~?~$?5ٝp߿_Ka0)Wx&ǙN\]zajXϿCLw#艿G}bFOfkejsf9zvxY{^x)^ۑzEq3ԕ*Cpnjai)fg^w\0grޑl1-~wx~C?~v,giFgpȵęX/ lp_>w5y||85<^-N580>:`q YD b,K,h٪6wyLnOgL> /z̏n}[j!]z ϧ&-m0il_^׃(⑯.{za|z=+nWC oiV:<'8q`s=_7/''p#.~y~3W8ORs]\,ް0i?vbqOqۭ9WG~w{:_C ~I<,Jk}>?OXfN|0˷9 _fPnq߿(NOn:n;$ #.ge帋뾟Z .>%ߧHDSBV]8.^\oa_ >\xufjeY|.^v⠇߹aego|;={Wr|{IiP a[p%7_*7 45NDq旫 F ;A,<- )֕W~9􈏉[ 6>b,q<̝m^9gO{s/&Y=35cy#^ ǂelWK^q7bljCp})u痛N>}Ӳ&WOf ָm^i`"9,bDTPA xê WNOp՝gz*כւͿ90jΪًO-q;\w|Y136l/5k~~})F$0iLU\aW.g}i߳E<ߛ{11߳epK1ywUֹZΖ<+0bU+M?U/4:\ ;luo3_8Vڪɇ׮n$swX>|5ዣ:T=mr㏏ro۽3?͟Srm+SD$-Y|iG}'CBGޡbG};LwiյbΰƮO,MBL>^pfjWW;.{,XHLNߎchbb񺋥9䫆庯Fx8 G#.8ՎW<~t Hcq33Z?5(GK#{ëA~g/8^,q~QS`ԃDMjx`3z4v-G)tp[ _¤[|Y\9+xo~=gzP9 pI=\vx9K+|r猗8,"G\S&Bj_̎HNMW*8㶪g1TSL]&^&_u^]wo@; N}iN55 w1v܌z5_AC5དiٕp2{.JCt~Gk_ըN 8gsc 6W~3? u?qݹfKB2>? G0rqɳ7?x=R~ߏ7{1;v|h #b4!nS^ϋUv18~X1/~ #ӳ|bȧU[ͯpɧN^o,#K3~ 欠 B@H(; Ap΄;}1pU}q; |yp;br3;˭~}y-&NL0|SrՐoOҲ='rYրP~ ׏Zը.m&-]=KUy{UێfAo>u>8%#O43a8UBԐsb_jX=uѬZ1qxpb?ZKmaj-x;9~yguj񩃻\:\0}N,|zQ)oꙃ|:-wif4i^rQN5_a,/U\ [e4|5bsFb7l rF)ܫgzaq2_Yr{U'Ë5q N{S7,^: ~.|34ɡYǝvhM8,ϹՂ[}P3f1VO]`\<*5˵ƃɜaE$&©Stɳw愷o}X:T~pK{8q՛|͢8Yzއ~~& {_\YIsՖ37y4ȝv3ᚏܾw85'F #ΈΏY<9Y||pq8'4XXח:x>Y}}8>sWg(w̙g1;Gsb| 'փksi>vrԯ:˃OM F_,0˳s#q =?csܟ[9\59x0Ƨӫ|<Յ0!=n8?vwn\?:m~—.|09~8=6+uA"C,㋋|rOL~raZ&n?8O,S1/g/RLbg1yw&v@\{bqW/ N,sF2| x&pV/+_L=n}"^QfXiQ׽8Y[mxiZ0<#w|&-/?|J]/gx=,Z +D0P9.8ng/px1raY5iW8•Vi[`qW[;\z|jrL._|a{_8j6igi[濇1bЁ^#>9ᶜ朮ՠ6L6c,SlrԾv=K{C|zmp|/yȾ3S,YN;j~h_ ^Օ?]ҏ?Oq'3$V.;tg{-G]WWڪ^,j_,8җ6+|-xprwwpEw@9(;{_1p?~>ۻZ_H h\pcfeW_)@/p_F/褿 KzQ /X,z'9f kָk^jZC{a)<3/Y 7dF5@D(G\A~ 7.E{h:˅ ]xkʃ6ahp85Hqyގ{aŏnz0>}z94٫CI{:ʷ%_,üzwht~5G*=_2\`pwf0xXЇ>jY5{F|^bOk\V|fTf,; rtI'Ə˝.>|{CmL i_Z_/xY1v: ]Vy=? 9b˳CN/>fp^қ.xbpGw0i^9xaK3%'-tߞ)t×.>>8+L>u;a9apO~g8gr[!xqWOJ_|/'8rhǿ|ohz_C`E'=Ί'^wX ZvjɷGt!_^|r~S^:;[om<!Gcov9xۜb4.~sz^=8w| LJնVqYxo=éQmXu(nx|w})/C~/O^wэA˪?pO O acr)4 zbv1{˫\pFݸptb4۟G-1UC8M\MjO{ЊCr,,GWoy1?9/+_msߎv&O,=?օz+43szꔯӼ?b4˗O,3'G=v.e)؋XCLܙq?g0 ]Ca<2K۫-'ng1wktX4.]X|FSVr,a/j/nMjVGZ\^)@IDATiK.)E˪#_zOm={yWzbiKz>5j擯8nk"v}4$"k%e/ f j@~D[L8˧Y_-9 M,,^RON~sPO.0bo|r YS?TV&K7sU^gC=XL2X+Ⱗ^- _z[z!N[9PK w)/_acDP#F#A1xFO:q+_N5r-gi7|wL-rN|8UaiW[8ŏ/=!h㦳4{Շ&l33kou>]iK3:rq1<1~w~[3 2!4bX+"N=>;Lq0 D>qa8˳OÊO"Ó6*>qg󉙓OsT[.bL44jSCbW|pjK9iG>;av5Ospӏ:ssOc| ?a|z~$?b[)XO'&.n㳻/nk9N9S3έ_􋻋UK,W?z4#vyr[3˹\1ïn<L,068ً7#]j#ΞN{z+^ΰpv>8T>n>B;ϻ g ~;wiG}i#;L>5a`jY|xé^qwgSN;z]~p^;>~}WO>).ˏ}|k~8z*=WckW'ڝʩ_5#7|Vzp7p+565bVO,6̃jY>%brYxsWʁKGhLgpxx_p~ZW g>Z;3] &Z9jVg3wn*Gt[] g{C=]N&PSUg"yϗz4{>B)~ݸ/[pbp巗6?xj gV=~p8[rY^ұ{u)iS\i gE>n⃩xpSNyKZ61XLu\:ˋ N{K?rp 'cVZU;[Sy'p8׻w!|>-vuqv' #|vx w~xe,FE{~|Ԅ5+ΊO-=Zˁ\<}ɦ~gF\igzqu_`{V|8[tꁆ\g`Y8K?^+%9Qm /^umffxյpYA]Xy[: " THx8g`jDȇK _/R1ri2| W ?iq]\뎯ac&r3#&EN5'NX~YԵ`-'PlǶpv,M㶫f(_Nsy!׳#?iW~XZpmq/K|>/{YOq︝O>~8ˍ0r[+N#lu /V+8V;.i+zahv!˅{ՋZb^i[jÉw㯆xr`뮇;0jݭps ;$wc Gg+W|rs381h pz&p[ΪoIJ~199ӭm^\Lvi'nϻ/@ll`Ӡ 78+Ɲ Y_qS3㧓!&>;=jۗE/N9xOZ9,p 93X&,5~zY48מok£OxuNyk%'.x>oM7_9<=SfD؞1&.ם|˕F; ,G-x˗P82b&3r `, 44\̠qnbqִ8}qX=^j ;ltu?_};36\uK|uq6ՄV;n:㒓c|pf0ik6i33'ʽuGouVguܝ=?=)v?; Ɂ;/~ث q./19YMpa؋%jg{5Wo _ www\ީbmw}9 baO3>bSo_]XKჭ;̧ybjoU&:.gZ =lԪfTO~~^erܽHAL_|jկ891?][W>yz,?\\kjz_2wT4hsW.'闯v_~W9JS͵j 2![ P!B~ql#5F4W\u`}J>㐋O{0V3]j^iVzҬ~j0.&&}}덩'r{qcW .f^ Xrs/SM4g9M {j[xZ41u,=zµ s'!b||5H}YTCG,W1j]]ʭ0ͩ)>j`P˒gua=$z`isa N}nɵˑ [Vu '7SǾʉ#4VWs>itN?\u~>Kx)'rY3++9U Ҹ=#?u\ǎDQHn`vo{%q[L>, daXZlw/R?ylN0#j/ËZpQn\ht8pOk͸O:kҐkƐ1gypbdbq&P}q8p8w~ߝ{ANO?nyb%j4> FMgԈf&&{ǝzˇQ+&ֳ-fE1vW׷xԵjc\= C`hj0Ղ'fwW?.;n~0f,fOS3X#7-5lM1x I! N ^ X>qL aZpgi˧>-̓? 3]x 4+#{훣XW[1fǜcqXqN=].4/isw Z>t1+ag 8G`ˮlS/qb*h@nO;_"Hcg5?uk|_wzi2uqɵ=^tv8 խ]> Wԓ-3kx̷gbj|G'GOebV8|44~זN}ݎ˿Gy5~w~~ 5qo%_<]Ԑ;_Ukʕ..<8Y,v~XGʓkZ3[}{+]rª,Vo,y-w1w<,yk:ÅqS[i[Y|r|d'ww_>6}8l'i_~>~'[jUN}J9/GCoB?NL<zՂuV3mˁGMV,]4+,sK&Gz4v.ϙ-SO=99V\͸pvg<:^'=~'EF%N `kxvbL|؇cfYm;t7f;M0VzvO^}uCN:̾p15<,SϽW;iqQ]Ⰻn<+Wτ?-a:}8".scQ(7~O= o\8K^6G-1~/*~w@d N}|rbzww M~zvOpwn3 A}vyVwGs#gx|L~Zi.j{Ib@3ϦcՓK _̟Oh99l7ӊy8hb8{biNj9[b4(48T7 -DYEQ`&/0ݙ|u{%{XA0RӎϢï㓛UFSgoJ>ׇo߹Gpvx褫Otf|+W6|;c/fK+=[>r|=eŁ_-௞<:!gUCL9o-g N 0l97|f/_~y|xq럯9m.`"Dh`;a߀9)H%q>qu|zj⃓o%&r{|ă<{ij+G-oO|r-unM C:v Ug 4^roVxծx}Ow2ϕlj]m= a<㮾+ (%WȠȇly b8Aq8q lɳÉ-g!^xk93Xq?Novk -u,9Y~7UO|voqƗ^Wp4NwɖO[-;a?i(Ͻjc9ԧ9^)F~7tCڜɱSG꺫׬뱾eg%5OiL]83mͪ3rp /F}pjW_!5[EA+`g| =XqxF >;41r34+~yc魇jm_x[8`V#Nor؍8ǷvږNrVq97~9ՠgGʇO,--V9lt3;&#bis o7WVMg15hÑFzX/lE8KA*AD^1sqg>j™55|?x,=4t5pWW7 8=w`q]~ա^y,_y8`,}18g`/{=9ܹ#ל`7Y/LM]W;^faw'M lq08ĭj|Gz.KZv|~a8k~+^Å8<_1>qO juf7-޽U zy2WIZ>->xձ˳;cpL.Y3xpq#Mv\ͫO6zYl9vËb lqbiqޘ‡M]|[0,){j\;K3lV}ߛp a.hxΑ#a(!pS}0ĭtŹXaיnu2}M}b>K fٹ1Y V窅rN#ox O\KX=^p̹p3= /IX}GtӅKU8&7M8`ȫg >wף W~XV9/!<|OD/b5N7Qpgxv^=r^2Ln1֙zs,ݹIQ\x|k-¹ϰYN;Յá^}ؙĘoMp?_oZ9yfzfӛOzv|O^{_+%"Hs= _WD_-3}_krp‰IV2>mR]K>-r䷫eXg8毐:A7=<ʩs=SL/~wUG8.>~q`o4lΰvUU9 riuIjnzgYac9f3ϊ[xIg~,uO ׋K\hu3g4ߊqFDh짭`!wyvf6!|5/'ώ%<&N\1ګΠ)Ƃͪ-j>i`ճΧ-j;{1)WL|vZ񺫟N㭧>kG Μ:z^m}gv:S|r9~\8VEh H?b VN;~q&dc <~'ZtW+_jz cEjw_5~ys85ǻXĘM*כ>;nƑW8~(pZj-7)ϝfc|[r1,8q|~'F4X q"83P0xZ 9pi '&1OSՎC_x͆6;^H3,–_s<.{q?syԏ\ି463pMz'{GѽN~uiTץ{+wɀ$DDNt^1TV1+_?Mk,]hwVקFtxkSŬbՃOk:gX8V_/F,.skrհ`gp^`-<&k]j'ngrqǟN\}b4!YZ|5iAY-b& t3tM#yݝ n&^r[saMA//䈳zu1wzbvŝ&4}X4>^9q^x|_j'񚥽ZO #.^Llq_N':N[OKT "Y7P~dHYL f/O ~gɱjjq3(q3-voR u}ש4U۽MptKfáOyvvw>{"w_?O{!6~8qŪ׮n]x ?ኃӁ+\zU4k[ |tTa/'n8S,.;}bVߪt/wzqW#.Ou||rjng<;#wKw+~8-9V_if3awS>ĝ-5L]g;+B67iaeq5 Z҃xrWO gˬpi3~5W7]qYr-Ԁpq#NwxX;{ok'=xEޚ1t_]9/zqx?% 8l_޻󓋃)}oXp5Pa-COygחa;$«3 Ӟ5:ޠ\x ?fN 6]_ cF0̫iKՁ!/aլa3rq6խ_q|xgp^v9ml˭| H>_`_d/֗'_qTxX\pvwg{x;[Ne]LJ<qd!G~r>99.ߒkh^q`GӼ{gXэj>'yb6 ѝgwτ᥃_V?g^_8洦p||7L0~N_ԒkWح/^1o1H*GP5N9 G\95S-MV?ᓣ3ptԳz,.m?}z6ՅihnNgu bgZi.lx&OM+QNbW7zb)q Ws_kūfu'E"oHj>xr&= '_#iՌ!tc 3"n>>y ZQNPLz0qg=xf+VKZc3~c9=FcW )=_u鑃a^bLl (_8ja!PD4_C ^39bĜ3g0z׫\uղwcgщrɹ˳Zq/ǹ\wh-WɷԉC_dgo;\ |Xґ_]^cE\>zU,a_xa|׆e78iRS[.[x^,8E^k=lO3 W?,n{/F͟nx \\Lj3n+֞O?;Mqճ1xq|ϑUr54|Ϳ|ɟ}W_^O+Tg9 N^=;^X\5^ՅO݂we=ȍtq^pVzゑXpqV їFXj{k8LJ&t䯗 :8⇳Kvu`-Cs|>9{}DwweL9ٽڝ'r'sjjÊ1w5;ꠓt':ZǷzm՗S?tg8n(^~3KZ0_zɽ}:6+04<4!O Y{npͩY_zg8͵Ĝq0<5ݝ-ˁgbzC[ w=w>l:ӃٮjSWpYi(ޫN|{-pHr{a0m65?3WU]|xYS+?߼('.z&p|_RSuwɊ 83g!k5@NYp%tŪ)ANx9x'EC/:5\XyjVNnaȱyqT'~9=<\摆j<=+ޛ1w~&-LMҘ>wfeoa83҅7<w(8qXt SLmKD 60~= \((_58qDbR˙M'PN8V5z!ᬧxp ѥ#nzw/ \m\|ʫ?1wX|{3W|䈋1=C<ۻ<}\1~Ԭ)ؿ2a(vyY"% >\%.q$r__ 2Ե{r,u C=^ Og2z⣑~(7=jʁ] y8 _'f.5;,>4ijbqoၑ_._s|a1:{0 `ěC5<AѥgHms.LID$y5,4N$Cb8zȃq¸i$.cOW: <^SV郏pꅿ{,xko^9W.q/׮-1l ,fS^mq%fbVzx`WճX ?ᆳ&=qgr?L0 㷀+)hU `5J[I.W N̛bi~`ZjV6qwjʍڋQsoYs/W}t8;ќ-~{-q /^sh/W  fOk3lfַ|5+08'^_KI|1x/kؗ_f)ϗ1_`4!˞8Sώח։P5|9~s58K]>p[>1 O}qַ:zTS.{ؾ;!.K/vхT ;\;}L \wy<4}]\+|Kap7 ~u.|0V|9{~Z.jT.^M>|b,;!M,~:W/jmN7;gq␓#缳J#oyY8Zq>뛿<؞M8[wEݻ%yYr8w+~]>-SOqW'=zoV̝bU7M =ϬĊӢtk`h(xݝi35ę<|j2xÊaJ}l]9L`D`BW#VCpcq;b Kn>-{(prpl?U#|s[73{x~:-gw|g}U08i۞aXjU^Ҫ>_5XnX<{&,n?zk6t,)Ͻ3>_B񝟶មϷ1˼\˗x ߗ7{WݛtuN6nI{KʍX8f 'ּq5zϗwkk\{YC^>mlWr]N\οϚ?g?a|ώ,ϯ9u~֬3nr}uSϤ Fst:4&x ~>~>5~8n/{0WWz0PStu9h |L9oVδSmsI焫\3>=6[9ճ\|4紈9ctÙ]x-}o|鐣-.A-҃X ?~>~>5j> `0H-,&ӀܝԤ1).E4—z!k[_C8~{~wq>3ӫ;n{&0/WN1}3;j9Ë[xj`¨Z&,~K\;]o&}{{Ǘ?!|H'k?\0rqO5qsw.w{)NptwřGxwr`U.-qʉNco_-W{y|ś;ŧf\o>w9zv?wu ^a_}i燇iv|bK_HI/x> ^=ϻ38a{Wf],g.Dzx|Ws?1z[aݝ׿ZO:oz<(G=iƜ!h`8p0PsYU8aު x̰C/›jfazX8`^Mn׋']QGTcȀ q{D5Hf-jf0|X|ձ„K w{xYw9,GvƯN_PE$XMahjE!A˱ML/j)^9}SSW\w.C?~+Ngq :x/S{1w9;/>᪙Fu9[N3JOpr'Os嗟 mngbv75pV-?\yS,A/Hl)TAwf=$ /D.98Y<.wvpn՛<߆nIDATiw'x?xd5 +|?Qpsw~n|~Z#?'rԒr]Ög>5¶|ȍs^nܫ4iẆTհ`3-w=`W-c|7 cܳ?. ˿_K5,Y1;FYk16,G!րwkMQ׾~y5ȟF[p0vprŧF/V_a;cYK?ffK[xg9,r-ՠb%̏˅'Sm;oӼ9[bZO]Vϻ:-| '>ߵq3ryN$X|B8BYAU_~5_OmՇ2xac+8,Z 52Xlaqx&ݫ\txÉ3:Z{NsD=g,iS"Շs!p{&tPhWqƕ nx3/GcWC-kq'6~=a=;j-_zůWۼƐw6L#!3;jajXLcbvqx-&|q8ī Objy{Ç+0a?ę{>yq|qOLi嫧jǍ/L5_p/Aўvjg9iSoIxw/(_U_F 㗳>M[DM\&}0tG Qzw%pG < ^u|y՗jRoS9+60Y}+BK3X4;?l'f߳߮xXݬWkzvY1'f}} O/h;M|~yNjӠ^«7 "3x0~xw8ܝp\} 3^i=qi!q1%+ߙ/qK:v/Vm{\ gOj/NzI 6gXpVΒ|- <ϊOiռk4^W-OC>|ua\rYg~"._)bK`Kv߇#`‹Saᗯ= q9E׹ţNua7jÈYiN\hh;ᡦf͝ νz iWӌ,fWU#\>4QtX NLמ60'8ݒcqi)^m9L~z},| m!yxqi '{5㌃?ngnt|s/_.^@8,ƧV{z >q87+w~bW+g\>G]|V:Y\N>gX/̧6s+ע&\{n3,>gH#wy, rihhg0,[<*B\{%qqrq3w< tY.σ ě7_xqдqlYrYbhVtMuiA6+G{r-63Ñkb+Z HHD+hאּ:+W_#b,֧:0za}ˆI/<6-5+] :| ^~IyWK]/rko*҂n =_u#>-|bv3%:%ԧ:;:8ځHsW3D' GLmLږw)Ɩ݃Mk5iROZW,9L̂ŀU+;}V90tsWgF'L-1ÕQ>әW]m8qy3 F=IsrĚ|yo͢UX.nï{ôkG2aGP`q7%p:={߽/&JjXdxXM73S?Ӈ}ovo&it͚طfZZ׬ҷ>]+^ʟ@G\`/- ;|k "G5ŗu5欮sv9;,||[8}ʩ>gm.NgF5q8܋V? j܋_?|0jy.nvXϯ:8𦡷~ay+ Kꮷ ~&8[iQS{ڙ޷X53%+@GeX~Հn_~y=h9|q7|b=.W>p X]> :TW⸺s8{7;L#t {O;{ɝ!f4ɧn)WX.=31uN;|5.Y=F+vX8e'+_;;pbLCq9%!"m8X5ίO/rs.K+Yp[${:{U-wC~9GWvʋ[M],>qɵљY^+,w5ojVBXzSqpqWxa,ë՗!Ԩz>Z,?-fQ8,m4O] ^g.␯4''uKɯw5aڛ鐗#G~zALN8a4WT9k1&*Ԑ,%'x b)rk@&-ViPNqopqݮ&cM/giӮ9,#4u 6j;U| ov˫ӈG=y,`ě?0dc;.ZjүWzi_w'QbY8U)D;^B,X1KNՃ'7-pʳjk=vƓw kgc\V~v{`[bvׯ{sW=!UzOf&'<umkNK1EZ aqbIENDB`jgrapht-jgrapht-1.5.1/docs/img/favicon.ico000066400000000000000000000021761402514743400204510ustar00rootroot00000000000000 h(  #.#.ޕޖޖ1ޕ_ݕDޕPޕFߘ ޗ ߘ ߘ ޗߙޕYݔgޕEޖ:ޖ<ޕޖ"ޖEޕMޘ ߘ ޗ ޖޕ8ޖ5ޖCޖdޕEޕ&ޖAޕKޗ *ޖQޕUߘ ߘ ޗ ޗ ݔDޕޗ )ޕYޕZޕLޕ9ޕTޖQޕWߚޗ ݔXޕ>ޖ,ޕs, G2 ݓޕCޖ&ޕAޕ? 00.rM"{bb  &ݓޕ;ޕ5ޕRޕ-]bcZVe [ 3mݔDޕޖXޕ[ߙii $ie  \ޕ2ޖTޗ=ޖI ih H=\X $Gޗ ޗ ޗ'ޕlޕ _Agn=) P7?oT )ޕ:ޕLޗ ޕYޕ6'&QQ0Xޖޕ#ޕMޖޕVޖ#ޕޖ6ޕrޗ )ޕiݔJݔޕ*ޕdޖ7ޕ=ޕtޖ:ޕ-ޕ(ޖ.ޕiޖJޗ #ޕޕ&ޖޗ [ޕ.ޕ|ޗ <ޖBޕlޕIޕ6ޕPޖLޗ &ޕKޕ8ݓޕޕޕzޗ 9ޗ ޗ ޖ/ޖ(ޕbޕޖAޗ ޗ ޕߚߘ ޗ ޕmޕ0ݕޕHޕޕ ޕjgrapht-jgrapht-1.5.1/docs/img/logo.png000066400000000000000000002635431402514743400200050ustar00rootroot00000000000000PNG  IHDR8+Z!bKGD pHYs.#.#x?vtIME o IDATxw|U7^dzMR (*MEP W7 J @zےmf?s?n%^s5YeY,LK;%9z{ 6VR01$\)!6\pKv;.Mkce2 ÷]3Y>%FcCQ`$N, 6FRve糚y7Qvu:%0UiM,3! #1,O!(CD0y~*h?AbG~kz{l?+tZpMNٝc;L)d]/>/Nt$G| Q6ͺ\f O!IvA_,j+_{e||$Έ+N}9fk6#uYE(pǨ-LУ/y7~g[kx鈅7K7mXrT+:{qXkP0ӬW ]:myޥѢ {N}1A;UݛM Y դYϔldq BG]y5UNp)sg^O~fH lYѠ#EGW+>VFꑢ?7`/+VZ{yn`!GlBl` C$){gzJ n_I2oWqylmU7 f_V>oUU NpEgrM}|!_jN8P/oWu6>QUNp+nZu57Oxt+V?T8-t9XI~Ob̋ek˭L/tuOa$ (p[5,ߗrۣzU1>gu00sM< m(p[\7żUCp/嫹ekm}Ox }1;%a|:Gr['IoC\Bg勹=RYT1^H;fY/xܾ\ Np당)͌:+t8<M(pJMtf3"3 y|&(̚^3ބ'\_͍C4"m< |+` Sނ'ư<_-HH9$A%WeoA*.^u_e_1T)q.53o!0wDr 伧>޾63AxJ[P,ٜzQ9]qW`WocxZ[P6G*kʻg𼿏G)_)oA^RT\/2w2)}ڟ ( 8=PZ}r]Az3ג\9o'N_ oxK,_#O6XE*S2n6$8 (33!_VCm)_)07w%xyu5 lz+]r[֜5gZ#:tUyOM>n-Q/=\S,OaY!.8j(C~LվWwkN⛪Z+#sVSC"_9;4!3꾸i+t"liFח\?Ս붿R“F,$IX w.^Unj{uy_t|L}5e?{zs}lnvڢμs` Pau,]m+L)mЗo7O%Ɲ,پ~oaK-~O[vK6 6.AO x 5bKoj[|JA?j)V#/wڹ t=yI2-YV:\+緖/c'8 Nj,Mmk WCu電|iDaa۠a""׮=rfGn6qHm0=^o/|lu9| P^rKhsnayE$3dW?+fV*[J_/2wt}#9]Ňomʿm3b̿q0W#פ9BQ[h>)UΈ\mwNCwܑK èw4RJ-*Npev~BrwQ5uPL4+w4:Z ~1~q"}J ÈYa9:{r޳Q5noKs>DɞU"I OciTZEھDُx(p1;Kp{hW۫Z>hoSh#SJ-k #7YI)em7uNRa!,:|2د*H+dF|n|uܩZ+#h;o6swxaì@e evCm8% -LOV0Yqy"IfUWŜe?lM-:Yn[E.>"THbVP3̽>vmIy~NUs[WHF׿)n;fE_wOi7:d;Q4QO@E 'Y[~rM;^IsH~_6hiSO]\Zh*  ;Kpg_V^Z>Ky~K߰IV Vd#,4+q#$"EX0:PqZ{o0aYʕ(dוc0+!i+XQ-h#_^b~`#.hY~EvA?o+9z{AGYMt?'>9(^|}&E F >Dq[Gi-1t ]F׿4gNwESFkceߦh>Zu|H]me?':ѭR/YzKЙ iJL+Ň9}bg%H':! ڕ\Cz6赭B)E rr1Agsn* 8&>m#*R)( # _Z>s͂2j{uyYK- Aj^F;#v"H;&%E KCiF^+1az#)\@ibܼVO vV2tɶa6\)7 bxHU̾knXM<]Kxaa۠Zh7 P=OXtSVBEO}5lK1dɮ=?,gh_ț%rM|Zo 8e]SI7aT֧o+LBO5tޡSy8Б\W-{6u-)iVoeǛM(pT_W~sնxi{Io -s0CT]-t+Rbp⽤Z[ =Zt0Wo'w#S ǒN$|S}GJ7GFG<xCŇ˿jq"Ifd=鞇cc=7pIA}I*r{ttNkd_7m *^˿4UUq `z蠉fE $=nMmO uѢ ;o&ѭʭL`¶(6Z:>K| |鯛t+z\$ K':6~xͲet}鮝`VS8/(xH IKUbi˰n"w/;}-ckOo5q&!3ۄ HZGr(Oi{W0Ӭp3 2v^*[€#E'q3M%<ع;v`]͇V&eK-@Ixhk[T+v^6"?S% -赓lY+t:"X11x})>˜W7THbPp#C?N"=BDf&EfgcύQ$աm඿kƖ0hjk^)RxmE⚼^*i'&ò>-Wꢘ N7` 0/2mu]mO£ ZB# AG :;QMC=k|̉'s*zm{`Pİ֡;5u;\tT_M!p: .:cjYJm(6qE킖D9ݚfytO6.kf ܫʕWxG~z]5m,'/nbO?vS'܉TέK^ ?\x&E}4G(n9mNkC]l6U!`c$cOK Țfdz Cta'@kl P/0߹-Mۇ nbt ̄&lMwzGbTo^/t|X|vhm}&Hy&;>RYz)cbZ œN7ϗo# HW!?_xȞ(n¿+gO|U[۵ TڃY_kW5\g&AAxZZXZek5VF^na%<&┄8NaFA4WXy?/`c$W+bIEJQ`F+h+/e9bĊΜٷ&Ί')ݼ'C?׆8{%X]g#Ηv]zd7<.\  ؛a=]7B3 nNXW#>\fkkgHS`W!D)wP d%g7"m̮)"F}q[A]jC_9TxjuWMe<&W-O:vpO+0Qn$\= tnlQ,ٜzQ=Hx?oZfwA|fEߦ"E3Oocl;V{ȻkۗiMi*k#WQmr͋-p 0h7#/VQBQOMP:^fC*}͞1{T)euGa/RHek#/uMRw7 r!Gy7A\,0w︷{]-nR$ +9gt7+S:S$X>hQM.nA4Sݮ[RE=g~OG*[2F:ܯ5W۰|+VF@^F=9/ cw=Ztqy8*"@4#3ah?7&$hAޥ}}v[߰ uR[^D%(҄8!Ԗ!"ׄq躿s̉quȸYX8d|\V!((sA겎A LBEͻ\7ft Q!o7S̹\ecXjc'_ӹ??оs6ٿ|)k%G&4n':qsiŜb^p;0jɒΞ+j^,ihBC鶊𷫅\r?cٹG1ra w4]_g9yS)cKvM CB6M(sI$bi&}{Tt›ΈG/ޣ2w7d\o.m'Wm,NY U}9?+w?3L1sq ݵs />AW;r; P[~dgX{smMۗi +uj b|9ϮQ˗^w719/5Mem(VʣO6+<|U_,|(dݦemm֜Vڒ#?uW'`sR7 ;q1pڙ7 >i#MϘ*pAC)d|!eei-)&2WG 5ٞhvb?AB?ɰ,u3PffBR/r&N /Ö0j!1,3 Bm]Y1W@ԏ|hz펶8$A>^ /'${qbpD" ཨl[eĮ .sgc-V_rMUZ1cucݛ{x?Fͳe6{3 n+ (AC*鳶/K z<.VVvfZߺ/\AU(?ş-0أV+aOr-^XAsqG~3kKƨy/$wGnwˬ-O:C|5UKã謘7EતirygyP[>jĠGHymR[˝yw?TPk&㯹g}q[yZOYR>^ c }YaYiCwEJ8+xm|gG.v )JsC#{˸sS1dqg5\åJrЬ૿ԟ6ۑdNWbŧ}-:[s/vf l['*6s&vV55j"竹5w,ξLpgb絒p>LB?$ɬ2$Vu7!OR[rwBٝqRnNrwR% ĈΤM\.hC+ӆay|dGC!x: PeYWt*Qk'FmPHek2 u&6)A1BpVb J>H}eMV8S9iM6Tmg^S׼aY.hcEg+N7h!GqxJg.A3U[z#۶o*HZ+#o;fƖPy +9@(Ͷ%rt}K1I au%d<;|'lMw2^q1+@ ;Kp;ͿvҶ*d]ieDGq3=䝊_im"9a3}B\B@3_[%vo/nee$=i;J!<N O5=^(2#W%>O:trkr>iOadd".eXKt,)A<(^|%OkSg:g@u}n aO\=֛ܻ8<+qqi#q)d|lú2nfE1/lf/?|z 4Kp~_[Z+#l;n᪮9Iv-(pk8RtVSW I C*7gͰ:Kw]Uv(Ga=1j+UiM}oe#:G9@ Fח/lLei+?.O}C_|=Rٚ}v̾'>sw*S,78gԖq1B6Jq GAG:Z6~P(CZ=B7?\x"zӋ/)p9V9SR}u7loKTS /=t_wQܼUbiLq bn+JWr$RK _h 'jm/1#/4a';KpWh~q[Έ<_S`llP!UfF;C?*9ݐtܙaYBm/{LtgR}Ir3Vg2v3o7G (2On=k&Hw:]% 5x^gv#9=$ҥYs_;k/s EYGiI1\cf_Џ mTE);KpOnO׏IBi/>T_Zmw.&)ݼn3'G':Q~ tCk, B.4a"NI`d\W}ǎ&:XY/U_{WmO:c!ΐ):I >3qV*븧3;^y0jn:Pks g^{Ȼ3ֺŷԋR/zYLt|lg;Kp[՟lԗ#>13X'3 F(L,"E) +͌boaDr>rObqJG՗N/M)/\Qd(N!-6}QwnGs*W:WK!M`qcE^!IZ3=^.0/tZ_Rp8!tI U]sEbbCjgJn{Llm/^RL _FenHJN'/4tx/$Hv&%H+kT?a钭~RsāWgkNV(vIE ʗ:{|-9َ Rr@qestʶ$ਿ&I| PchmEr=gO-0`>|~mw<Տݑ %%H$Hw֕q3kʺ1YI?rюbŜ q 8 Nk%[kdG#9m&s{Tt[z p88xѢCxi{6!~ O1r= nS%Ǻ7?Zim'^{c7{Ƌ*<8Ʊ1kXxXC"[D/-4+dBi/>>t<ܤ ?^ouGqI2_+SAy_kN5GÙҬ{v$}^JL+ʕ۞'rܰ?t y?Fj88Fz5h+HZ~0A{3 #?ՏR`B,ǓvHɡ%ʼnOc\WgMJ&x%l,,];,S$2QOT.wˬ-[KqLPSB;~NSY򻹸]! +y(pBv;~v&V!7GEd[ tLr>)9]t8#I&%HD NX%8EF:`*6".e (ԳS,(CڸFRN.*!G6 v7"z2+C~Yi-}&Ml Z)y,0ڙJ[U 嗞!o p PgџG9Lp{tL[EXm.vfyn]I !Uq,A7,e2 y[^|)&3tGCg^>?,ghO|g}G9-W}EAM,λ~:8mpH~/;e\-4w~6TY`Hs"Ĝ( sxѱp (pBVlw䥖0gg\bNuEQF:5 %՗&m QWt* 2E3yz{;ڬ{6>Qu()gK7mNMv.\xȨiҙ-[&,gj0sE*_olQn]'Nkce۞&lxv69$I 㧌'N3.iN|n|uC쐢H99$_љ:4Kp$dy$v Ic;#ŜBwmg w?crgۻUO۟FCi(6KtOi$܂H =ZxQ%|]wkN3>bx]>o^(_M GV+ϡP-]@;ӕj2mc?Xzbnт6eo5ʧ+:W(p1Bٺ_jv&6ZOMI P>.f6LtqfCthᥤi򈺒AJJ{u%7<->OsjcLÈύN0Wik=ZtqPhR49DHW>՗=ې"y9{WISjm=BŖmC£ 4|$;-e6]f  Aծjkgו; |DM#0LN']7wIi_K|")AZHEw3~xB=#EvAK/cD?a-lveۑ)-"ï瞷Zs^8CמoMo[,Lbs# #>gpnRe闞!o״áz,^5"m"7wd{tgF;+րxq l՝!I'K6F(vA2o~=5xsI ҝMʣJu^Ql23 _z#"nQK-xE\ړay 0KX>&*kK'0ĝ0Au_pJ3". ŝYliO0htRnZ,Lp]wkm޾w$#F6/̅!x]*ׇڷOtA5bD6 VGrG{['N:]řXEڮgfG%듎UOg IDAT>7]J0QCVe+RͿ\~ƊO1;+¡CZ2'Սp"Ln9 ;Fx3q^Q~Wl}!^1sgF0MgL}{)M7ŝp3JLK]H7Ml :t36M8U`g%K!:_Fz;VtCղ~#IUi7T_UEߧs}?Vs&.)A\UM :2nOi+:o\t |z3 >NMi)c<ѾH:_Ռ/=BގrM\V,Oih!}r~EAptv.O̯u66`ҙ-vVMμ]bi^q=n97I2ZWTe$A!TGqJ(pVwHJ> aVס ^ȝ{fhT7~ 8~w2 po^r++nVVuwSZHU_ٙE;r(0U_s 28chqwWU]*HG"6^ĥL{nv&V?NLzYFG +'f|7݂ۗ#L t Vc/tZG;;|T{ȣH{e:9:{Tmm ރ+K OJ-|M%t!+48?3 g^f9Y G>ۣ4xG;ФeVK2>K(9(}I6`9!r6_'_"cLNu_go.)D`T$kSSZlͺoL$h(NS ^dս[ew@iqyGM־{ެ677r׵+O@š ngrYhwoq;/I[je|'/Y\ɕ#gdzpMJ-[r,\]YNr,wͷdϸ8ϻKWԱLsߤB'idR/z 12Vs^K>.Em1BWXnFD"gncsqe%w=evŘ)zڙ l '{@dyipDMMY"HG@(YbHvAkA|5zjx))F&#OLh$tqKѭ6k_uCe}ɮ%C|; Q_S^#eGy'488 ,畝!>gCw4~WW?RyHQWzK=6YvK7\7KS?ѽvkua'`9j%_B4adxu4J6d<48xSxL'\_B0"ISsn s6WNdYzp0g[q[;Coc>"}ݱ[THYҮ9}#dǪIeU|j4\M~Lޘx?1soٗ{q-ŷDI{Ӽo/rc#ZgTܔXچjÚΣJf_1"w$qT)wFnH3Lkr<יqxŏo"_Bba8qnigyb'H czg >\RcO>P_AA/ĵ,!٘·?0$$ o'CarpS枉 G2L15&m%zwgM A颟~{:r|ro&{gj}Wŝ\9uncadTTP!A',6'ydfroΚk13%XWD46Η\2E7;7 L-TznVKղ!}4I0A/ߵqxukB<&<ޓO ;:U]yҸͿ xc#:1Ôp,DS}A2:_hB4_h*g̳WΨμ~8uY73Tr{s3gPOS|p䫯o_מg~0 <9l10pBd*VRUٗ 'v%~u^U-FͲ[Y; #]4i1zy@w`gT]LJ%d!ےVjkRS9H9elx5;K/Y:'f<3UdkYSO$jxv \g!'Iɡ:/y>vKV[bԚ?>5 9| Xx48_y\2"Cu^޿X+J9*uCࡷ֘zcet嘻R`jsժiq!2͜tvNo_+.=>$OM>Ý{λ/ԻM7 0bSe31愿,235UKҙS"^6TNxwأ[>0)/U8樰 :SٗsZ]u\uɡ!"J@޼<;P:ko3w \>?<_Kƪפ_-O_|{f q+Y᤯(\aA# iL?18Pn|]o;nZyfꟘaNghg9a]NDic ސy],.5 bW )Ž0dʉµ>#[R4M jWvw\xXji`"ZnͺUje|]矵ꚮuĬ>M?O $ ';4zYpɉkݴJYw8r=Ս_*YT]XOv{s>=ᙡwDf$[c[$f{Y\绮.|id>m}jC${Ő~UmUŖlv%VHC$ךȲ^vK;S@w?!QW*׽B;癘/,eÉrtּ9X|׻g-ZU,fu?wh߱Ls3ٖ&+޲Y ^,Yܺ>LjX•Tdkᥒ| ą>l B:c~·;uio Noi_SZ3_{^5w`K FӤvEr5kmZHH #R)WOc)$-RlsfPM)$,#vƎƑYN$Dl+\rқ>s-]_:+"M3{ZsʚO Id['fd;ƛP$ɦ m#\Ce!j^C7l4g+ͶFBW\7 mmڑni:+^ ^)Ŷ~ʞNf8g;MoZWLU3\z䐐^|K%_}}}gUEv<L,t6v Z%_r/'H{(ّM/SD5:847?|*JF{F.W*U8#}CD3:X&{#N˴檕RԚs'>5 klg[ ';1X|$Qhaf%ffyb'o _ P(-ܖn=&1G"Diw@xwېfXK]=gV{S=*=g`а>)vvؙ}%&=OzJ6V&UꏷuoU佔j 9zF5fhm:ޤB3Q`,W IhwL.0K0%\ȵtFTriDX'>o@FVjots=gq-ۃO ^:k$tF-k zEmh5l2w>5%P:~n+|WR{Ӷ۳Wǎ Aɡ׎ZG̊["C{gT"9O+xyK/Y6j|?ƌwއK+bTCQß_-dԪ.HF-]{IY&$M&g}OΙCBz7AȲ^vK;S@wWLL8OQJ3|Z۔ZY޾w*Els?ѝ+%1]t" _`bK&-* ǿoJӏ7䵚kDw&5UP I.~s>jo[&ZjZxTbX*#oO;x@Efƿctܛ{j^ku}ƾ3ZVLj\χ]*Kknf&j Jv, n?<;f2V'/ΝH.=1+^l`쐷o<@w{C b B  1%fV8eeVT,s/~Ot8O6SSTtE:"4Us Ycb[ycѽEo2k_jk\^\r! Q'7 m?gyt9goCe/ZlHOܑnynC E:FHMl0$Z*tvN4l7,HK fT/CCa3XN§" ocK-g Τ"[+8p:<<`TbvOvV] :. %MD&6QnX)TN`3 VU ]zsկT?,5ri&=aJS^ Doc(yG:12{d҄UQҍ'+hg9au^8%_xbtt#6,A}Cc(ufK M4iƨA]6w&U.`9AJ-?Lj+ Uwsӣ%gg s>F$)MR۞9 IDATQ1/,ӓo.rbThk'TUhp暘aj%Vecn[%)@Bnk%'~>{:>0TNJ3X^\r5 tP%T]qG 6ga|btZjaSdžǾZ mS8jGY/׎tV۲X󽆮,O.a'uZ8T7.Hǀ(mFN t,.9FG(_s51!!M%$u .cnns-Is ]YRlc8ƌKԭ,!wd%Go@oƩ~o)M?oN4B3NP{4_ N/7b@e'8IѽXk\v2;8)_ssug1Z+A>9iPٖ|՝ߓ XURO{#'392Kk1UetF|b44G s-Z>Rev\p@b[E|szQ %4it}4HBs!2:ʈ}#d<}Ad+Lu೫ܓ{*bs3$gqIVeP{eo3LګFtE1BS_v{O_&L?v7FH29c~.~[VOMn.줎'8jmH7̔iX{'$1B%g1BICB=å kf' o8ށBP!s;;Kkf^dfL8fTVlpNVjo fwK0k'4 _[^MDetFzKN\qߠ!7+×$RXCBz5g+M뮌 T,Q'k+#AMʕ'8 jjeVfZ{CLkl-5QCylQamO^̄HYRD->@JQlW'DD1(]#WzudXU*k6۲nlM7ݦHu\ePy$qv>4y$Kπ76St l`gYv!$#Ex'<!$Arr:/XF?^?UkSbUk?)G̦tg@g=P R+M晘`Lf5E,_TOvMϑ HcQ.! TGUmnAh(ysTXʾsmnJRmqYԓ mXV+:| LL btiЃy{ 'C>4I2,x*sC[W~Sj-Μ˫VW" gzGT@egevIBc*>{?p4-NC F{ )¾ߜ?? +z8U˨ĂZtru Q+2Х·Nė"EctjQCɣ{KOU醑g߼Z`m<]YNh*V68T7嫑PŘtqG 6󩹩Q=)#P{CB|3E:*zc=sKs=cE x'TJlpS zSqQ b!0q±|1 BH An`jrvA{LJ S+zD./gB ]Y\'8ҿ> :J+$V$LE6U?eʉµ%'2nI; Jz#PuI?`e??~kV,K/w|R[SK0PQTmSސg3?]a*zUwS_?U+qh$tqS?=O>;7Ys?T,ڳFtKL/;~t}dX1[iJ>B箌밾Owm>P|5itx!lV۲|jz4ݓФYvRʨ N4MUH.4T bz79_]ErAΩ!= ӈR pBD|P$ɺr 6>찐ct B%$uG3 2U"3?`1$huTHj=;lLӦ/w|Hhe}|䋊dզv N N 7sv:!=~Őg]-"D)N Y*sf~c)l^zT `ZM|iz;gTӹ [zeO־Sy'J;54WFo4XzU]+f&`}a*i(v&&ZY"J?b F;$щۏ<3/ʞ2ZHu8I@TN=,-^/#itx.! 9~CKmg߸Z`m_^\黧s#Q񮟗/.XSM l#l} K͒3b[oP~rށ%kצ+pq҉!!}%TD-t*70A_̵v*GR+10Q"C$zwUoK7qvetgSeF4E2arAM,F_vN9HW+:eu;쐐"3Kim~[g~0:Q/G4S}5xMi9mCRTى,K 6!9ƾ$TSٖ本95f5ӚVF)0J5Oo9\dw \894"l N=wI)gQKM<}=+ÉK,h~L;b8w`˖9SC|DO7QnJ)x77*@T_%xRfc}޿Xs+.Z{+w \ФU3!z0߅\Kך!28"Vu^}NdYzϿTj'C$syDXǶzemLb] /U丮@m?R儕7j·f&?ushl/FySgjZQ{m~`Tgqa Av 1YlɅCbg #;=6qäg TjKyNcl@$I{?mF2I؟r/6RؐC%g0:ufuRUZbe vVQ: ]/4U'/dۮ{KB112?XYB5=q3x-ڎy2#PP[E3S g0G:WȰvJoGXVM D*(*ϧWJ? P$R$Yj?('yd88sQoc=EH PwU'o8Q`mMXRAVwBkJS5^Esm}`SdφCIVwf_MG3[DCY*҅z;D<ޗ{ךV0MmO7B/Q NQIâN~]39^/Us<sOΩ[E֖9~:^P>Wt6OGп Ȏ;s._ͷw+I˝qPERӍkeZ+X* WtVB;\zJ612H+5fǫmTSٖD%/꫄OOF%ӃH~覙uwy}Zp9狗4#S~h~,KW76h(1|wPɹ)' >(7uY\kۚ '&&U[NKc)n,vcY>}¥jl,!ZvK;k6V]oX(̵?lˎaU> H3P~˅ѕطp]딘aNݜ:B47 PqeVU> y@݂g5Yƕ;Ŷֵ-^?ē@ ;}T398|"sQ j2zIR̕O~Vje}B]!X%_MwZE3o~ET$X&u YM$4iYoި#۝WK,릙U IKy;^=f'@ŔmYXje9J`97@p#\y8 % PYF6)TsbWMmZ+LJ'ĵu&@5T 5c]B0JU#ʼ9 F njs4I2,'||񷵵G3VZc-5l8(5G=φj>w j_Jr+Gxj\j`rpiVdfaFTM@u2-*H #p ';<&@݄g XCȵw͹Pt_+]yL;]pRHʓ HS0i5Fǽfū9?$W 5 PJ|KWb/Y[]gT?!Z{T+U32Fo9@ #^WHxN ;[jD0Ru5dVKa+_^+Ԇ-U?Yoc|g=48jt|.K>I||6g|KH;}଱oju%|xdamSk۔b[f$@Ih2z9_y[g{w5r}ZAד/Rvԕu$~e!0jV/T>C$6Rlxw4F>]hpְiRYz*ܓy6wL)MuU9)K7H0" 'ѤsQ(A/2 @݅g \+gw=2z.ҍyjN5aHlߌ5u%]4s4 \ZM@${ 0"ubp 1xE4@Pcjsժ#@ ݏ,'p94iBф:W I=* Dr*Ca-w 'v!HrЎro8e RsӍLVVc"J;r-?Ũ&88B`cƣ }7Ѥ[ܠ(*vE(LTʃ'w7o޽PwIe-@)]?%*3zX.Z\ȱtyXc|tY3?d{EHO)Šӆ4I ; (hپ2+Sfe}y2:/HF7S I hjr" _dfE4iSe AV휲Ѧy&G0 0-1GaKJ,IJD4iFUGd-YwRKlbũV:$XBqLsc IDATeӥE7FQlHb (ˎzpl#e|#WB,QD6ÉW0i:%[C%OL/R}n+%[31?覯Z`f+K MF}wa@u h#ڍkgReN[O)xz`>1>ϛ_idBdP9YPqv\gqN[6 Uiu olLZǕinANqHW&/ܘedM3"IYJ^('"a=hnTlKWIOt^iW9_*ܑ qԦ4f3|]!#w39|!}|̖oJ\(mT @䚘i kOs>5hŒ:n4I2 '~\wpOc Q:O_f[% GrO־ʊz*эP14 N;K^(^‡dWտިV@M\=881Ü 7Bz̰~TO_DkOׅarlϪ;i^* 2;/;3GQlQ'Jw%98B /JN}"ǯ kf'|tU9 cbP68O oHO9U];"WbSKl?{˽(¶;c#h^j#,|^ B#:@՘N,Y;˕ؙ-˥4iF:#73A22,'S[Ǝofe qZ{X? ]F Ums- cOd{6(43+HG_ahMbԂt|ݛo)YJDMQeTr/zjȷ&A,sSui͜a8-0~\)7>BR?rfUAJM " _la4b0 Ft[& MHIw]\ MN68X1u}F ~}UW˵tkJ{}A&P%|M\-xꧦ~{.^:?jOom @M()oWLbC@jq%VgcU^.Y@ynZyfJm|Fl#|ǤBdb#aB[T 77c9r!Hrg*5v \c-E(`1|ojѤYqEP1_;G7"ǤJc[cZt%B˶thT iiR R_G&d \mp)EF2@gJAFM4iF "ߐ`b |eRzT _L~e]qqƝ\W6y q,ǗkDwwjSOrwD.i;P9 XvK7˕fB9b3*?'N蝕sΙ;w * |tѫTkO־mg9!~5Vl̔kV%Y.Y6wrSo 6A`4S&OU(5k 7$ t*"V8㟯z?A%@gT+St\}ϷBjPOʕP p+VpQ '*[uG7n>Z+E4oܛtGSw-ǢrPl6Ѯ;G~Mq]a޹s͛7[߾uerrr|jjjl?A{F}O("3/I[.k^-2>/^k\j@E=y'~gkW_} p%pA{[m&"a&9=|GbGgK*vCMLL8w\7"AUkNe{^ɳt0\>oP(&Tg[lD6PC溥]5s=3t-~:2=IA%2L}؛eYGxag9JDoߌ%5]fͫ{zwh{aԊu^2hZ _\ Xd׆4N(mC<};^^/]5R!؞nihyK.Z5$4i9jNVEskV~sPv"{Ÿܯ5SS%2"+{O48"Nf{UWR`aJ!pU>6'fab_?Ykeaa8ɦ4Mi5;_u`p=TvhʕӾZh~aAA *݌N>tϿ7rS$ΎWJTR498a3Ց\RqfXh{ʑ HN~zwm͟?Ămf8ڸq&g~f&Ǟ<$f%VǯL@Jj''ֵ0 T5+6v\knO7ݜz&#/oܹSK'MZwlt?;+uwPM76i %)]B !YBI覛NLqnK3n5H=d;Gܻ=U)P=C]^^} Mt S_Ujπi!ͣLt}|.$YGPثvrW7J*=?S_.p1>?a2>SEڧqK[RjGXDco]dA/8^nnNxvN:p/_(v?re p&22QEJ Qj aF-?ÎMCWI+Z" ֚YCyۯh*c>>eEEDFduJB'JC*cƌ0bĈ-zC޾߾nw{3ψ +pbN^sQ$ zrv5 Ύ׷K.Gtzk׮#"";|_Lյ;AByKl!fRa[xbXn8gUߚ8qՐ!CI$Rz{^l+|GKӚsp:C(~!חʱc:9-[dm۶;p y>%Ic` 8] N($Nkl!!׷aÆ1,Ў#H:^3 vɢcjw-עg^ߑ#G8X7oC32}T\=ML]a-M$~5Ckymm,N;EWl(56PLNmj{-\nf-6_сa=_6ViM;;m"'|$(O M^E` a L+nNu6ᒳ{P/&SmHu~ܚΡh\P KSX*\dWl?eM[Q{˺q"0j,5SNEqyzv6B0jE}T&G$;yY}fy#8#Y{\Bcl}UlOLzx(o^ap" ."䱔nCSY[{Jb!2[mh(/8 NrNioOz`mٯފ^|mOcB E\vxE v}J]pEYdVFpZB'|~䟶.\zzwb}3˙(k1 a'*#z"yh;ٺ:ޛ&hRW<&A`Nw'**rE-ٺRdVuwWEa'+6OyI-J/cNM=~D"YW'FpY(60$ vV!9c8.P/ȐEڐEd~Rd GpFyaM -Z*,b_ͺXh1z[¥/ he&AX73ҋ+iC98_0x1_ٺoeWJ A9sxEa'M uN$"@^][[J/2a֌$FaNOQ9JLmOB5IFUl֬N)uJ8Y` BLyγ6.1A$ vF!9e|j&ʚ0sp 󃑕, U޼q{o~M4ԩӱ=zioFgU?{Vs M_jg vҵkÁAu#7`ޮYD[Hf~%NSq[aٳg:x{k=$'#[޽ݻ6|/M!Csm[MIIZȈ?vc:͜1cidTTfnRsG Kg>yR'շl>z=9jԦkd Kq y@IzlOBIqgWPVPT*֭[7n5=}Z {߻W?jڬٕ ozذa^jdvee7o꧟Ə(f֬^V5?~رcׇ)p,W\ivʕf .t|oԷo=4-0#C$ּ.HM;'bem-nI那Ud ј++;p|AV9f-Bqծ^lĉ"#",.. @VR兾?o##"NMx欙3WZ٨7Gnx\YWcaYѣ JTT_M3 bdANwf`*b~. hF!9ؕ&Z*\!ʋF\޽{w/mB¹ݻ@T^Vղeӣk|tOzM2jusԊ~ʕSYq>(e˖1 +U! <2ӧ/] ֏XX YOVspJЗ7J?ڪ52[k!VѾGΌߚ7 spk+(0~ܺFqqҒY3g.]ݻw @Fj߬h…5j2?FAo߫hDVeefFM7n]\llK["#051~dO8Wcжf]Ut SE>rs5aNpU۰aچ2f0~:Q\\0tcFڔ@W./:%%ʌBFUݽsAvN/?o242Nk5dOy`՝6%$-lKNpK:V;~ק^U^^(2 ׏mܨ η?~S\ll;p=NLlwk@6UM&IH8yf`2LTŽskڲ&?w~sv3:Gp6PBvWٲEK~md1dd蔔t|N+_n\=dgGJp]%# us pe/]jѺU W\nllspk&-g_L+4ڴWJ,uspFzx=\Fzzmݹ};p,^/z9/^٧zk5-s͛7ecpe9r+#8O]9r4p[/_ŚQx=\ŋZkLU `~o.\0uϙ5kE! {0!2ZշO۶ml*VfRXV@l!^)U7ܹs;G  ?wUw?_:㛯=vl>)SV"LFhTdܙCr,AP)Gg ۺRUtJ:\ h;| ճ灲R_d?޷tsھ}ېY~z㏓-cd\`8pu pWyEԘ-~W*jSB )s0 kp >鴲}kc6o 5--5]…ǎ{|d׮ peEEzN'Eza ǯʌB6=Ȩ1V#3 L-4ƶܞ}z!ޖt>_WJk$u^6nL;w̟7oӧ/{YU)+-Ea={˖')u9c/hו_&XExoLe˖MzuG[;v5k&'=בÇ[v<2W^2 w98]`oO_ز_bAk2\4L#8ET*||Ll7nzsW'¼0}ڴU!N]L;98-AN?WS?XΟpEW1U슝oN̿ |z+n# r-[\ȟ~gOVCBYiK@&ܹs p67.;HGQmX3(d5* 5spFz 0&9sy$K_,HD/ $IZg\F7y/->obEEE%%%~EEEEEE%%~J2֭[qZF·x.X0gذaҴ uRs|||`0zDKJJJrrrJK}?8iOiӶ홠|=,xqzJ UF/=͝;}{*_ty:&mOBuB %;ߚ'Fp\z{!XlhٲqqnUZ1za'`YNKKkxС)uxbK$tF,=޸qqƯu}w >"R'$$ިqQQQRLn׬Z]ٳn݊Ku+ϟo͇:t3W|r'},O۴i{**5oܸ/]1|._ԢyW*]f!yT>'e"U{o&>X:WToϛ7ϙ7^^ӷΝ;˽;F7ݜ1%9kV~kڵ23ϒŋg7nA{d-ˏCPppֽ:Of4ݠAGl!鴲'O&:_V\T֬Y3Y) /GZBBBBBBZj:BNNvDJJJݻ>|+c6 g(Npg6j#8Od;&9gkq(VP$2 3kC4RNugϞVKIIcת]+degG[aL~w Jϙ=g5V~a-<ȨqرN|Srrr|siڬٕM7|o>ҥaO)nT*oxÆ1M\sF,FAiӦQ4=n{㚟Κ? tΊD|lq98S/Pe?W^)eY` 0e c=z3$44o߿{Ν;;oo2 'Mz燇Eߐ:|ݺq|Ϗ3bSn;]t#xzQeb~Ԩ/_tێԭ6_?=U g?zO>Y"5<>c6 PwP/Yy#8g],^4j4z\yfM'rÆ cyaÇ~~IUDx2\?st;d\Y ,0DYgv (/]Cn];ޚ~b[H6>ر}`G߿#]^,jiUٳmկc߅-[GgΞmӶcGlVZ?w|su/d$)vO?oĎϟpN'T r "U): ouT3z:ѣG;s} _?Nuh:2,$$4o=CBC<ӧ1ο(իM>NtZϜ=ۦFqOTTgΞmӰaS*RSo!7~BY\\l]#8O۵ޑsQ)ږmWf'xaOTдm Ʀ"&k!wLZٳgO8ё 83$"(@OGpkn)?$0/OTfg>zk Ŧ_#'wvn 5spF1&w4+c~͛iJ9҅c,iIIeV>/,^e嗢QFmmR Qtr$2*|<32;[3S@f$*B:Yz慢ŶULO[) A|Me_D۷}$Tn\B侧U_*mvd=\Ŗ^>$Z:Uk_)[]-4Gc~? 'ؔf0np4[]^mv 7n|]fͮػM R|Uy}|Y lC۩Sc\8RbbIXlQWUghٯ[m~t؝{껴}ܞs1hDߞRc_R]*6^vC1+mw0/9ODŽW/6$$$#҂ .!7sK5WVZ]-9d2&&&涽-/+h^0 ,AP;kv؝{ꛛزo=[~+=v,KD۷s5f27bgabd\M9vͶf_t(oٺ9H?R{j/7K?x0omis|}*zJ+ʦu`Iz9g/Zt:N5-'h^^y߹!9Uxp gyFk\f߮[> dh}<<]jd[H].&nO˖+@ޯwipD>'Պ uMGv^_+|!::, /vkU|!_ޯw~pDk!Y[ \ B 4rV"Q.R8cWz+bM9uquyJA&ê_E)26LrF) 51󆟸( (pZWey:&ݽ4$'¼ZPZ|.EbA*i]Eю_ (pz B0xHdĵ g?oOcН o][S;.SD}Z`,V.L&D0 g[b"WPy^^60x\ 7spYB2Y51}4I@fOWg /~Ughv:G߮]4n)Xl tRZ텞 +H$zdp-)p~vx+%Ŀ=Լ~H+{NAo4hvO9o]1z:MPyb؀,3Ǖ(_ wL >VO>.)x^eq੶gSL34K*Eo8w9?\[ sΥ"c|ԑXtL#pK 3BY@/ǝ"S m?:)ŕ IDAT'7& Ɲ:%*_+LRÄtLJDŽiP SiJ-cͬEs9kۣ n+pV*Z,_}Zfz^=f !2*Q|QT SjaJ-Vg-V(24-$ ? r24{cU4HJҲ?be s+yZ&8 Dc {r :"ҤXBԘZ#+|Q0a˫ dI@{+3v]Hl=c#+=ϥ2!(ZGA\SJ\폑?^ gC^iYndѭ-$ˑp$Ač|CcW xc5u/p$ts'BneL6n%? ^֯{С^A4\QF֗ iA$N[lϾeB}H-'L&~sW_MKw2BA(8F;ft_U<3L@iy:ۺIA/8WzM0a ` 뻒&IdJ;8Z]Z/KHqA{Ɏп_r޽=Ȩ]+xؽ_7"]DIH *.%=~v`xEN8$>={"`+狄i rA'M^yr>{ݶRo0pE@ Ul=|ޭ[JJlKތ&~K䢖UP)ghߺQ_t (vOߌ5̹ 2ә???gf1| p`Mqסl34ZL|UCet^;M Jde'3.]d*~-Q1gXuL3 n2z(`G2ז\-8TV1f|C_-xL=^jOdYWp_r!qV`?^ H 2:w|}+N==@ΚEt6G{vj_ "T|iGA^Bs_F P[]člw2[l=hz B?v"M97(<j*|~pD>.,-+D#hϞ>L=Qm(J4$#^"}ujCwd,!zVnz1ZIQVƕ HF!9[v7K/no>@&l,S$m}:|ҧej qƪMCDWqY&rO'> q:6XRRI!ss}kt*;#$ܩ#'=v6f"=[߾Ou^.fϚh0 7-oWCVԟnT2O|/۾5d{(`>=O}\- ZU@=LLnUe)k$o<(pzI~Ύǧ,..Vj՞yyy)b=”RU lAL~qc//( >ztt# -RVa V e K5 Ӥ!PBHiU Etت쌅+\VԊFlZߦ94~L 4&W| ߓ34Qqg=7*>(ڰaGO" kk;tH<pv%]!s{VˉY7RT~E:3+}T1EB?=#'+*p u*Kap|;nʼn':q}ǯ]/F<[a[l˾UqϾQ2=f !p\2'nU>Fj՞>ǟVMDq]SX$*j;jzM=] **pطo_o.ڭ[JjZZäNǐmv^34sqH+{eb:DHOUbcl [\Wh⎝;^jdsUseZٔ_;)Z-1Rj':X͹j^w8S*ii6R#;Rd0Ne:X|d4E0 m6 Y$D܇8 ?_c+1~Ok,1AX>τbd5>8X P{d}|_L5`)jz,=^By'X,. N )]u|vIhY: N׆Bx __Rd4_L(EfQ'0|3BNg50t| QjE۝ѷe,[BX,6aVVV,G\"T|IDF:LrGHbN#%(ps!p\aNԶQH-d]5¥ihY:8w"h Y Np4Wf0%(pNbB5J*I}j>dy񵊶;ocb !n#X.ugZZZC\= Np$N+3"w[ Ν; Y`vŸ- kwK|ӯ,+41RZk+oBrU>!WqdeeFrMEEEnS$YۿS#pkyܢ)&~K M̼\=#%'D#n#{Ө^\9rիW*r=FAZn#L&ၧOVd .'^uEΎcH-4WgBrNB~<'GU|y`2csʕfFAlvQ>t t0(;va6N!Ek4Y@fgP[vAC>4k&Tv:"ŭ|pȑ.\9w\$)Qb86J2- \ʌJw'ϋ E.2 ;LHɉ-g;!&v ( %ég,ָ|N:Ձ=ag5ܹɵ) ,TT*ٻM.]|w(w;MQ7&$m?j&>N~Ni2w`:֍ZΟ?oJY,.9N+** yf#{#lڸq46w 9&GK@&MnE\HUb3>0WN|8π߲exK.,*, Dg!;I,n~w]u/^Њq 8[8i`6t QcI=Ӥ~[!o^yQ1A!9WvWU&7n'pӧoҥ\ț'bccSj;_ YQ.cڵ9؁LfZo>\ېÿm/|rW|>Qzb$AXJWXo\`ɞ[+$lڿl;_  4}[N,gZe|{`:ş}hUJKKn:Q{ *&{y\CInj'Ҩ^\ϷQຸX]Yۺuлw4 g4ztqXrpNNvgY.pk ="fNbOe;G?o۷8uMONsbbIsʕfcnjـ_V}\UCdf͚uƕ!c2E ϟUt9,W&]p 8NǥmW?@tX;%%"Sb~|z\薄&(Tի&.\`#յk|A.]h?ooDDDdcޟ:uLjMl=p1gpyY?Ό,Ac…裏mZ 0'%%G ̟˖-#\l8G8jX?;*,W {/b%[-&1|>ILbbKGM,+*b^AŊ²}g~/evy9rg繻>̝̗wM1]^5{,Q,O j=w~IV#xt 0fz}Ç M<.&&Cc!!9;X6v/\ڷw={t?iotֿ)-[6OԱõqc` GuwN2$JH7G3ŋ]%?,&::cԩ}ĉ[hgB_d*cEbySOTmߒ 4iXu!ߴT.>pᄒbӔ)S6r=vS^}uCѣ wp|ys.?p8...ۧ32wi}ΞaJ&L;2 cǎW=<=|8իn1%z ""88z8QbKc=tʎ^«=כBbqǬYVzzzś8od8vA~^^`~^^`c=bnkf c/ƍSޞ?Ay#~Ze?8o֭mY,rquU=Rd=z0LӉ,KzNt:R{ IDATt ,,( }v{lHb}gnT.4Ki%DJ6nIտa3Nzk|MTԭ۷nEYԣG=:uDD9sOӽ ⯥{6K1Ah$:NT]]TTT䛟PVVɥg\ARW12mD[D*cEay^X5&m&mKL7lx՞1Yvzt۶7T*gd,٢Ep&؂H$~[s,PEEx%g@$^n}g_~HpOXnnM{rC̭ Cƴf[x-̛7Ǯ]k E6R}=9a„ 3fihz=<7 WO.4'vP̖lDjcK):&&Q?mÆ߇Bm6J$hx|+f M MWY3˻{'|"7Ef),A/Q?ǵ11,K.K? ->}א]xoݠ'ZBCӧO_H4OڋHzy„~w-KsW|Bâ)aCOYh׌7~ݻGJe25aDbvʕfDIQ̜s޳gH$"" A2b_z]/Ux4gs.MsKC\9K"`V4/jo͚r$+1q'N.A4SK/Ǿ[ G~~NXÏ(6#" 8kAKLz&}W.=cMfVV^/@kz왖v\{&niѲm;vxrl߾N "ϡ TA.NqmL|4Gv6mn|ܼE5̙,ɓ6Mѳgb_x|qN`bttL:Ͽয়y=Ν֓&OބBgdӦ;bSڵyDrYw=?zwCu*EL;.Hԟ ˫tСNK E'm8qw^vݻw|pShXéӦ2eF"DUf6loM6M޸aëY<)66_cҤICGmn=zi "ɡ EpWcGs52- >HeeKSc"H$Ri_a@@@~@@@_\+c<1&ݍv7wV#)(((((𯮪t:ѳz!˲dS!IP(/hK [(iĈ= yvc:R9tC4i~~~@aa+**d2wabX{X"Ѹ*~>}i 3{{2$$$)VbD"<;Ϊ\f....hZn}!]ZO.].R4y}}cHu՝fe:z?)]hrg;[Il_ɾkyL\ejvT߱Wi`\f.*$ti-,I-" A o0.+|$t1@h&=n&=F$,Qpt{pwp2Ae+:={Xe V[UJ=*5^SLE | Qp*S3. ;p̊>$~\Rov_^(Sx_0 @pN7uV1~~Y{UFn1&]*uTU=isGIw"M83+j&A`:@cҚY\|8ȹ = =.9#ҍUMm2}u7EH?=2vQp铋/_ ź_j'2@xN Mq<_?vG[(k_of /*OJ-ld>"yWi ]+ `ku(JЙݭזC'̬plU|\ejVcdttvԡ]z3+UyOEC8-AKlEg\(NVg[{vooC88DfW,W.({pLUT"kk*~RӅ볪6L ˛VpLMn:_3,o*H3p<Sd 4]R}b]x'rgN)Ģݰ:-,Ϩå\*w~25Gf8A+L,f8İ,%z"Xi긃1YVׂuBDו}'qm\9!c9ʅXM}km.M)`L-?Vy&bfYi% vd rq@ۇUa22OFd 1u#߳? e7OD*#OtS !#[ʛY?7X~6zX3 m俌o)*Dz2HN~Ya?z8\n`[Uom}8`c&F\s(f׭.<G~֚-w4[׌o9k+nOm95U)՘:pvW;jK^+Ԧ8ecw\\ŴЗP{ྡྷԗSJȭ659 kL~/1`_ѱ<9QhxeZ'WǦ3㰛̊_?UK9UkqTߡ۟;ws6 8`#ʹ"}wD?:6g<#_tgE']òVenT;`>a4~-1Z{c)8D8 (W!ʑ!g}gK%ζ`NMb]iZ Ķ; =X|@mdlR`֙YC%ӊt=}&\.C Z3+NJ.{آ1O/ ;XM;Pز_>THcE^\kWNCL/p.pzlC}wn(Jt'-b}ӎ+PRkߑKxpg {w)g _]p}X.eÚKU1]kǃ1hs '}7g'!/ MfX8YT#^T.Ҙ]R.ʩ6`X! c+<>S\W|X8YK3\:] S:WW0Kz"b^mKi&?NFf Kkq1Xr4/c1mޏy\ʛU3*LEHWre28DXcA:kjQ*S޻ O7Fqڕ1:y 3΢/\#Dn*}vWl/"s( 3Kq'p E\@U!T#-:V1s=!XrvA8&Vty94I dT-J-Ӛ=-="Ifi~RpÚQhW'K ^n%喲?ں n"ӳ!)Ԉ,u,rb +nV͞s|9R\1t! ]/3hn14Iƌ5OMgfEyځw?_m (1("*%tI{ON^+H`}3wpJ",P+N(tf7HӚ~36>" B@d\.F:KHP8[E6|ub+D^g HpE[W~"Ҋt=20rKSq@ZH!8SnL!Ak.wO0,A1,KH?xsCsqPk%\x{w t},>622KФak݈ 43$DlΡͤ伇> ʺiJC+.Gc\\Wcb$#Ix1ÚI#А(. ^y84D~"2;.x?_Mҡ =#%RѤn`Q(n5pWtWnᮂ]|1uKͥn.ɕ{T3ݶgt=8I\GkbĖ#Q}>Çc6N8i0+.vߓbX/}<^ 2jSP=E'_\p`K{iF>\SgfE#S5} EZHVV[s!\ W蕠P!E1uV!JS(>y$ ],@S烚&n1Y8 >C{N#`M#M@b]?b../{ҸilO?,@S~ GKKqY@ GqlKn'6u]ҘHǷnŔs9\W=x.(6G]+SK7o1.B2eo\7yDl蠘]GqY^b>o`V֥;k&ʮc[ȶG 2zeoU{w\)G.zڴeL ksD"uK g7ME{{(mc2:T!wl)qf_mq^οml |[m7<Ҍ@Y̓,K[z.;ۿ*"D/Z xO=$Aں>E5qP8Sqv}n;5f|)>*w|o' !MG7 e&AĊW-1%y$aFY5{ ZGP/1]zL1r\Jɶ6S0}[׸V.ldtwV|_c|$#|GAh vQ$OI{ͭW s_09!Rm}qҕs=SlMfՌI@U֤Vؓ%U ?_M] N$k¿@cenKc;Z0Hy /wEqtܷJ%o?Ӥvu_[; (U]Pm`&nzXo5&V=X|ra]2^ީ~}PF`݄bw /w >ѐ"I:ֿ&8jL )H]z!6ZH]оJ}Ʋn eT.+?⋺"׻3"Aw^!=ژWIҽ{{y=rtcYq ZsD =嘖.{G pC8̊J4foqsR )L7,d5C&Dy<9Y]]XcZdo}"l[x4/eu9&Upxo?)]8lO/)X|Eym(d*fnJ W*~yEayu=O].h$$๔zz2}jSPq.ӥ>R;O j}K[W|nΈtdK#8~9Pڝקt}9]=h|)>Kl5SD!k,zX3jsvS>fk;MD+NMpZ?8Xr9d굙U2haFj@_W qYGǼe/Wqrl+m5_25&V͵HW62 fD:yzW'Cce[J]Sx#u@wU WQ8frc!]r}.*$z]}&|ע}EG?4tmVOj9+ʅ/R$VZ~.\Ua2=9lS]W'Gqwp鳧mi)q}Zø#%s|/?&`gbNZY5Ҥ>CqD[.,TpSp6Y C! gOڿRG}?Sn[9 @Pc<)xHםK/>}xoF^ 'SnIrC5(w˰,,C5΂sLa&1=HK%\׀@|d|R,=AN ˒M1?WK;l˿~xd|y95\S\$@P1Mj!7(pS1MY[rC5!I/}<^=ƒNe`_N)c+AfWWriLCB$}hR =B%ԭ]|?ooGt>ǯ˪ςK c$2 `^;Y 2P=}F)BBR;Oa;WjS-I.P$6; .ɪاiڞ\ψ0?_S{'86]A<0}탃>^bkLd扲RJUYtQa>YQa.њ'X .'M# 2dYQSmd\VT2,܂>]eW1N \+OSҤnÿ40.+2f/S5{Եgz=J˂.K܄ ~VgV|dٯ\׹ݻ#C`ϰV3 ˋYe}xu=^ofV,z@qgFzIE'^R|Q&A7*-W*?SYf@ݝ*Nk";`PeNꏫL^y/q톇J951OzA&{{'-1GD6 [tl_Rdqޯ4g9D0̬űݮ0Aޡ %sn+_qn.u=İ w_60N8'%ve+2\o`P+%tIm 1\w{OcOh;WckfYzG0LklSe`!w(pCdzz>kI*Sj.İ)[L1Mf>8YL=ib jkن+c;E{38>6LC}3m=7kkōM/(^oKk2f`R}Բ-!,\&Ḛf;B.C~Hym2 tMjS@ͥ1=T*uԘYft ź0N^+'8'حsE2Ts>Iu$;mT-my]I2 z%],#AK62,}ж<ʼnOVyVkb\RaƁ12eWJ!r~ά#: (KGjdVͰ5.u]42TG'4 1vR/pI#W1ЇOF,O{Rc<~]Ϸ(P=is˒B{]NLE~U-BgvG MR;<6?]F!'v,72,?tSޣ`񑁁r>YJ; CٻXc)ј4>8@=63Tsd'64m!>^kSo7d/T~jWܬ=8DrhP(f8eYDC%{=6ޢ˗^kyd3fr}|4v] K\' ®t4f߃9͐yj##{^`'fC%'ǫqA$Ȩ}<_۟aY!)^o#(Ͼ.>2yA֌Wxs|@[wUFTcKfJ=&>/ϯT~bHx9wX3lxr>{ˁ.$9$N$9)eE+ʅ벪̬>?u<0UiFi}Vԕ7UQ[sMh%]D95cZaFjw ޕ= 3 6bb ^Zgrfȡ'TPGi]wK $w^;޸bc⽵˚ڕע1Oʹѐ%h4x ҟH|$؉,Pޛ8+~6~Vzܮ5.Z[̣h#9!X|ؚc<],C5cP6賦e|J=)\ynV.l[<Wm UrMAeZOո )e g«"k|xA%tPbBGt{tKU+f=WqǦSO*:,hnnsZz]xQsMp'>͊zNۺ@g̑eZ5 >#{ ןXi`\\W-XwqoWH4 !zO=~Yj2} '^Q?͌tZ*\CTE'j{YҾB(T`'(pZA\z]3{.Dޢ'&.HrdO=\O#)8Gϛ&u=iq ɑH7~5;IydaV rpfek˵f6l+Hk:-t{ SK7U w[x w)_zrxK>p= ;(лAs],9$N(>"*w],l@Ia> \*Xy?޶ L~|] ՜cufVԐcФaTt׼c%[=qRjfK)xf_.‹{gZ~jfݗ&uy<"TlɩjkW85E*yi?.PmDeħHc Ij|H'o (|آQr=¼2=H]k۶&; k&v AP{֌XQ57P۳!gD׼%_/ n|Jm,iܙ }|@7h((p6'!Eq))9;ʯG mMyjkwxoB|=\%E)mw0(:\m|J&D~|.(N S|$tǎ9R3 g95hڪO!Mw 5$X ڬnVجtdΎv^19\ILZGx40.[3rMA)_ |J=BUû, Usy*C&jgb ל*?W]>r]:=ςˏTP['^9@J?f/뻤jj⯗۲,$hAƠ сԞ~4ERC\jm3D.ԆE{U)1]w L-Vdfo[=85܈ ̍v^$NqZ3gDRd/>)ޠtfV =[ihXc >y"ɷaN; \-w|^q-d۶yYah5?}5Oƍe:hfPJ6.5W;XJƧ}')#arç?҆MzMJ1>Hc#K/vr4z2>.<7yY+~-8X,i)nh[}wV|7y|̪. wܽ3!=];S/[g~O!s|F!ȓ+u7YAcŬ ~|Hي﹒0g{[.Nܜw/_m p}/(fF:n``A19<'`OP|UFV)actd|9mc.k?ݪz嫞׆&Isk!r 5AFyGaZQGYm+$L qNOĥjlĞΏGx}~}/(r8v]ltt'vL1ӎ+=V붾&C u=Sr5qGsnU{ [fU3gvW{F(*)]w+@#O4m)q%|0"ru(p2CqiL<4L vR dϿ[Ps7~-gt=z*LE̺ӊjLWKT:ƭHt.ŭ\كŇ:z 7"hvPJ&.5O;+wO[SŌw[{.L;[aqpd͉v^>9\ILZk(zk~FiIڻ|DOrfdbXޑ'[@/CPh{KL mלJWe.!Jӑ\m|jf}a6O k~(pk>)6I2qA┹)xW5k_F*mI7T%N"zAj9.mSvUab8R#,-/hā޸Kur(eӃ=w&x)_u,1ڢ._ʧjވr@䡡>Qܬ=",ikfYzʱҍ.ԌzT]-!u!ݯ4hu|{=K SEzN;j>jTtר0.k#\B]Ouk>R3c}3N]bcOy "H IDATkk[m zd[#rڻ(Sfkz'PCEt)ɵ T,HD"X!- -Bzl/3y&Mv7̙ss&'gx*8k!hľ 8 ߯{j3_oߔL{ JnFRvS,0:0Ƨuڼ;ؘ@ck9*z62>GNJw(G qJ]9x%Q͸-(nV yKdWն(d< ROHl,v:mw8=02/:N;'tzC侔Aϔ0d5X\?*3T[?Zx N1)7/* SemzϷidة, F;+DSYKT۞دD&f92N3zk APv&vZtGi0O&ᒺaMD{=_)QW p=yjM\1/#isi{Eb}^ s{3 k)Pଥ]U=0;! Mw\vfGeifs̲s<;*]'\p7\p[Fs˛gTTabTXu]sTHq&s9(pRJ耳>MEavk-S\q͹>&I6̰sE EKh(Sm3YACxsG5I2ﶓ:& ]yB_BŜHn*g}C'*SnɤeRl,ꮙ*KP2-6؂_8Z# :N“f\SI|NrnUXZ7t_ݽth .ff6o*lOMYST<8koTX8Й?7_p)]C?%ZtVxמKYf|}Sjg{8BROckXY9T|5C V$AK(yB_vowѩGhQpcC]e>Cgafk'AX? p2MwsƁ)ho.뢜ceW$lkUj[V{>BYɤ?J7P " u'fݩ&ܩ$4t_X o[~SB@W ৓".;pCGZ˭EM?^=دzex =>23{*t!Ĝ†{.03%ꃐCE x\m`Vj7yƁfHE4" .:n ma,AP},vpSc}0Uӎ鱣4^t,)ݬ$}v]aE1u^;ǟKVw+Z}tldH_O9|E7լ=Uo:B '\x]Jrs_cnƊD+/}F,,j[tʌvz=K7dIC1SBS; O8˚٬>VabTQ>[_kjsZ̨3tsɮtG„'  \~Iȸ9LJ'qj ])t~xOi}<Û`/~ȭpmI^VbBGqN~ΔȘ^ql-vڱ5}]զCh#bqg|I;G?G!y:[x$흂GNlrifFbSgW-RU=T3_lC]a UXh#a:+!_%*ݟ,}C% 9Mt 4peWs枯\ꊶ4i!<2|EP)whg{mҍ;0dg"#GR$i#cĻ$\Rk^yW9 f&VIwBXscjkb=` 41J)*TeS)k\`!M*V?Zk۷!]gK~msG(ȈGݟ үMKKx y-_mې(MpѱCy!Ƣ0d/0q[%PMDR$iDyau-+,ڙXM. ZŻ' Na~~]amW䷽_nml9 )hRL}Gq=E$i;)E3' OóG~]wz3NrsvKCtaS߆-2CfǦeR/}?+ޑ'I&8 l?xGgb7/ ħ3*p8}̍ K˿ˁ |4Vɼ% >g֯0ŝ迧JM7K@gg&V}],p8}K䚌isU.2m'V^(ƐY*W~I1U[7SՇe_q&ذNKX^pfw#&ͷ'Dj*<1ߨIԥnԏϭF~{$&K6J9ޖ Æ(L,E̡ WCӇA3OWJԥ֥1M%>z3TLr6e&Ltbi]Z;TeknaG*wXw_7åN}v+*gZeRfƖ_r?'bo}[w%mv(p6G_9QMڬmHyvQr6A0;U֖ 2]޾]6ߐwϙO~2쬅 y PG"Nq8OΦY>g쟖Kݕ0RwޏC~£q]R.lL5Ι1M%۶>8O#7SwT,dvj-wRpR/)^ν/PGqΜn^vϿ&ҫ7 tQ&AMACkL-y>*hSAcGƊw hsa#kC~-{]9yb~DҰa`$.EZ@7{g+ag#sߞ,0/}zkwK:+xƗwg$VS+[jd}hcb7 41N\-GkIؘaGW. '׆Jw<ЏNԥ/0qu!?Ob.8i;?~x&}|x&RCqI$EO7kq B%j! eO׻ S$£ltG|݅G">~OY{J>v/*8LȦ)]AE01wVYekoz (wz'ʾ}RGiӢ宺J~}[i+~_b73$6 9Z[LZ.usn K+w/^'IKm&Ns3r&m7k,̙ŝ  N Nz|یJKBmФivux}XbN)1RN+f[g*>sFF8]㷳}2GkQH$\J'S(?Nn+bg [֎b\JoRDp1=٭ K,}LZkw߯crj3I8]c٩GJ:˥HgBu _&VVH|J(Kty=OP Գ^Q5Qmۉs3jpõƊdki}OoQ#cDz?Kpb=~\mtbчz̏mяH/}ҫ4%𯏈T _Ҿ򯜉]\0|1V3]2u[ƖAM6n*Q't1Fqscd2K[;\J5XpWHNKXU'8b_9Q xvW$K--7߹[iiWMyp鯎Dhjdt*s8/;.ahm?Q08JtNx*3h>/dW9ޛ]'7voҍەc+hҔ-:'IK23Ĵ:[6>#Q:5N(p@?nUEz[HmP%]󦷒~MLtL݄' k"޻'%hx]:-3{5QW=Ub|%F{ЌaQ_5XD.3NUƩc!ka )[?2L׼;lŊ'Dqs=]5vxN{2<9oGy~/ -vF&pݘL]bswl&fMlJ˹]ծ!moܯGބʸ~@ѱXE ;KПмbBԶp '7=r#ߺ<>2O0Bsm睉6>][^z]4|򏯪߫Vnwx= G(Lտj[ԦL݄L]3U@8Ifab.m1wd+_4IWP͜(]x .D!$خK6hlZZK͝ +*␆T0'%ot_=^?]*k Gq"DgxVxBbSWCTX9VJ )dG˭ߎi*ٮUz{xQ~Ez{}D!x2nNM0|ܡY$` :kL;Lx+q@jDƣ4qmj,6d~SIl`Etaq<7Sm{RL.#]Ӷ﫭o%~zSq,4pt3r#yBb\PpO5l}L.ʹ…G( F(b.ϭGeW?a z507[S$ʸ9=7[.n}*-߇WԡtVVR/oh^w搒e|S2-\5I1Ij]cGqW P1m81-fPyl">5!Cxb=8~C}G; +xnMo! 2eRwFv;'Qڑ1]q#4IߗĴEz[HCFȡFȻ(jK%utSsilX A_EΨ$Ute?O[Ra*w,- Qn^dgbm'ӫ /V-l&AC>9术\Lq P3"\QA;TMġIz٧Kn*<_lXP$03K+a'K7꣺ R 芏*{O 9-&vlGqdU~zX7rV Y8Goe 6DʐlDL.Ұ.;vVxSSE?4tVV+G?2-Sz$ؿ.T}!esnbQrX ;8%H`_m-qڶSa^8VcgnWZ[5xb9vxҍrKgZY隌iľ$M Oo{Bq `cDkf)ixo™&&-vWy/ Mmh¾ZPF$# IDATi$kSO,nAUbU.8Et񦁁~T]vߒu+jpsܜl{Nwfwk.WK_jo utS6.]Ĉʸ|}iT`&8m4im{ k}B#5z⽣#4+UO.")RWZKK5 p00OEXvE=@g s&\R EɊY#]yflUScXއau߄'] Q!~J@WԶ]-7m;KwW8vEn-I1,Akq/$Su2s;O\WK4ԿGնϮkr&]s-~F ϺWdđ_LЕ4 u0 N7Ϸ]謌3Rp|;R˟{UѤeflU]kQ=9SM dQO/\ptMiEEN 1dC}lGcPHew' L_>Q3BuQQ*wݖkeOqs`],}ÅBDtAD9ersp +CpyXr0|+ڑ)3""+Fb"gb#8k/Yڰ1Ǜ[eb5߃'xqw'x[$ilg/Wo)OZd?J7g_u5>_ܕm +ޅU^zlL~k!kPBܛ'oB<^xoJа탃 pkΕRs_?UJ5F@C7YuuS\ް&ڨGiFjv C>S󱴋r.uZŻMqr0kNc;Nh13ICڴagYzM͌ S|n'ce_,1'-<%ݓ.%ZtOfb,3wT|(p=Y. s BeL_Ĭ8H. KĿ\v%dO &QWn;JOj+g`(1ڃ\LlFJ9ogrAR޵ c:u˜hMS+?'\xSƶ~hϝ9jnʥ #s&aEUR$iϾc-D,,W+C<]1>0 䈦et’qkegNԮ/bNᮔr]j}ւv>%!x!?UJO#"mS"b#%ǘ1߃Bt֧28F$ toi_uE[*=i|z[hE'Mu7NLqGUU31p5EXFNJwxrGĈw PT[XƣInj\#G3:}^P oG]ՖKj )Hq߽<Ȑe[l-f>~ϙ&A(n+0AzOlӋ~<^b| ӞoI-&7[M>ȐT!}FĈw߱2nvMSii|S?XVۢ>yǙ$~@֠?r )m7O=R6 }^Y9/BBa| TٺSwT,>_+3su"i5$hiNy094Ikr}ɮX:W`cDĮꡚIAm],1'Ut}oI6( 3PF%[Zib๣r{BxWe_ 9.m8aZtMƄAﵓ};%hGijҖa*?XNznҍu&'5L5%ۻn/NsV?6 _;62oM Nh4FV{O~ذN~jks2sj[VVSUB;*F_ŭ,5ņK8'Eģΰ0뛚W^Uuũ2y|IRa؋~pf"m ;) _h| Nh4DtWW]'}S}^Ѥ&ꬌdm^V>p@r6Ku⳺74i$[`JdyKRO1h #cŻ(tW#G k&@h:Wb5ߪi 9R*4|`2=׎i*ٶX'ߴYOu ;;:xxsW.QnnS׶8i/Y0Y0Ry喤Գ=Џ1,.mOMD{0 h֙ϼBe4L R{g+?vթC-Jɽ]벵 u?6 TbaFNxI~jzO](pB+0ro^- ޽[\mx#kzm.18xtưn5IWtة-/_x2ָ/T}#[?a:{.ٮx5Fvڴh(5K@2:PHhȿ+dc6PFCcad?9S!F=To|A~p%WjZpӤ$A &ך,e?ys;Kо753)nAX&Ike .V-j>/[ڗZ܌r8Fjx7`'4*kylJO.!ڟG2/営#~\m#Ely*pTfbevئ2 i߳0[ڗ\Wü_Hm/_jk/T ʈn$ǔ(сVHhK46Smv00yk{U澔y+8S$7 ȍ-6}tu-nJn~G)3da'4:mxz'&I{U[qn, {xkVOr>NTwoCzm-,# x5srٍrsţIˋ w/%.x8b[n̻czl!8-)nALo%'Kck6ҭ]wEqsPЕqa6 &x#F`IJ˿^jzJm'_rfeqWe5; .4қN7Ya wsQbU"krD3Ji EtyW@$rNp1dvpB" ãI88c@.9+Nͼ0&Hѡ^{ԹւKW,e+if9S$XXfPme\Zl}^ַ/׵\}p3DŽ& ;8Q>˾k>4q\ Ū5lOoZ2?qM[sdGZ.UocuiO&+?80pR $Mvp /U-dX>'fI \pbsKvm!5vXލ'JΓtԽ΢ά];JM9_,ܶm 9DWs˗a828cC)Ki-z*J{ڠT9R#5_皓;[k֞M0 [ 83M2&F$H{\-\G"S[01]CdTZ&Qz9iv[;+S g玔9K7m tGYVYZTEQA @]*_rI-AOwXُδ=bΊ}ߑ;_d H#ڳr^ .  9K?f\&"m%?0ʏ,Nm?/I=ToJm]l3[?*Ғ`eX63TxjI%{{9'E{P;F&p?dh_AÄ'wUN_DvAXdhYfwOWD _U;0bʑuƯNo%nu/= W\SϺPl: )um靈O|v]Vm_XE9gp  N(2C~{dr00RSw|*ʏ;(Jxhp`ΠnUXZ?Pڤ Tp&r;g*?9UhYcK\0&3f{Ym.W_jֵ7gQb8&и PK&FUlm8HHMgl?Q`]kce=)܌azBG_UH9LhWz 5غ$KuP,J"0 cl,ydW־TkxT7+52JҎx&7[̬9g0\kevumKʣovS|B|׷<]с?Ed:ȗt; O=Z{N'لW,gX~$ؿmi<;*>P.'cc/Zajzf?xYx~%! R߇<-3l,ɨ$ܮ*5+͌$VƧ4B4џww>_b_\%K?0I>!^iQbAf >-[kxOF%v_2FMCAΧէGhnsQ2uNzmI4+1*VSƣ4W rk2v_REuQi  󪭬?J7{chݾ<6cc<ȪĨ>T/w2ڴ!QڗZI$["\/|z]g귴FZ /煮3Tp O-PF%rޅ%yF=To49 /W W)QE3HWSԦ +Cpdh}tY~\>%(yK(V ШlMxX#S$\ֱ]SQdN*8}p3AB&ץeS_\ֵNއm!V@˥#+Uy[߇D~;tp/ϭ KXcy 'o3}r2s_Hn/_Z+>ED (!#XbJ֥s/K֬eF{@}O%+N O8]+}r<ÀK̥o}>|Kj hѲ7ܯMo/kQjdn/8׿k"X)0&s'οPxK~\]ʣI-~QUBw眫\fgY+磂}f ^;N.4hw\8=:GQ~t5if;˯KI2c}Y~S4@ J7j,kH1ݾook_~D7 هcWJFZ׶G,漢>wO{QHAD?[{Wr_9Wq,ط͎AGF?<NmᷖOfou+%ﶧOw@ WiH\ _P/B4 _-r Z {{ site.title }} {% if site.favicon %}{% endif %} {% if site.touch_icon %}{% endif %}
{% for page in site.posts reversed %} {% capture id %}{{ page.id | remove:'/' | downcase }}{% endcapture %}
{% if page.icon %}
section icon
{{ page.title }}
{% elsif page.fa-icon %}
{{ page.title }}
{% endif %}
{{ page.content }}
{% endfor %}
{% include analytics.html %} jgrapht-jgrapht-1.5.1/docs/mathjax/000077500000000000000000000000001402514743400172025ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/docs/mathjax/mathjaxConfig.js000066400000000000000000000002721402514743400223230ustar00rootroot00000000000000MathJax.Hub.Config({ tex2jax: { inlineMath: [['$','$'], ['\\(','\\)']], processEscapes: true } }); MathJax.Ajax.loadComplete("https://jgrapht.org/mathjax/mathjaxConfig.js"); jgrapht-jgrapht-1.5.1/docs/site.js000066400000000000000000000057051402514743400170570ustar00rootroot00000000000000 $.extend($.easing, { def: 'easeOutQuad', easeInOutExpo: function (x, t, b, c, d) { if (t==0) return b; if (t==d) return b+c; if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; } }); (function( $ ) { var settings; var disableScrollFn = false; var navItems; var navs = {}, sections = {}; $.fn.navScroller = function(options) { settings = $.extend({ scrollToOffset: 170, scrollSpeed: 800, activateParentNode: true, }, options ); navItems = this; //attatch click listeners navItems.on('click', function(event){ event.preventDefault(); var navID = $(this).attr("href").substring(1); disableScrollFn = true; activateNav(navID); populateDestinations(); //recalculate these! $('html,body').animate({scrollTop: sections[navID] - settings.scrollToOffset}, settings.scrollSpeed, "easeInOutExpo", function(){ disableScrollFn = false; } ); }); //populate lookup of clicable elements and destination sections populateDestinations(); //should also be run on browser resize, btw // setup scroll listener $(document).scroll(function(){ if (disableScrollFn) { return; } var page_height = $(window).height(); var pos = $(this).scrollTop(); for (i in sections) { if ((pos + settings.scrollToOffset >= sections[i]) && sections[i] < pos + page_height){ activateNav(i); } } }); }; function populateDestinations() { navItems.each(function(){ var scrollID = $(this).attr('href').substring(1); navs[scrollID] = (settings.activateParentNode)? this.parentNode : this; sections[scrollID] = $(document.getElementById(scrollID)).offset().top; }); } function activateNav(navID) { for (nav in navs) { $(navs[nav]).removeClass('active'); } $(navs[navID]).addClass('active'); } })( jQuery ); $(document).ready(function (){ $('nav li a').navScroller(); //section divider icon click gently scrolls to reveal the section $(".sectiondivider").on('click', function(event) { $('html,body').animate({scrollTop: $(event.target.parentNode).offset().top - 50}, 400, "linear"); }); //links going to other sections nicely scroll $(".container a").each(function(){ if ($(this).attr("href").charAt(0) == '#'){ $(this).on('click', function(event) { event.preventDefault(); var target = $(event.target).closest("a"); var targetHight = $(target.attr("href")).offset().top $('html,body').animate({scrollTop: targetHight - 170}, 800, "easeInOutExpo"); }); } }); }); jgrapht-jgrapht-1.5.1/docs/visualizations.html000066400000000000000000000250331402514743400215230ustar00rootroot00000000000000 JGraphT visualization using JGraph

JGraphT Visualizations
via JGraph


THIS PAGE IS OUT OF DATE

Modern web browsers don't support applets directly, and JGraphT is dropping support for JGraph and replacing it with JGraphX. If you'd like to help with making this page work again, here is the relevant github issue.

Demo Applet

The following applet shows how a JGraphT graph can be visualized using JGraph. Try to play and drag around the vertices and edges to get the feel of it.

Note: Java 1.3 or above must be installed for this applet to work correctly.


Java 2 Standard Edition v 1.3 or above is required for this applet.
Download it from http://java.sun.com.
a JGraphT graph visualized using JGraph.

 

How it Works

It's very simple: the JGraphT library comes with an adapter that makes JGraphT graphs compatible with JGraph. To visualize a JGraphT graph you just need to initialize JGraph via that adapter.

Example code:

     // create a JGraphT graph
     ListenableGraph g = new ListenableDirectedGraph( DefaultEdge.class );

     // create a visualization using JGraph, via the adapter
      JGraph jgraph = new JGraph( new JGraphModelAdapter( g ) );

Is that all?!  Yes, that's all. Any modification now made to the graph g will automatically be reflected by the JGraph component.

 

Source Code of the Applet

The full source code of this demo is listed below and is also included in the JGraphT distribution (download now).
 

package org.jgrapht.demo;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Rectangle;

import java.util.HashMap;
import java.util.Map;

import javax.swing.JApplet;
import javax.swing.JFrame;

import org.jgraph.JGraph;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.GraphConstants;

import org.jgrapht.ListenableGraph;
import org.jgrapht.ext.JGraphModelAdapter;
import org.jgrapht.graph.ListenableDirectedGraph;
import org.jgrapht.graph.DefaultEdge;

/**
 * A demo applet that shows how to use JGraph to visualize JGraphT graphs.
 *
 * @author Barak Naveh
 *
 * @since Aug 3, 2003
 */
public class JGraphAdapterDemo extends JApplet {
    private static final Color     DEFAULT_BG_COLOR = Color.decode( "#FAFBFF" );
    private static final Dimension DEFAULT_SIZE = new Dimension( 530, 320 );

    // 
    private JGraphModelAdapter m_jgAdapter;

    /**
     * @see java.applet.Applet#init().
     */
    public void init(  ) {
        // create a JGraphT graph
        ListenableGraph g = new ListenableDirectedGraph( DefaultEdge.class );

        // create a visualization using JGraph, via an adapter
        m_jgAdapter = new JGraphModelAdapter( g );

        JGraph jgraph = new JGraph( m_jgAdapter );

        adjustDisplaySettings( jgraph );
        getContentPane(  ).add( jgraph );
        resize( DEFAULT_SIZE );

        // add some sample data (graph manipulated via JGraphT)
        g.addVertex( "v1" );
        g.addVertex( "v2" );
        g.addVertex( "v3" );
        g.addVertex( "v4" );

        g.addEdge( "v1", "v2" );
        g.addEdge( "v2", "v3" );
        g.addEdge( "v3", "v1" );
        g.addEdge( "v4", "v3" );

        // position vertices nicely within JGraph component
        positionVertexAt( "v1", 130, 40 );
        positionVertexAt( "v2", 60, 200 );
        positionVertexAt( "v3", 310, 230 );
        positionVertexAt( "v4", 380, 70 );

        // that's all there is to it!...
    }


    private void adjustDisplaySettings( JGraph jg ) {
        jg.setPreferredSize( DEFAULT_SIZE );

        Color  c        = DEFAULT_BG_COLOR;
        String colorStr = null;

        try {
            colorStr = getParameter( "bgcolor" );
        }
         catch( Exception e ) {}

        if( colorStr != null ) {
            c = Color.decode( colorStr );
        }

        jg.setBackground( c );
    }


    private void positionVertexAt( Object vertex, int x, int y ) {
        DefaultGraphCell cell = m_jgAdapter.getVertexCell( vertex );
        Map              attr = cell.getAttributes(  );
        Rectangle        b    = GraphConstants.getBounds( attr );

        GraphConstants.setBounds( attr, new Rectangle( x, y, b.width, b.height ) );

        Map cellAttr = new HashMap(  );
        cellAttr.put( cell, attr );
        m_jgAdapter.edit( cellAttr, null, null, null, null );
    }
}

 


Valid HTML 4.01! © Copyright 2003, by Barak Naveh and Contributors. All rights reserved. Get JGraphT at SourceForge.net. Fast, secure and Free Open Source software downloads

jgrapht-jgrapht-1.5.1/etc/000077500000000000000000000000001402514743400153715ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/etc/ReleaseProcess.md000066400000000000000000000105431402514743400206350ustar00rootroot00000000000000# JGraphT Release Process 1. Let other developers on [jgrapht-dev](https://groups.google.com/forum/#!forum/jgrapht-dev) know that you're starting on the release and ask them to hold off on merging changes until the release is complete. 1. Review the README.md, HISTORY.md, CONTRIBUTORS.md, and update: * Version * Dependencies * Release notes * Contributors * Copyright year 1. Review/update github issues to make sure they reflect the current state. If there were important bug/feature changes, it is worth mentioning them in the README.md release notes. 1. Run `mvn clean; mvn javadoc:aggregate` to build the javadoc and make sure it is generated without errors/warnings. Fix where necessary. Make sure Eclipse build is warning-free. 1. Run all the JUnit tests via `mvn test`. Fix where necessary. 1. Reformat all code [using Eclipse](codeFormatter.sh). 1. Commit all work and push to github. 1. Run `mvn -Dmaven.artifact.threads=1 clean deploy` to push the latest snapshot to Sonatype. 1. Run `mvn package -DskipTests; mvn release:prepare; mvn release:perform` to create the Maven artifacts and push them to Maven Central 1. Publish the release [using the Sonatype UI](http://central.sonatype.org/pages/releasing-the-deployment.html). 1. Before continuing, restart from a fresh clone to make sure your workspace is clean, and checkout the release branch there. Otherwise, if you have old files lying around that are hidden by `.gitignore`, they may get accidentally included in the release archive. 1. Run `mvn javadoc:aggregate; mvn install` from the new release branch to produce the release archive distribution 1. Upload the release archive distribution to sourceforge using the File Release System. 1. Add the javadocs for the new release to the [javadoc repository](https://github.com/jgrapht/jgrapht-javadoc). To do this, push a commit which replaces the contents of the existing javadoc directory, and also [adds an identical copy](https://github.com/jgrapht/jgrapht/wiki/Website-Deployment#javadoc) under a new javadoc-x.y.z directory. 1. Update [the website](../docs) with links to the new downloads, version numbers, etc. (To be specific, you'll need to update the [Jumpstart](../docs/_posts/2000-01-02-jumpstart.md), [Download](../docs/_posts/2000-01-04-download.md), and [News](../docs/_posts/2000-01-06-news.md) sections.) Be sure to push this commit **after** the javadoc update from the previous step; this will trigger an automatic rebuild of the website (the javadoc gets loaded automatically). 1. Announce the new version in the mailing lists: jgrapht-users@lists.sourceforge.net, jgrapht-announce@lists.sourceforge.net 1. Update and commit the version number in HISTORY.md to reflect the beginning of development for the next version. Finally, remove all existing deprecated methods. 1. Check and, if necessary, update dependencies: `mvn versions:display-dependency-updates`. Recompile to check whether any of the version updates introduced errors, e.g. because some methods have been deprecated: `mvn -Dmaven.compiler.showWarnings=true -Dmaven.compiler.showDeprecation=true clean compile`. Then use `mvn package` to rebuild an archive distribution. Unpack an archive (either .zip or .tar.gz) under `jgrapht-dist/target`, and then inspect the contents of the unpacked `jgrapht-x.y.z-SNAPSHOT/lib` directory. Make sure that no unexpected transitive dependencies have crept in by comparing the jars with those from the release archive distribution (which you can download and unpack). If everything is OK, then submit the dependency updates as a pull request and wait for a reviewer to merge them. We perform dependency updates at the start of a new development cycle to give plenty of time for any incompatibilities to be noticed, but it's also OK to do this step in the middle of a long development cycle (or at any time as needed for specific dependencies, e.g. to take advantage of a new feature, or to close a security hole). ## Notes * The release artifacts are signed with private keys. In order to sign this release, you'll need to make sure you've already [created and published your own key](http://blog.sonatype.com/2010/01/how-to-generate-pgp-signatures-with-maven). * To rebuild the full release package after it has been pushed to github, you can run `git checkout jgrapht-x.y.z` (the tag you published for the release), and then run `mvn clean; mvn javadoc:aggregate; mvn package` jgrapht-jgrapht-1.5.1/etc/checkstyle_suppressions.xml000066400000000000000000000011221402514743400231020ustar00rootroot00000000000000 jgrapht-jgrapht-1.5.1/etc/codeFormatter.sh000077500000000000000000000037131402514743400205320ustar00rootroot00000000000000#!/bin/bash #Applies code formatting rules defined by the eclipse formatter on all java classes recursively. #Details about the formatter: http://help.eclipse.org/neon/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2Ftasks%2Ftasks-231.htm #Details about the formatter config file: http://help.eclipse.org/neon/topic/org.eclipse.jdt.doc.user/tasks/tasks-232.htm?cp=1_3_10_1 #Path to eclipse. Needs eclipse Neon or newer. eclipse_path=/opt/eclipse/eclipse #Path to Java java_path=/usr/lib/jvm/java-11-openjdk-amd64/bin/java #format configuration config_file=./etc/org.eclipse.jdt.core.prefs #get the root dir (1st ancestor of the location where this script is stored) SRC_DIR=`dirname "$BASH_SOURCE"`/.. set -e function format(){ find ./jgrapht-core/ -name *.java | parallel --no-notice --eta $eclipse_path -nosplash -vm $java_path -application org.eclipse.jdt.core.JavaCodeFormatter -quiet -config $config_file find ./jgrapht-demo/ -name *.java | parallel --no-notice --eta $eclipse_path -nosplash -vm $java_path -application org.eclipse.jdt.core.JavaCodeFormatter -quiet -config $config_file find ./jgrapht-dist/ -name *.java | parallel --no-notice --eta $eclipse_path -nosplash -vm $java_path -application org.eclipse.jdt.core.JavaCodeFormatter -quiet -config $config_file find ./jgrapht-ext/ -name *.java | parallel --no-notice --eta $eclipse_path -nosplash -vm $java_path -application org.eclipse.jdt.core.JavaCodeFormatter -quiet -config $config_file find ./jgrapht-guava/ -name *.java | parallel --no-notice --eta $eclipse_path -nosplash -vm $java_path -application org.eclipse.jdt.core.JavaCodeFormatter -quiet -config $config_file find ./jgrapht-io/ -name *.java | parallel --no-notice --eta $eclipse_path -nosplash -vm $java_path -application org.eclipse.jdt.core.JavaCodeFormatter -quiet -config $config_file } #switch to the root directory. This allows us to invoke this script from any directory. Then format. pushd $SRC_DIR format popd jgrapht-jgrapht-1.5.1/etc/downloadJavadoc.sh000077500000000000000000000014721402514743400210330ustar00rootroot00000000000000#!/bin/bash # Downloads released Javadoc to local directory set -e : ${GITHUB_WORKSPACE?"variable value required"} pushd ${GITHUB_WORKSPACE} rm -rf docs/javadoc* git clone https://github.com/jgrapht/jgrapht-javadoc.git mv jgrapht-javadoc/javadoc* docs rm -rf jgrapht-javadoc emit() { module=$1 stripped=${2#"./"} file=${GITHUB_WORKSPACE}/docs/javadoc/$stripped mkdir -p $(dirname "$file") echo "---" > $file echo "redirect_to: " https://jgrapht.org/javadoc/$module/$stripped >> $file echo "---" >> $file } export -f emit # Creates redirects from pre-module javadoc structure to post-module pushd docs/javadoc for dir in `ls -1 -d org.jgrapht.*` do module=${dir%"/"} pushd ${module} find . -name '*.html' -print | xargs -I {} bash -c "emit ${module} {}" popd done popd popd jgrapht-jgrapht-1.5.1/etc/eclipse-formatter-settings.xml000066400000000000000000001205211402514743400233770ustar00rootroot00000000000000 jgrapht-jgrapht-1.5.1/etc/expandMarkdown.sh000077500000000000000000000012571402514743400207170ustar00rootroot00000000000000#!/bin/bash # Expands transclusions in markdown templates # # Usage: # # export GITHUB_WORKSPACE=/path/to/jgrapht-clone # expandMarkdown.sh [ github-user-id/repository-name/branch-name ] # # If the argument is omitted, then jgrapht/jgrapht/master is implicit. set -e : ${GITHUB_WORKSPACE?"variable value required"} shopt -s failglob USER_BRANCH=${1:-jgrapht/jgrapht/master} pushd ${GITHUB_WORKSPACE}/docs/guide-templates for file in *.md; do outfile="${GITHUB_WORKSPACE}/docs/guide/${file}" rm -f ${outfile} echo "Expanding ${file} to ${outfile}" sed -e "s#raw/master#raw/user/${USER_BRANCH}#g" < ${file} | \ hercule --stdin -o ${outfile} done popd jgrapht-jgrapht-1.5.1/etc/header-boilerplate.txt000066400000000000000000000012041402514743400216570ustar00rootroot00000000000000/* * (C) Copyright BEGINYEAR-ENDYEAR, by AUTHORNAME and Contributors * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ jgrapht-jgrapht-1.5.1/etc/jgrapht_checks.xml000066400000000000000000000031611402514743400210730ustar00rootroot00000000000000 jgrapht-jgrapht-1.5.1/etc/licenses/000077500000000000000000000000001402514743400171765ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/etc/licenses/antlr-license.txt000066400000000000000000000052131402514743400225000ustar00rootroot00000000000000[The "BSD 3-clause license"] Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ===== MIT License for codepointat.js from https://git.io/codepointat MIT License for fromcodepoint.js from https://git.io/vDW1m Copyright Mathias Bynens Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jgrapht-jgrapht-1.5.1/etc/licenses/apache-license-2.0.txt000066400000000000000000000261361402514743400231050ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. jgrapht-jgrapht-1.5.1/etc/licenses/jgraphx-license.txt000066400000000000000000000027121402514743400230240ustar00rootroot00000000000000Copyright (c) 2001-2014, JGraph Ltd All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the JGraph nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JGRAPH BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. jgrapht-jgrapht-1.5.1/etc/licenses/mit-license.txt000066400000000000000000000020511402514743400221460ustar00rootroot00000000000000Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. jgrapht-jgrapht-1.5.1/etc/logo/000077500000000000000000000000001402514743400163315ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-black.ai000066400000000000000000000434331402514743400223220ustar00rootroot00000000000000%PDF-1.5 4 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R /PieceInfo << /Illustrator 6 0 R>> /MediaBox [-0.0000 -0.0000 479.9999 479.9999] /TrimBox [0.0000 0.0000 479.9999 479.9999] /CropBox [-0.0000 -0.0000 479.9999 479.9999] /Resources << /ProcSet [/PDF] >> >> endobj 6 0 obj << /Private 7 0 R>> endobj 7 0 obj << /AIPrivateData1 8 0 R /CreatorVersion 11 /ContainerVersion 9 /RoundtripVersion 11 /NumBlock 1 >> endobj 8 0 obj << /Filter [/ASCIIHexDecode /FlateDecode ] /Length 16523 >> stream 78daed5cd96e5cd7957d17a07fb8fd6020e986abcf3ce84da24cb401263162079d7e3218ba22b353 24058a72ec7c7dd65afbdca1c8a287f45bc3766c5fadba67da67cf7bdf7cf26f5f7cf9e9eb6feefe b2ff34eedccb179f7c7276bfbf7cb8bb7f2570fafc70f8f8e1e19ec8f49baf7ef7dbc9fb9dd35fd3 67dfbfbfbb7fd87f33fdf5feee663abbbbdf1fdefef1f57f638aafae1f0efb57fffbceef2eaf9729 afef6edf5e3eec5f4dbfbbbb9d7e7ff7dde4c314dcab185f253ef8f6026fbeb9fb78fbcdf5edbb37 77dfbf72939b52d33f9ce4edddd5c79bfdedc317f77757fb0f1fceee0e77f71f5e4d673f5cde4ebf bb7c875f2ea7ffd91f0e777f9fde1c2eaffeb61df3e5c7f7ef0fd7fb6ffeb8ff70f7f11ec35f4def 31cd87fdc3a4637e7dd87fb73f84af5f7f9e27bf0b9316fc8f47ef3cfcf0feeeddfde5fb6f7f18ef b9c99f7a4f3bfbfc065bc27b05eff9d3f36d48fbe30b5f7df816870a58af9dfaf9c3b7972419e668 da14e778fd79b47dfce903f6012af179e05fed6fde1f7013a471486edafc33de5849bdffee7afff7 57b8aedbbd7ecb5fbfbe7ff8f2fa1f98725ccd34f0df7fbcb9b8fc61cf2b71033abf3eeccfefee6f 2e1fb87981f5ebafbefd78f397dbcbebc32b3040d33f3ad59bfdbb6b32c825664e394dffb5ff7e7a f3c3c3fe037ef4c9175f9debe1ad3fd75fee67ff85e1eefff0d7afc37f1dfeebf05f87ff3afcffc7 70bc96433a4bf9fcedf99973bff4df18feaf0d7c32bcc29e39effcf95b97e62762fab3e756ed0968 d273717e9c71f921ea396a92a2e73c4f38e3a5ae3887afabc675ad472f6b72d7b6fbc3bf3b87f3a5 ac3fd6e3adbb05dd4c97ed57fb650ce7ba9b01e9782fe76ff3f99383644e21d28db3daaa6e4c9436 83dfaebbd0d3f9fc9e0ddf1026bfd6735b5ed52ac7d3af77a1cdaf3f2ec36dfb75730f679b617e3e d0bcfa7a517ad96f2e738be7ed84e9e8ecde4875a6d7fad8f0860fbc91f58d7e1fb7330f1f9771fc f2385659b69b8eefc5b86e398dff6c9cff99eb73dc53c0dfb6cc6b176cf5eda98bbd622fdb4be76f c39b137cd18df29a4fa70abf74aa2de597ad6938077d7672aab7f3533837897bc46558e5d144a19e 1a3cd8e688f6fef404eef590f130b89258d4eadaf84ccf3e53605d63e6f08d44bcb1dfe7e17143a0 3a56389a72bd2c239a4d8ee1e17c2b0463f36f8e373f0697a3e39c6d2e6e214dde0c09c70759570d 7627af67d2bdfe79ab3d2267de9cfde7acf678290e8fdb41dbb957b23dc296c5169e7fb2def946d7 3ccb0d5cfd5f5ad7161d94dfae1b17b5fc63ebdaa2367c05d3f17a2777b5d9c166f3cfef60d16e6d 1d6eff35d2e5c7f0cfddc7d8fcf3371efd230b1837cf6d3dfb9b1fa1fdeb23b6de2c62eae2f43aab 55af26dd8fa4efd1d9db23998f3f7da8596416a3ec171beedd7a98f4e6a9d6f78f54e5f9e6f5d7ab c5f1e989f15adeb1e1671b23957e7ce5e3374ce2e835f48dd7908e6d2a7e4dab6edf1ae9d9fdf31b 7d3bbc260c8ac7f67419d8c75b79b67163821303fc338e539a3dab85ea256c657c3944de2ae975f0 e25d6060d9fa4bdb410b57948d07d637366e234b69e3c9b5adf5591c95bc79231ebb46f9b13f53c3 133b9f374e697aea94d6477ea3a679de291e8afa5ff5a97fd6f0fe5644fae4b3db6f98611a8f6777 374c727d58b24f5fdcdf1deedef18f9fdf5e1d3e7eb39fd3853f275bf813439e4b1efec4b0e77289 3f31ecd9d4e24f8c7b94690491569a88445fee1f3ebe7ff9e20919fef3faf6fae1faf270fd8ffdf4 0ed3edbfdf5fcdafd9a43ff6c6a3ed7efddde5fd87674e72929a3f36f7230afe826d3cf3aec832e8 a034a708a3dce7cb17931f7f3379fda99fa29f4299429c2efef2f2c56ff4d2e47f3b5ddcbe7cf1f1 e50b3ffdf98f2f5fb8e90ff8d7ce79df26fcc7c5aeff3837fdf9523ff031d5beebbd97e9e6e58be5 7979b8d88063c0c532f409b01df6d7972ffefdc94e6c54ae25e33fad94aa9df89276d02871f2d9ef 7ac08f3704cbaef54030ec8a7e2d0d436b039277d0680148ddb590c0beb9ed920f793ae3c0bacbb9 e1d7dc77c9e5aad760cef8e07621b73471f2e8391048277d38b0965d6a0e4c5d311b76c763f85a77 d1c52430b612f4e0a18af590b50d3cf4d26c205eb16d8016cd55feda7721b63ec16862da1885b498 8b909e9bd743ceb81f0c0caeec6acb5cd1ef720895db10e83a4110ae76163680e49085049e9d480f 1db3958ed9ec503836c84e6a000c88ff26221e7f899ebd262f24c46c14ae0e1be3fe5bdb85e2bbd1 b6e181d46818dbbc13dd7cae58bde59deb85b485ea89dc4f8b3b5793513b753766e3b5bbae4bc164 5d44a03d0082d5833342613b9821a75df520dd1898bcb611704c5e04e7f77acde1b590b487ec4931 f04f6c108d33db6a05a71953b508b6697de77b769adfc788079cb1b786ad66f0438dd928d67625d4 2e26692d75a33f38db3b324cc6b56650db7bb025f913484c2c73f888ad2637dea9369b2f381d589a b3e5824b09be92e5926e048435c48b632bced8639d07269778bf79e71d4ea7f99b107248e740b72b 55cc5940fc9a6c20c012663039db3f76eb6bd4915bcdcdc08079a0467c876c4687255c845c34227e 574b2453050a2a16ed583d075b82a07306160abca6f2a015911e2174448ac75df89e282f831a3034 09b2cc5f7165b6370fbbd0a3810e54d649715baade81452174c1277063737a07e42c33896af536b0 c782070fa6856701042c1173176de1db70870d67ec75bab2813dba2e02e60411e3fc31b270c8cda4 5875bf2dd4a407f086b383f75d0c14190c84b71d253be082acf9bd0b490fa94bacb0870e79bc1a4c 55024f9470225528c17b7920c971f39c3cfaac5be814fc33bb38302d499df17e1eda00f755a8c77a a102046f77e8a2928850425dd38393feecd22785dbe05d94d66da04f589df785c3443dd027b33b6d 5423e081e8676982a6aae47fc85aa5f6bb30158721942c103953c1566a9214c466095b1edc5ba2de 4905b7cc6d80cf21e904c9e41001eaba027526e9ce9d4a9befc4c2c9e164445f6d60a4b669a64b43 e76bd86d86aed6662a3552e555862a24ba62fa072626820f2074b8df62fcef0b05361a182b044a0f a51b92a2942488107b1002c3336683cae505f15790364aad8546fa0309f893d466713c115e8e9025 0d4c2082eba68da1f8b3b69160593a8d51a5d0919e782d9ba5485468983665508f032b6d5f72361b ef8e9a0a92de21ff780d6b8566880f104c1fa166830646087e33fa47e8c6a0dbf1d475b8f4485b16 8b99800841e3543553a2613d1336612b822cc51769b39423a64d0982964c5b22ce2002b5e392d740 10d50642c967ed1f64c176ccb6028c140a3ea49ced01ca23492167d9170e34a58d835767961a9360 4df30d5aee83ec459a1cf25259ff7de2549c9df234cc539178c664ccd3c900370682dc06e60a9b45 61af4dc4844ef3c398a64602920f9d5970726c0dbc8e46d7055c7131c04cd52afb450a0ba13f0324 ca546920b7d7a8578b5f66abcd0c6be1ad5d0c41cee41918b550c0c302219b9d9702596b857e0594 0064d94d66b8a51fc07866fbf013b4bf96000899304186421d26ef314d8657f7a79fefd96173496e 16d45385ef43b242ab24d92ca82084e47ec25160d5dc0ca4a9939bc940364a22c783506373ab0942 3275aa5b47bd0e96c7410844fa07d498107b9310d803b06b1408b930b60308fd1c3434076a13df29 5d4eb337da271fe0dfb46cebc510ec5e01f648cbcd8d359e2be06e1ae5bf53e9f32e03ae3c495f03 49435f844aa10ae6d2e11cda0640c85317880d835d0298be552f970ef62c0881720e42607dc736e0 5eb43a3c3fa8ab8948e9b10b0151b2901ca2299a5a920da4e7d7e4a2c1576b296a1bb48bb4be64d6 262f8426cdc961a5ee26d1e15942ad54e9ebdc864b47bb05ef75e87a3a5820322c5f5811f2957c48 1bd7c74db620964cc61302343988994a1aa34ca5776f5e99d7ad8a5ac3c4832e2083f43c5981efc3 e0e217af81c90d5d112029299a3a2a5cefc24028902457ad770a2310f8e4597a03842b83f65c119a bf97346e52ceff081c1c2414f683029507225d4a1326cf9003b38958cc3491e6403a290722a99b1f 0efd5fc7405f8f6f9b609b59a07893f444f6e4122422949981deae9a60a01b4c0381bd5521453105 d475add4d2b8db0e3fc8b43a043c0f732c1f86e6c6fb601717649b8004535fd0fcbdd8a13263906c 3a0d5738b43a7c92429f040c1532af835a9df728539ec82999572684274da6e86043e13a365d6809 decc31dce13a23763bd5fc100d4c3267343d9212aed8148c2c08b6dd5adc0e8410702a9903cedf46 20c06dac7b1072bcd531f0ca2c5749d104a228186414d3fba0a1c20720608768e109ec87915a14ce 9a1fe3cc34241299e7951fcef00ad46e8993405b66053bbcb8c2b8a9c37366cc72650e004221271f 3b262a7c32464de67527f906b0ec26bb180823d66685e4cd1d65d4d6aa0905ed5d74f23f1df996af c1f816b9b2455130f44a7571049b65b81321ae3e76129b41531644cbe6752b06878a856e34cf1cda dd2e8eea998112411c15c7f4dc2d331b8e9e8f33245016b8ab0c7f5a3a046f45ed2bd2e6489fd220 90d7393273004d067c624d9e18e6c8ae68ee627a9333c15969d96260470e800d8558670130b07962 40d3d330fe3ecda3e09a0283b677343ab63ed49961c6f6302f85d62f04062f52900bc2703e9b8d32 10362ad05f0565644ba09ce0f5d2898edc1373d72b00da615bd1b9ed4b9e162b6da61150c7624917 bf6260a7c8e3063ae0382eeed0a5206b0c2eb763c4d8e651a52ab4a98ce2a542c1aab0204518fc09 e87ac6240a7b1836e34e66e36fe4b670bec9bf8bba2408b59f04142f5bdce8ac3f7119ce4eb811e6 8950f59654cdac16dae31b039d022080662623f32ed40a407a6798021221c24903c98ba168cc0170 15089f8508e0775f25764c1524b3acf05f8a3c039fa9e728136c5894af408e1b46ba7a455170d0ea 30f9f0bbe1647b097aa77c080433c975a02b97334d0ad5eaf0e342651412a584fcb1da26d88bd9e9 16ed5a9ed2e4977b6de089c21b852a8565f665786d0de1153078d00e8117f80f9e0c01d01bdc49e9 8a95ac05cd1ca3f95fbd0e3901c6c2839cb4caeb06e01908913a9d5ccb517d180204c22932b82ecc 338436fb299dd90c825073b83c3aa8d24516a3752199ee6b60ea09bfd96c102766ff02747b966bc6 d770ed40dad0de9cbc71209476eb661d980063901112b572d72e9a02a72a2ce15ac9bf9e712281ca 3c00cd2345844077e6f2d02430901056319c17563b3b6395340b0472032f09c8267534bdcc82f1c0 08ac64e3b927879f8955666028750624a60402819c1d4715de4b18f21b02d83d3083cafc5253e08e 0d556617a0bfc8685a8b4253cce5926b41871a9138423c8c02e13dafbc72d7b82b5c7d4aa1d9bcd1 d66ad40b4c6a617d585ec7972038dc50a4c5c034d869602a0780c35c1ac5bc9337ba22560e3a2bdc 1966258979fa5334f989692f307ea351a77fd860a7a05d208aa6d0956d25ef449a446e92195e9a22 2a4a28e6096b554a4b88cc9ef938689d53301d8cb7bdd450618688f2e470f8ce2c2bcd40a46135bf 5e1885825828962285b5698549a948a9b4b0890e74a8bc26bc096b56e464c3b0735b81120da504ae 0f4c27135134328b4262168aa00bf65aa14746049e8a21f0ba9d21319a6be603b38c3435a02895f7 85814e5934863799de8a27519409631c496dc9145de46c30bcbe8df0ddcbf764822dd391f47aad97 60b93ad764e141046656984283ba3647c9cb4d68caa255b97e58283359ce81212573160233739c7c 56f98ab53ced9bec3f9d5306695419dc3cb60015c7c91d937c4199f230af08523b81a02207d2d1a6 a962b426520371622dbe13871dc6b683d409c8584a1ab122d41cd3d5016e786054c963e6e6068351 4d12b13c3a6f9cea4adbc0a26424322b44cd5e835e523b3c3646af9634649847a957c1e1cc564c88 6ba08ae0bad6518530ca6481660a99d7a453498d05f7bb0a69302aa6e7c8b2c3c302b366290ca74c 12e943378a1a03f4e4c698f0f3a64360e7cda7f354bdb4d4749c158dd98d177b8dc72402f36003c1 d56d0ed42c050bf9cdae0fe54d3f89fa853aa4d2903101ccac7c603a44f7eb94cbe966629a1fa620 9251391b3d44128dce6995e129a47fd2c0c2ca4ca033119bd95f1d93bc410f278e84a58846cbd6e9 1230e111983043e01220f04d861e7e0fd406e6a7af44153b07913d8cd994f9a61b00ed01245b6681 ef20c604c7767a8e9029b9c64c9f0b84ca859c8b8d61edd314997d28e2281a154d45191c9942ecdf 05e6376032834a2ee0015fa88f6969795f44923c47386d9926710e9465dce84ed41193392a01728b 8e4969650ad9e9444a192aeac6a24100f615663fd76901a6829b9c6110d8699e022b3f3df115ce4e f80fe6827021477b4af7861af4c63428f3bfd4e09191406305910b0090374653e568bd19c730389e d551625a07b1a61fe68260f59208f8483c00f9d3b140440466d7098921997081f683559860aef61a 1485a94aaa593aa894cd522def4ea5dd99572008f72d5b66a438335cae319724fd5f8a097e1a413c c14842d26723952f4ed0e3d85373273cb513fff9dbcb17d44dad5381661a5ff3835790b124eb8fcc e98147fb117820c86c76d9802c9ac07481228999493e2c080e200150196506a1da0acb22a39a534b cacf8091e6190af7b00511ff42817288ccb05b0fc20d3bfab80b7275eabc17cf804cedc239844317 857796e7481c96de2a1da019842f87dba22d4ff0d11337b320910115f6cca5173014ab741cb6a06f 96005be79f89b06e6346385b6250cc52fe0c72b6447526cb3a8f8d7246c62d0432f48230b4a4608a 2c0b58cd173c6c413a10226962e98d2a15e60a11331d2c667aeb06e16c2c6b32465ac08b23902ea0 b36a144ed173dd803a45a135f52b085b0fd92be663997bbf228398a2c90242a9c1c4d96c0b48dbdf a27806e7cc725c1007075da5dcc905b9b2db872d492b78b053c0b6b5cdd8c2d487374f028ece11c2 b826d9ed2f203d50b8f436db0c26a5ef6820e9de531341a0ba944851fab9ae884ecad02bf815bcd8 8233675e9ce2e1c33320183e322f5be8956f183e7a19e0cdb4912e4567e66366f815d930fc0a6e18 7e0567865fe79f9976ddc696e19baa518f94c30a761638e431c041370bbd805cd731c74f176a06a3 943e55100cad02ca0d32a440a79841aa2f761d1cb620b51c33c9ebfcb3265cb731233c054be14e03 375a94093d6ab6558b32804bb5aff3afc8bc0dd16406e1c744df074dfc9c8b3b05ce1774d8828be2 6a4ce7ba0d03d06bc96a219891ab537c7278062453416655ac106f1708d4cd16dcf02a02452641fa f1b4195bf25bae8e0cbd99c65cd96f41b6ecb7801b8681e7ced682fc0cb8bde2055cae38fbe1e52f 579ce84ec5e32b5e8fb6b19e2bb85d37a411539d0467725d9ca2e17320080e1bcbc8dc9449ac5952 bc01f1504cfd465569ea73e07657ec3b0a8fe9b680dbe170e833abc6cf80634b17a7f6f91cf857cb f047a784c17aa81564c1858583c316a48661bf04ebbf39e40d27b04ade8f8cec95954b4aeac707df 80ecbc6a7ecebac35fe82ba875e9edb5bc826c168287da2caf1bd465b4209ca13bab08cce0e6c84f cffb1ca81a3433bafdf8c637e07a112c6f15359fcc2077ce2aad22f91964074596ed43885895b25c 9148efd1cfcd1806cec7391c81f3a997f917ca2cdb98117598c0170a12d80d551111c1f5d8802a43 6765ab61c5523d42061b5c5961dac0d9e0aa92ce4028d56740487aaa739f0c4b94cace9d02e90cb3 4a79d882e037684a6795112597167e439415d46eb4e53755a67a3d72e1987b4530b3b1862c3ee22d bfe1e705d99e770171412d9af25cc1c23447b5d4b6cd3fb3caba8d19b93ac551bf3c17ccaa63660c 85189171b7b7decccaa00ce637b0b2d7ada1d2d119a1e68b7d2095e6373239d32ccda74e4c285c80 8e9948eb2e91bf8369d81314540e0d144122aed5a5e3c3fb80818e05b1914e67cd8d5e5554775bb4 b611b5ed4495a2b90dc6746c1125e24715420d867023158357c90a5b0299ac2082db60412f0e7bce 8e8394eb5c3783e7689e42aa23e34790dd3aa035dc796624882468262255157d2088ecbbdc9394ea 5285cb4cd1cb1349aa39b39d24d03bc30d07e6aa832a264d48e873e34fb16e35d19619c88bd15bc4 5656d2adb194e3e19843ebe1eed8f1c06405d55bf6709ff94e76ddcfd470cc9ef1523cd3d3a66b03 ef17215aeda6f35af6bc38a63bac0d504463559f6c50d51bcbf9d9d644b3062d10b407c7c201f9a7 f8d18683fdabd82ea6f24c8e25061630989c5fe13391c232566475318c5e98c0d03e935b22f821d5 b9985fd8d610216d9e3504665dba77861415a803bda4d6c63bc1cfdd21916b71b614295808400b54 8a6e24ab7cead9fd24fe21cfe73e0f546a94ee5253c703e777645ac73ca1f248117b107332ef1d8c f108321832308f021076abf200334e506a731da7f108f48ebb3aa722136b0c07590488a3f5a006a6 1a94be1ebc4d900d56ca8f51c76b2a76fa107164422220b665d5da2c14410d9eb04b954c38f29fcc 86b1684cb03b669b1921b1d10932ca46863c72689494ca3697c1a20a55bd0d7465241e23bb057108 760256cbdfb2d8c2824267eaef6ae4e8549a660c9dd5dde2d8894c8359d9026939bacad090b15709 23714ace647d8103a10aa33a3820a3c5aa1abd5a3f0da4bcdb1e461a434ca57c55656b46b32a3774 401192d51ca3c919dbb1a9b696a5352376250f137dd851e3a397c438bb4596e2bbca794e26911d36 f4115807cc4a6ab16f62f8e3ecd453131fc1a6263865998225d28204534525e5dfe8178f83535305 3680d340c5b90b83d5cc60be7f645dda7aebe8a1b0a7a0b9d120cf9c95e203f6cfca04b1a59197c2 0a5fafa3ff4589b55eecea97b64726272bfddc3110dac6146656ab42196d4c54aa5030de1af0998d 2052ba35d55a1b53a06ec4fd46eb3ea389e35766028b679a9acdd16c9c2192addd1ed4638f069196 06e3b165b23a537a3ea98912ba85ac4ba42a550ea4b76aaab28418e78671dfa2c594c90f2791a5bb 2eb9a6d08d1639e73a1166f8a9f49ab2385dda20f6d1f2c58c294d34251daf77731f6402d8b8a4ce f79e594325c29e116ff4674b6f89c582d1aaee6cf628f46426a0b07ed1e91d340bbb338306ad1858 8a4cd266997d546a27c97a2db2406a5d2799fd8051c58e319012aafdb33ee2aa9fbf7b08d19909c8 2ad308c9a69093ec0bdb4e3b0825a31fdaf215857a7ca2fadb9c759ee20d73219afaed9e381567a7 3c8d1134503cd97944823b904a1e080439d1f52398a86689342762b23d6e1853cf162d9237f661c1 d9315a9875656d1a7f326109f4b6b2381c072ad6586ad61f1621a9c12750a0aaa5dfdde84a23a81e 0755bfdd8836d843c41802f487db3d947cb3ee9b60d794a5164cde3b2761bb4d64ef89f326b361dc 0ef44967de8560682311fd8426bfdcb36364eb55846657162b308c37e0287b7e8c412514c9e68cc6 6b6a758ba8a9b48f81cefad4791af6a1b2909f295244928240c60951594ba931331d853fa9899fc9 2f169a00566f475199d16c1113a9d16a83600e6b2ff079249d93534d8975dc6a0dfaac655120b437 7e6010ac90d5473d4aee3b430ed9432039b9f9ab1ba834679e5d2c2354605fb2f3020b3b6bad9f89 2e033c3ba7f3aadddf052112bbf10d893ea4910358c835405a574689150ef53371866ede5f184548 26ad921f659f3e1afb182198116673bf3a5769d91aa9c1e6d9de1835f1739d94a4b6a1bdc6a5e0f6 59f73095cfbe8b486d44eaad08f96be87b35128ddbac457ccdb63f653c1784f558e5593890beaa53 e9c3db40a864a3067d13dac6c82c9b543e8d3053b7540355feb2b30f24c697153d3bd34c31fbf165 88638938ca6b73d661d6d9333d9295a5960dfd3d638bbcdc66325dd8d9dddaaca72d8bda44447ffd 1fceb5a3816af3610928aaa89bade7a827a538bd35f4db405f1edd386b4d62153658d73e77692739 712a1226f3ec28ee41e6942d4254879d4d1fdd90c8ca394d8a2565308363355b06aa531575dd14dc 197b4d5fac11a92cf91289918d24b4dece3e25a2e66e2a70f153101f960fdb2a3f152053c14d09a6 e06bb72bae6ccad21771ecc8964967ebcc30b0851f92f142635522c0d3cb4e1ba470ff94df79e008 6e3b6f4a2baabb7445d86ba2efca968154b7a3963bcf4fa67529db7711da8321f3569b363f065e99 2deaa91bc5d4cca6337655346984d9410b24ab06c3cba2a61f1fbf2579a632e3dd942ec0a85e3973 c9ad49b426b204dfd78764ba38de515334662d099235663f1bd59a1a3dc918a3ac8d8029c8c807b5 cfd10d61fd767cbc97f49d92be30f461fe1430f1431dbaa29d7d39eac5646d9c5e6d54a680da46d4 e00733e3e3194a13a4cd9a7cb22793405b76b6b0c90157e18a25e52a3f9785b7dc6c20dda56c9e7b 73ac67a8c982968a9fa378a53f8af560715770c5dcac4602a55560b0a85d7681bd271c9bd86129db 4195cbf91164563331623cecbf96f1f11540d575092606972ab12ae9a30a5cb76e0bcb5bf1e02324 a2a6aaaabe1409b59542590f13f554d352c999da9276aa8c88cd2a55ecf5284c3084a1a271e981cd a79915731221b21b5b05e442bfbe5b0b49e3195579ce5636886a72f5025dd76b4c27a8358d9fbb28 b3cfef70b235abb96cdda634c14e5fcdd1a1cec18e098f39a9ada33b1d7cb88d6a04cbe3534635a5 717efacea58e0a536051df402c11951deb6ad363d62f8facb7f9065bfa273a806ca8c5c53576421b a2b6317e5aa952e263a7e2ec94a731673cb1f99487abcbecd1c878661fcc107bd50cd82bcaef23a2 8c6f33a43a662c8458c7934c830ecb8a08a56c7cb7a96f32d5a6c86f448325698adc8696d8214261 4965381229f9d982e34aed3505d91796c7cc592e076df7f826968dc75dae22b338d9bec16b71e8a2 4abf98d29dd88b74a4cfe933f662dacfb3e1ebe2144d56cf0effbb7833be0dffecf61b7df4fde9a7 fc70fc8bcb77fbafee2faf0ffc54fcdd87cbeff6d3e5ededddc3e5c3fe3d7e9adeddef3f3cdcddef 277e314f84839601cf7ca4feb0bfbfb9bec5143ff9e9fbf36f3efe9efef937c7c7fccfbfb0fd3f05 38f5d6279f7cf687f3972f5ebef827956842b4> endstream endobj 5 0 obj << /Filter [/ASCII85Decode /FlateDecode ] /Length 34 >> stream GhOt'1B7FZ"#S^CKb'rAF*Y:rk'%+Q~> endstream endobj 2 0 obj << /Type /Pages /Kids [4 0 R] /Count 1 >> endobj 1 0 obj << /Type /Catalog /Pages 2 0 R /OCProperties<< /D<< /Order[]/ON[]/OFF[]/RBGroups[]>>/OCGs[]>> >> endobj 3 0 obj << /CreationDate /ModDate /Producer /Creator /Title >> endobj xref 0 9 0000000000 65535 f 0000017284 00000 n 0000017220 00000 n 0000017403 00000 n 0000000010 00000 n 0000017090 00000 n 0000000291 00000 n 0000000331 00000 n 0000000467 00000 n trailer << /Size 9 /Root 1 0 R /Info 3 0 R /ID [<2919245bd05467827549e8412ab96fab><2919245bd05467827549e8412ab96fab>] >> startxref 17861 %%EOFjgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-black.eps000066400000000000000000010322501402514743400225140ustar00rootroot00000000000000%!PS-Adobe-3.0 EPSF-3.0 %%BoundingBox: 0 0 480 480 %%LanguageLevel: 2 %%Creator: CorelDRAW %%Title: jg1.eps %%CreationDate: Mon Nov 12 20:34:09 2018 %%DocumentProcessColors: Cyan Magenta Yellow Black %%DocumentSuppliedResources: (atend) %%EndComments %%BeginProlog /AutoFlatness false def /AutoSteps 0 def /CMYKMarks true def /UseLevel 2 def %Build: CorelDRAW Version 14.0.0.567 %Color profile: Generic offset separations profile /CorelIsEPS true def %%BeginResource: procset wCorel14Dict 14.0 0 /wCorel14Dict 300 dict def wCorel14Dict begin % Copyright (c)1992-2007 Corel Corporation % All rights reserved. v14 r0.0 /bd{bind def}bind def/ld{load def}bd/xd{exch def}bd/_ null def/rp{{pop}repeat} bd/@cp/closepath ld/@gs/gsave ld/@gr/grestore ld/@np/newpath ld/Tl/translate ld /$sv 0 def/@sv{/$sv save def}bd/@rs{$sv restore}bd/spg/showpage ld/showpage{} bd currentscreen/@dsp xd/$dsp/@dsp def/$dsa xd/$dsf xd/$sdf false def/$SDF false def/$Scra 0 def/SetScr/setscreen ld/@ss{2 index 0 eq{$dsf 3 1 roll 4 -1 roll pop}if exch $Scra add exch load SetScr}bd/SepMode_5 where{pop}{/SepMode_5 0 def}ifelse/CorelIsSeps where{pop}{/CorelIsSeps false def}ifelse /CorelIsInRIPSeps where{pop}{/CorelIsInRIPSeps false def}ifelse/CorelIsEPS where{pop}{/CorelIsEPS false def}ifelse/CurrentInkName_5 where{pop} {/CurrentInkName_5(Composite)def}ifelse/$ink_5 where{pop}{/$ink_5 -1 def} ifelse/fill_color 6 array def/num_fill_inks 1 def/$o 0 def/$fil 0 def /outl_color 6 array def/num_outl_inks 1 def/$O 0 def/$PF false def/$bkg false def/$op false def matrix currentmatrix/$ctm xd/$ptm matrix def/$ttm matrix def /$stm matrix def/$ffpnt true def/CorelDrawReencodeVect[16#0/grave 16#5/breve 16#6/dotaccent 16#8/ring 16#A/hungarumlaut 16#B/ogonek 16#C/caron 16#D/dotlessi 16#27/quotesingle 16#60/grave 16#7C/bar 16#80/Euro 16#82/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl 16#88/circumflex/perthousand/Scaron/guilsinglleft/OE 16#91/quoteleft/quoteright/quotedblleft/quotedblright/bullet/endash/emdash 16#98/tilde/trademark/scaron/guilsinglright/oe 16#9F/Ydieresis 16#A1/exclamdown/cent/sterling/currency/yen/brokenbar/section 16#a8/dieresis/copyright/ordfeminine/guillemotleft/logicalnot/minus/registered/macron 16#b0/degree/plusminus/twosuperior/threesuperior/acute/mu/paragraph/periodcentered 16#b8/cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown 16#c0/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla 16#c8/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis 16#d0/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply 16#d8/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls 16#e0/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla 16#e8/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis 16#f0/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide 16#f8/oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]def /get_ps_level/languagelevel where{pop systemdict/languagelevel get exec}{1 }ifelse def/level2 get_ps_level 2 ge def/level3 get_ps_level 3 ge def /is_distilling{/product where{pop systemdict/setdistillerparams known product (Adobe PostScript Parser)ne and}{false}ifelse}bd/is_rip_separation{ is_distilling{false}{level2{currentpagedevice/Separations 2 copy known{get}{ pop pop false}ifelse}{false}ifelse}ifelse}bd/is_current_sep_color{ is_separation{gsave false setoverprint 1 1 1 1 5 -1 roll findcmykcustomcolor 1 setcustomcolor currentgray 0 eq grestore}{pop false}ifelse}bd /get_sep_color_index{dup length 1 sub 0 1 3 -1 roll{dup 3 -1 roll dup 3 -1 roll get is_current_sep_color{exit}{exch pop}ifelse}for pop -1}bd/is_separation{ /LumSepsDict where{pop false}{/AldusSepsDict where{pop false}{ is_rip_separation{true}{1 0 0 0 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne 0 1 0 0 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne 0 0 1 0 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne 0 0 0 1 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne and and and not}ifelse}ifelse}ifelse}bind def/is_composite{is_separation not is_distilling or}bd/is_sim_devicen{level2 level3 not and{is_distilling is_rip_separation or}{false}ifelse}bd/@PL{/LV where{pop LV 2 ge level2 not and {@np/Courier findfont 12 scalefont setfont 72 144 m (The PostScript level set in the Corel application is higher than)show 72 132 m (the PostScript level of this device. Change the PS Level in the Corel)show 72 120 m(application to Level 1 by selecting the PostScript tab in the print)show 72 108 m(dialog, and selecting Level 1 from the Compatibility drop down list.) show flush spg quit}if}if}bd/@BeginSysCorelDict{systemdict/Corel30Dict known {systemdict/Corel30Dict get exec}if systemdict/CorelLexDict known{1 systemdict /CorelLexDict get exec}if}bd/@EndSysCorelDict{systemdict/Corel30Dict known {end}if/EndCorelLexDict where{pop EndCorelLexDict}if}bd AutoFlatness{/@ifl{dup currentflat exch sub 10 gt{ ([Error: PathTooComplex; OffendingCommand: AnyPaintingOperator]\n)print flush @np exit}{currentflat 2 add setflat}ifelse}bd/@fill/fill ld/fill{currentflat{ {@fill}stopped{@ifl}{exit}ifelse}bind loop setflat}bd/@eofill/eofill ld/eofill {currentflat{{@eofill}stopped{@ifl}{exit}ifelse}bind loop setflat}bd/@clip /clip ld/clip{currentflat{{@clip}stopped{@ifl}{exit}ifelse}bind loop setflat} bd/@eoclip/eoclip ld/eoclip{currentflat{{@eoclip}stopped{@ifl}{exit}ifelse} bind loop setflat}bd/@stroke/stroke ld/stroke{currentflat{{@stroke}stopped {@ifl}{exit}ifelse}bind loop setflat}bd}if level2{/@ssa{true setstrokeadjust} bd}{/@ssa{}bd}ifelse/d/setdash ld/j/setlinejoin ld/J/setlinecap ld/M /setmiterlimit ld/w/setlinewidth ld/O{/$o xd}bd/R{/$O xd}bd/W/eoclip ld/c /curveto ld/C/c ld/l/lineto ld/L/l ld/rl/rlineto ld/m/moveto ld/n/newpath ld/N /newpath ld/P{11 rp}bd/u{}bd/U{}bd/A{pop}bd/q/@gs ld/Q/@gr ld/&{}bd/@j{@sv @np} bd/@J{@rs}bd/g{1 exch sub 0 0 0 1 null 1 set_fill_color/$fil 0 def}bd/G{1 sub neg 0 0 0 1 null 1 set_outline_color}bd/set_fill_color{/fill_color exch def /num_fill_inks fill_color length 6 idiv def/bFillDeviceN num_fill_inks 1 gt def /$fil 0 def}bd/set_outline_color{/outl_color exch def/num_outl_inks outl_color length 6 idiv def/bOutlDeviceN num_outl_inks 1 gt def}bd /get_devicen_color_names{dup length 6 idiv dup 5 mul exch getinterval}bd /create_colorarray_from_devicen_params{/ink_names_temp exch def /tint_params_temp exch def/ink_values_temp exch def[ink_values_temp aload pop tint_params_temp aload pop ink_names_temp aload pop]}bd /get_devicen_color_specs{dup length 6 idiv dup 4 mul getinterval}bd /get_devicen_color{dup length 6 idiv 0 exch getinterval}bd/mult_devicen_color{ /colorarray exch def/mult_vals exch def 0 1 mult_vals length 1 sub{colorarray exch dup mult_vals exch get exch dup colorarray exch get 3 -1 roll mul put}for colorarray}bd/combine_devicen_colors{/colorarray2 exch def/colorarray1 exch def /num_inks1 colorarray1 length 6 idiv def/num_inks2 colorarray2 length 6 idiv def/num3 0 def/colorarray3[num_inks1 num_inks2 add 6 mul{0}repeat]def 0 1 num_inks1 1 sub{colorarray1 exch get colorarray3 num3 3 -1 roll put/num3 num3 1 add def}for 0 1 num_inks2 1 sub{colorarray2 exch get colorarray3 num3 3 -1 roll put/num3 num3 1 add def}for colorarray1 num_inks1 dup 4 mul getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks1 4 mul add def colorarray2 num_inks2 dup 4 mul getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks2 4 mul add def colorarray1 num_inks1 dup 5 mul exch getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks1 add def colorarray2 num_inks2 dup 5 mul exch getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks2 add def colorarray3}bd/get_devicen_color_spec{ /colorant_index exch def/colorarray exch def/ncolorants colorarray length 6 idiv def[colorarray colorant_index get colorarray ncolorants colorant_index 4 mul add 4 getinterval aload pop colorarray ncolorants 5 mul colorant_index add get]}bd/set_devicen_color{level3{/colorarray exch def/numcolorants colorarray length 6 idiv def colorarray get_devicen_color_specs/tint_params exch def[ /DeviceN colorarray get_devicen_color_names/DeviceCMYK{tint_params CorelTintTransformFunction}]setcolorspace colorarray get_devicen_color aload pop setcolor}{/DeviceCMYK setcolorspace devicen_to_cmyk aload pop pop @tc_5 setprocesscolor_5}ifelse}bd/sf{/bmp_fill_fg_color xd}bd/i{dup 0 ne{setflat} {pop}ifelse}bd/v{4 -2 roll 2 copy 6 -2 roll c}bd/V/v ld/y{2 copy c}bd/Y/y ld /@w{matrix rotate/$ptm xd matrix scale $ptm dup concatmatrix/$ptm xd 1 eq{$ptm exch dup concatmatrix/$ptm xd}if 1 w}bd/@g{1 eq dup/$sdf xd{/$scp xd/$sca xd /$scf xd}if}bd/@G{1 eq dup/$SDF xd{/$SCP xd/$SCA xd/$SCF xd}if}bd/@D{2 index 0 eq{$dsf 3 1 roll 4 -1 roll pop}if 3 copy exch $Scra add exch load SetScr/$dsp xd/$dsa xd/$dsf xd}bd/$ngx{$SDF{$SCF SepMode_5 0 eq{$SCA}{$dsa}ifelse $SCP @ss }if}bd/@MN{2 copy le{pop}{exch pop}ifelse}bd/@MX{2 copy ge{pop}{exch pop} ifelse}bd/InRange{3 -1 roll @MN @MX}bd/@sqr{dup 0 rl dup 0 exch rl neg 0 rl @cp }bd/currentscale{1 0 dtransform matrix defaultmatrix idtransform dup mul exch dup mul add sqrt 0 1 dtransform matrix defaultmatrix idtransform dup mul exch dup mul add sqrt}bd/@unscale{}bd/wDstChck{2 1 roll dup 3 -1 roll eq{1 add}if} bd/@dot{dup mul exch dup mul add 1 exch sub}bd/@lin{exch pop abs 1 exch sub}bd /cmyk2rgb{3{dup 5 -1 roll add 1 exch sub dup 0 lt{pop 0}if exch}repeat pop}bd /rgb2cmyk{3{1 exch sub 3 1 roll}repeat 3 copy @MN @MN 3{dup 5 -1 roll sub neg exch}repeat}bd/rgb2g{2 index .299 mul 2 index .587 mul add 1 index .114 mul add 4 1 roll pop pop pop}bd/devicen_to_cmyk{/convertcolor exch def convertcolor get_devicen_color aload pop convertcolor get_devicen_color_specs CorelTintTransformFunction}bd/WaldoColor_5 where{pop}{/SetRgb/setrgbcolor ld /GetRgb/currentrgbcolor ld/SetGry/setgray ld/GetGry/currentgray ld/SetRgb2 systemdict/setrgbcolor get def/GetRgb2 systemdict/currentrgbcolor get def /SetHsb systemdict/sethsbcolor get def/GetHsb systemdict/currenthsbcolor get def/rgb2hsb{SetRgb2 GetHsb}bd/hsb2rgb{3 -1 roll dup floor sub 3 1 roll SetHsb GetRgb2}bd/setcmykcolor where{pop/LumSepsDict where{pop/SetCmyk_5{LumSepsDict /setcmykcolor get exec}def}{/AldusSepsDict where{pop/SetCmyk_5{AldusSepsDict /setcmykcolor get exec}def}{/SetCmyk_5/setcmykcolor ld}ifelse}ifelse}{ /SetCmyk_5{cmyk2rgb SetRgb}bd}ifelse/currentcmykcolor where{pop/GetCmyk /currentcmykcolor ld}{/GetCmyk{GetRgb rgb2cmyk}bd}ifelse/setoverprint where {pop}{/setoverprint{/$op xd}bd}ifelse/currentoverprint where{pop}{ /currentoverprint{$op}bd}ifelse/@tc_5{5 -1 roll dup 1 ge{pop}{4{dup 6 -1 roll mul exch}repeat pop}ifelse}bd/@trp{exch pop 5 1 roll @tc_5}bd /setprocesscolor_5{SepMode_5 0 eq{SetCmyk_5}{SepsColor not{4 1 roll pop pop pop 1 exch sub SetGry}{SetCmyk_5}ifelse}ifelse}bd/findcmykcustomcolor where{pop}{ /findcmykcustomcolor{5 array astore}bd}ifelse/Corelsetcustomcolor_exists false def/setcustomcolor where{pop/Corelsetcustomcolor_exists true def}if CorelIsSeps true eq CorelIsInRIPSeps false eq and{/Corelsetcustomcolor_exists false def}if Corelsetcustomcolor_exists false eq{/setcustomcolor{exch aload pop SepMode_5 0 eq{pop @tc_5 setprocesscolor_5}{CurrentInkName_5 eq{4 index}{0}ifelse 6 1 roll 5 rp 1 sub neg SetGry}ifelse}bd}if/@scc_5{dup type/booleantype eq{dup currentoverprint ne{setoverprint}{pop}ifelse}{1 eq setoverprint}ifelse dup _ eq {pop setprocesscolor_5 pop}{dup(CorelRegistrationColor)eq{5 rp 1 exch sub setregcolor}{findcmykcustomcolor exch setcustomcolor}ifelse}ifelse SepMode_5 0 eq{true}{GetGry 1 eq currentoverprint and not}ifelse}bd/colorimage where{pop /ColorImage{colorimage}def}{/ColorImage{/ncolors xd/$multi xd $multi true eq{ ncolors 3 eq{/daqB xd/daqG xd/daqR xd pop pop exch pop abs{daqR pop daqG pop daqB pop}repeat}{/daqK xd/daqY xd/daqM xd/daqC xd pop pop exch pop abs{daqC pop daqM pop daqY pop daqK pop}repeat}ifelse}{/dataaq xd{dataaq ncolors dup 3 eq{ /$dat xd 0 1 $dat length 3 div 1 sub{dup 3 mul $dat 1 index get 255 div $dat 2 index 1 add get 255 div $dat 3 index 2 add get 255 div rgb2g 255 mul cvi exch pop $dat 3 1 roll put}for $dat 0 $dat length 3 idiv getinterval pop}{4 eq{ /$dat xd 0 1 $dat length 4 div 1 sub{dup 4 mul $dat 1 index get 255 div $dat 2 index 1 add get 255 div $dat 3 index 2 add get 255 div $dat 4 index 3 add get 255 div cmyk2rgb rgb2g 255 mul cvi exch pop $dat 3 1 roll put}for $dat 0 $dat length ncolors idiv getinterval}if}ifelse}image}ifelse}bd}ifelse/setcmykcolor{ 1 5 1 roll null 6 array astore currentoverprint set_current_color/$ffpnt xd}bd /currentcmykcolor{GetCmyk}bd/setrgbcolor{rgb2cmyk setcmykcolor}bd /currentrgbcolor{currentcmykcolor cmyk2rgb}bd/sethsbcolor{hsb2rgb setrgbcolor} bd/currenthsbcolor{currentrgbcolor rgb2hsb}bd/setgray{dup dup setrgbcolor}bd /currentgray{currentrgbcolor rgb2g}bd/InsideDCS false def/IMAGE/image ld/image {InsideDCS{IMAGE}{/EPSDict where{pop SepMode_5 0 eq{IMAGE}{dup type/dicttype eq {dup/ImageType get 1 ne{IMAGE}{dup dup/BitsPerComponent get 8 eq exch /BitsPerComponent get 1 eq or currentcolorspace 0 get/DeviceGray eq and{ CurrentInkName_5(Black)eq{IMAGE}{dup/DataSource get/TCC xd/Height get abs{TCC pop}repeat}ifelse}{IMAGE}ifelse}ifelse}{2 index 1 ne{CurrentInkName_5(Black)eq {IMAGE}{/TCC xd pop pop exch pop abs{TCC pop}repeat}ifelse}{IMAGE}ifelse} ifelse}ifelse}{IMAGE}ifelse}ifelse}bd}ifelse/WaldoColor_5 true def /WaldoColor_13 where{pop}{/separate_color{SepMode_5 0 ne{[exch/colorarray_sep exch def/ink_num -1 def colorarray_sep length 6 idiv 1 gt{colorarray_sep get_devicen_color_names dup length 1 sub 0 1 3 -1 roll{exch dup 3 -1 roll dup 3 1 roll get CurrentInkName_5 eq{/ink_num exch def}{pop}ifelse}for pop ink_num -1 ne{colorarray_sep ink_num get_devicen_color_spec aload pop pop SepsColor not{ pop pop pop pop 1 0 0 0 5 -1 roll}if null}{0 0 0 0 0 null}ifelse}{ colorarray_sep 5 get $ink_5 4 eq{CurrentInkName_5 eq{colorarray_sep aload pop pop SepsColor not{pop pop pop pop 0 0 0 1}if null}{0 0 0 0 0 null}ifelse}{ colorarray_sep 0 get colorarray_sep $ink_5 1 add get 3 -1 roll null eq{0 0 0 4 -1 roll SepsColor{4 $ink_5 1 add roll}if null}{pop pop 0 0 0 0 0 null}ifelse }ifelse}ifelse]}if}bd/separate_cmyk_color{$ink_5 -1 ne{[exch aload pop 3 $ink5 sub index/colorarray_sep exch def/ink_num -1 def colorarray_sep get_devicen_color_names dup length 1 sub 0 1 3 -1 roll{exch dup 3 -1 roll dup 3 1 roll get CurrentInkName_5 eq{/ink_num exch def}{pop}ifelse}for pop ink_num -1 ne{[colorarray_sep ink_num get_devicen_color_spec aload pop]}{[0 0 0 0 0 null] }ifelse}if}bd/set_current_color{dup type/booleantype eq{dup currentoverprint ne {setoverprint}{pop}ifelse}{1 eq setoverprint}ifelse/cur_color exch def /nNumColors cur_color length 6 idiv def nNumColors 1 eq{cur_color 5 get (CorelRegistrationColor)eq{cur_color aload pop 5 rp 1 exch sub setregcolor}{ SepMode_5 0 eq{cur_color aload pop dup null eq{pop @tc_5 setprocesscolor_5}{ findcmykcustomcolor exch setcustomcolor}ifelse}{cur_color separate_color aload pop pop @tc_5 setprocesscolor_5}ifelse}ifelse}{SepMode_5 0 eq{is_distilling is_rip_separation or{cur_color set_devicen_color}{cur_color devicen_to_cmyk setprocesscolor_5}ifelse}{cur_color separate_color aload pop pop @tc_5 setprocesscolor_5}ifelse}ifelse SepMode_5 0 eq{true}{GetGry 1 eq currentoverprint and not}ifelse}bd}ifelse/WaldoColor_13 true def/$fm 0 def /wfill{1 $fm eq{fill}{eofill}ifelse}bd/@Pf{@sv SepMode_5 0 eq $Psc 0 ne or $ink_5 3 eq or{0 J 0 j[]0 d fill_color $o set_current_color pop $ctm setmatrix 72 1000 div dup matrix scale dup concat dup Bburx exch Bbury exch itransform ceiling cvi/Bbury xd ceiling cvi/Bburx xd Bbllx exch Bblly exch itransform floor cvi/Bblly xd floor cvi/Bbllx xd $Prm aload pop $Psn load exec}{1 SetGry wfill}ifelse @rs @np}bd/F{matrix currentmatrix $sdf{$scf $sca $scp @ss}if $fil 1 eq{CorelPtrnDoFill}{$fil 2 eq{@ff}{$fil 3 eq{@Pf}{level3{fill_color $o set_current_color{wfill}{@np}ifelse}{/overprint_flag $o def is_distilling is_rip_separation or{0 1 num_fill_inks 1 sub{dup 0 gt{/overprint_flag true def }if fill_color exch get_devicen_color_spec overprint_flag set_current_color{ @gs wfill @gr}{@np exit}ifelse}for}{fill_color overprint_flag set_current_color {@gs wfill @gr}{@np}ifelse}ifelse}ifelse}ifelse}ifelse}ifelse $sdf{$dsf $dsa $dsp @ss}if setmatrix}bd/f{@cp F}bd/S{matrix currentmatrix $ctm setmatrix $SDF {$SCF $SCA $SCP @ss}if level3{outl_color $O set_current_color{matrix currentmatrix $ptm concat stroke setmatrix}{@np}ifelse}{/overprint_flag $O def is_distilling is_rip_separation or{0 1 num_outl_inks 1 sub{dup 0 gt{ /overprint_flag true def}if outl_color exch get_devicen_color_spec overprint_flag set_current_color{matrix currentmatrix $ptm concat @gs stroke @gr setmatrix}{@np exit}ifelse}for}{outl_color overprint_flag set_current_color {matrix currentmatrix $ptm concat @gs stroke @gr setmatrix}{@np}ifelse}ifelse }ifelse $SDF{$dsf $dsa $dsp @ss}if setmatrix}bd/s{@cp S}bd/B{@gs F @gr S}bd/b{ @cp B}bd/_E{5 array astore exch cvlit xd}bd/@cc{currentfile $dat readhexstring pop}bd/@sm{/$ctm $ctm currentmatrix def}bd/@E{/Bbury xd/Bburx xd/Bblly xd /Bbllx xd}bd/@c{@cp}bd/@P{/$fil 3 def/$Psn xd/$Psc xd array astore/$Prm xd}bd /tcc{@cc}def/@B{@gs S @gr F}bd/@b{@cp @B}bd/@sep{CurrentInkName_5(Composite)eq {/$ink_5 -1 def}{CurrentInkName_5(Cyan)eq{/$ink_5 0 def}{CurrentInkName_5 (Magenta)eq{/$ink_5 1 def}{CurrentInkName_5(Yellow)eq{/$ink_5 2 def}{ CurrentInkName_5(Black)eq{/$ink_5 3 def}{/$ink_5 4 def}ifelse}ifelse}ifelse} ifelse}ifelse}bd/@whi{@gs -72000 dup m -72000 72000 l 72000 dup l 72000 -72000 l @cp 1 SetGry fill @gr}bd/@neg{[{1 exch sub}/exec cvx currenttransfer/exec cvx]cvx settransfer @whi}bd/deflevel 0 def/@sax{/deflevel deflevel 1 add def} bd/@eax{/deflevel deflevel dup 0 gt{1 sub}if def deflevel 0 gt{/eax load}{eax} ifelse}bd/eax{{exec}forall}bd/@rax{deflevel 0 eq{@rs @sv}if}bd systemdict /pdfmark known not{/pdfmark/cleartomark ld}if/wclip{1 $fm eq{clip}{eoclip} ifelse}bd level2{/setregcolor{/neg_flag exch def[/Separation/All/DeviceCMYK{ dup dup dup}]setcolorspace 1.0 neg_flag sub setcolor}bd}{/setregcolor{1 exch sub dup dup dup setcmykcolor}bd}ifelse/CorelTintTransformFunction{ /colorantSpecArray exch def/nColorants colorantSpecArray length 4 idiv def /inColor nColorants 1 add 1 roll nColorants array astore def/outColor 4 array def 0 1 3{/nOutInk exch def 1 0 1 nColorants 1 sub{dup inColor exch get exch 4 mul nOutInk add colorantSpecArray exch get mul 1 exch sub mul}for 1 exch sub outColor nOutInk 3 -1 roll put}for outColor aload pop}bind def % Copyright (c)1992-2007 Corel Corporation % All rights reserved. v14 r0.0 /@ii{concat 3 index 3 index m 3 index 1 index l 2 copy l 1 index 3 index l 3 index 3 index l clip pop pop pop pop}bd/@i{@sm @gs @ii 6 index 1 ne{/$frg true def pop pop}{1 eq{bmp_fill_fg_color $O set_current_color/$frg xd}{/$frg false def}ifelse 1 eq{@gs $ctm setmatrix F @gr}if}ifelse @np/$ury xd/$urx xd/$lly xd /$llx xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul 8 div ceiling cvi string def $bkg $frg or{$SDF{$SCF $SCA $SCP @ss}if $llx $lly Tl $urx $llx sub $ury $lly sub scale $bkg{fill_color set_current_color pop}if $wid $hei abs $bts 1 eq {$bkg}{$bts}ifelse[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]/tcc load $bts 1 eq{imagemask}{image}ifelse $SDF{$dsf $dsa $dsp @ss}if}{$hei abs{tcc pop} repeat}ifelse @gr $ctm setmatrix}bd/@I{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd /$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd $ngx $llx $lly Tl $urx $llx sub $ury $lly sub scale $wid $hei abs $bts[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse ]/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def $msimage false eq $ncl 1 eq or{/@cc load false $ncl ColorImage}{$wid $bts mul 8 div ceiling cvi $ncl 3 eq{dup dup/$dat1 exch string def/$dat2 exch string def/$dat3 exch string def/@cc1 load/@cc2 load/@cc3 load}{dup dup dup/$dat1 exch string def/$dat2 exch string def/$dat3 exch string def/$dat4 exch string def/@cc1 load/@cc2 load /@cc3 load/@cc4 load}ifelse true $ncl ColorImage}ifelse $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd/@cc1{currentfile $dat1 readhexstring pop}bd/@cc2{ currentfile $dat2 readhexstring pop}bd/@cc3{currentfile $dat3 readhexstring pop }bd/@cc4{currentfile $dat4 readhexstring pop}bd/$msimage false def/COMP 0 def /MaskedImage false def/bImgDeviceN false def/nNumInksDeviceN 0 def /sNamesDeviceN[]def/tint_params[]def level2{/@I_2{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def $ngx $ncl 1 eq{/DeviceGray}{$ncl 3 eq{/DeviceRGB} {/DeviceCMYK}ifelse}ifelse setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale 8 dict begin/ImageType 1 def/Width $wid def/Height $hei abs def /BitsPerComponent $bts def/Decode $ncl 1 eq{[0 1]}{$ncl 3 eq{[0 1 0 1 0 1]}{[0 1 0 1 0 1 0 1]}ifelse}ifelse def/ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt {$hei}{0}ifelse]def/DataSource currentfile/ASCII85Decode filter COMP 1 eq {/DCTDecode filter}{COMP 2 eq{/RunLengthDecode filter}{COMP 3 eq{/LZWDecode filter}if}ifelse}ifelse def currentdict end image $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd}{/@I_2{}bd}ifelse level2{/@I_2D{@sm @gs @ii @np/$ury xd /$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd $ngx/scanline $wid $bts mul $ncl mul 8 div ceiling cvi string def/readscanline{currentfile scanline readhexstring pop}bind def level3{[/DeviceN sNamesDeviceN/DeviceCMYK{ tint_params CorelTintTransformFunction}]setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale 8 dict begin/ImageType 1 def/Width $wid def/Height $hei abs def/BitsPerComponent $bts def/Decode[nNumInksDeviceN{0 1}repeat]def /ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]def/DataSource{ readscanline}def currentdict end image}{/scanline_height $lly $ury sub 1 sub $hei div def/plate_scanline $wid string def/cmyk_scanline $wid 4 mul string def is_distilling is_rip_separation or{/bSimDeviceN true def}{/bSimDeviceN false def}ifelse/scanline_img_dict 8 dict begin/ImageType 1 def/Width $wid def /Height 1 def/BitsPerComponent $bts def/Decode bSimDeviceN{[0 1]}{[0 1 0 1 0 1 0 1]}ifelse def/ImageMatrix[$wid 0 0 1 neg 0 1]def/DataSource bSimDeviceN{ plate_scanline}{cmyk_scanline}ifelse def currentdict end def 0 1 $hei 1 sub{ @gs/nScanIndex exch def readscanline pop/$t_lly $ury $lly scanline_height nScanIndex mul sub sub ceiling cvi def/$t_ury $t_lly scanline_height sub ceiling cvi def bSimDeviceN{0 1 $ncl 1 sub{@gs/nInkIndex exch def 0 1 plate_scanline length 1 sub{dup $ncl mul nInkIndex add scanline exch get plate_scanline 3 1 roll put}for[0 1 $ncl 1 sub{nInkIndex eq{1.0}{0.0}ifelse }for]/sepTintTransformParams exch def[/Separation sNamesDeviceN nInkIndex get /DeviceCMYK{sepTintTransformParams aload pop tint_params CorelTintTransformFunction @tc_5}]setcolorspace $llx $t_lly Tl $urx $llx sub $t_ury $t_lly sub scale nInkIndex 0 eq currentoverprint not and{false setoverprint}{true setoverprint}ifelse scanline_img_dict image @gr}for}{0 1 $wid 1 sub{dup $ncl mul scanline exch $ncl getinterval 0 1 $ncl 1 sub{2 copy get 255 div 3 1 roll pop}for pop tint_params CorelTintTransformFunction 5 -1 roll cmyk_scanline exch 0 1 3{3 1 roll 2 copy 5 -1 roll dup 8 exch sub index 255 mul cvi 3 1 roll exch 4 mul add exch put}for 6 rp}for/DeviceCMYK setcolorspace $llx $t_lly Tl $urx $llx sub $t_ury $t_lly sub scale scanline_img_dict image}ifelse @gr}for}ifelse $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd}{/@I_2D{}bd}ifelse/@I_3{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def $ngx bImgDeviceN{[/DeviceN sNamesDeviceN/DeviceCMYK{ tint_params CorelTintTransformFunction}]}{$ncl 1 eq{/DeviceGray}{$ncl 3 eq {/DeviceRGB}{/DeviceCMYK}ifelse}ifelse}ifelse setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale/ImageDataDict 8 dict def ImageDataDict begin /ImageType 1 def/Width $wid def/Height $hei abs def/BitsPerComponent $bts def /Decode[$ncl{0 1}repeat]def/ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0} ifelse]def/DataSource currentfile/ASCII85Decode filter COMP 1 eq{/DCTDecode filter}{COMP 2 eq{/RunLengthDecode filter}{COMP 3 eq{/LZWDecode filter}if} ifelse}ifelse def end/MaskedImageDict 7 dict def MaskedImageDict begin /ImageType 3 def/InterleaveType 3 def/MaskDict ImageMaskDict def/DataDict ImageDataDict def end MaskedImageDict image $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd/@SetMask{/$mbts xd/$mhei xd/$mwid xd/ImageMaskDict 8 dict def ImageMaskDict begin/ImageType 1 def/Width $mwid def/Height $mhei abs def /BitsPerComponent $mbts def/DataSource maskstream def/ImageMatrix[$mwid 0 0 $mhei neg 0 $mhei 0 gt{$mhei}{0}ifelse]def/Decode[1 0]def end}bd/@daq{dup type /arraytype eq{{}forall}if}bd/@BMP{/@cc xd UseLevel 3 eq MaskedImage true eq and {7 -2 roll pop pop @I_3}{12 index 1 gt UseLevel 2 eq UseLevel 3 eq or and{7 -2 roll pop pop bImgDeviceN{@I_2D}{@I_2}ifelse}{11 index 1 eq{12 -1 roll pop @i}{ 7 -2 roll pop pop @I}ifelse}ifelse}ifelse}bd/disable_raster_output{/@BMP load /old_raster_func exch bind def/@BMP{8 rp/$ury xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/scanline $wid $bts mul $ncl mul 8 div ceiling cvi string def 0 1 $hei 1 sub{currentfile scanline readhexstring pop pop pop}for }def}bd/enable_raster_output{/old_raster_func where{pop/old_raster_func load /@BMP exch bd}if}bd end %%EndResource %%EndProlog %%BeginSetup wCorel14Dict begin @BeginSysCorelDict @ssa 1.00 setflat /$fst 128 def %%EndSetup %%Page: 1 1 %%ViewingOrientation: 0 1 1 0 %LogicalPage: 1 %%BeginPageSetup @sv @sm @sv %%EndPageSetup @rax %Note: Object 0.00000 0.00000 479.99962 479.99962 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.73 0.73 0.89 null ] set_fill_color 0.00000 479.99962 m 479.99962 479.99962 L 479.99962 0.00000 L 0.00000 0.00000 L 0.00000 479.99962 L @c F @rax %Note: Object 138.39987 151.22324 217.76117 211.53288 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 164.08035 151.92652 m 166.89231 152.68025 168.57780 155.60022 167.82463 158.41247 C 167.55817 159.40573 167.02072 160.25839 166.31461 160.91178 C 176.48050 178.48658 L 177.30340 178.38624 178.16202 178.50019 178.96847 178.86246 C 179.80724 179.23890 180.48331 179.83559 180.95811 180.55389 C 206.78542 171.52271 L 206.70945 170.87924 206.75254 170.21254 206.92913 169.55178 C 207.00822 169.25698 207.11112 168.97408 207.23528 168.70564 C 188.26186 158.48192 L 187.18101 160.15748 185.09669 161.03254 183.07474 160.49055 C 180.57090 159.81987 179.06995 157.22022 179.74148 154.71723 C 180.41187 152.21339 183.01153 150.71244 185.51537 151.38312 C 187.75757 151.98378 189.19502 154.13301 188.98866 156.37351 C 208.62794 166.88494 L 209.91061 165.80551 211.68227 165.34403 213.41395 165.80750 C 216.22649 166.56180 217.91140 169.48176 217.15824 172.29373 C 216.40450 175.10542 213.48454 176.78976 210.67342 176.03745 C 210.66236 176.03405 L 201.61729 188.87584 L 202.78261 190.03039 203.31865 191.76350 202.86652 193.45238 C 202.80047 193.69956 202.71487 193.93569 202.61310 194.16246 C 211.14028 202.51502 L 212.09329 202.09096 213.18917 201.97616 214.26803 202.26557 C 216.77131 202.93625 218.27197 205.53591 217.60157 208.03975 c 216.93090 210.54274 214.33124 212.04369 211.82740 211.37301 C 209.32413 210.70233 207.82346 208.10239 208.49414 205.59912 c 208.66224 204.97124 208.95222 204.40630 209.33150 203.92271 C 201.19550 195.95565 L 200.06844 196.82476 198.56438 197.18079 197.09235 196.78564 c 194.68942 196.14217 193.21030 193.71997 193.68907 191.31307 C 180.97058 185.76198 L 179.72079 187.65071 177.23424 188.41946 175.10627 187.46334 c 172.74132 186.40176 171.67521 183.59575 172.73679 181.23165 c 173.09679 180.42973 173.65663 179.77748 174.33269 179.30665 C 164.36013 162.06576 L 163.41307 162.37984 162.36907 162.43115 161.33924 162.15562 C 160.22268 161.85628 159.28441 161.21594 158.60523 160.37631 C 147.40923 169.58154 L 148.39455 171.76082 147.58328 174.38343 145.46920 175.60403 C 143.21792 176.90372 140.32885 176.12957 139.02917 173.87830 c 137.72948 171.62702 138.50362 168.73795 140.75490 167.43827 C 142.46164 166.45323 144.53490 166.66016 146.00409 167.79827 C 157.56917 158.29257 L 157.36224 157.45521 157.35742 156.55748 157.59496 155.67080 C 158.34926 152.85940 161.26866 151.17279 164.08035 151.92652 C @c 208.63446 174.96907 m 208.17553 174.57987 207.78917 174.11698 207.48699 173.60391 C 181.72375 182.68441 L 181.75861 183.01805 181.75776 183.35764 181.72035 183.69609 C 181.77846 183.62948 L 194.65455 189.26306 L 195.79323 187.86813 197.68904 187.18441 199.53298 187.67820 C 199.65883 187.71392 L 208.63446 174.96907 L @c F @rax %Note: Object 87.87118 161.42117 166.14510 231.41934 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 89.41465 198.74835 m 91.47345 196.69011 94.84498 196.69039 96.90406 198.74863 C 97.63087 199.47600 98.10057 200.36778 98.31345 201.30605 C 118.61631 201.28961 L 118.94117 200.52680 119.46898 199.84025 120.18586 199.32293 C 120.93137 198.78463 121.78630 198.49776 122.64548 198.44561 C 127.73820 171.56296 L 127.14293 171.30671 126.58706 170.93622 126.10318 170.45291 C 125.88746 170.23691 125.69386 170.00646 125.52321 169.76494 C 107.18249 181.08425 L 108.09326 182.85817 107.80894 185.10094 106.32869 186.58063 C 104.49581 188.41380 101.49420 188.41380 99.66189 186.58091 C 97.82901 184.74831 97.82872 181.74643 99.66161 179.91354 C 101.30287 178.27200 103.88268 178.10164 105.72009 179.40076 C 124.64306 167.64831 L 124.34939 165.99798 124.83553 164.23257 126.10290 162.96463 C 128.16255 160.90611 131.53351 160.90696 133.59231 162.96520 C 135.65055 165.02372 135.64913 168.39468 133.59175 170.45291 C 133.58324 170.46113 L 140.18230 184.71515 L 141.76488 184.28315 143.53370 184.68539 144.77017 185.92157 C 144.95131 186.10243 145.11288 186.29461 145.25858 186.49616 C 156.75562 183.28762 L 156.86476 182.25014 157.31348 181.24384 158.10350 180.45411 C 159.93581 178.62180 162.93770 178.62151 164.77058 180.45439 c 166.60318 182.28671 166.60318 185.28831 164.77058 187.12148 C 162.93798 188.95408 159.93581 188.95380 158.10350 187.12148 c 157.64372 186.66198 157.29931 186.12794 157.07027 185.55789 C 146.10246 188.62044 L 146.29181 190.03096 145.84791 191.51150 144.76989 192.58866 c 143.01099 194.34813 140.17408 194.41786 138.32872 192.79984 C 127.16220 201.03874 L 128.17304 203.06523 127.59562 205.60337 125.70350 206.96825 c 123.60161 208.48535 120.63855 208.00573 119.12202 205.90441 c 118.60753 205.19150 118.32265 204.38050 118.25291 203.55987 C 98.33556 203.57603 L 98.13373 204.55285 97.65638 205.48261 96.90265 206.23691 C 96.08513 207.05414 95.06154 207.54652 93.99487 207.71490 C 96.36888 222.01342 L 98.74885 222.25011 100.61461 224.26384 100.61461 226.70504 C 100.61433 229.30441 98.49940 231.41934 95.90003 231.41934 c 93.30038 231.41934 91.18545 229.30441 91.18573 226.70476 C 91.18573 224.73411 92.40151 223.04211 94.12186 222.33883 C 91.67244 207.57033 L 90.84359 207.33080 90.06406 206.88605 89.41465 206.23720 C 87.35726 204.17811 87.35613 200.80658 89.41465 198.74835 C @c 131.64718 171.68457 m 131.08082 171.88753 130.48668 171.99071 129.89140 171.99581 C 124.87351 198.84784 L 125.17994 198.98447 125.47361 199.15483 125.74800 199.35638 C 125.71909 199.27304 L 137.03613 190.93861 L 136.39776 189.25512 136.75323 187.27115 138.10309 185.92157 C 138.19691 185.83030 L 131.64718 171.68457 L @c F @rax %Note: Object 79.80605 212.25458 124.79669 299.89587 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 92.63083 286.82164 m 91.87767 284.00967 93.56372 281.08998 96.37569 280.33597 C 97.36894 280.07008 98.37609 280.10920 99.29537 280.39408 C 109.43235 262.80283 L 108.93402 262.14038 108.60350 261.33987 108.51392 260.46028 C 108.42066 259.54554 108.59953 258.66198 108.98391 257.89153 C 88.24932 240.03978 L 87.72973 240.42728 87.13106 240.72350 86.47030 240.90094 C 86.17550 240.97975 85.87899 241.03219 85.58476 241.05883 C 86.21717 262.60214 L 88.20879 262.70050 90.00879 264.06822 90.55020 266.09017 C 91.22145 268.59373 89.72050 271.19339 87.21723 271.86350 C 84.71339 272.53474 82.11373 271.03408 81.44277 268.53024 C 80.84183 266.28803 81.98419 263.96872 84.02797 263.02706 C 83.31165 240.76318 L 81.73531 240.19228 80.44980 238.88835 79.98520 237.15694 C 79.23231 234.34413 80.91865 231.42501 83.73033 230.67128 C 86.54230 229.91811 89.46085 231.60472 90.21487 234.41556 C 90.21770 234.42690 L 105.86126 235.83912 L 106.27880 234.25257 107.51159 232.92170 109.20019 232.46901 C 109.44737 232.40268 109.69455 232.35874 109.94202 232.33323 C 112.91159 220.77241 L 112.06800 220.15928 111.42085 219.26750 111.13200 218.18835 C 110.46132 215.68507 111.96198 213.08570 114.46583 212.41446 c 116.96882 211.74378 119.56847 213.24444 120.23972 215.74828 C 120.91039 218.25128 119.40917 220.85121 116.90589 221.52217 c 116.27802 221.69027 115.64362 221.72173 115.03531 221.63499 C 112.20350 232.66460 L 113.51991 233.20602 114.58035 234.33080 114.97408 235.80283 c 115.61811 238.20576 114.26003 240.69770 111.93619 241.48658 C 113.48816 255.27657 L 115.74850 255.41461 117.65792 257.18372 117.89376 259.50472 c 118.15654 262.08340 116.25987 264.40980 113.68176 264.67228 c 112.80728 264.76157 111.96255 264.60283 111.21676 264.25276 C 101.27197 281.50980 L 102.01720 282.17310 102.58384 283.05128 102.85994 284.08110 C 103.15899 285.19767 103.07367 286.33039 102.68617 287.33839 C 116.25591 292.43197 L 117.65083 290.48882 120.32759 289.87994 122.44167 291.10054 C 124.69294 292.40050 125.46709 295.28957 124.16740 297.54085 c 122.86743 299.79213 119.97836 300.56598 117.72709 299.26630 C 116.02063 298.28098 115.16343 296.38176 115.41430 294.54038 C 101.39953 289.27757 L 100.77789 289.87569 100.00290 290.32838 99.11622 290.56620 C 96.30425 291.31852 93.38400 289.63361 92.63083 286.82164 C @c 90.30926 236.71559 m 90.20183 237.30746 89.99405 237.87354 89.70066 238.39172 C 110.44630 256.16324 L 110.71786 255.96624 111.01238 255.79701 111.32419 255.66038 C 111.23745 255.64337 L 109.67811 241.67565 L 107.90079 241.38652 106.36072 240.08683 105.86665 238.24290 C 105.83433 238.11619 L 90.30926 236.71559 L @c F @rax %Note: Object 133.64957 202.31915 400.19357 265.86142 @E /$fm 1 def 0 O 0 @g [ 1.00 0.00 0.00 0.00 0.00 null ] set_fill_color 233.89965 250.84460 m 233.89965 245.83890 L 224.99972 245.83890 l 220.22674 245.83890 217.83940 243.35943 217.83940 238.40050 c 217.83940 214.62406 L 212.76454 214.62406 L 212.76454 239.23502 l 212.76454 246.97446 216.54198 250.84460 224.09688 250.84460 c 233.89965 250.84460 L @c 261.91531 250.91405 m 269.47049 250.91405 273.24765 247.04447 273.24765 239.30504 c 273.24765 226.23420 l 273.24765 218.49420 269.47049 214.62406 261.91531 214.62406 c 249.12312 214.62406 l 242.68195 214.62406 239.46038 217.82239 239.46038 224.21849 c 239.46038 227.06816 l 239.46038 232.44463 242.51896 235.13329 248.63698 235.13329 c 265.39143 235.13329 L 265.39143 231.03071 L 249.95735 231.03071 l 246.20315 231.03071 244.32633 229.43225 244.32633 226.23420 c 244.32633 224.56573 l 244.32633 220.85830 246.22753 219.00416 250.02709 219.00416 c 261.08135 219.00416 l 265.80784 219.00416 268.17194 221.46123 268.17194 226.37395 c 268.17194 238.81748 l 268.17194 243.59159 265.87786 245.97808 261.28970 245.97808 c 240.57213 245.97808 L 240.57213 250.91405 L 261.91531 250.91405 l @c 304.46277 250.91405 m 312.01710 250.91405 315.79512 247.04447 315.79512 239.30504 c 315.79512 226.23420 l 315.79512 218.49420 312.01710 214.62406 304.46277 214.62406 c 289.02954 214.62406 L 289.02954 219.62976 L 303.48879 219.62976 l 308.30995 219.62976 310.71940 222.10923 310.71940 227.06816 c 310.71940 238.46995 l 310.71940 243.38268 308.30995 245.83890 303.48879 245.83890 c 293.40935 245.83890 l 288.63496 245.83890 286.24791 243.38268 286.24791 238.46995 c 286.24791 202.31915 L 281.17304 202.31915 L 281.17304 239.30504 l 281.17304 247.04447 284.95077 250.91405 292.50539 250.91405 c 304.46277 250.91405 l @c 329.14290 265.86142 m 329.14290 250.91405 L 347.35691 250.91405 l 354.91209 250.91405 358.68869 247.04447 358.68869 239.30504 c 358.68869 214.62406 L 353.61354 214.62406 L 353.61354 238.46995 l 353.61354 243.38268 351.20353 245.83890 346.38378 245.83890 c 329.14290 245.83890 L 329.14290 214.62406 L 324.06803 214.62406 L 324.06803 265.86142 L 329.14290 265.86142 L @c 400.19357 265.37499 m 400.19357 260.16066 L 385.10759 260.16066 L 385.10759 214.62406 L 379.82353 214.62406 L 379.82353 260.16066 L 364.52920 260.16066 L 364.52920 265.37499 L 400.19357 265.37499 L @c 159.30283 265.37499 m 159.30283 226.16476 l 159.30283 218.47124 155.52567 214.62406 147.97049 214.62406 c 133.64957 214.62406 L 133.64957 219.83811 L 146.99792 219.83811 l 151.67849 219.83811 154.01877 222.22545 154.01877 226.99899 c 154.01877 265.37499 L 159.30283 265.37499 L @c 203.30957 265.37499 m 203.30957 260.16066 L 179.60230 260.16066 l 174.92088 260.16066 172.58145 257.77361 172.58145 253.00006 c 172.58145 226.99899 l 172.58145 222.22545 174.92088 219.83811 179.60230 219.83811 c 193.22872 219.83811 l 197.44668 219.83811 199.55537 221.94709 199.55537 226.16476 c 199.55537 238.81748 L 185.16472 238.81748 L 185.16472 243.47565 L 204.49106 243.47565 L 204.49106 224.91383 l 204.49106 218.05398 201.08523 214.62406 194.27131 214.62406 c 178.62973 214.62406 l 171.07512 214.62406 167.29710 218.47124 167.29710 226.16476 c 167.29710 253.83402 l 167.29710 261.52781 171.07512 265.37499 178.62973 265.37499 c 203.30957 265.37499 L @c F @rax %Note: Object 116.83162 268.46674 196.19320 328.77638 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 170.51244 328.07310 m 167.70076 327.31937 166.01528 324.39940 166.76816 321.58715 C 167.03461 320.59389 167.57206 319.74123 168.27846 319.08784 C 158.11257 301.51304 L 157.28967 301.61339 156.43106 301.49943 155.62460 301.13717 C 154.78583 300.76072 154.10976 300.16403 153.63496 299.44573 C 127.80765 308.47691 L 127.88334 309.12038 127.84054 309.78709 127.66394 310.44784 C 127.58457 310.74265 127.48195 311.02554 127.35780 311.29398 C 146.33121 321.51770 L 147.41178 319.84214 149.49638 318.96709 151.51833 319.50907 C 154.02217 320.17975 155.52283 322.77940 154.85159 325.28239 C 154.18091 327.78624 151.58154 329.28718 149.07770 328.61650 C 146.83550 328.01584 145.39805 325.86661 145.60441 323.62611 C 125.96513 313.11468 L 124.68246 314.19411 122.91052 314.65559 121.17883 314.19213 C 118.36658 313.43783 116.68167 310.51786 117.43455 307.70589 C 118.18828 304.89420 121.10854 303.20986 123.91965 303.96217 C 123.93071 303.96557 L 132.97550 291.12378 L 131.81046 289.96923 131.27414 288.23613 131.72655 286.54724 C 131.79260 286.30006 131.87820 286.06394 131.97997 285.83717 C 123.45279 277.48460 L 122.49978 277.90866 121.40391 278.02346 120.32504 277.73405 C 117.82176 277.06337 116.32082 274.46372 116.99150 271.95987 c 117.66217 269.45688 120.26183 267.95594 122.76567 268.62661 C 125.26866 269.29729 126.76961 271.89723 126.09893 274.40050 c 125.93083 275.02838 125.64057 275.59332 125.26157 276.07691 C 133.39757 284.04397 L 134.52463 283.17487 136.02869 282.81883 137.50072 283.21398 c 139.90365 283.85745 141.38277 286.27965 140.90400 288.68655 C 153.62249 294.23764 L 154.87200 292.34891 157.35883 291.58016 159.48680 292.53628 c 161.85146 293.59786 162.91786 296.40387 161.85628 298.76797 c 161.49600 299.56989 160.93616 300.22214 160.26038 300.69298 C 170.23294 317.93386 L 171.18000 317.61978 172.22372 317.56847 173.25383 317.84400 C 174.37039 318.14334 175.30866 318.78369 175.98784 319.62331 C 187.18384 310.41808 L 186.19852 308.23880 187.00951 305.61619 189.12387 304.39559 C 191.37515 303.09591 194.26394 303.87005 195.56391 306.12132 c 196.86359 308.37260 196.08945 311.26167 193.83817 312.56135 C 192.13143 313.54639 190.05817 313.33946 188.58898 312.20135 C 177.02391 321.70706 L 177.23083 322.54441 177.23537 323.44214 176.99811 324.32882 C 176.24381 327.14022 173.32441 328.82683 170.51244 328.07310 C @c 125.95861 305.03055 m 126.41754 305.41975 126.80391 305.88265 127.10608 306.39572 C 152.86932 297.31521 L 152.83446 296.98157 152.83502 296.64198 152.87272 296.30353 C 152.81461 296.37014 L 139.93852 290.73657 L 138.79984 292.13150 136.90375 292.81521 135.06009 292.32142 C 134.93424 292.28570 L 125.95861 305.03055 L @c F @rax %Note: Object 168.44825 255.61984 246.72246 318.57874 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 245.17899 281.25128 m 243.12019 283.30951 239.74866 283.30951 237.68986 281.25071 C 236.96306 280.52334 236.49364 279.63241 236.27991 278.69357 C 215.97732 278.71058 L 215.65276 279.47367 215.12466 280.15937 214.40721 280.67698 C 213.66142 281.21499 212.80791 281.50129 211.94816 281.55402 C 206.85600 308.43666 L 207.45014 308.69320 208.00658 309.06312 208.49017 309.54643 C 208.70561 309.76271 208.89893 309.99288 209.06957 310.23496 C 227.41058 298.91509 L 226.49981 297.14117 226.78469 294.89839 228.26438 293.41871 C 230.09698 291.58611 233.09943 291.58611 234.93146 293.41928 C 236.76463 295.25187 236.76463 298.25348 234.93203 300.08608 C 233.29020 301.72762 230.71039 301.89827 228.87326 300.59915 C 209.95002 312.35102 L 210.24425 314.00164 209.75754 315.76762 208.49017 317.03499 C 206.43137 319.09380 203.05956 319.09294 201.00076 317.03499 C 198.94337 314.97562 198.94422 311.60523 201.00161 309.54643 C 201.01011 309.53792 L 194.41049 295.28476 L 192.82791 295.71676 191.05994 295.31452 189.82346 294.07805 C 189.64290 293.89748 189.48047 293.70472 189.33477 293.50375 C 177.83745 296.71257 L 177.72888 297.74920 177.27959 298.75606 176.49043 299.54551 C 174.65698 301.37811 171.65537 301.37811 169.82277 299.54551 c 167.99017 297.71291 167.99017 294.71102 169.82277 292.87843 C 171.65537 291.04526 174.65698 291.04611 176.48957 292.87843 c 176.94992 293.33877 177.29433 293.87225 177.52309 294.44202 C 188.49146 291.37975 L 188.30154 289.96980 188.74517 288.48869 189.82290 287.41124 c 191.58208 285.65178 194.41899 285.58120 196.26406 287.19950 C 207.43087 278.96117 L 206.42003 276.93468 206.99773 274.39710 208.88957 273.03165 c 210.99118 271.51483 213.95480 271.99417 215.47162 274.09578 c 215.98583 274.80813 216.27014 275.61969 216.33987 276.44003 C 236.25751 276.42302 L 236.45934 275.44649 236.93669 274.51672 237.69043 273.76299 C 237.96624 273.48718 238.26472 273.24907 238.58135 273.04724 C 234.78973 264.97587 L 232.43471 265.39398 230.09244 263.96220 229.43027 261.61200 C 228.72557 259.11014 230.18740 256.50085 232.68926 255.79587 c 235.19112 255.09090 237.80069 256.55301 238.50567 259.05487 C 239.04028 260.95209 238.32935 262.90913 236.86356 264.05348 C 240.72066 272.26715 L 242.30466 272.05427 243.96888 272.55288 245.17899 273.76299 C 247.23638 275.82151 247.23723 279.19332 245.17899 281.25128 C @c 202.94561 308.31534 m 203.51197 308.11209 204.10668 308.00806 204.70224 308.00324 C 209.72069 281.15178 L 209.41398 281.01543 209.12060 280.84592 208.84620 280.64409 C 208.87455 280.72658 L 197.55723 289.06186 L 198.19502 290.74535 197.83956 292.72876 196.49027 294.07805 C 196.39616 294.16904 L 202.94561 308.31534 L @c F %%PageTrailer @rs @rs %%Trailer @EndSysCorelDict end %%DocumentSuppliedResources: procset wCorel14Dict 14.0 0 %%EOF II*#   !"%'()+-/.,159?<ADMIQUWS]\Xacinolhprwtxy|    "!#$%&')*(!$&'(*,.-+048>;@-./,013543668;98<>?=;@@ACEGFHJHKLONPRSTCLHPTVR\[W`bhmnkgoqvswx{VWYXZ[[\^_^`bacegfiiklnomqqssuwuwxzz|}~  !"#$&()*-..304569:<?; #%&')+-,*/37=:?BDFAGIKOMLPQTWVSZ]^\X_`bdfkilokqrwtx{}BKGOSUQ[ZV_aglmjfnpurvwzimage descriptionmmfqae_UĭXVS˜˲`j˾[ 35˾3:+ʲgTˤ3˖`q˯<S<˿ˑi˷Xˬ,˥+V<^˾e [\a`6k˶.-t Sn˗RR.˩:Ǒq/jf,1ʜ))l˚/˰˷Z)ˤ3˶ˬZ ˯<t$a˵O:$\6a@ ˞$UVt}~Ȓ$Rj˲ ˾s7#TR$Nˤ˚$=˕q$7je$23TR$ .R˼53CKB}&$v)f˲ -vL%$ˤ< u$@!˕[˷I$&-hm˧I$11T˙ vIDDDDDDDDB %vxJDDDDDDDDDDHM$DDDDDDDDDDDDDDDDDHuL7N˼5oB? %$I;`˲ `zuy@($(Nt˦O K}$BS˾ ˔+oc xM $v-r1i_ˢ$#%$y$|NT:˕BCJ$9>HHHHHHHHHHHHHEEB$oS)8TdnO|EEA$˸R|EEA$˿th\N3|E #>????????????????EA$˦i\Q14˒|EHzEA$8Yʏ|EzEA$gjˑ|E zEA$q˿`A$:sB|#E~A$0pm˰w}~EuA"$ĞN9"@ IEAJ$t˨[$GEu(JAz $^0˶!@(HE"LGAC$7˳%"'Eu%#A}AE$3gU>|"EI'A"$Z˼s>LEuA"$q˦ˎ>}HEBA"$tfT> E$wA""$a˱yyyyyyyyyyyyyyvLB  GKxyyyyyyyyyyyyyyyxKC yyyyv&JvyyyyyyyyyyyyywI'A yyyyyyyyyyyyyyyxKD yyyyxIyyyy?Ayyyyu7-˲A2aYAY˼9mnSAqˤ˳)Aˎ˹Aa9gA c˧Zt9lˢA!˪/mq˦A˩9SqA\RQ˜A˲˲/NAˠ1˽jr&˯j3˻,S;˾N4˺*˞˰s 7˹!˖!_0ˬ!9˷ ;mk7]kV;˶s8˒rDzgP˴o˥˰,˲T˿f[˸)3˛gɺ˾O˼ 9k[ˤ_˾˝ _˚[˷Qqʨ˿1˦[[rd,4Ð,4˿ǰ_8˲r;Um OU˺h+S;ˣ.˝dràd)3^4oh˫UPe\˓˸;4m˷ Ț4-0˥eˬ9UN-U.S˿q˰U˿9˨0i3h˹ 5r8˵..OXs2k=ːY˲Xo ˴k)˹VrrZq˞t˲h˭f!˚Rˠ˹Rk˦t˺4Sˤ,nɤ[\]-;^ˡO˾f˙W!3sşT ij˼:<˨:˱7Np9S.4\ O˽4;p:=kʓj_Y˩)˭U˶p-ʟ!)˾\r˜ːZ˼\6^˰i˹ Y˧-X˵V)ː˙fʸˠo-˜;˻q)k)6˷N[˻3V)jORh˝˾1˧˷<o˱;TT -˹1˥`˖-4ǰ_PVc/V˯;,pː;ǐ`q˞0ʚ)*sˠt˰.e!ˤ3˿b˾9˲tˬ;˦7a;e[f<k˵VNS!ˏ4ˠ_˻_ ˫˴S;Tj ktˏ9ret˲ɕY˞-P5<ˮ2 R˨nd9_ťNmU!es`K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'*u-T |V7@gcD,Sz%pQ?f džk"(@jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-black.jpg000066400000000000000000004432701402514743400225140ustar00rootroot00000000000000JFIF,,ExifMM*;JiP  >RESTU6868 2018:11:12 20:33:242018:11:12 20:33:24RESTU http://ns.adobe.com/xap/1.0/ 2018-11-12T20:33:24.679RESTU C     C   " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?%G\u?9딨,((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( |)o3'ӴyEFz:N6rWZ0yҊ@@+|2q8{}A+^XLIZ_ʹ%ϗ `~>!Kϲ}Ɵ?e =T#Wdxš_ttZ;iF2)c8/ (7O}xD(vŧ駩Ev2Ltgy=Cj*aʍUiE٣,6&2q%x]0+ ( (GMK⎪d #7 ʎ};w힜>.hэG&/CFXDa8@3Wѧ?_?p+,OßS 9${'~KZ(@Т((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((1ĚV߶]EoM?Wn;=>k۩WfGI{7BkYR+ r)#W嘌ƴcJ ]u7>{9pNRQFm>Uվo]"A ,qU¦I%d$Š(G~Sr5"nrJ¾>iV=?=x`AC?ƿfIw]K*ڞrQE|1PQE=ڕy"f85iO h16@9C9|aN;I*.,?P+gpuqM{^Kwh`m?(0z+%G\QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQERU&yWZ:9s;Sձ5݄1=O{/&YKb(=ߢ$8-yHRֽ췫U%:52a{O'ھ/¿|=.bi#y?3aVeA5B;bFRwK'9?^Lp fsN/hW|qMM{^OjCzl~ͣiZ0tǓ/ǿ<7K|<#ݱ_>|D5y~F,W7.>޼Ry乙FW%=I4Yd[Z+u>ee YFիOZw}f@][CMbfn/fSqI .+9䵹G4LU+%Ykz\ CDo}O^SO1K%Kgghux?ޫѽ׫}2YRIcfv8XIĒ㤷*5:xMiq2icuʫI}:{W̻˞]M}< fتWAS#Ӥߣiے'&s۞Q_c1uq爬Vи  %h÷ޡEWQ@^ ׏[jܕWPpFE~ym*GSFA_T~?6WIxdž+'Kl]'V=菇φ\5Qp~tb#>'޽Tլ+/5 l#ifp?_ee+/O|/aB3/ =X涵vdt_ßRk{an:4EX^ͳdnOձ5e!>\7q_e=H6ِ= aoB;B8?+S^'#éi7OiwúA5y'ʤޥ۷-|;Oܬ}u_şWFBkq"Uދ !v?ut^խSO+"e=r>$ۥ?^EIeVwGo0'BުC޾2ɰ$^~yQgfqn>q}cc&'>j^U}O~I=[_WtkGKt81%AEW9QEQJ94]M7Kd O'™kgM^ZS0Pj7H..\(8z `ƾ"*U^b'ƌ]Ӛ[vdpDTQU5ό5]"ic &_tWUN9*5٫QVT7wM|aEv:a fvE~jzmXBc' `7MغK=Z2!#>#[80奻?}n J9aqn`[_=yQ_ ~QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEu?9딮bG=rQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEScYrUFI'_E~? y|[$Xd0أItzf_S3G OK.xiK'W[l|7]Ķ1 }mB'+taYp0Zt&+\Eࠣ JOC0l1:+bRݞe~Ξ!eM,5NW7};\Gx]>sӰsҾ]5>W:sl%?fMU޶Fv: a^$my+[hW%DQ+>.lZ"6S_y>H䌳GAȯw['TahR )}My+ؼ6/_YsœXmKS{7_?ڏGм? ƺKd[|k:NKvAE8ֹ+,;եh*e9x^(p(((((( D&geEu'V}}9+|75.âty?`޵9t<\pv%/36xoWy=&{o<%i hc1G29_7?/c.ltTAw/> C)q┎=d;w]K<+wcNI&ALJ^|Osd~DtQE~J~QEQEKmu5sOI#bt 1GCy/K{E7~噮'*(=?LĭV[O.f~k:-F;9k #qȯ1 ˪i"Kmh y7$HHZ[a쟨힕Mݦaż^Y&tOE~(Y揻QQ5hc<ہI۲^]j΋kYgH-yscK3@Z,^T## _fk|=>oe5×D\==] y^ -U]ow5Nk`+wrj{?=C_xS_GBp$#f:¯ho xg'׎E ==k-=i7<9 NnW?ƟG<|߄`ihd(N' 5 (k;Xe*`4tlu$I _]W^P|/az6PI '=Wqa w} fT28K,v讓5߇u&1";zwusuZU(MӫL^&FJQ{5 (7 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ(((((((((((((((((((((((((((((+z\zG/ [ƻAf8߫+G/fm\w s|Ѹ%b꧿.z~K*4ZyvQE~~QE|_iq Q"i k'E!FGWxG/uh[ _.CC--+mW?q&x`qu9$kK7}||Et^'}?WX.ly7D~2v4J8Tՙ ԱU(J/w_z (6 ( ( ( ( ( (:-"q(y"-}>558עW~_ ԯ"۫je}Øqڣ~$#Y˷UT c~ڿhpXJoy,e^'aʻ_K[Znx'ǿ[i7i~`~y?DqyWXMLey*zNסK/C AZ0V_aEW!QEQEQEcu d{yC1)U[E|_c~7]|.XPגN~+B1ymc=5iEWGhT7Mmqc6 `TPjiϊm(EԐPǮz?Ȁi۟ҹ a4! ^?3\-*>'ҹB[ X]C80ܧؓ^> WSmD#%U?ǟ ltmRB'Rʱ/R@ Hƿoʫ<<Ji=pθ-vm/z^Z~Q^#{o{7xD eOO^-}.K;mZTPf3^ |эNMc% IVnӡggɬ+̰/ǷA=*/3Ceuv„s)aC KYierYYrIQwx-p[n{mo/4[i6^J0L߉߲ŗxEYٛ<~~VW嵝pL؇{x}Qzh3(Kweٗ=}+jM/l{ -axث*e#C_x|CEBȿۀ'1_(I%(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z((((((((((((((((((((((((((((((k,V]Vo4s97LEaOlٯ 6_&]S<.g/v_z}G*u yw>~о!XYғmz#;bN)s7v:F/CdUV2T%?Zه*S#[S۫7^"+ꚝۿ=? /-ha'g><~rD܎2<`${lϜ_SK3ٽ5徭>ޡro4X.4R')4i<8^<⽑4˦󯍼UFulO=A#Y5)0U+k=T?z^KRL2pVqz5?~ٕbşsh#FnNF*̢e|=n2y]noWMg웮Яբf̿AW:ׅn<_L8td+} }?ı!2g$awgoۼPEs4rua \4/پY?cQ\E}ٯ$%-sŰC+Wl$d^kkk-6F+K18~5w_Eh o\yr/G<{ ʿqu}#;:tjhWy+mM`FI~##l"D>\rkܿj>T0B_Mߕ=F~x־gW_WXO݆r8$5Sy w (;?W ( ( ( ( d>)xrk>_r wRo)]ܖ1K "BG}:=GO)YT0uUTWˣOVAqݦuvgu5l9F??ڦQ$IN- ]b뭥pF}j_ir]rXdԢ(qqgӿ jX.?-WR~F>q+.-5{!2:Hߡ]> qtu3fKп}?)mGW('nWY; _WK7|SkْBW!┥9i%8"yq5ףϨhht;= S܎HGz'~:%&HBAZ-J 'zWq^QYU5?ʌMviK}bOxQ_v^];X/e>lX h׫=abRfϦ|']C&|GMc}{7%gƊ:ŵ̤g%+ r;F9_i ~ѮEaYf*7J^Z4~5jڍ+ïkY-S\.W2,s^?^3ċTmBPr ֚=ȵ6m2C}Hw Im> ~֐X*EԠI0"^EߞA v,q"*  bȞ~'<{CZX|?h֎M7맕Ϛ?g_%׼EMb,x GOs^Z·mqI qdw)&a-/ьDOlc{d'˧Kiھvel9mNg.Jm{(7{kz׈u{JC-ԭ,}I Sϧ]ksAq XG֠97=#6[QEEv  k./H&+ ^ m]Yɬ\Mw!=@럭}6[>ҚQw~_|~qYvM?eZNSX׮/GW:0öd`4a"WqZͩxZYu x簔feֻ'a)ߖ7.㌯UQ鷷5<R$?B ( ( ( (7|Yk7FkBr(' 7O4k+XVѬQ誣~B~| Yx?|@^_|?6j<`rd<2y}ղ]L8a/%ǏsHdߧ[`BϦ+ΨE~KO^uo's< O KI^{}KWWnd1-|#_[ɚ<}b͗lDu~6%MYW?> r[KiǙ?~"[^/lu%OR7B5@.$b?PEξ]+({ Ҳzs OrZ 6`+ (:1GSpApeNjIfhn! WG*aGX'n+4/#9S5u!r~濽v[|ֿz?kȼ;G Mzl_h+kʩe8T&o[|33٦>tuMM imaU*J=jj+MY>D~ i\ca>`g=0{z;J 񗇡_uM&u v>VW磩F*F85oetTjQVKvk{yj Ϊ)R;ΓJZ{_F7XKZƑ Lx[}}k{C6wj1ȇu_`Ο?4пu)kZz7{?kL5#?5 Ig85fn3|Gïi7`jwό 'aA^/^xU]blyR4삊(( mn粝&H&C&*}N#/ 5U˿]ϏglI,pN\z/};3b)5=O5j\hs 3^omEΙo㡯mCխ_|VֿP3V4Y%kzh~11e^og>nKEWgEPobke@8H ǀRx}'aG- P|JP֌66Q5W0򩽚)4Te[ s$sU?oYqJ3"r?P+QXM:/iI/,~!0qWpܛ>_-n3G'>[Q_4”8+%GZRUj;Nl( }UcV Έ02w?]Ŀ-:yM7FA称쓯X}S u?nu k}t~ qn4ܵ#MvMYWO/~)chwY{+3WգR*jkGԗt_QEbnQEĺ厕fnXPvu?J} } 'xZncݰ6Y}>eXf8xuz%9N.[7z &vB/rrO־Gu@]mmTqe5d4leQ_}!jG0ŪNFԯg}_#΍ϙlǂ׻eBK>kpZ4~?fV*hW7:Z%i#^𾯦K?~x2b5\mCJFs|WX|l;2V%Q_Q@Q@Q@guݟKr$11*5 ?203UvWUPv!g%W4JϞ_h(O߅񧄵=C HF? ` ~}C+ૡ*AE~1I<+_\fT:c +8*WJߕ+q ~?(QEiPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEX ]RT|aaPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEuU2/HKvGɯm[&.7Sk`6л {,YѦ{\Lj̮Ys/rc`UYQI>WCrvK5֍ϛ~J6=~`}kO ?" 6mUwHW9cֆ3ÖTā}}hяս-Ϳ8q6ZVmsZV ckn8qZzG s0薗r+p3@['F?: Wl,ԣ4wLfe?{UkߨQEymN-3NoJ{*I~sO)y$<bߙii:$ӧj7C&.OMQ_)W#giWvOf2+!9Sԧ t+]?A<[bX8YFb'-!~޼/OT?)1 z qS^w+88F:%/Q@3+)լ);Kʲ2H?PAhNXUҢ]$CYk֔X5Lᢾտd}Jmzd-n&҅<`8&(N屹6$1~_ֿ4٥;Aku?`y-^T 7m_yUgC-i-ge*G@g5ҌdPjENBQ^-,v'Mn~}ԏU_߀ǽ}ُ7@>ю"k23 J0yh]_|iy^UxΧ<َ_7#:gdI4m>x:׾x#LuX.rzk+|7XH`[d;o_.W|,$]ɸhs)M%̯,49;}ICnS?=k_kAhɴy0>oq6_9裯G>W٦>UiP-<̯?|suB[>-p|'ǹ$ v+EzүURwg KBj*тI||𧊴]&%eYAcQYSMNTFiƵ9Si3wMm}>Q5k,R/FV5rxFVK OKbzw\ܑ~to-Α[_Fu ,/U>W&SJIIOzyʹbZ?.y}ݡEG5V4ȑF,=x?Ez kCFwA f11l7W5Epf/3)?zߏGAsrPimBOE 9[m[=v&oct|Ϡ5-JX./f{2K+cjsF=_߇ aRJZoG"Ƞ d5OU:YO*C\_Ǟ")ҵ%q|CWs>5[;Mb1%?{-Gi[)/پ_/kVR~M_ψdm|.sErd6漷Gt-:+)Al?5~#MAniOS4~dydOм[/3Jm/" ~5ɲ \,o^ $bGll7_|#>;} 45)+=uf澩}8xkvK[wBPvtZ3 ~YX42NQUCjZkFflr(%q}H*H<_qnTy[K|)byeI?4%Q_~}T^%ҵpm.\Yt*7 )uHF%V5a"!AH`jC{(AbW"knŷ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((( tҿc%mKđ/xmfʢֿd ȡgҼC ̀q0J[WӴW诅N_e3eƹ?rߕ< MjKGlG V;-ÚnŶj෌=:s_Z4iV԰o*et#fcZ-g0?_5.ڭj~o}|F%,=>_>Z졯j&ׯ!x;$L]BPĭP1~|YC V3|ɏrG^%FO^2k/e#?Ӎ7܏߻%(d/<M׏'OAw'ھJW_xBƭqu9n"JI^8*v]>.-<¯3r=?á f0?ϽymuO}pe~f_qx͉}`r[\%%;koW ziF+-B4׬cTNfQ$#~Ta1؜n/lf Xjq_#kÙ4MEeVBG'Ɵf[kY&6l{>tW_JnIa;%Tvݾ\\Kw<#4Y䑋3Ԓz+۾DVAER{DCQnX_rqϠ wkHfy$Q+?CDPu}*6l؅[󯮫 FXIz/9⡗R0+sCHQ˝90+k8:W|^..z֣u}u c-,WMxύjD-wqp<1_1ZߌnӬSɜDuG ů2U0Q]ޯ~}j촏߻ƿUjO ˕ǡ\MWWVԭ7'ݻᰴ0t,<"%`+ ( ( ( ( pHѸpEGE=eRB]:I 3UhnN1QVQE%Q@IJ#"A5ӶM'>?&÷2%įj30$Wc^)xfXu?!^_>*w'gF4Fjkѻ(O֚7o/Fy }7W,/5<r1_|T#6{GEf/_A'ߡ+n4׭JJpc{׷b+ףVS/*}m{s((+^An[-cs-; t~|9*[gog~\zy h v+#?zIe2ܱULfTߞ_i~EWxA^7PjWo4FІm(dFS=+jNFؑ|E5e8m_8.[KGHř19$Ms?Š(aEPEPEPEPEgNӮu{,{$qF2ǰrv[)();${/읭Ea X)a q+_-Y\dտ_V޻gW`4:;n~gk:Gx&זèsBJscch< +7m/ᾃ-Ӱ"[ W:߉VO2S,'+1i0wo/gթyաoGo$7gQEQEQEQEQEQEQEQEQEQEQEQE{E֯|=[j:u۸xNQ؃־}RhŬV%H A!%~z22IG{? pKym%_漙 O%ZL[Q{)1>[[:of>_WUSY/>=Sռe҉zOWs%ѴRtqR:;gt+hkqHDuC5|VH a~K׉=QW~mqVֺl;zmyyW}C9NNfnמĴVxV}?XkyG) 9WʪSuLju骴Zz콫fSܶX1'ĝ#{> 岗`l)+_|4 x~Ԋ-[&`H# `!5˫aeѿK#!,&mC?_XIZ+N}[M#dW *ʯ'NNh~NqR;5(@((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠(((((((((((((((((((((((e?qX]]xRPthfs_*RSV~hmN. 5ԯiψ02lt\_UxK;Zasqg s_:ujv7vҴRu  ~x԰B>~?_韩x}TW=7)EQEQEQEQEQE\4{RGc%ڪ1rj1WlJ0wz֡]#1bk쏂>ح׈&OJ9Xҥ1Zᮞ.]%+?ާӊCLe'kdW&GG(?2{]eYRn MH{ >%Lm{|mԙ[k48y({}pƺO.oNkmDqIw5?꺬kG EJ+fui߻ x[nS5i=_ʯӿ5 acpbv vZxNӬta4+`9=v_=[%ѿu:?j2_j_]Y&lzagQEx3')}~Ot/%FfUOA\52UzU4ͧVvqux,=l4I{.^gW!vt#_jJ_3.{IZ"320@ItǨ^7c|/k6X{w_c>ci׫h6ޛ}qn]W C^n#vm+hLJQE'EPEPEPEPEĭVKhVļ 3O`GGf W^8z ?яe7~'h]2kݧ ƟW8O3k*وϖHC_WzUa vv_83N 2r? xU.}?>)'o-Z*2ͧɼ  y<r#G"20Њ#-N&5Ik1N('=Gӊ3N) 6}}ɸs9RO--mk#hgrFX,if~֚j(C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((\ަMVY#O*SMtKJI>]>"x:Vj]DQGW}b($? pďAbWruB˸9_ak:Eױ .hCH_aCJIl\e**>ɟWC]xw5XR0$>9ҝʝEi'gj5)Ƶ'x& (6 ( (_T _1@rkjTWJ7# aʵi(:G5E}ُZ>i.`z}I࿁V b?|9/ﲷ~k_,NJp{%&sEo|E[u^\6rO_f3eMϲ^q='= 6| .t{6K ?ȿRyx_ō7ᆍVTkd?oEr:9c\˯Hwo>7=LOU[KY?>^l?g\qLVH}O>,f]n}WU3J~Tvpk+mO $j>  ꕩ ɴݏ#6B`^S$$`QEKEP?gMWUk~/̞5#KH\3WWٚgPYmā'ɬN6`2?UZn.Z44~<iSQVӻL jbKƏN|9A3#U2/ ?w/H k"LƏN<9A3#Q?eGxϿ Gȟ~?DG3_WwgG4oiT/ ?{>#K>Cl@x.*fψM=A3?6 iyG=e 6@4_W1V%Qen_>UM|=A7؎Eу )#W&NyYQAF)DͽQEŅQ@ԞB 0~N&n獵 B1Szҿx( &VpipөM$QE|EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPWcJ?1wJ,Q.s)@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@}=Hkw.[L "cZ^=׌E,֤7Q/O WS%;+*rB >Wig;o OƻgKJRs$X~_ ;e\ZFr2 >7ÿ ү3qf݂OLzV%Z=%Y˔e~n~cyQ_Q@ ]N/Avv"0/Q5R9^_xҵv;:gY`p_NohvXO3C<ȱY&"TŸ_ݗFϺ RW|L|=!q``|ohGKגWs`R}ykĚvȜ(8]&4YVI pIw00:W5Ա-^wj}C;^R}o6ӿgoiЄ:/ڜuy~BK/7YE%urxW_O'˧fFމ~+Sg:8M|IU2VAr^'{#+/^/4}B0΄+- 5x{Z.mͤ T#ڿ,e5c::ӖO?|>#y SVf(B((ez\76`fXgO 9j/?/-b@u;lf9L0ھ+Lj<'/Q"0،k x>,[6Q O(z2prҿcm<<z|qer<E'I^SH9*m{w;/lƟ1#}CWL<5Mڲ–kݟUaEWz2Y]Cql';r ~x#V5𮝬Z)sfQ8e?Bj~߼2^ {P~d=7xt8<5C+J5;]kggqAS:xӻK{]n\ׄ#xw.Ap2mٶʞ?Һ6uE,I&rZh*}S?kPZ2]^ W-?@nL!Հk,V1:K[W }:W)>rV36IG`W>{BX,<-.787p1Pq Ur},-u2hh ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( kz)OJEّ(kJ/~ Qu-V0cFS$i(Z]IWr)ѥEZTWQEQEQEQEQEWUxƺn}ïDW+_Wʾψc֤|7V=L?%Z+.0g kj/_??2S2Fo_ߧɓ_W|Xf2J*QH((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((?dKx9v?de@EŸ'!{8f*T>1Ru]Vkѥ (?hh/k~X8ę5vO3Pƥ*I~#xΰg&,L@֬'9_䓿<:RX¡gkg7EWgQEQEW?DŽmʾ״Kk7ef+YZ)ҾG/ w(7N=}qڼox|]c" Qz$O}+&P̰ڸpveW&g4Rvz2QKAQE*F  YUʖyc~YU)5dŻQEIAEPEPEPEPEPEPEPE}Y[ {M^mhzrMB|ѷ~vGߏPB3x_j)cMg_zq@Ģ)QEQEQZ'Oj1Xiuޫ})vP4ҹU=?hD|0խ#aڳŧ?ݝѿJ֊N B]ƭ.Y'ՐUsx_GôW_珏 x ѿ+/sx_<||hn9/FtÞ>>{4o7Ev??7|;E}x ѿ珏 `><||hn<_ ^"[M>N\H]/8X(C ( ( ( ( ( ( (Lub5+OI+ZtUvߒZԨ.jQ^m/̋! v PM}Fmck%4T`W_ |65-wYO[xnc(lS?>w $M-!|\`,r|?寝տqf6yoK,;-%m/ɟ2|z'?/7?<oųϦ+hE~IO^uo's\ O KI^{Q\hQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQ^%h?^﮴;X{o]t&L?ɿ {WmGMgCߊo7_ ~߲~#I^) ((((((((?_>?мZGnl€9+/sx_<||hn|;E}x ѿ珏 `><||hnsx__qÞ>>{4o7G9/FtXh?7,ôW_珏 x ѿ+/sx_<||hn9/FtÞ>>{4o7Ev??7|;Ez3xZ~<7+EpfO(2J}+ɩ ((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((֙hڅownHCA|2 #iJʻ8=&vc+83`qpIi4qNO̰5+WV8IWv#zV7Y[ K<~&4kVdb= VUͽO!s_7$cOlgi9UnV_xmÌ饹sxAVQ'y=8|+l~a_2dDey^(ì66]_V(O\(((߀_qKIqOsIϱ5j}ŝKqiqXۣ#&/|DKahHŸbr=3_p87O8֝z:r^TooOvNlU{+<~!x-0wGkvQ|o=wS'|3g>%Ɲ/= e=YGpyy _ԫA 9&/CO. dQ~I9, X*[]~\K[1rc%8]ZꟚoQE~t~QEQEQEQEQEQEQEQE@_L_1߆_}}I_-1~5%YmKA_ɿ_5Mj_M,ƆQE%R@&^-46{BU3$ I +Q>%՝>6K>%O m z\Χ9H?P= Y}ği71[8TgS=T|۾-ß^ E ~M>ci1 }ؓ]}SQEQEQEQEQEQEW~oXJZ;Mo@IJzTQEQEQEQEQEoJҮJqQ'VcҪW?O5]~t l`:;sp3^Yy.;s=}<\1SjEݽ=GWMvp_%] SҽO0:*J*1_ֽsξ*nR] ڃ֧m&^JtBr/5dFFPEe`8im/ϧmf5rd1Uz>u?6߅cg=kKv[$d :P>W_Vtg[OuCB kѫQX!EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPL=Hn+ ' @H_uC~vQE%Q@Q@Q@Q@Q@Q@Q@{-'{ð3^^ (0* ( ( ( ( ( ( ( (?ൟqZ/+Z ;h=jYAERQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEX ]RT|aaPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP^:|b`go +ͫԿf]ҏ"^P_.UK>ܞW<@Cj=kdWß7s_kW<8_؇Q_Q@Q@Q@Q@@/H@կ޺27.Bc#.{fw Yo\~~> 4sLS>؆Tk1 STCPF{ JrrI'M+෗tC)n5H4^{y ?mw!*cS_ %QB.>W JS_ %W\}OƯ| 7G>W J(?_*o?*}OƯ| 7_Qp?@?U/U_*o?*?~~5Ы_j^ XOK{;",lTg(QE!Q@Q@Q@Q@Q@}K ]AtwI#Y0?_-W#>.~rǢsp.,ʕZ?+\>)TrhF{;vdR$ѬtpYNA_E?Xx÷z H`_3H‹OJRnj٭*SR4+N.$q|ZV2 cr=vEy]hx[&}]^L>:8àib+VRogYx,4O-B( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (>f3Cq_5??L=Hn+D-ض?j-ض?j#(((((((((+O+R=5ൺ4KYw^²>}k?@?U/U_*o?*?q?_*o?*}OƯ| 7_Qp?@?U/U_*o?*?~~5Ы_K Sܼ7I:^ꦿ)'OIBQEQ'ǿQo?'} 3Dԥ֮na]jTʘ?1sھjWyU\>$JG>W JS_ %W\}OƯ| 7G>W J(?_*o?*}OƯ| 7_Qp=UYM?t#MN=)c$ya$wfF@WQE!Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@_ŏ*>0z+%G\QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE{+[-f?ּS[<׹xu|˗'??O_?5">5I|*)a+6tV|O+肊(Oւ(()@@ ^cdUK-NWd_EozF [pc4 ?ߎҗwvz.$Egengr#P? +=XZoM n2-ry䵷{>5ٺfeXmHU@=k5ogaѭm- R[0*=NGk~5~w>1im9YgdOJO6?^?2$ L~e ZɥmK%yGTg7MU'@"oQ𧅄Q׬"UI;\]IW珋￴Y^g>}'sS'J)W.Yj7-w{=Wq[Ym 9,4y>Ynu}xWx~#1b%w%#/.0UC?}Š(<(((((((( &;/+&/O/澤 :75KA_ɿ_4(; Y{8匷2 G+%[? t>04&{m!;A'g~oh7ҮRg$wkhxIXs.ow$9˱QEQEQEQEQEQEQEQEQEQEQEQEQEQEzXzu:pXG7< W韴qwu9{fb?+:+fq+G|:ry{LP^>Se˒_T{Ǚ<#>Q_ WߴL_QW5j/Gxv2IQEQEQEh7SxТlf/!OA?Zen{8_oSO'9**f-/^^_Rb*߄};?+g\I$m~WW~ʖ?jc"Y3钫WcB?_9_cbg~*ӴR~sbI'9>,_g|44e*?| _k/Z>ɿ?<╰&P+# (((((((((bɎ4rKkJo &;/*+j_M,kRurokekM&42((+>~صcVb4߄-X蕪BgW>%cKIdB=|KkRY)3=M%AaEPEPEPEPEPEPEPEPEPEPE?eo]#}jr 8Op> sހQ_ ?3 W!7xr~=.?]} $OEiYz"1bsM4릯~sSôf&V2X.>W?[Q?[Q`Q_O;Klem+=^I[~͟M[晤qivnbc *h\Q) (`?__֏/xL׵"{۠VRpp)?wDD~?JOO*r߳<+?)?,?+#~?JOO*wDD⨰\w(<+?)??߳sܢ?wDD~?JOO*r{YJzA/iS0ǛaݡEiJ,?hω?D0/|{2jb>%i|cEE 'UvglDE.?{h zR?EEDKQ@ Rz_wDD⨰\w(<+?)??߳sܢ?wDD~?JOO*rd}.E4vie'i,;Ȉ1EE~|B,|3֡Oxo;uۈs_||`|h#mui}KzAܒM "QNthܫ)V)QEQE}Ag]o⿠j~{ W Ta_[/lL'^cSfaX|s?Ěf6qmq'OED^IQ蟔UMsܢ?wDD~?JOO*r߳<+?)?,?+#~?JOO*wDD⨰\w(<+?)?G7  EE~x#đψ|+6>Oj>t`FZ?/}M?&XiI ;rsGW:tKX8Pz=5HaEPEPEPEPEP_G:O-|_G:O-1нQTI3_踫J[&|+~ITQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEu?9딮bG=rQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW?yݵ~o֯<'@|ڇY`r?浼'z,zfl yw<ݜ?`Wim|hGyI>v_WgC+'So(?3Ë_tZۿ//-|gX[nU6Jw==|WxYSծNE;TuJWy.bM+nf'5Z;sK?v?.?xlU{](¾|sWUoYy+@9cmES?=}_ /+u}G_ϕ}_~bE8i*_WۃO&G*+z/Pӓ(?J ( ( ( ( ( ( ( ( (?/&/O/澤c ?ܿ //]&FYֿcC(ػM7'V?%k.MIbՏZ&{Ey$_Z^^{Db֥S$gSIJzJŠ(((((((((+&) rr>Mb0p4_')~(wďAiJLF F \ovJ-#[±E>vR$$X@QFO (+X^YHb@Y*I&$? Nj<#cp տ>?s?l[ @Ex6O+>Q υ`g*6O+>Wؿ~t= Ou]^G[+ky&s*恟QEAA_IO3= w8%~:mf='5z&YԥQ%Ne{Bg:kʌц "E~n_K'ƚf߄dZOq}eo4 8_{x$Hf6()Gc_~<^?dx:O'6)69%44~oE%Q@u gxKO}GZԥpVQFInW#ֿs62l%ԭ*|vgAa%A= +hg>>,^x<4v'l`(7|=?]t+^]nu@TW~ٿZ9-AN(Ո?[ @Ex6O+>Q υ`Woo%?[ @Exa&X-x<]fna^xJ-ihvu3e$P'ZZ(o O3M7|1%T._.Sa ҁn|'ĺ>}+Ygkk;H8>7}|<=f] ȝ1VqNRhiTQEIAEPL=Hn+ ' @HQ^ymT|i]70}Fby#Wp## Eg0J?Y =ŠmV|-' (( Eg0J?Y =ŠmV|-' ((\T0O#A+|!_??W~>3J? h(~?~"h668K'?9SkcױяM m(CJ3Wpp M~Q_@|$|w06=<ތ7ʲW.5wJˢdu9_!M ,|>{ {zqxsKOvq*_IOtǚg}l($(l~e>{,\mO9g$xhZSvo_} O_Uy2C_ab6`V@3_4W~Wo@r!}HH #j{LֳGG]| (` (((((((((bɎ4rKkJo &;/*+j_M,kRurokekM&42((+>~صcVb4߄-X蕪BgW?@>%ص,U翴O-j_K%2Op4,((P 3Fk_ִ-7Wxu 5#1ɐ ??=?v'i4m>oS++J i.1;Oi5;Ÿ ^\MBWWE6C_ӷ)Е %x{p4X.1;Oi5;Ÿ ^\MBWWEAF)+ OšoShvgS X0-qhO_S_T(C hWU߽r'=xEǮZofM >5;,zIAh]Xqm,Kxc^T (g?g/%:MkhOp$1F@qdI7;A`>m'[Dc#'HRƾ0uH&瀐&cqk5+ռI^ɨ:]J~i$s}8RP撊) (((((((((+G@MVT5;#RmXWR?CYP_+Qu7_/ŲK`<7%hr +`y?%/ٶXkxNa]K#nhrS䘉=j,GmWpj|j8~xP锇LGqo%İJGF*ATu?WGE1 \\fGY,(|u 2\߮2 *fOBa_"Ɗ:_E/.(0hh%=M~U"XU=cXyjwpipÄxI&WK4vж5ƉZAfFvGؚb<xŚ LL7?pZ ({o<=|GkڞkZ2]/<}ى&((C ( ( <' 5_ xSq<,q##՟ط u>6In OD%xQv ?@a"0ea_JJ~ ^^k8`V`sc*_sT-xVo=ajiwP?I"J:$V1?>]|&y=Na1crFP{+g.}b4=S 0 m&ܟjJ (Ϩ?{ W _3Ou,+ a%-)d a%-)좊*J ( ( ( Oym$VB:G P' FO>? -1{Sa9l/xۜ_++F }_u<# _ ]K]eB퇄g*XK UcPG}QTI7?6ʳ?]ǽ|_Y.3F ƇBB9_J(0(((?z_ _'p?z_ _'^($ș\U%_\>$YH(C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((izmƱFeb3$[)();$]5_j=OO{o˿ q\HlxO_m|/qap["wX?=ɮ¿n8ro.&[|y~cp-Xl4/9m?Ge~?q'jqvvז+wfύZolfy}ާͨbc*'}</><]?ϻ (ࢗXy i_a6ux/CiK3S195Z~ ٴ-J= U(J*׻]d\ x&zyzS>T"P@wha Ђ?xN:']G/~#BNi&;eA۞9s־;SeMo /mv 2B;B }\O&0זqg!@xd~>AQНzQXuG2L0զFM+?~|1EWGQEQEQEQEQEQEQEQEQE1~5%|iԕd/]&FYֿ5:75ET~i ?Z+_ G߱woOJ!3+h ZԿJ*'HĿ/%'8zJSTQEQE~Da?%~WwK|K^'cJxn#jf՗vwu}/ǹ;gj?V_~=ؿ?G?_QEcj 3ȴZ.?V_~=ؿ?Z~\xN~En/!-{ݏ 9hXG}ii3v~QWA붫k򊥔(3c)5گ-tXJGI_Ώ$kO躩¿9ൾ9H3 !ubK@4OTW~Oq/Mo;bYC~]QE%Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@~>'K!uդңDI=#ncj#^?߸+gծd?ֆh((((J_ ~ؚ6$)o-:M{w@Ulnu`z4n4DtQEQ'LH1>1 ;0ц7 |'_ 9νG_U,QE!P3?[!&g?{7U"XWgK?[S5~gK?[S5 ETQEQEQEQEoHtMVQ'UꎬX}Q@G|L5/|C-[NP:,jB/eR9%1+j>Xi_w [3d~5_ЧhP?S=u,QE!Q@Q@Q@}]_<}]_<BQEQ'7Dυ}*mș\U%RAERQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEX ]RT|aaPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP^/hbE ,-dPzn¼V#' 픰`U2EPSۙO75=Q_чPQEմŵHeBє_3^;#-nfss_Mh*$y#BBpèSw7xG4{^\_7:Hkon.iiɏxo_*SVէ0fnOp"3k[Ek\ @?Ww`iizoO6uOv-WKϬ⿝1#mT$[?ViSX5ddЉ?eݼ>m?QXfȂJF?g(~4_Ojz&ϧm< }@-˳x-aMKP_=ϗkkЁK9ϙK+/,RP\Wֽx'v=k&|CNi/$#߳D'[B/i-,,r' e|QC2Ĭ2֍cθ31SIgQEaU->bbY-ugjz5#?)}H,n&<Zka%'Ÿ@}SN 3v~QWA붫k򊥔(3a<[_Η#'ÐZj,+O #¯ѐe~Ip?pU^72#((((((((?4_ h9vzuM+{PxA޾_}kxnG>kW0ܛ;.ژ֏4XbC|THH@t~Th\qd ?ygK? ?ygK,?rw|Yjrw|Yj+Gs'|0ş-ƨ'|0ş-ƨ\nwMb|T!Ӿ~˝CNѧ(no6r[hwMbJ( '&Ej f.((C?_J#f_GU0'%t+%҇žWK';}I8(((((~?f?k߿`(׌?S=U F>(׌?S=U,QE!Q@Q@Q@}]_<}]_<BQEQ'7Dυ}*mș\U%RAERQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEX ]RT|aaPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPZ>.|3Xme3Аzck:P]"pHM]=XxÖ h_<FkᶨnC&0G-j]PϡcYP>̠ )&2&J?43=vImdN='Kx%\g5:l-ܑyv#Op[^';h$|s\mENKͧnuO|IYu8Jy#ù5^ֵjws%Id9$A9k<ZV]odt,Nz~^K({U&KH g%b=ϰ+޴_A5$)ȱ}{ދ,ω~S|ϕ(uC:N}g?Q')^ozH)>84?.w||ǖq.YO{՟r4QExh=2omrH#==k:P"pHM]=}O|R:ͼPxFTPFJֽE״%y 8e>_O^ wEv 5>6>ո{18L0x;gWק~!|!a'`㺳vӯ^O_"UZI%d,JWp<_ S:ڮpg U5oC폆R^"]69:?#x¾71}^rݓK׾0P8~2}{+5EWEPEPEPEPEPEPEPEPEPiԕLw_s_RUVԿu\YeZS_֥ /LheQRPW}ɦ$jD85~i ?Z+Th=|KkRY+Ыh ZԿJdi)OSIPXQEQEQEQEQEWMJO?-kIrq~j}S?- ?]_k_Ug+Ze ) GOG!|(?ֿڤK od5_\>׍Ţ*J ( ( ( ( 7)d\o|گ*]3v~QTQE{#'ÐZk>r_mR%~Ip?pU^72l?. FCC~bE%Q@Q@Q@Q@ x"/_xİlG{$l2m\#Ј>t/%Emx-۬Vڃr5Ta_HlK{}ᙢ_̆F~ovP7yA ா/탨ɺ JEL=(`5oQ&ujw!VY\Y$STh((((wMb|;_q? *hG%QTIUrc|]_h\5__TQE!D%_3z#*a?M~Ւo~|kCt+%҇]QRPQEQEQEQEW~x 352~2JƘ=QTAwdu_[~:FG_U,QE!P3?[!&g?{7U"XWgK?[S5~gK?[S5 ETQEQEQEQEQEH/'J*MGw{YQ2ϊ_UBQ2ϊ_UK)QHaEPEPEP_G:O-|_G:O-1нQTI3_踫J[&|+~ITQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEu?9딮bG=rQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW~^&DH;s _R+kK.G"`rkO~W_< s,\$կ&~Q^Qw㮛,5 (Wո?ޏwPRUJ՟f%r~>k ( O=ƙOd2ڟ9v_c10]|D仱~'3><roH~ͺkT6r{yg ?k։"q ZG˟s|֟ Y/"_Jim-Stt.:;[r *6ot6oE袊` ( ( ( ( ( ( ( ( (G _~3x[b9mRdl4ҟP1  ro ȿ gO.V*F0Ͼ$SI-M.N` %航T}n3bÚYd$_tkV|SgOz܏xz9d08AQPNI4ДQEIA_ѧ9v/d_ Ǽ[GM W7S?kH Anf3Cq_5??L=Hn+D-ض?j-ض?j#(((((((?c5_+NU㿱/'J*?e?)?e?)R (Š(((t|.Z¾t|.Zb?z( "g¿qW~p?LW_.*e ) (((((((((((((((((((((((,Q.s)]_ŏ*>0z((((((((((((((((((((((((֙i%kkDʱ=I:ms(+c◆-Gysݷ3Hvcɯ8"- Vܯ_îe;keuۥyk;H;pNH=kI$䜚>]I&y[IuV1c+ʭI7vw~  4dJ-p+((@8kRhPIk Q ~kwH-q")~k~(v1$8SȌzvϩ J2,b~޶ZŹ5M97v~K|w i/ lpŽ"#ğ!{cMKK^!?Sy2Xx9dVV™v" RFSt`86R?k⼏+տh?=4wZ|{4mGsƒ.>}9{v)űJ^_g>G2CA2W4(=(o 3x_שxCŷmM$<1&&Hmĭ39`|E~ÖY-寂?2EsV-|_0Z?_e ,Z+G_h-|_0Z,?h?37GIZVxW ?IW%#1YŐfH0? .їJ4 ÓoZ ʹ MN¸QE!!A$ b>"Upul0~"6J)+޾~~Şv^6e'vn'S3 Ƨ|RAERW?3wÛ=r"ydD3 dɊo g o}2)~˪Blew*0?LGEQ:w}\]@ےXC+ ^$ Mkɴ JEukX &#0(}g/ˢxANm[_QPcp?0k+|iϤM1-n Fʕsn|mgvI ?q!˝P?Hز/TW^˾=oƶub?bZV+G_h-|_0Z,?hگr|u ?EG_h\Uj_e ,r|u ?EsV>97|q(j vP>[>#tU9Y+~E/3o 'A)H"I~Xy~~?>$|,,rũxSeֵT\  g)!sԖl_c&/ii W (g_#O5HѬ伸~(Uَޘ+aYɣ˾ hs?(\1dj?~*|D'5fݨw_L3ܶ젅W1RPQE{#'ÐZk>r_mR%~Ip?pU^72l?. FCC~bE%Q@Q@Q@Q@x?ߵOjg[Ǯ[#f]Y +="f{y7hF Ѓ_72I{ufQlHSީ]?+yន+@{[beOɎq:,O _ɡ'TY#pUCLGE~G |A/CW_Cy>[.^O`G@q_ kFx^p/K+StokSb|E} OXZ?߿7o诡?߿7oC+(C+(?߿7o详o&̱_Z?Jsz?x%Q0/V#:}*>/ Y.i6mmK,XI$kgLM>Np[F#4UTu؅U$_$K?ix>~zavqɥ+~8qq>;F=ޘƿh> xeHrP8ҼJŠ(?{ W _3Ou,+ a%-)d a%-)좊*J ( ( ( ( (?OMGw{xq&};Iҽ(gJb窿_(gJb窥(0(((?z_ _'p?z_ _'^($ș\U%_\>$YH(C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ(((((((((((((((((((((((_SJwķ+YI >J_o#k-јm_Յx}b/jL:m735ۨ`Ǯ dr.nMKG YONi'Z(?B((?dzϏ=e0 Ьz2r';'TES;`HtcZɫ\sԍ?KQZ{m $T#FR2*ZM5tӳ (GPq|+u\2B[a5jExJ >ѨܯLcZ?% fv*?8#{9ɯMQEEP_{.־ GNbB?l(dzE#Q(hdzZ)2=E?:Z+>0|#fnkNѭGY ? W_Apv;l|3i!@Tt)GFz>ឨ^#VGXFFuaG0U_-/4 C “EاԂ+KI=+Ni((({  Y¯ZI$ۢSNvlf=$ 9NS hdYaC$ XM"ђdwojżܴ_ᓩۓwG*ωC>uOۄܛ}a'#^<k?!#pKqcTIMާK(hdzZ)2=ELQFG#QzZ+'_fK3w==B 7{y꩐O?" 3>wZ$HCyTI$:~8j}iMCRMV~OXc<*//ouXJJZk |RV (Š(y>r_m:_Ck,H-~Ip?pU^72k)9¯ѐQEIAEPEPEPEP_g6mT<{?Qp|C*#NVp9Sh#u+McO5 w* *~~_Q؇Fod:MNX˂bۆ^IۓL78n8^aUO1+WYz4oYVխInd?%$V)Z)2=ELQFG#Q(h=ECs{oeq@n~߶?dI{Și:Oۇ@sc{}gxQ[[u-l!oeDP~f>9|KP|T(s.0LUԲQEQEQEQEOKwOKw I(D\>$ ~Ag¿qWK)QHaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPWcJ?1wJ,Q.s)@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@c~ZW>%7r͟P0!KvsMSǦM^Jelm?bVjWlB=k\!GP_=‡n/W+(((GykeeCr-?S2.QZ&fy.ז+5 KW}WYk4< Iim 3_$~<ƚČ7~ם~ ԼIzzZO!bA+q%6o< TOU(vzIznNy+3j aGmoNrOrTQ_׭SRUNퟸacBm$QE`tQ@kxoŚ߃@/K׌u!BA*Y8²hĿ(^)G/PS.J)E ?9/ss Ew~%BOn?|KuQ@/PS.eW"]VvQ@_\LKq+uy\5R((((("H^97Sj:(.5Z/X59 W|KuQLĿ(^)G/PS.J(/ss _/|KtE ?9넢;_?x7?]WJxē"MZvz( 7ڝ9{Y&j(EPEPE$Iu2E m,B %֚W dV{0_鷓YZ9coUe +/ssս|޷̚#Z J7N] mKTdT+({Oa+wx9]Od0̿̃Ŀ(^)\q?!=uX[R28OJƒ6J:a6 (Š(((((( v8{9I u6>!٨Xq[[Rybg8ef ҹ(EPEPEPEPWt}kP_\麄tWVRq)pH֩Q@/PS._?x7?]pS?x8#5mv8 hSRFOR8=+@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEu?9딮bG=rQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEU> kXdEG8Ϋפ|?h鳾kvMvSv7 Za犯 0M;O$SzveicĊ=Wc⯉'ݸ%р0eOC_ |Aa Sy y%nbI%I׾+3YaiBm-n~CZi'm&dptQE~6@Q@Q@Q@Q@Q@5ơu\HvQ)fcש?Yҕ@ P1C`q8l=7/Dyuc {-WǍtH?, vӐk}@R? UozV_[}V~LaV螿v*( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (,uα[X\HEf'W ~  ȥxd\z˝GGÑj;Ԧ@M-{Hh?ξ?*~U^M>In[? Y]Z)'+uodAEW㇕d!Bӧ;Oƥ@Q>?O\ }8mx)r `pAk572KT,z]:V_UJ4>}O3Nmxߥ^vtQE~R~QEQEQEQEQEQEQEQEQEQEQEQEQEQEQE~|F&Ic$7`5OVKWK';}_Ӌ};_OvJqtQEIAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPWcJ?1wJ,Q.s)@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Vy5cSv8/i`?%g!Eu|kBƧG*б;QW??3T>4cSv?X+}K?,*k;9 -mi&u8d]O*/б3z7{KoEG 6Gׄ8D(rJM+軜8FdIIj-GI#]#%s{(zz-W6 KJ4(F_)c1KR+I=ʂmy zz:*QUL$(?;ob}^7鮿|'Hnֵ}+¾G8 XFrֶ*ٙm?}7Oέ#!@~i_(O_#QV_ W캵 4e޵:c =O`'kNaom֫:ފ={\0yuNyGw#xP^q-uy;ngnހv8(dT:_vC%ļ4mo/%+m=:7j(ռ?2L>O }2in eѴ b8P+bfi͹9=OOi~\ҷt^5RNˑ*6={}z;Ejԯ7RݽϲFF*1[%AEVFEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPןG _¾"c:xkvfmPy[Sny/\W{#F,qg rAbŒ-~gxg1uN}=HϷvqZ() (((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((~ e_<2^h, zoNQ؏' BFa\e'!~"=[kYx%SxثE}OĸKm|}Lo {:ZWOi~?Hk/'|+`ךS> >ÚM~&"OOIXWڍީp\w;uy M}=n7'ho7|nÊvOZo} =fXv\>(5k|D'y/#.exxᰱWv+(((( ?xS{-t#B:xqg,ֶޕkvs XpXO~1_=҃^ 5eKOxyKՙH9>-x5e-Ԑ Y`xC`1< o/CKnk8cQ|Mif o`;ꆿTV/7I=Z^g|B̫qpRZ_Tk}!.2RZ.eor|9>"fV s}UWWuW6IoqX)#}1|\oqGc?=qָ%CyWz]/׳=C;VUS3mG[/>xZ;]K@[ۡMp;skkؼGE>.ivחLG!=|*a{Ӻ}Oxޖ.\ͨTKh^sD쎥N _QHaEPEPEPEPEPEPEPEPEPEPEPEPEPE j2tG/ 15!*P{$gR(ԨҊշFEd:_j\#O okq H⾵|c̊}.C&.|n;ɩTjRO>7_ xcQoVsw1ǰ{e-oM?ui, DRB ݘs^VE[qe>Gae-tQpw}{_)˷ݺR? ?۝Vuj#?_Ej>0|1.˽nu?fOGzG--0 KvuŮxA_n# o[q7/uyp$@::Wg9 _^Obi}wEyz/'kkKS{s+>+JS?}#N*VKD@* ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ(((((((((((((((((((((((((((((((Kh?~XD[sM-8:t~Kl&Z[7"E ~GX1>C'H~ |fSQ袊l=S?ǽ[cyTгl=c's_\xSO".ۆc_uՓPQyUqb%ju=_~s\ͯ z~ks/?I-/]~ЫϤCJ^|Ijoirܫ)Gs$Bu=Ğ3{uk񟁴o-q&9DFC5Gi}s/S}~> *LÆkgn +_UfϤp؜QkekU"2Yɂ=ÿXZᅫ(v8% Xhog*G uKo~sj.O676;6JE O Z7֯=|YlI)_k3Z '{f-_.ekן}CƸlڪׇ_K>}r{ǿ,> xr` Rj+<~X:s\\l?3 Gh򗝴KpQEpWc1𨽂ܿnX~;$ #ɍyyJ5Qvi+IJ|KhJI5ߙα^ơ\=YKTe'6'v0U (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠(((((((((((((((((((((((((*DUfHzR@inGE)0((((e}c?o ao$/.~:cⶑVaw3YH=YTW u/-4vV;[c_|M 2: b),VYW .9೚uI^E5=H}UB\$Gp8 (%,NJ2#}?xZWë{nqu71wpc 3;Uw&4#tOټn)}~Pn6~]# ҽI(i?z-xeMG+BEN.2WLNTfG&Kj|yX{?/1>+{M,jWw\!3g,C`kȵ .Ih/'zpW4G{-_Տe'Wv~OuaS[ձwBǩtU+x8HQTP¤0,>}OvcjتMz-9o2׍UҠV\ơ&Ss=䟌?j *;^,DD|z~ߡ񯅵A"}p@5Y3)B)U[5O>x#ʋvqzw]OZ*kY,JM OPifQ& (Š((#C*H E~S]oAӵ˻heM}?k? 6--c'kkPh?'^#a1+ɯ _m>acXP.#ESCxy{u}> ufb'գrZ*߯}~au/tQEFĘԛ(WG~' ׵WCJXey7kw?MϞ >W- 7~S^wc*mePBGv<*O^c1).7Bߠ#,F7 ib*(k3c c1RҔߕ7kX+ރmoQ\HňN>-4{8$|0].?N><_XSeQJ׋0X`ک?|8>`*.g起>ѼK7KԭumWS5seo}K"DW]S,IrDX}@mUP喠vGk EMW/Mπ'mў#oxl&o㱔[+^Y?V&[_Et7VLJ?k؛jk!)7Wxs㗂Mm-oc{Ϧ[gqoq'3]|jRﯿV|?.hWi$cǮrj),Nk)c^7YC)"u{VFT009p^~#bմ%=l'ӊao?>x/Nt ~袿C``(#d7~Qf8,N&W$|MQKҞ+ I獻z+zzXnhEυV֍| y"g9Ɯ\얬~&Lon{ &nǁ_Txc_|#ڬ`2 %o6OnxC֋e"EA$օ~d8lo*d˲?3&g7]"ˡB@58 7u$c˚uЊ߳Z}df~oa}1ǰ^Ezܷ U/]UW Q.l? :&m]OF9-BGlZ~xtO&؉&>ž6v,PG: 1{z7$DQOX9S_C2 u9~3k칥m,ɿ1<>tQfG`{Vλ=/Mw[za_42VKBxw I1 W^#kZTz'T/O 8傗#բ?^ko#47̄!j*l=ܦvJW&>}ҳIzv}O8,3<X&~z:4NU ko?MxKw.h8SOwk+prLzl)Hb]T ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((+H#pz |)w(sufX`?ޯ,L0ݹӊk﵏8S9SWrR~i׹Q_AE[S[ӮɣK\;glq\+|Axy-\ǧخ~?x^F圐YgU+ƼH?r,=7FjQZ`+ ( ( C>]kL-kv$\8|^%kd֣a/,е}O .Һ֟7rJoKj>/QUZ^yd+_'W4 .^EʟO-b.1Q}]~8I5vaEW}0b= VB=+o Ǟ4tt K&K˟`{_|A 60BUG _f_*5t^w_9 ~;Px&o&XlVs/-_ o=5rQ7o)I'-"_?qH.7|/dIZr|QEօQ@Q@:u HIծ9X&eS^z>&񅞁Oou 8KI7+_6UUе[MFO*U'e9e#/ Fo5u}&4ԄǝiY];h9|F̀6>އ(֧LPP*(4Š*xsJuBH==;֒a);$eJ #zz3:w_EUO>(xo14v[Bǘ_>}ɮNsdq\D>k|;+j|I]_++ѧş~@`?\WWtMZm Xԭ''CUX|E:h?q+ Vwd3EdWĶ~/a 0TSAVMBq8;?jSuQEfgķ/ ̪[1ӚZ5=.Hkk[K:ݣ+ws-4FRݶl)"h:o-Mcow|VsG9ZimpOpŻikE}y_t!P9g/ o5U̱mϏt8l3_xo2]| ,э^I><{ d9T)if}i) (+gϊ^!&ۡBLj${y%ۃ׎"[ўvaa+KךԴ]oM.lc1rH+:k7Ie&e#ddC"i <1͝J?dsЏeX5[om/“XXs-$B{ 2\87OF~ ck~m<3Rv}kVu>Is8V_4?#%$tŠ(PQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEX ]RT|aaPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPSYOum+q #3F PM6КRV{N8$<6hۦwA?{ _ O骸%Toɰk+p_S6ͱb:\ߢI?ſ9n鵻mٞr1~_!xDz"Skk(\sn5_嶟~/*XgE9s}Wk|~kz]:KN9ˑs_5M7P6+~ CO ez[Xї L߼)}U++([gfdz(Wx%ES}Wd|L6{u]JO{g6Y$<F^9)A="ݚڈ}x{ď!q+q۴/Ԕ:M~p/,D]=WOK#qjR~ί}}7<(>(+b|U嶬g>? *Ι}&Dq-*u ]XZ^eZsōìV:ƞ{c'ϣ)Ukq,2.#r=855eu1Mȧ_|^?&x.cʫх~TP.]~LgÚ8YuIͧnV,10+_AX$ߧٴ~v[<_fsS~/O̞ew~gER⒀ ( ( (6|+_Z5w `]x#^DSZD35C~u`s|n\fY]5,]%).~'Ӛf|=4u8UWx⧈>#ܫ@[#nϮ;rIBf}zedvapWᨥ.{|(`r#Hꊥ$b=V|B|9_~  b#>=>$ch^\^_b/,7~x' u}.qf_ug엣_@f<.?}}I5c͑ll_c}5+UC=L͋>Σ?UҶ&*2̬%g<8u`gx'%P_xϜ*>aFʺ`pA 趓׶Ky+? 5Do}R1(?/iG3ZeCUhgo.|9 [jr#cş[h5հbǩ+~8y'nUmV_ę^geB}{qRb{VԬ&h/-G"#wg/|G𭶫o'WsnLRwW|7OGpϥ]b+G9\ysGzWw=G~^n@'}OUY Vvn`P/A$Cھ^&լX;K+ 2H?PA:+>'2FD&Kf#?>ݽF= {Y*rïv_>oV?8GTC̨(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( k WuԶwqVҩU8(4DJN1at]J(Qǰ!XY"xo,LB9ů|7NlY%hOs_<]V< G|FX'e/{OÐ~xwS[5|/tՏ˸3jK7W/'E{O٧X}5,eju|{+J #/0}Oڲ RI/y5(>/n|-m̶xɌ5YLW-re%ש~~^XeX莡뺰l #=U4޻2 _f7 ۸_~g)|_Jɞڗ*ͥ۬?EϳW&s bMO2U5'W;m/ =p`1'$kC.-⦽{o]ǹ0+Mw v^99RleHѓ \I'k?iOy7X?ဿ^=\ ʿrԚ쯻_V_ xgQoivsQQݘ|2oÖM unD};[9HtoMI*r¨[+^_iÜ=B8TT-R{%N|8UqSNͭ^m7a9>JhbŌlqq5#yt;>IWjgLz:+kLL=ZIEvLvB_3Ǿ~믦j s'fS:a kWʌ\zWfd k) TVi~zw (d((((i~Qs^ŻMҘ$>}k4McU48޾| [ '$ YC} _^zܧWsթ?U=_e[O#^[ZFdoV={=| Wn𣢨}+j_?ں^6L$*xyAϵx mř1?U{˯ݷs6I k/U=>ࢊ+ӂ((( Nh7Ks\X\[Q1^?ڛ:&ȵX-NO9>DC}GCҼ-]5%h~mxyy`*>}oş#-ըϩ+Ygu|9c@ :-v b?m?׳OgGtPW?^f^NJonto~?v|?>Wm#~WnHUJO abş|>4ۦy}viEx UnK֟=Jn(}U|ijZ}sY2,YCH~W=]ս_ex:\ 0ؙsaKT`ț^M`5$^Sx{KhlЯvImDveTW19uOkWSrmKऺ>>F1/د_ xXN5s$Z/xʰz.pRqo)c޿T~wO깄dߓaU7(_[IM#/^yZ;EyZ>jmf'yX"Gfc:5-o e*G߂~76ؒf޶/Zgy AAG*6>WeͯKin\˙۶uQ_Q@Q@Q@WM7?<]cRV<} ڕ)שT'e0^UvSm#e_\Sx/A`tA^c9eަJMMr>º}/MtkH Kh(^`WƟďO|e$4[6~y?0=)Pz/9=G^1euZ4kYs$9f'$ƢMJ(C ( ( ( ( ( (: >>(91+3C?|~|u8t^x< wA>)hW36%9=(A+LҦM&F|dLvWE:2i[u=h?Š( a;İDOqݏ־E6w`_><ľ4HbHv͸PXz13׼9/5[f Q_Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@_ŏ*>0z+%G\QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEz"𖩧OM*yc?vWNGz:9Kfy㛎++R;x"F# \^Z5f-DG>!`hkѦvmE1(QEQǦM1>g,vգmYGgL5iۙφO/ck.TMGVE=Up WV?F]dLDy&Rkǿ:Lj1균|$$3c_dWWlDF #pa)RW%>[GfV,Xfy߷p8 W=`y꾪C|)wz57Z&}}cgG;-oot=+xd3i=\VIWK(o:|Q8j|ƱW6Z90] eG\~xg4Z.%uSٔzOP]K4Y>NJ|Կyp͒b ߄;?/( ((+_ٷ![uMUD08zg @F=N??,"9ӿ4.X-mi$ 2O+VZ+~1ǹ˴r;,~{->$Ӭ۫jM7sھ4gn>!xWrD\CsMrYfLdE>{u2rҒ䵗o{QE|EPEPEPEPEPEPEv^M?s/eK_Kd҄yao|qvL*KWz<|=o9۟]UaoÏzw |>az'ϹP,乼!g=WCt6¶P/ۮXTp>5,(*ؙsO۱s-a"OZ/z]}:=DeVuVc{OύkǞ }]MnQ!_(U_cǞ͏|Z7`p{m1J3TUޏoC*+}9 DG?A%z "c$rϽzX)Ú59ddy^ α9'KwVo=#oinl6=T0'H$K3$$+4bz-쿭GKV˲ (SB(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( k|9ofƭ&؃f}=סZڍ֑} ĖPx扊PE{>iS)ĪWOF|0o7i-b?}O+~b;xU`H/۬@ >p~_:%^`G9_8KNFd0ßr7;_7.UnqSmȊ>fSSpN/6~ў:%Ku'Z׉V]ҹ2z`8³99ݳ28AY- ((RQ@C<[3KmA-wQbu.%k{쟜{?-ߧ8͡yׅ{8eZHr}qW蟇CxJRed`+>3ζ& GrY,w'Ck;ԋe<9s%Ms*Νy-[D+?f-s6_k3aY8 nmy0~+Sa[zUUbD#ŪK{y.QF%8 U]!%mF̒\%P*g KVݤ5Z;{WE~ƒQھG&d|MN?T'voCA?> ʩ┎F}`{ז?>ileij5S:~6e1[GLAN| ~%֯uK<ۻL7=@=}?xem#ύs8j73袊x((((((O_Uf:E-.BV={+诇yˁw/ 4f"1>'frwz%+\FHd_< ׼w{mO66xNÿwFy)ZoEWp=ٴ.D+H(P" /g85EmyHۓ2~^!2JkMJK(}/"]7lQU-+[XS"(_i~m_0z((((((((((((((((((((((((((((((((((((((((qCYiOʹYHw0W~$4yM= aoB==b_ S=G^;C_]q lJOUg |+S+-G/y}ߩ>K-zLmRTsXtb;gj誶$iֲ\q8Y)ӧ KWiV mGE/ +cӼo\^mÂ>m#_x·~ 5{ֲmX{Aкoc״U .#Aҿ> p\Kޅ7SsJ1/*&$N#(?(+Q~ۅ&[^%>EoBcF_3j igWz Ӷ"Өo-!%d1) y`qڄ'ѥo--̲کԨS;ީզiWvv^]Hpٿ^S eXCalcՏ_3I?:,mf蟏>ƾxG^mB#˷8jN%$$6aeivQ?ʸy}g8N_SѾ!|w7̖qbb۫/j(18ʎ"nR?`` J ]+ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((()A/PGsm֥|.|OKF`vıY'}J0Guʱ0yQ^|`˪sNV;g*ھ/iW::Yۡy$sԞw5goOjcIl1.1¯ܚ.0B ֞IzH;rژ{ZsosTQE~(EQ@{ox\楚'AUbċQs PCg#E,AC}3kȫBeBM3Ĵ*brE*_-_袊?B(~'k }7JѮŊM4ko޾V[iyerYؖcܒzץ^0.KM=n!\#c3 pM4T] WFQ(Š((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (%+y^ `$lUBkkeGۅm8?*Ίp+,>vtW4ɰY5O g^߀/`#%fчV|IMx/Ds6p:EkUϻ0LtWό C0}V|U?rT%JNǭNUagFۢਢ+WV~PT0Q/ ((>5IZpoȽk++mJ.-."VX\:#_5nVţ Y3. FKg{??+͸ *{&j׏UovHӭ+he*rxOJ;+SAmM8Hq;gX7dt3Ϗ:30bXh{4wm`x *{VJ֏Wwi(>?T ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\u?9딠((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((bG=rX ]R ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (:T|aaWWcJ?1wJ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((,Q.s)]_ŏ*>0z(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((%G\{ٟE\ͪHq2OJ~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr<_e߉-wo _=m(ʨU]BO[?~&еr?jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-black.pdf000066400000000000000000000206011402514743400224720ustar00rootroot00000000000000%PDF-1.5 % 4 0 obj << /Type /Page /Parent 2 0 R /Contents 10 0 R /MediaBox [-0.0000 -0.0000 479.9999 479.9999] /TrimBox [0.0000 0.0000 479.9999 479.9999] /CropBox [-0.0000 -0.0000 479.9999 479.9999] /Resources << /ProcSet [/PDF] /ExtGState << /GS12 12 0 R >> /Properties << /Pr11 11 0 R >> >> >> endobj 10 0 obj << /Filter [/FlateDecode ] /Length 6443 >> stream xڕˎ%GW5fp_#D_b}kGnKxUFd\^#|o?>?\>-VӃ^Z㿁={Mq|>3x;hR=v1X^\BkT*mYK}}Zӏ^k L^3윗j+<5gUSm?(?{Gb:&ݯRuy%M[U0{ ,i1z)eLP8w0K7R;.[bSڶNXL Ytj-gf̖v'f>t)%ODFtdKw_9iwAB6kL r-,=gk`vC AVkBHߥ#l@2fNlͶBgŝi^fʕD&H2rep"1b!`$Lд$X{PZ/zU"⡽@y,ɳ/M.ZJܝ]1,l_ùkm_!H M2J=A-X"Q$kf{h2Oň1=ːq> cmDҍR7h1GnZ20sSr٩B%P1.GQ,1M} 3$gP7Yd.a&Wص \F;w'+V}Zb\t mDe?-/҆V] P|y M:qΞ7h&˟8 I eP~S#Ɏ{QX pE mKFmn'EXjW kmp n5A"~]G%.)AQ"=bSo IҹH{enHJʌd&(OpBGa] ۙVx` :o\-nD^>J e`R;TC!c;:"qmFQ׸!_DK:՘Dlpq0h&9\M\`Nt@uR lP lZMv'rcÞpP+^q[]en31PNCjQ1m{陸P[63-Nnul..% ki,# d䍨vsMfz Zw2dcsF&Ox,{+Xy#D=lTQS'c["rO\Y&Er,A~AJχ2=10˚/I*-ڮ0bk,)mԺQc:RЍɯØ|q=!`e#:_vת/IJ? l[r fGaB{ɱ0 do" v~E[hK巰U4俭*u pc2?V.M5Lj_)pɫU/LwĮ8UbA$E[ msKPH,dMAr]- Y|1P$v6)b`U,5ӘIB>K}(y; mh~|( w991{)Ō؍'NMv۠(Wzn1Eol°DaT<:xyZ*+d2+"YQi/"Z Ll2RK E9 ;1B"s)pz;#1R%o'>'kT2ͬ vho0J)z$o< Y!mǍh}K n2GV,BSq!ClQ{#,8H )S9prpkW'o锱Mr-7R b`Q,|yE=2.h_A9~joAopa78_|yUzɤ(J "<Q$V/ nZvXe3#p9L xƩA۹PY|vn[ܾ ٟ;H7rF Qxxrc lNapaiʶ|N)+?%$W!5=M777xI{KxJru~͵0_A/Xrۻ@gFH˾翸뽌 a>x\=6{7r-grr"jLx]fEn4=~!7UiS!ҥ`׻S+-ɾ\Sn! 򇩧?ww||NJzw= >[ډT~ ^uluzcU)柁ES~<|۩ xtqM߫}H>A>e^(/F_wiZ&ˏ_'Eۊ|eq3̍0 Xo]#wGG=f (iwzT bt k_k;e\r$uq\rekU9m~Ajik:hO@)]W55~"RVJF)RΞ܂0)H.\AnTP-S!7 z8~F~6D彌 _issM YBiG^g>ĊU +RUn LdӢDWPTZEQ@U3!QAB"H>qr9tp)2c>Z<0m !ED M2]etʷ6P'n\ ,2R[2!gK-ɹ- YKwGjY%M;dL5xpMZ=sqiCҌL_2?3&)zog`.[2 - AJūBڑ*yh*0JU Ƀ992\4-8kgJ!<9<2:2w"cJAtz?y 4ezqDԡ\iY!MlFI}ʍ3\Jqhv,N"4 MF9?$9v]=CZc ws),vWq0ݰɉ}ۜn xjQ.ġR.;%[=>ԫ5[K3=) *VLFC = =ؠzuPu+BWjѾwA{ӃM.nv=3wq)T<=NWw֑`xg&|>Nk *M2vmFkMu5bT+4s79Ff }zDd:+dT9{tkVЬP~kfARCzYm6j1"tfջS!)uDL7L|0Xvy,Ӛ٬*EU'@4'ǟfZKMaK Fzr}ql˺wRsQO Sq h"/mҩِ69s;L'Y1Rߩ[maE%ݙ-N) %vk ha|q RZB|"Mg`fVL;#历4xI:RX %CC)3ar *޹iK.sPZb|{mS"M`&L]ddGɚAfQKޯS1b-: @2< x0gO%b1|9C8 3""s)lTȅ{#Сŕa٥ D$q=3rT2h-LSMdUj',EӦτ Զi\6} >g tw :cVݜ)XTӝjm\:YT:ҙmv b."!c/)9w'IVz0Ʃ΋0|<)ԤQ+*~m8rZC`s 77/P)/^#@RqblT4t,|.h*Vc}! >8[MҶ;![n:ONCsm Yh_H߻5ň V%*]4RV%p!hr_1 c8t+4 N;"OSM:ؔ|z.2 [b 0C _PE ".PiusjX]7rV#;n)>p/7zOUJ ئN7'c崚ZzEu>ks<]?) -mq'rSq}u~T>'s [nj;\跱( P>&9> ,^V?h _s1'ػ `6mϏKV=3hQF'#.?gendstream endobj 11 0 obj << /Type/OCG/Name >> endobj 2 0 obj << /Type /Pages /Kids [4 0 R] /Count 1 >> endobj 6 0 obj << /Kids [7 0 R] >> endobj 7 0 obj << /Limits [ ] /Names [ 5 0 R] >> endobj 5 0 obj << /D [4 0 R /XYZ -4 484 0] >> endobj 1 0 obj << /Type /Catalog /Pages 2 0 R /Names << /Dests 6 0 R>> /Outlines 8 0 R /OCProperties<< /D<< /Order[11 0 R ]/ON[11 0 R ]/OFF[]/RBGroups[]>>/OCGs[11 0 R ]>> >> endobj 8 0 obj << /Count 1 /First 9 0 R /Last 9 0 R >> endobj 9 0 obj << /Title /Dest /Parent 8 0 R >> endobj 3 0 obj << /CreationDate /ModDate /Producer /Author /Creator /Title >> endobj 12 0 obj << /Type /ExtGState /op false/OP false/OPM 0 >> endobj xref 0 13 0000000000 65535 f 0000007227 00000 n 0000006927 00000 n 0000007589 00000 n 0000000017 00000 n 0000007176 00000 n 0000006991 00000 n 0000007031 00000 n 0000007410 00000 n 0000007472 00000 n 0000000324 00000 n 0000006851 00000 n 0000008083 00000 n trailer << /Size 13 /Root 1 0 R /Info 3 0 R /ID [] >> startxref 8154 %%EOFjgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-black.png000066400000000000000000004152671402514743400225250ustar00rootroot00000000000000PNG  IHDR pHYs.#.#x?v IDATxT' ņm{]P" DVem-uUTTP˂2;eNL&r w&;gi2d𠁃 $դ= hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ PĆ&s͋?#G먻Dis]n*v]Qt\ *z^7Y(vT^\  ݇DAi_(rź; A,Ҍ iA͞mĈ--urV;տmyCozqƫ򞗳>l#~=uu|A&{*|-v6! z kaǝ޸fm^}-\*0'?ܢgmKMHu@BwV_?:~EW_ӺCm@&- Xm1*j;b\Ɲ!=dw{̴|X[(z,tٯWINMEsjpvj,Lֵ'e{fA)Q)6+V,>}fzPsfWWzzB"wwcדoݨ)Rnr8uBS(qsB_.Ԅ)\e2qBjW]KIQyEn =uREyz\%@puS|-JZ~lfU * =Rn1dw#杽*ѭA /.b_ )WƜs=|w 7H΋@1u_Fwlc|w9*U~A}{E.<-ץ]--V_]/ZklӦM(?#ɍ( /jzu)/acvx,>s44.ve Vjެ^(R>Eyt)t3v LHqܿ:ݻa{dG}rOmߡCzM:dH߂ _߾eγhSc cbtI?^:{!>fWn;AZ=9р(EsDTUE i:i+N풦'.t;~b%-\z`PLz>N/VzVv7!U| ׮-\(?^EVut[~+"km,Z-vBwYZ*ZS)WBBfW:M)rB#ju(B/ QgHmo؀/OB/NS꽍i|uUHXIbI?a"tز\Y.޸WHo@f?2qB\XV>Pr)vѿw\Ymdun%k㱑b>wz¡|/xZg}#(tyél D`QʴŞ1Aq6tƼiOA(WEl~pv]6RܕoEx\g2O쵵˗fgpˊBm}0㍙bXfnmOb]stx^߆tQo4>}+ku9y&t޼oNWL9>6%9 ų䷜/7дYsb|V eou<{2=']/ɗaCT $3v?Ě@zQ hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ 4 q " P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB 6ᣆ'@)8jb|K 7}rK,rF[fb|VJd_ŋG;{ȶBoPE8l.gN]$7_iY@QW-TwyݝZ 4oB!_%ѳ8Vӆ½41p_͏/6s{P)ntsV.FlYCBYQ@9-b ;yKD2t@,\EfqykD'<1}j=hY<+ְݎƢon\MML|E'ls^_sF- =uCX0WbWp? m+?y:-w=1>P6- ݕZiEֳM{>7ԕ=_-·D%h[rf~$xr\'ePMnnMwG9ѫ| եeg.&OϗYEh޸2)G=r/Ԇ[.~?Hz}iW虅(n.m$ס[+i_9 b<_}5M|n޴+B'܍vb5^=;XpKylȫ"kIͧuABw;VPg. ggO/"+/mKj7|=z/BSNf[;\]fmX8qxvyY}Aq#ABO+4[{/niRѝ}L-ruQ+x YƧom ,_lS+q~9My ^{gԤDg*8qt[=`\9%絪' *%y~מ,wG*% = (1_\ބ-l;DKv.iZc'9"KYN.(zKss"Dκ-~Ѓ_AbYb/ݗQ7D s:9V7 ˖4\8b|4;ԓK\{51}pl*| ]LL~nq9A6fT7J^4ir}%_[װHr~EwMU'j;2~Q+Ee˜T}a/?Md뎕g(|QأVKRfUmޭ[o|^u9SRݸ7\{ABO=񣞏~+3)[^;X=ZW+@U;5e~qb|<;u } owWU?7+g~n#(gOm Aڏ=]fΘQtB-oQPB UUw{!^y5vkeeVQWl{~bLI)vE RPWE^l>z<E^n_|O*tYmA~/dNIK|w 7/}?wn,=SO>Yr?ݓN>Y[s]v,T-W xw_Be˖JӦr%~̘֡~ǝwQWǴOs'4WLY [? }4bU+27ϛ끧?uSZYrӦ0x4V=-@QuQZ^𤍏)/uQO9}̹aAN?r5rB,/6_%o&s7)՝j:ꨛBls FFFҷzFRn WIC>vPׅn>8HbO=~{ź{o]s=]@@u]ԫ@gBCZkgOy_RK9n4mHe`n_}=G tr1M+}N!}/9̏Jr;sFDOB/ZXIbwRչxCvک{ñq0:u_݃MrPr;(OQ#E/tО" J?آ/V'ŽzW5cHV _K+n>**7xj9_?/SzS>qiӊUtt_}Uyג/t5hpL( 0ۯʩ]Лt}zr~ϠF_-J_4m.膍{NVDt^=JSׅXXWa3{s}>Q|$ Cz,{ȽC^TFQ-\)g 7!&7pFb*tsH'r /Vjnc6m]yb_xoM.})U׼}Vp^ͦݎ5Y}u[?[_~hm]+5/ԏ-0_]V^ՊVR(Gzb ?9u =~޻ܤab|PoXKvLZw>ҾЍ/5\'t0pUOz ( |IZe˷{/KBNj#>^1~X/Lw_}Pn>qڛ=襆suS\ R4i&5!lAO=-|Z|_l;yۨ~jP =QA8o&Ʒ ;:E:v9]g/RbG9*>-eeoE˺Tgf,bWҹKE2)U虋ЮU5ʚ%ۼoR{|Pt|RA(U\A ľzNI9νxa fr)uZROs];Q9nK)sĘ;5x =!uQx1}cqs7\Gi^7ļ1>|-dq۝/]VϞ0ĜhI~)tW/ιf|3Q v-Ɯ4T}}_N1":Ъ?37&-t0۱l(2E$<ųr.Rq]vۆ;m~_Ds;#/m =!.T}ے}BB wvb_nr8[ZNc<}]aBqtdyPS,["Vm(nIwi'=u P" LYsyƜYG(˗^/a" =BӺC QC^c^{aCTJ*{G;RvC(*dT hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P(t@: hB4@ P *u摴]NPotDX')5|cg,E5(+w6# ]E<9v@:BcEc 3*w.M닱HF#0Ǵa>kiRxJ|}_ּ6c.uǼīžrzξJt$>RBҜvWxYX0383~؇4m="TbWLYi̾;/Uꑈ;bO߰4+>a$`Wb ܫ;{zoS|]CQ4_%kΎ}iÍ^AA/tMͨճMr|o?5}yZ쉾(`-rw0.r Bv{spqP$`J?B6:]A yXuB~ǽ{Κʿ9OJb_lgS|$j=MoʼTi>b{kEQn;׬ZxaړŘ?=kL7Cǖ%w! 7;m:}b|RX$t6tNFyCg$ړI;,mn{8N{M¾kpu3 O3nyw-A5Mnyk4/gǒ)x6$5"gl2<.>Jl}2 )\Dc^3PYz o9[}MՓBLjs҈ƫMUGoo@PNBzֆU};EQq3d|H}Ȋkw&4 IļO!k1nq:\tJ&-.rWuǨs@q:tRuD@Mz 0(#쫮T:) :;1R{'4?bWLYüN$/Pᖁ֪VUpぁtNceA ^G.o*-."]>JbHb}%@j!#^M̻I Gļmz0Jv]q9O3QLw-y5@c6{hna/ftP7ޜ&kkI;0GG _sseyO_q&{#{#t=s7sYGOG'ƔbNPvoy-aMAG|lc#*y;=$dg b,(~NreJvOEej$ >!#ϺkJUQ/JƘ׊ B zP? 9CkùGc>ZVjwٯnW7W謅q@cΚ=|_:}GZyBhsh@C1o+|+&9yx%UlgYWR.2=B+Ô`XN;a:#Уe7BTiʲ:詒gߵ1'[F;LxsVkG({֛?t/z-zlt5O\T-StG^|n1i$WͥȐWխ8LPF_и_5ZboP  guIˡ OZ|Q:Co{6mbc۾g{ѢkY>bUW\ ";iL1'_YݼM^ߤŪ>J}ٷl3YQ`wW> CjW_Cn93sLU\˽L* IkzI^ܬUKS/O.~\9roִر3QJCMzD#;]E^M߬t;9|bi(ج~*+@csY^9 ygJ mV~݈zUZ_jYjɴzʏ?Pn%!k@bUg5z\(]ΏMɧ"qg)³Wg{s;STd^|#k9Sr_dܗ4TZGxZ׮ޣejw!K5?+9jݢ0kzqθڈ/Z<@}!kyUb[NPqj@E@XD66m}%֘ΑY}f}߼l71}ɼu|H@zj y}]wg\z czs6V쾏%_m8/ab|N{W8@03x]*ৗ㬲W)8sWϸ1:4E}@ 3_S$(eWKm?ُu4m./U>;D%Α$_`R*/zs>bzIdkV˖$;+~p7=Yb_~YGkqN]/?V@לy߳n>3 u0mU/H>sPt$w{*(u:g|Wi+Oo?.dr8N7'oE(@G{|#mznZ{Q˷Rl!xv1' _P ^̛r&mc?t8#αj7/[Og /oB .8KǴa|fIdKcb|vS{v3/z I;1Jb@/g<(NlK?3Kbo[r?Ry,νMdv3\vs:X ͡źb;.wƇ e?}ٰפO~zg y@!z-}.8Janl#y"k_t-%̋᪃ľ4{3|ܩ5b2(f^r=D0kaعCc{+#5̱cgĖ/-C,MΆ[sPw1oN5}r|GF%+#kKۋ ^WźKIm@:`xj熏y_6\FQ̖!lkQy&ĸ킒KѸ3,2vc0I3Y?#mp'ַړu#aj4o;DN nPۏ]=gǃrfu?m\_nx˗lnYGr]t{naY} +D܇4@f(2gMF'd]W<|Hd#yLk/;rs +tVk, kG5Տ0Lϧs+I@td}s%M^ŰcƢ|Mrbj@#5EW` BE<xhp=CPa^ʵ9OyG{@:BSNm%R(~$f>,=}kacX8'}i*E#{ǵrB8y~zy%g{Ӵأk}Q#qaOktS/LWtF#)Njs7~[$VC{VZ{j0.0T4~ۥ@GEG$"jm~ 1haz7yחؕ3D2'*5zaӻk1>]EzB1x@-Yv}Z8zDO؁xrlmOZ^9ɣ7U͋LMc_Yg̫ Dfok> F(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: P@ڝa (@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: Pt@(@@: PM(ku}"m:zύ @G{!_NGSEbyCW1m7:"/Yayw9ho1~KqCcz9PtDY/kLKA" 7kRk+b|I@="\G/Շj r^tws1%|j׸ُ@ :sd7a!j8/˚μl1>O+8: }Md ҆423_M$pV_O}-btMMg}- }k^ @Gi{x0sb|IE߉5l‚oij>ɱb>s]$uH+^ϴ\/w5@tf,ɣ҆+ޗԵbSC9<_^[Go>y1󔘷 Q9xZ3źg&m}fW/G/ e^ 3Db|3܈//Ͻ]d~Nsv9T@19EywrK>SYsC4ܸ;Xn>bX¹/3G:z#54m.7]QV<' b]{R]9 BFkswYp[,mVe_}ݒj>WQG7v @Fy&s_&˷5֤ͽ9o]ۛ,ouNtM~{Z Ԡ5g=jVm>lq[գu?XߌVnѰ~`G1~o@n.v6j||$V$3/og@G}'gzұ _~Cv'їFn"G?R}nxH{/dB#K"tja 2{Ry xԼ}D1iX8NUYo&ϖ4MrmEb]s ??{;٘ z.u' J=iCӧ};$)m\"E,q}ow_{oXrC=值L~>3j"5zt-w SӧB9O;z@tťwK>]ƈygHW琞G o?+s˛aUƿo}#"U鲹B#'zkn9 )НMP2!twk=GwӷN;f7>>, '}{wc!@t9,kzH6{<{JOg>fM~"vs>=_VcEݼ:" | OvW {ye;&yZO7f?&N#t"ާ?Z詾L!㗆A/q@=T\kƵ{K J.0zbNZ4$?q8^ǜxn`L. )bSb֖kN(k}OC(~%i8Aiwv_ϝM'*eG}O )2Tzss!b|A8y7Xl7ynכuY9Bo(@Qn@.z .iOy?u?L=9{ R.n=*jqbヲr@W{%Fkgy9րFMQvQR׽3Ү/j^ ܋"q}H e^=|7k=B9;ȉ@X)%+Y-ef =q > VNױ":նAq& TƇSzMJŻFϛ񅶝!QPll<"&]4nj[&V Xv/F4P ^eƝ2)_~YE62y|pPБle_UuW}z=XH<)3 1eSƗ>9R.^K{w3+敼ً.>{[C碣EɽPYz0&ůږG/x爾"ziwI3mѸN/L݁T<Q[|-ŅPW:bB WrV__E߆ھ;bbmC-_kA@ups顯H?>!^l'k|/.[OWv*@PrC8u+:N^*:?uyަPyzDm eO_aI6S!ob?>[. =Q_WZ϶sab_D{qouPz 9\ϸi/ܝsgSĘ*{ě #Ыt;1>7&xF`^-_-]@:J'J =ϣ!yQK4|lɦYÛFz0X?Brvd1+i׊"1|ν`˰[˻s+KeƗQ2:ka/x_PNMnjw# >!#`zDۋo~X w%jG?o/AnLVD5-J{^Qql^@Gļض-Ξ{5s/vկ…Ҳejwb#*r$<[YKsm)Fևj4yaYij@ !Уf_kkGqdP=O @Gi#_|ԥjM/@oj߫Ԉn{n ˵Am@iU~_TT_2 'W *@Xħ}>Wdm b]s}0I?]mD6.j#k:vM<̛Tx3#<39^ȧ0kT Q}WzȆ[cpE2jio?*ܓnb]5.v)Uj +1@O g5ᬝX=y =5 '`<ܫ ^ !:b_5+:sٻK(, ^x~uM~g=@}"+}EV]+:ҵ7.k>EWB3dz.>⪫}/ 4M7C?7i{7O(ݺ^N|7}nϏ7at~AZf= ϕ =u٭}G}"+XG|,&k:󅉁YlXئ0 "?_}󍬷eSȲ"أЮݮ~P J;}qb|<"\[9S F~yo98Vw9Xm*yBgs9 ?/~*P@!'sk\m:yB…|P^#b_Jzaf]Bz _Bi3QbcP eV?<~Z;񇬲R$vn E5 Б}O GZz]2xnN#^~9pS:|m֬y(  Ӕec9a-]ozb]{Rv*sJ^4|FxZߡ C^sZ*vAWQڑcol B葞~u _ gz-λBmũSCm/UzH׷od-G,\S1-}|]o66pzYmzhѢ]=wjѡU/S޼iSґ@Hk˖{q?@3oJWnmwm7#k^;V Us!Ȕ*yG'O7jdM?v7PzBմy<=B6mfBմjժ]駧A\n"DP&<=m>#oeGY 6jUWvzוZ-=Dqhg (q)V%i{tɒjw( "g}k|aCH*AWsp5%1k8O CFׂߋd1f?*yt׼SpC!/@ƍj*\Wzg-7l,CYkMqYC] @֜{C95}-AZ?ʫ~W"B BC::?'Eg]70GUV]0GG +-,_*VKƇ^-.|ŋ_/͝[xDϸrXf^m)6+J 6@z9+ \ow:I@ #F90BW=K'jQӻ ks">HD@WTٻ0.&tPD@PbEbX)-t[DP,`bAA@EDQ$'YNK&>OܝLra9s{}H; Ԍ3|4M_MF=; #$SӇvMDZ& _d\uU~d#kc1P1'ffbLnXdȣ;뮲߳k>s`)sOuXpW[ԬKr˶[Ü;O{,iSe gS=D"[2K}t6z,33n+%)~%'ڥfV(_Nl6ǜ %7\<4a+mfOgG2[75$N풽Rի>,岽!R x=8ټT6("T$ YqF[u?ҩspw+@cG_Nvq=cƫdLT,_D{(W@̮2N7l*UUx~rs,QXRvuټفmxRn]BB!qk"SX5m[RUgK^&MzDOpS..HHڴiS>wڵ䧟ֹU*W";wF+#i&6o,K,'N_2n';x9s^zi|rywd2_eܸq'bzץfs)no|ǁ$tߙ'j{cit䱵ۧ}Y\Y0kLg ;[N;w6A H>uwM'@zb+=@BڜHW = 23-R1z$6Q+0!Zࡒ5tў%:1K}:,: }dotR(P, H>)~ 6u6VC++i ;9Ҟ27Jyz%Z4/y9i}h>z(ֵ!q$2ĺtDc.*BBwUl @HYd\&S#7$]rHYdeEzVQNѢ]'ڔ[},Z(kHY}B;vy(-N+kކ0Sjvҁ;&sD^t_!1z@~q͟狑 j_D%.#PгLC>.~Gy2ӻٸd>lQ.1e =999/{CD]|BɈSj(KH>Wײ1Js|,/ۣq^; YMBT2wdJP֐}y;0y앢y^}nPog _ RJ?>d`Qu"z>'FBe絝nMD[}qwt2q-!qju^=U: E~QNVD?ZƢmYb8mr1JI6KoH՝ sM<9HDBGҜ~lZ{:KZa_DcG"?z>X*941LOO<~@BGEЄǩm[DpL}XxBgg}_\HTU#M DBhc/̻-z̓n-1Gn5Vպ>d/CH9t1UjH" ,*6{/ӄgm.}H۔b4 Nj`Q *9ƻCě &3zO7ٴnNk~"4%%Wu1vqԒf Wmy^[q֤`rXtl!jW /Kf<+Ɗx-u嶿>ؼ69+]UعvԹdd^4H>VƘEy_]0O}QE{_}cblO-/R =z4Gͬۉ$ЍHxLgE-ޯ/bDDjnJS|oިĥdSe3\N戼uSn!Nf;𽔎7OTyLۗe̷#/*hgA6TUp =Գu(ծ{7۶fIl,yYRb}-a?^TٌK|CB()b$J.&Ӣ%o-߲_ǥ^=%)g]o "I@WDHpzr(TX)%gWqO&jۈi/ SJ֎q>#?G{Ly:lUĪXU}),sժe"*R~ ;V'Tr{闢rړŰg5d =<3IN{~1$ts좴N靘}ⵥ"j׽Z|)}Q6FBq%"˙$u j*'Q2/Cq̓"N*xjQ~#q{#G"6aETC^ԩ$}ڴ"U/]ceցy6wWQ[z$i3ωT ^vV%x/_vpHBw~>-zyF@q$:ZxW4ww;Q}R[6ؘ} dsKjJ͟f1 ,2.ޟxH!\TTdm'Ԩۙ#}ei{EVg?s.rej\p =KG;/\1xI IDATP zQ3{'^U{$#Oal FW!yol2,WzLy4eIafOEgMLb&7_D*byρDbCT}טg_('İ/WQ %5mhnO 0ڶih-Pf˸Ad5LDa0K;FqMdN#L?H==Y?DBGjw޻e ˜JVqqL MGQ,b^PsHBBGjX)>UF/j Ό/j xuKYq v"H{( )<1ڋNtcv{Nϥiv,NX*ӹx"H {{%j]J|0qLs~"afJXNR?s[1[}Y RABG`ԭI1(ku2:ʤ%5$t9\DBGNRs;:%ڋRo4 V~+݀ "#pۏsG%%qkTVoT8jy"Y_ EvzFmv{r wkMNI&:,{v :!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H  $tB@:!@B H (7h w e\q ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@ ( t@*Vjb^Ⱦc(ʲ:gUQ̻L|Nj9vqKKHnzYvs@oZ'z @ӌߊTֹEF "M*;qOηOr1z^>FwgHLX#q!":)_P/+m."͞8rɗ}Tΐ>G_&y\oۺ|M6Q*TJ?MJSj[E6}{OK1{w[{FQg9[U9V/ ;]Ԋ @)nbrSqWZ^"yZ7%r`ۘ颞!2c۴yk"uGgF7~ Vu]ܶELjZs S?-sQp^]nw|8}XtVr(޻ɸ?F| yG"E~{+2 t*TsG"w(տNXc^G\;ڏZm]^vz߼>yI{w:lj4?9Fvڭڰڝ|@ eO4JpҲDrUKTŻ;^G&xگ޻yAx.ڞ'Og-.+mEd:bց'\'U?#y?ދ0Ĭ껋9胘ZFq'|KCyY# ]ʉզX{VSԪe>yQԢ{Rvwi@BȪþH-EU ' yb]<,f_k.ٜÿ~R;Rwcj)aD{ŢEI;Fs} @$;zҌwk^{!g\H6;-ukL*wK"{s^Bq^d֫J=VRL{";?ZzghSt/P!jqnˋy}1vC)6DϚ.*|'Uה<ügh?K] 7ǺNSw[@uxp=<6 L\!}&C.߾ϱw\ڸ>|E1ۻ$Qd>r @ج])R}wכ*&jroMs\`U"1͜gsTu4f;O ōxw݋gvpu}Յ}X 9Ƽ퍂D{ɤp:2z#ŰgNqr}7GdcK謜X#EW&tZf5>RӡKϋ꼌һ_qGhE4jUNz:۪ws]E٢O6RE޳XFc}uϡb^H՝[Ί,ў&jۖ 3RYG_?׋|>b:J!io{iW!qANjoB1?D3[&ڗcZwf)-oFJբ:MaW~{+ :Røq&G؂Ur?!1%71#[L{bP{]Dܴ^>- #jN6/ cbGV8KbYO$=}WsF)" >hZźe@mZJ6 t ]c"lMrWX{A{+*_I6xc"Fdyhn1vqS;Kin^|p}Mg?̄zjbٵXJ{QfNb٫X0JaӘ@8Q( ]OL1j%6.pүZlQ|ql{?-~✒gMzS̞c%j.~FLJDvo98(ņ ^ZG\ q^masZ91/RR1Vf_}Y6 t1<XG]\|Ep$j:fO"rļrHJ<:gbv>|>u07ֳ< : Z=>L3yv_g(>'B?1o~*jS$Ĵ=(xm*ҠYA(eO&fj_yv+\(Ё22o¶ w[Eͺ_Oxb=OLϪ/Bљ/Õ,{wc[{4g4M ٢D$?Okk~բ{7cڻ`XG^TA+F bD6/#5bpMD\QbgV{nGSyj,Ν6Ou:J (Ё1FM^"Xw#Ӻ4eş yH 렔'y~em%sSe^ySE{KaU*42oDC(T.p.N+j,Ld"\'<`?//&/HsA>@B1Mi!߸&i~GU{_1Όמ@IO-ڻDg壝dzHzy6zL-QkWfܖHlv_0ѩLQG3[_Q?dZأX4(u):(ЁgzU~5Rug1F~W$pO w1'zF1g ^V8}0Q?M5]])|b)vyX~T:2-8,VgӺuͅ;5-oug6w# 4vTsG] @ſ`O,7cho?^_I0G~!:W : ~nQ-t7=cERWG^b]r,q6W}M5+~ܹK8f9u{)ў_jVbv{>Ub&N."-S>-śU}_+Xm5}'~D=s3KYJ^6^wY?0v":_]zmi4\Iu t ̋UNfhXؼVjbHb36dQGL]9':R1"Mr?aܴMrnZ/ݧZ"ݐEokkE7g7XܛޱlȞEUOrDP(sl@V#ļUcuozR֞)^G&tN7353aU, 7Ð!v%16E=FaADO bDwRS?72)ZXg"%buN[%Qq#Br:a5?E̫=3Ι9̣.q=y#6I1# ȸj0Wp۳~K(Qٳ>]k j뮶 E{IfH'{þmwhhdFݞ] `$@2=6Wk?-\iK+}+ j!"]xբ^|\1V b>f9xw􃳗+ B#o '2fvgi5 ѦJ[Z\Lm[ 81{$;5-yyxSyaR;~@^.Rn IDATmwʀ랧m˿hy; w u"41"MRQ>Jo3o|BnM\ ;(Quug. ]m/hJyS;(x,5g`uޚ x"r>ᚂߊT~r%-2ܘx"zn 9 t@f wH%V-}pj чqT W֞b1;Mbbчs}ݼ竨}f:ғxªݧWǨWG+UWPمb1UrTho.N{n@=e_~<N1]Mm'-8,Q󦉚y(sgUÿNvl<(BFA>{m!Vbc:̬rb!?ދ$m({0Q7K(rq̱E \d̊XgL@P(Zc~U~;>y6n.*TsĂo~_݀8_ #Gɨ-.bD6mH[Q.u#ps QOvm4@2N3zu;+Tr>DڳVb<{D;){0o˅ruH tȐH*UEm߈`1>\5{hӇy!:u Բ/%QGtwKQj~^ h b/'u_w@<=XDl[f=g cKCK20z@))N[ڰ: yG{cHdKn-?Z?y:ryb~KME%{OW>}b'1[/wTʻ!bE4V.r [I7BV+εNJZ܇h(Бsa_T٩(yĈlksb-|OAGwfxPe֗ޭ+ϧ9lqR(Б3i|d'jZއu~G@=qc6U\y>vb]96)\SOt<n@GN0{$⼨vcעlNPi[1[ԋDkh_*bovK>xW:׵xǸSWG4"n@Gn8wکXUȇ[?wKS1j6~b4m#i^(bor5jFsJ{"n@Gٳ"}kGmoVYGq|{5/0"8zD{6@G|.w$9uՅ;+Unw9<P(EwQ#'{v;Z&.DmPٳL~r0$/_^.r+䨣5lzm])ցm:6}x`/Hz:]uL| }BQ#g8EMO{D'~P.P?GS1G|? (]zԹK5zacVb_ %zC 9ERU?(p䃬6LQc$ F ޷mMAtȖW_{MN9 .e41.JH!o?- 9ImX-7jyZ\nxbFWL@ܲd2i`@X;"束dY8\2YVz~K)1;ϼ!M( e?, i7Qp=; t䮚M( r|.ry9:|Q;  t䮪;{\w"y&+͝bDU ~ `4Ҵirg%ulsΖO8" P5^3{N3}1ҽ[7Ai]7A\q fڶ%v@`P#<6;J;ڄkD}+mW\yeNk(LoՆ"QZQ @Gx^w)I{x~` :#Mx_u0WFݤjaQ#CAP/ЍE7yh3&nd闢rdiS݊lf)ϙj}i]s{@GhտBbT77!8}jLCPIV5p>}/aYVXr:rS|,Ѝ#xٶKƞei.^x>H9äYfR\yO0ax)UWŎ%+V&|]8L۸q,X@֮]+7ov_}y={הR{{l'|juDFmG:t 5r)B*W 7w}+[N,NK&֮]@֯WW~_ĬӦMߒTVMW.͛7wpO>%\xue:rGwЍ ECi[w'e2ypC.o.Guk:[mr ѣK)> )S䒋.r%|?PϽM~4n$~]ow;oK2tOo'}oUl@Gnrq^bV7[sd?_~Vc~]^nygA"ݻus=S~Zr`\p+e,Oe۶ϩS{w?\+vQCVwJw"lF>r};)Ё2eyp%^:J7rD۵8՟~QfڽC~嗌R:>{aeN15kJʕ3{ǝvMӓ>~ٲp37֬^|+i֬yج,k# +!*i|rpؑu‹e1;㾧n!Qsg*dk'?b(("bEwfÞkIiƜ+΋:E Yb{6 eU\ټyriF~v3:yG h([k:ğ~XDk> c/s˻!u'}lN0do>IGuL<篿V.;P;쳓Σ@B3|,c*T[kZ0vzm1e0[u>\ɯ`۶uk֭;~Iڔ)S;Fwѓe=@ t]E8H9^:vɬ8/"nk&ONb~>aFr8YgDmM̺i,Vm"0P#,yҮiD@Rw>[Po #_bam[j @M͸ A2E{ȒDY=;RSDzfr1*a_|Q>CI_# t=Ľ1NO\k/9_;?١f'_6:MyͷC9KeBTԖ]=mwѭku bq{QA@tZypi@G|5nzJd#?4Dy`Rn_XmVīD8P$9`3_zܶifPa4Q#g_ڹ4E!H!im]_Z$7EXZŪVCԆ gfMZO^@9 t,3jü|XI+ᶇ{AػCy`=d'}'|,Gˆ$(ߊTY"ګY}:Uâg\jz']/J֯WW~_h@(Q&Y-N͸8U=Cb9`wh{:$~5 gDp}uޭ LX'^QK>߲@)wW,Wݖ-[Rnݤ=$=0C2e:^Vϋs1fg6Bӂ}!]^~9cXHʖ#8B>x}ȴӓ>s=$L@G5^$Y'u$w/D}^s!Y}nV9'+WIYfرcKI_d \;(v^]ڵ;0Be۶R\{}rp˖YtaVzd܃>(m@]wmpھ`+ A-m3iwiݽ[ z;N}w.`ӦFG={%G@EU8\^-.ݱ'~'sLj,&> \|<3Ɠ cWnϫSфO޽*m^#6=@;rG|sіeK/aDoJ6mS>$>2 t`;kF1ԳI4\1G}I{TPuBŊY˞m= {-uYZmoܸA.$J 4H. k/==yꫯ۳g˒%KdŲl21Mӭp뮻>93nsd?]#W_Ա z%3S$8͚7/*sф[QϨߜӦ( t`;>yeb7 6B]DųY (5nIۍGfm{bms%K~3Nϼ/K:u(vԮ]'szzr{믿@ FJ˖-]f }h7r3U QjA[y>sBWOQdoy)'$UV{6lӵĉ~׭+N ;^v^Z>ȼקw/1|aZ.~@Q֮Cs|$_/Zh. :kmڸ[Nun޼O3z ;urn]1bHOUd @̫ oi")_CFk XQpw~>}ңO!֪%k׬;ЩX>oٵk:v5/Ogwvb6o9耭N#",|OљXg"}͙!CIJR_j-[xqg+ep$+Un"{Pq |!2z̘SOJ]e~JD={&L;Pb+fz~@Cwv.i b"?=`:srw ܛ:v̬7r롟A}uW|쳽ПFm%{6Xt=/i{:^Ů?ެjʥ׈OzckʿmdwS(y])@g+6)ʍ+ulKS~i"Uy('ceLijˬٓ+F{2^TY؃]z\߯x8IE`jLY\/YsQ^([9Aώ>I[Yh|@@ϏZ?wΔU}̾[mYcTOQx|Q'z;!.t%ed}ƪ|*QyqײsN^gD3ee料;}|/d,FQՎgnmWMzG35cWxIs咫F78_͚9QQ__/G@gS;#Pٕ7"hAy_O W;qӁײ,jRm~|Q6= K*1r5\i(_zodQdCGFZ='T0˯89zoD;|zmeZ_>>(TvV=3CQeךOWC["^Ǝ)T"aNK0߿zQ{۷LkK6~>py=S^]|ncc8\>'B.?Q~}֟~ hn oX`msJԟ[.=cYʁOほZ39Էbv=N#`r0bRvj_NxY>/k]tCvqTk{ IDAT́:q#_gy˞KW<ʭ|n=ͱsswW-z @"ǣ8Af/hn =.JwV?*_H;z@:]}D}<ԳZԾMct{Z/{nBF-wr+/<ݽk{7KLbo7FyrkLhɯrg GzzoFVcGi+"MgA!d~,X:S_>|$c7 #Z?uNcu7,r#{ѿ` ХQElDL~ڑNó_xP{-D=[['U_'E~̍/41:mm,6; wn6#'gmk{s&j'o>LƩ|ۣKCs!Sj}rGf6~[Fq W.ndw\4wr^2ko]~Q|𐈕9?:n,K>V|sQѸ ͈k>8ѿaef]d]50 bG,э=ȞXV21{xYWq>ۯ;_QJ{>MovH_~HP5ƨ|kˣ#*9F}@{N9fy're墹uPU1k ł bSzw?9=FfL~:=@@w$7Ykaz8M}_}}.hlnkq(Y'ovug~2;eyCE:Q:F:mk7W`!1=DWCmz9o3]zl߾ϰ8m.}"+;W.y? 3bI S@qtx恑̪w_}+zQ;C~]9k;u;FɾcSGmU>gU!_.6xgٷگwwh.X.XG^;hߝ''@ tf{rQ?ֳ;`| QlG zs凎zc~;4ne=8ÊSn_k76S@.dߠ8(Z-u> z=pW?JdïXUwOL|$j+?u(nоr5#c#_hоoF1W/?x=G~ANoQii_翺(>:4WζS"^N1kϏhlm pED{vZ==DPvhn AvB1^J5oʮ ʵfYn /޻gl4x#>ܣM}ΈXy2j-5ȿuxDsu/Mo40tK"[CDqUmǔۜ瞓\;?wƒ拍v];]50z3V`n:j_Ĩƚoxтw61ʿ~˞{N޼m:Q|yxK^>핍=)[D8Oqo/NQrG}"С²4R2ˣ8ytLspO%?1L{SK~ð;n+|][$J:޺ozNQno|ӻhlm}wdz<;89[zGD~͹~/@? t$>+}}K"^7ۊ]Rjd|_T }eȊgێ-_H{~mHKw(r[ *+yw|V쎫̦wfQcmbc>p@ O<܊1tE5W=@Iٙ+,bM3~z<8k}Kތ33gg2Gz9Q~_v#{3W?fQ.T۟k;qEgo/Q/N==%&a /lDsVboSp5/oh>:yKΊ;"['?{`n~Ke^t̉0?nd7jGm.O\}9o;ؿs:L1ٽϼo}F烎i>c}GXOQ6wфN1(߶m~.҈ՕU"a Dv_zG^׺O}ffE^D@ll;<]gl$b gߛ:Af]ʟ[)+7'j9Z!㻽ly~/&H{ڮl;O3I&cUt5at<0gYx뇢ޟ {X[ Eq̍]3ȏ|{1uU1ju$RY9懮c|\@俺(FM̼ٓw/b"]Ec3vu澦{oi[CeseSm۟nd%#?W-j'lҟH=Q;]>E%Wbo~qf~"{s,ʗ/?no1v?G~"{9%io#@f#z)6=o8}7zs;9_rԄͫx'|@C> `VD~%m0 t`>ܛ۞tidw^ۓƪ_}"Zf͊zv_7?_rv>=y|Q5 ͗ 7B4l+*->8~D{4.D4g/;}c7)E ,"kΝL>Yu2k>mƢr\҃^q~񚕺:Ws*]ѭry"_w ;G? Gq䵍=]3l<6ݭ_׬޳-`e'r{زQp5O[Q;l|/b7 ??5#{?1K"f~~oa@OǟHttˈeG[w.fCyO#b<\D̿{>"γ6~D O9΋5SCxq^G^És!=Hd٘qyY w`ޛq@F9Q|gw=Lj6w7HQfBH ?kQcF֟D7!d֜'>>'"> tQʚ( }+67D,X#D'Ͱ-_).iw>Ӷ0;Ѕ'3?>j/z C@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t :T@ P*@@t ^?9 jY's2@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@b89$& 1qsHL@bRy[M/wda090%6r>`E/6쏷LF懶8wE~鱑qM0a90w bF}|%/8<<:5W.N_m}}u &΁I/)̟W(h;Yũ=ھoӬHIVD];F3#{=̲1 /?ݖHNVqUݏq QcfkuWq_?ƨjo&@%s`R*xYoivVo؛_(7'Oo 9qLJ{nOxCZyÞ U|d -qLJjv>yɾ=\d='΁)nXC(߱},:&jmqxy19IJz;8&r ̾_qJ_@-Wwu|8*|K8.?*L2@/IOo'+ ;vW.7L+ IDAT8qTF}Gjzfdų-ũ|ݗ#Qu7f娟t{Zc0a9\}3"V~=ӈ"{vge]w6j{ѝ'9.{C6<+Ɵ)k|9L\Oxducj{,ś嶧 ?>=<{vV]?[?qLb3#g7]:q__7=WOk=^ʛ?D8ܚW T!/G #.f D)^#ɮ<=+[E=V5YFAJY.oZ#~ka[D}vzN "΁+_(iLa~sE_*b #}n(2-n¯>erÝ"^̝}~nb}"΁)_8G2b?ۣv3mNiۗ(Wx{ċG~qfw^ɲ];v}Wf㥯|kVbϋ[_f??/o>q3q\وFd-|Ǣ5/\y{w5n#:\Pymd_//|\o|6쏿UKwKOS;l(>>?DDmUzzn+m00^ʀ&3qtx.Qw+;3KD,p!k|ߏb8CcJNPlfOƩrOHA\uf~/̨ܶ/}=gv5(m9|g'??y sz7WF+;BW7?_O.t{\^si,NOk^;t 0Q90*K^DFڱIMqBPkZfS9Jࢽ`sCoVeȾy`deZ̯"{'E{?W;헹[M^F;r6r&\8)n4 k|ţV~^FbUE;l^Lw(/Z}%QGQpEZdO=Z?η9La eKkZqM#?;?ixK37|F8c'3"}(Nű7G~%EfQ.tOFv/"]-1|_ż Ds`0EO%2Bg o9@3bcyצ'leų 5^/v8+/k~_?.v\zQ~Cs(_q AT?0e'|ܩ㢶}Ϳ0-o{qԗ~kċX͠'ځ춫\cchYԎx[7qSL ,Aè}iގ9E߿PvQL$mNnqa> E{[k>/^ukFv_snt|7o߶yo~7/{~9*,G\;ha!y_9;(7?,<՗s8)C;SSQkuIh^Cft(_B䇬ʼn\Þ60?i]!Pe=~%Gw|K;>v.?`Zٌ_Sbv=NQ}ai}}/"Y?X~ίc>ydMqSH橧0*}ݿc*=7#f]ȯ8%ʹ^4bϋ#Wq _9L%g<37u j$lk{6T䪑w>ȸfGNtl|7dZ(߶m烖Zm`/lٝ?y%a ɾw||V^!# ۯ+̱ϑ\TPveþ^WK}_]ܺ %GE؊~3ՋOzz*uu60(NhތT7*`rbΝX״]>W.;1ʍXE}uˏ߸}.ir#cOc5»\v(vⶃF~ȿ;}LtCfOt#^!M5򭛷fUzU۾K''Έzc|vQola븅EqMv=Vm{f|]lӨ?Q-ߵsku5U~='x&}(߷WWcW~n78.Wo>c|Q!6iqQ>@/sjg|}vODmx'zgk俾'8)v[K2_)3C~ѡ{(>7}Agx8;?fsK^fX&~2}T;l~MBgO?91N_iI rO$YcN~ 1嶧EǷ~m.IV;y(y]dL+;uOH+jgn|+Kӱˢ;vS(.V̦jŚ[D1#_vhEdz/n}=sE%8臝QrWkM._(Mh m﫟xk^qyQriQlň9G2Q4ct絑e?yM~S·쟏Nd&H #[;[3~Dv]thde1/ meж|Eܭ5(Pc[~;[tKy?oç}傋Fquw6/YZu=ߛvભiG"js֎[Ƹ|G}Vv9q6'~ènOGl5OxO/8 lIZo 챿Fvw\uA?9O/hn ;Fɾz_f5z;m`&WKGzڰ^^j>ڭyIʁA"X0u}N$΁>mAQܚ~(بW6ؚod>U Xc'tY? .ڶG Ҽ}z6h]9|60,[|&>`3OO N3{Zjv]k=li+m05]o>#G}&V;?ն/?ew*)V۾K4Fv-˼t{Y(^IE4cK;MؼO34\ܫl.5e֌E?g#kg|*޺}[ϕQ#[jDOEvߍ=S+aq8ěv5{?͘/>nо-DvC-\RQs(7ğaw.uO4?nBLN$Qn ,7n}]nxol~7~Dgnӹ^3^Lsb\ {f6($M0P[R@$̿ *Et]9 ;gv6e⛏˽pߝ3qaئ7a|{Y/r zn=j ^QQra8'fj~C٣VTzHY?\U Ag`Q⌔́1I{sP Hrw\+mOn3n KzDPAvNc~<)8/vE!ˁ}[kQ`8'w7 Zϣڋ} L&< ugN79pqJ_B|٬!+ Bo0AK(i*}m:pnvZ$y+x`pQȚrز9Q'Ls=V`k|djb8:>ֽP CɵZOUF3%^<~, F gQ{]-" wu1ZDDDΉ✽&|ϯԌ/úghձ# <ڠ˫u #mbTߟacOŗw^"z 0{R_,&rs1F8(Y!N=ao=qPŪAyђ!h. v}6Em3U"!BU."""# DqL^Zꞗ!pcϰ'}Ϧ5ly݆Vׯ)?@3{֋_{;Ě׭gZ|=%.s8j6Wr#f1aPI[?͠{X19;=}`^~1uQk!سbǮ_WUoX8Ay sxZ]6zׁ|qzYr@IywV8\[R@vMy- {b+*Ȩ_ ՠ=T2@μs&%ܭغR__hEDDD(:dNˊK*K]<ʔoŸ{FH 궶5)w.oxq xl6- WvNwE\FD """9Qy}qt9 X#RHЕ0j*Q z?wR_Uy|ۇ_<豈վxgo4wWUl'rz2z^9bmdEm8}BOCDDDΉ?|>6ƕO8>TS}ޣ~BL۳ۓYՁ0M \߃!oJ{yM ۟|A e{ 1=?lPM`,sG̟uoTTZ? U/;"""XpN%oEzch'3d*ԃʔ.gm> rd !`4BW90_rx:m]}_}/\xPU9+l0X_۫VF0^j4b,یɃa\QUt#"""E D1 }h;[uOw6{87WS A>1kh',mkrYʉy`D0=Uk!&jF?$a¾^;~nۆ鼿3a+Q.VCxYi2:3""" D1D[%3#V6ֻA;JW1FߛÍ `ϟBӥ/ݐny9'C~ހ0'!#&ҳCP~sI*]Fn|q ÿؽIo]{Dzk?:~3Y]R +!bi>""" s" }|\?A>{-9lUX7 뼀:l3>WBXjӿx? ׍N11Ķա㫁2qMm`lgNAL LJyn[yw^B+-O ^9e7|_B+"""9Gmm[*TW=|6;hJ; U.Cg9 ,|百`6KmY wCXv[̱(V*_ =ˆ~f\{ݠͧP [ |Znv9Q [6 w$.Pu=s's=d_X/φ٣ďca|9 Nc8qX>khWBռǓB!vΕ`oQ8'"""9yZ@ș翍 )vxH5t [w?{\j7*R?ؾban`7䥺grǴX1#kQb+WpNe*?3Ϣn3 GMsɛjمwjT}K ͰJѿa-+=SGʐGZ2seS U<6IJR&ղ$Ϝ=҂x=E8o8FL\5[, (ļϡ9TTߕDDDDpN7Y!TM>1Űk=}t1s$׈S՚{$s> ʫ XpNr;;kWZ>v*z˜.1UcR-A;j?bρתєᜈ9\~^:G%bO|hi6UK:U>5tc8'oJDU{Pr*=Q!S\2SE =״0Xutq+A\D{7%|s}7R !"""9ENrVbgZ^aNV@.xV)oXTa/!τ܉Ps+9A"""xpNIMNu!Msv,@ݭg8WKr9WyP:BwE@|9t.ykvB@X^; 'Փ M{ڦ7հyᜒ̝T2woQhُ:PدPal> IDAT;oB ]ݦg`q/z5 DDDG) =CdN* ZjcRbؿ5eh|@W,e*B8.)DDDDQpNI~&VsF՘#4|:̮U!ok u[dž^ E:$""">sJjb|wΛJX"k4j_K-g ,R1e$/C_tX7< G͐ꉹCy4u 3=u:^_^*d`M^ b}[p!`t~R<G s'̓2y?cs>1{)~ƅj ycPw<tѧV lS9ƛ@X)֙n}=}gB./Y#ג`8'""8pNI?,L[Xs _6y0`.9j$ȡ+W7 s^c_E]ϡ8pxı^CDDD0clfuĤ~Mnjt_(Î.D>2̽W@dʖ2̽. dvP>;vw%爈(v0e?@,Jl^c[!YokZgtqdo;DfM _v} ̷I} 7G +Շz 8]0{r1""" 9ߌB:XsGA6n U8t\!/T_B3xa/s><'[#˜: b<MNʙ7eADzQ DDD[Ήb| w!_\;(-^V*7x"ĉ#QPfⰞ|(}mfzH;lغ}%Rd~+ ċ4?! ""pNt*oQ'.Bf: a,ZO="""9Q!GOˠr8Û4ڌU hWBrGD1;l9]ײ)Ȭ1Jnj5`8'"" DaMO*ޚjYg@JAٓsyAvY ՁX| /4CX%"""Ή"`xl*B@h cΧ4&@2AmA#T*ZjTN_ """9QZNM5Q8LL<>KO5u Au DDD7s&j1ll's$oΉ4w.̓#[ ̉غo DkL"| v~ d D=7۸_4߽&H#1&~gjbuJrOj{<M, ܽIk=""" 9F 6YQǹ~}{}5zw) =~{XùXZDDD9s"͌1NVwvΑ12*Ik"r1U"$󿀪zZ{m!"""=Ή4˧Les}]WhV0 Dn1> O_k( ue)aNz:""""]Ή\`t 9pqŪAq+TUWjD,G(uMXKj(:"NLxbF D.;K./٥;ubUb a2G@å( D.1xw+c+c =dT]g,Ԥ~/<뺘9'ݣЕqPwڱ?^.wDDDD`8'r6cɎ] ]ٿGC%ʜaG|m\#$X0 'C]\q/#""")n)3=TӞPMc,o? q7j s¹M6aZ%GԚ2RKhǏ@|_ Lўvo`8?D< ,`t mƈ{ }ji@w8?z@[-""dی'$""c8Vlqh!L5w;_N2n=5A ^xYf@٠Qtb8aOr0? O1- ѥ 7( );ĉ#ڮ#Mr9 m~ӤmtBDDD:1Sܰ'R + Z `"pTey0;z-{ XW>H\ÅHefŊtCDDD:1S\zq 9`v5tsxe*u-_^})X] }s"mm"'#""J ra@eBPwNM>+KnJ'y1Q3_R3e?!""")MRwtzNބuN8ɦ`|뉽\{} ߵ_#~M8փN 1Vm_-ù_˷k[.󳉝/"Sd|9F B}n:Nkc,9RmzLr"XfM,6ȑDDD):duZ]6WBe^o"UJ&g=hpN%@:yLO i$leoL]<(nܼ$)߂E jl~4pi5ÀƢibmưtBDDDnb8'6.VjZ:A76j$pND>Qv@D"ל*n:.wCDs bxEVdzȘ1s¹M>8{<(\tسw_H߿y.ίbJhCh޼9.O{q[ի`E^uL\V2e=k=pѧQ͎dWx1lܰE9" f/|A%6U/wT:Pe*T8}bs>8uܛ);x >1߿DDDx)n@]޹}5M(ph3oTj&15ĊHѕ>Ce ru+2rTB>w)n'at( 9l8å0{ .у0.DiܹZmPX/ \R9ׯřS.uEDDD^a8b@j) ٴ'Ե?Xt@O̚ 0ۗEM\o۠(|q0 %sKƧ=aΏX8Ro<$pn3̾6EQQdI\6(JTKFTz0_DDDW)akߎ𺍰޿EV o 9sfZ;^߿=ŬzQL`89GxR}:#̉(X۷m E0__C""""1S| O>4_9۠(x':CȁEE\Ƙ>NC3DDD+)>e C~MKk,3 yy=uh&sO)u=R˘2JKrӺuS'O yA( E;WIzmZFRZPl8`- !RPrvuf24EDDDb8w=}jߧB.GǏ1ܥN(tZ8ɷC;ap)""" 9ŧ8sn pY;b(M !z…9-W_};uR'+Īa) YѴoʅE9ŧ8sn ^dt1>R>8dсI 1-_rsq)yg^DDDISs%i7\ qxLɤ(>)Z4cb'DDDDk).ۄp*Gn>sة`-Pm -W\!;e;!"""XpN)sv;*}y .U+TѽNƍ]섈b 9ŧ8s.owsyfkO33f ͛6 sOq̹4~3SFF#'Ή 9ŧ,έy/Ć΋aWON:u DDDDE 2f4Y6x2ıCa8s"M4t3Y+G:s" T!}|0\`5ŦatFDDDDD(BgB3![;CD)[7F*UPF ɓ׵k3DX睑Js'eYgpB|=y2ƍݻvyR7hV=˽nwܹs?G~[ӧ0|wn9Qg?OL cʨᜈ+r{J0tV>n>|:wagU6bŊ{JPJc-h=,qOj &}WϞ6GD1u&;~I}ƈ{ 6.`#"}6q".۠o:r0.{R~6R@AL>lqG0Lx%k׮(\Nܺ5&#wàCе[7\z^ʔ):.Q6nH{V"J Ur 3ٝؽɽDITXbmP~m&}iQi˂GP%\.L()n'a]o׭)1<]0Tnw9#bSQʖ+%KyEW_ cF5}V|N^AB픩΍n={n<e)!¹7AE<#{Z W$6-$gߗ~wѣ)SfWƿ:oV]9J(1SbpaX5|MW8~X5m8;ָr-d{ydɲe;gȘ[?Jxqͷ w<س{׭$'L͚yy;*}FȡiLU;D?ޚ5c:tO"_Q06 E9PH+V %K܏,YFTlr0ӥu挦.qމ9<իW~='Nhm˔)rʅ2e>9ݴy3eɢ+ɵIZ?W\quM4V 9%MwUޢݾ>ݪiy]Yk%|Gx_L#ou !{]jտ'2̞3  5kPhQ-=#} ȗ/#N5j$^xy:yRSWs- 4O=t5ayî;5vݺ_uzGƛoLc](<w4j+" 49՛@|90_i5BbѹQa{.,P;ڎQ+%kV:|re ӗ_}͚6?M;6<5nLL1eK]xra֭aiР Qc82NPUGƌD}7խy}nB=5֡hQg=SZ>vj׮<dwda`Ooz.vX:Æ EC>B\숈b9ş`wse(Ȟue7-~#4*U~hΝ8v(^}啨_f?i]?#ϙ#q [֬8yt׭[ns:]v5㉈a89{6%!_)hȞk9QnS r I՚6mǏN&[vҵxd1{,ԬYVZ2eju菫+_˗-s#"% wTa' ܊A>nkg|eidsGںDɨy g-싡?ƍţ7Ν^ͻ7wup4W$pӺkowťn(0S 2]:~}jt<oDZ0u@j׮]b':ΫW?ryJ"!-(m[MzW3φtc" 9W@,)  b7CULӞ8fQ((? E_]zigHYfF|X0a'h,R yvr8?D<)n.`|lx!ˁ kی+B9{dPC+GnH7ԅaDnR*Y)-[6cdžT_,3zt\r/](;z4cg (< wJa^P ڧqw3d{`b?T-W\!;qD;f؜>?DDta Tr3f0zFY =٦ƎǶm[qr-'ɐDCDDpN1MV0OӚ0_{@_01i!wi膈ζ{.[ شiSyYrxc䅮y "?g۴#~ Ŷ] ſ2QsY#ky00'mطo-Q8y-Q b8u-Z*X֚0KJk2@^'Y DDDDD b*ue!ss_K~-r&{ipN1I3B־/%p/r;cOf# ; """""M)6pܦJ]OlYuDDDDD 9&!@=!vK*YzϰMDDDDD`8tn1$ IDAT3]{cJޱbk l𩾗AKb -u{ 6.JԌ\k!]O?A k ַ;% W]vۣqȖ-iƌwM^ADD5 oG@JV z:dts5uCDɠaFdd̘V( k~`2681.WmV WnBpNkt\mٟh)_@'9:x ı.uEDdǮ]ȝ;mQ)fcZ5yG/hc)6EauSQꌈnb8f|d#+}59ifz_efPE>:`ND!c0'""O Ğ0xw+m%Ɯ 0ZLMCDw6lu DDD&syb/0V7yXTv .Xؿ jm@S);ĉ#탈J%q啅npNqe~>[ f8oA뷅kQZ|y5~W,][l!YxvٵpNqq,`|T()n=S!Ns\S rڴC?|r.>cY֝[V׷Ή( 3di:/ĉhؠ~1Å s[b +*G>rGͅ8 6Wsi.tCDDD`84\kZC M,rTμvr="o[?ٲ1#= 甴 #:_5 οډ(.U(1SRR9rYsE\2f4tܹT9EرXvmQ 甔#_BO"0ș3co;!""H0Sr*^][){R9qxz>uok){kQʟ?Κ9N( D*\X2U{Yp.v3گEDK."""Ҁ(B*_1%#{7V|{2˛7-)O<^@q4M[ """ Ή"$vaW?.\TH[p(Ne˖o?a8'Ժ9? UTԕ X5|СF~@+(NjFǿDDsJNkg%jj)%NNSFsY7ZvdR\=x`sR|_/%rù2f:]$"0SR2xrKyG7k&-Z|}"]w}׋eESg𺅄v^٭"dpNIIX]ݽ3zJSGqь_' e-z5Heˆ>jsυ| \섈(0S2փ5jCÛj&mbڛPMMvVT~u=~_78qb nU^J* .t"pNIh[r:牏Bl]P\6<"}g̘5/[i?1[ר+VpĐL6m,c;IL%Krt'\ꄈ(91SRJlSV9@!cb@.s'jժ;:~gΝʝ;2d̈S'OFnr^ؖ?yC>>pĴhb[ "Jj D>fP/;7cćØY:#@\;mݾBd˒6ɗ??e k' #R*coV|7uݸG,Y|]&Ԫ]3>S%'s#{7 9K5jp3RgC xW/˗9kho_~ΐϔ)3>?-O[^ں#}b O>E9szFX\u _st\ꄈ(y1M/M,փN7Rt(qOAqV2ޣzxy:vCNs=܋cǎ#h x6ZIB'+{qDʕQdCϝ;ni]޽z Qrc8'T?`}1+?46ƙ϶~F*T8s|M[!ࡇ;K-2gƑN>RGg֬UvHۿٳ>/7l~{ԫw. gBuqxl/3f_ *U^t>!Yuhs ̙`٨]]EHѢXnؼY3 ~~KQl6l授pN|ż (@ם?`؅/} %G(V)%Ke˖a%?WfuE qdwk֬u|Xf ֯_E{iӣ/DKF;_P袋#;h`rϪW]{ oV·Soq|/v9KtxazS'M]Ήb*u-PbjqѴifǓŢÇaA׮]%JF\@x.-P\K#1VZW_#1 .O&L@*W]K/i(r8L49QZȧލ˳Kősw 2J%:-R=N.]X(?]|8W\ȝ;7V??[kǣKrW [{Q`8' f] u ^ܾ|!dϞv*_,V\u kQm%sJz*Om^נn3)q:^án%%%.25\uK/;n#)\3' )V!}}ѿPn]NJ"ƍQ1S :\ LfL{p~-uݜ9ghB['N\ti9uKuhK$de`ƍȜ1_z ]uCt?ڞ=Ѿ];0}r۶i-u+DDIᜒ^eA'Al"-efB7uTʕ+= ֯[ szժgSm+V8jԨ~eʔɣS(p!MX`.\ӧNyڗ/&tE\{{QdITV ʗk)]x`Y8cm7ok&ֹk-ǎ~^|؟c39ڷ/Fo`ڴiDpԯl7I^֮Y;wa9j^d$҅(JC u)av;E9ѹC)mA\Iv|"""""")K &~~s `) yPM^J@4aM ,^sDDDDD1sJjAgj_Li1~y_HN/ brODDDDDc84A'’oDDDDD9sJnxDViմ7%Y-<Ļƈ`8'""o~a; ޓ|/lZ sx6GDDDsm\uDB k.\ ֨ 0ڕgm)iK lcov" 5w svk!yᜒ=byv"J_Xֈ0ە3 甴~xQT{"+`?^fi!sJ^W"@>0LOG^ٶZDDDDN1%)9GʲR'z QDw)$:DDDDa`8tPiign 'x]rJg@sB>ƈbĶ^ADDDIᜒ{-atGc%k+C]QQy2'))lC줂R@T1wk2 cO(Q Е0;q)%"""JR T΃Ye0|AU>^,9΃٭bٖe^w@DDDIRK MQ D˙7/z'_k R8;DDD sJh֣o[0|D[=^CsqNiNȾ """ 9% *sZ.S^V"7jGKX¹1npN K^RaH{]/us0=wBv`ʟ!NC 田]]׼yûwH+Jp!qg1sCQ&}\"1|a}WkfzLD[Dnzr -aCoCDDDDa`8'( G5 k授pND|e_dCJ?T@ uU}^)>u"""s":/c [ ` Ȝ=p|Ŀ˧_DDDD׎u@k1 #u>!;NB3vZD٣KY#+d#xAտikt|7DDDD9%4cv}0NT+j=(SHNjE@Unt|=`"Ûl輬|A\8v  )\Ka.d٭ަ.•xcP#+];b'\o| 7!_)h uPX< O=7[$f0߿ f Q0SR٦(TLPCU,9Rv; KIbX33`~7̳¹}[3| ŁI|EGjH|&}F"""(a8$N;`?>fתmr_ 1m[QRs@l\^ 8uضbKٿizĄ(s"&3} W?놇`vFwgdP>tj&1s~QlZj"C"""DpNĜO RW_\{=YTA3!~zėJA]줴y0GySDDDDq(_Aj~7Ĥ!o̙ jZ'{bĽR|pӲWoS(0%1{sjùƙS)%  ᙳ sw+uK1jBK9 =95WX*}=o/NX wۜ~)s=đaB)ZUKs"""JF Dnf w!-Īa-9| `U*ԃ١tgO0GruZpeM#""" DINaTTl' R0ەztueHnOup/XS}ebW`mmՒuSs$矱p^._Q]sPB>Ux5e!>cGF.șG_b"""" DINMUoDH,n2 c=/C -(Qc8'@i-gv.ٴ'ԵkgC?/Z)[CDDDGΉ8yL۝c!{A6ZK-^_0 ? ADDDΉbDk[۴R?0{F p%# """w D1糀pbGQRyL!;8˝DDDD1ᜈ , ئ5f8A/^5b9U͌Y˕N5)5+PvɣKzgX2z Bɰ%"""w Dah/)|Mk=sWA 3oh'lcT=kZ8Xs9X6 (_.FmH@ʑ}i6Xw*W_J!/ ۍ /BM"gQ9DDDDHUd@޳޺T5"cΧN]^I }F5VeRB8R^e o~ړ]|9o?4zbT-M~偣 O&ĩջԍvR0^{b&_^+s_}[K ޖ3dE"""dpN!y3P Y dO{Qa9?⫁Zc dۏL}ޓ߮ 0& *VG!/y X;zSC<; Cԭkig85a hq0_tfڪ%g~&KzmySc,V+~219|sQm9NgV/[!5NAJQz蓍xO[f9LAs|7qY߮xLdZ">IFCU\QnV`xr=9Qlm n,wߢɓ̊ow=UkN>?iyǏ"֝=>g7MSP`~rB~luGyΑpTV_ToL[=,kԲ?ӣ)sYV wZk쁻";ezDu+&{Ec<7>7KwC-;_Ӈs&OgoG2Vd~/u\u*u-6u#) IDAT ]5k]|#l=)6?0}=~h7w=;m`F%d-XYmUS'hBxe :ZʗuX,rߟˏMe T[Ud]4{m=nɪ#4^1,D0&hSn7J`Ljy'U)n6LUEqsǞEգvN8I(Խ|r ?`: O9/d#|RDﺼGc|P0a2x4 .Q~! y}k4(ްe7t'; &%?`(pb/ 0 նOt3r"=]Γ<}8qhscǻDc `psAgWgKa(_󶈯VrgOJvvQnulo{WTۜo?$iVs6mʍv׍/쎿& 0I"۬WĬԈ|ϛf,QIƫw,=cصsDYY_ջ՟\sb|"?~g''DsG,?"'mjmg=]Z$!\d|#Qٹ?Skت[/۫!%CV`B[;m(zm EȮڷ{έ_DSbUA[4fu[Kk~ţ쑯zHPέ5^oE{F/(FU-61ʷ|5նm֑qd7_5y]~NyڍZ}bK'Ǩ?yڦK0Ȯ:/G@wb[qhyQ.cLpskݢ8Ybs#eʕ3\C^Wy~?8r{\zno [Ow#ۀ@~)RlcoP_y^4v[ix:pFcX}(;s#OfK{G}~]"/7c+Ѹ!*[?b/GE2ˢ(b?` 9v ϣ8&kfݗL̴kX^8&{h|{3ms ;oiQ-󺑽я*k;+JWOmrb=hN33dvN!L ,2q`Gͻ@kmOxwS˨{Q|ԟ{f.YQn)?gX29Lsۓ;OEbfK^# F3XPl3Lٟf0D9=Vn|'MX]Q ]s"E];¥yԟ}eʷlq.\vFL[m ct0L90bE2l#gb{痜:V-gQa><Iq煍Fq*=pהo]lx'3(6?(53]Wl`Lp^*E`8Rc#cwcZ&+zGve?DkI0_dq92̓/Xyx@3مpe\rnk?L{WUۓ-܎bda'u.)bFwo<dfcobۯ{]v鑟SnjK,?l/oyo~6]}As'hZAĸo/ƧWGx;Wzh.j+ 屔/_5;7z2*(:3".Go[->U /W\/ʗ&ŗFv/#~Oos-SƉF V]/{;L,6q.ͷ{r87.0wolM#2:Is?V` ]֥a7^AGv_>b/=;"v">P{.Gƴ΁>?x_ۡocMVv#W⺼\-Κ't[tg=؇7#>EeDv߆3)Ąs) t&zVc>̨w;=ϏXl#+5Gn:g7Eiwb\}^nf0΁Q{=ȿH&׏u>͗ïWe{j)(7{5~sC{9179oAR=Ϛ?b;9|#⦯cyu꼡6Kښ^W>HA8jt4w='⿖=y_#8d};<#WGDv{(W~HSj-솋X}lT\Q}\u^mL#8Q(v>#bfy]۳"?e!lY٫"}V/OY/n݈X|eߘ>QyRG~ۇ3)9p)+FO\q+`]%Yo2۝1mk7˭4|(ןB<~Sl-Z}wF'2/9pLJ=7ߓz}8QQ{ofi_;wi6wa'c_cPFd=ÍsNڟw=G:r픃yQQz]d]L{W(L5?k;Fk}|^ؠd P;9P{Ŭ:/1xRqn]?&{W)îxw[)rorL 0[zZHP#Yl hW dY>sߟtl@adѾ\v(>qΚ'Na&GؗoݗqƓUe p0G岈Vija`xDcgfxsq4,S?–>jg(}=[߄p0CFIݗRd?YZ#۷=i7?X4_s`քs ?.u[7}"74Y_D>Qqo?Z|xK!LR~v։xgCd7\zzd];[ߚc^v(+/u_.Xkl¥#y K"Y%@ 5p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p pH IDAT p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p͵~zPgs~/Pc^;=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<=sjOS<}IR}~$9'Ad (1DL"| (A$ JN$v}=D9nwogvvo+zwz zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs'< zs O:UE}шigdȣ&/<>".` 3y$Rնhԃ.m#Ps8Ӽg]0biW!H?m͇`7n䕶jݤyeã8?Y#/zĨ1}"HsW_litP`T_=6beۻPQD^qQˑZ#o&kݤ3yQ(NNk@ :\㋑7/QzW#kXp(?V[>ʽ/}= {FqIC.0(s>?r8Oy-;x: z/#t?;S/s!Vn{hǃ7UW8ǝ.3FyLR鸈Ct5 r"/^x"~i;.t)-p;e׼E.b5#>Cx0_t cTg:]9@剧#]Ky;JI'>=ӕoX;/i䵿1̝._ewA3$O=0Z ϻ\1R.eOK~*-=HN1陇;]sq5ߌ̆Erݫ/F Ƌ鶿Gȧ;]F0!4꟏s&\;8`H띫ME<`Ĵv56GHt5@ƨjݰ]> ?YGw.?+m5((8qm/F._ƈg_:]t59K4-oOꋑ.M 4w#ȟ? ~ݱ99FۻNom;]JM5SZLU/H#<Vjk-F EU0}ّ<(譩tܷ"#<[(bW;(NouTˢ'Qwk|]Qp <Fpȫly/EL:'|(]·_:ސw[,yɵ#/if{tY9-ңw&=rWH~"lQ{>w/}=ҩEc[# <oy\od?#"]v\?_?{Gϐ79'81lCxՍu+.yF|Qpdso "om(1 Ofbs`g[8ʃHSs(wߏh\yo&r#\v?WƱx&O0*J[G^~YI^x*ME:-#Jޭ?ɦO7Y4l yYQ}CGqzrn&y#"]zl?#N~0(~qoj{MV\|tDcԡEOݒ1PrS#P7ᷣj(Nٷӥt%90$+7-}Oz99FzLjU=Q~ɯ>}s^:rw⺳W0\"]Nt+Q=QNu@[Tcz;G^r(N? n\FLŏ'}bLr"#ϵTlvr5IƈΏ☝#~t>nz@[;] @Wx*O+yօ;.o:(w}B 'O"/N߼Qts}O(1]W+W"b:]F|7zݢet93F^٠cҏoGEH?O2zM#ifߥj#}"H_ԭ;] #Tu@i/ůŏ>މ ſpd.Q_j|'O(bǽ'xˍFqޑ^xr}[#/vmh|󗝮`(:0tʓ"sXgt5DES>>G 0vgPvȋG{|:z':z*yy5׍"]ttuPsʵ'+&tҖW"xn+nyeEp%׎r/Gq/=Uqo#_uJ1Ü-(nrt7rFL;h~Qc3~S3xHxP0b;]F?t||}aGz, F^㋝.=zDH0֟7k IDATKF>Er .;.?}_iJy(7'be6iQD^izn^#=V 0^p4``0Ugl{1l}_ҳQpH/=º\^#.a̪nl[2]CLG5ҝe"=?T:O>m O+E >;>uT0^pԃ``*rJrt%0you>=|g<2ba-O7-=S-1r1A +&D3xg"5j*q `|#<a(JG םk<"]{F㑻}t..:z#j4,h#o6F>:G^mI#_D9R5Üj=RJHם]trM"ou`M;2hsEt%?Mtۥzn(wK-~}떯G}ux폈Xc_s*=pN4FlDžs,>I&;֣z}#=D0LUgVa^cMcq5jKSwy.kO;S-_F,f!E*b߻(gNҳQܩ=^|&|,;|%Q\͟<9{ +l1;U;9FT\vQkdY¯?՜:=-` `(;"F˩3bn:/z~~Y]E5!ϼ@6lҺgi[w}ceoW4w7Kк<=|GrB+7]l:xijjOEHםqvFv@y(uFݲOw^=oThYr?DL6,\=LqȦ{|Pt|DTGE8=[ 錃ivI)zlo)M%$EyQsY=|0R`IO==Y ΫyGL2E O|=▋"5;IYu'VZQGD_tQi6W`n=鶿zvK%F_.i w|ځM4|Q~ꨏUZx"=x[w;*bɧ_*$~xzgg#Qt\|+N|8x:owkf>Xyz+[WaKEƦ{㯽q%Q\szĿ.pw?[$"~i+9ETNdS] jQ ?Qݯ{Py-F [fuFuyFDx+OFCg(7ߧ5`4[$z###sV`Xx.Umh~{!UK=GymQe{iYQ]1ީnڑ bZſǣlwvO^V׶f@'G̵d>:׎}mΨ)o$H#/l?D\lʓ>%֊I&y֣} <R\vg&'F(8u`e7lmjʟǻ<|g3T֭7QDS?>GzFש[oGRY&otvۏIQD^~Hҵ;%=PCz( nVǎGT!- w͞zDW>ѻj]>:LzHgQ7TD,r<ēn狼xKYF.ivigr-;_^t[7wL[HWcۍ¦IxNnLhHVdSll>Uy(H\zIxQqO#_uJ:#b$cxҩH(~34BݢyL'}ٍ"Y<[_yޥ;Q~|;>N׵ՎhsEt<2Qpd?.8~mN3<<mutZLĈj4.<'}y6ѴFţهFw-mP+=voh~߉7⠍">z*ls zޔgj'<Ü[p#/zՁzHں1s-yɵE8ϗY=e;U?1QU ֘e^W>OZtוѳy٣ 2I{֣.C~گc sEg{ ?T;sm[/v:t!9Ч_}>"5wu>nϲ@?5#tCS`Gt!xC}4C%>y/]:O zZ(=W>_t,g5۝9u ,GL9CG|fXWmUVoו^{vkUk~xVrU3k {FCP]wx^=RuvofՃ(}"\i<«DKn!߫GjS[qsꝰU\vՍ~%K-mzf߱leɵ8`za7H/??3ܥ솭m^D:Tݓ\<ʝ1j~0lGocs!zgAgτICv!ĽZm/<_Eq CP;|و]c\aRΑ.=6 y͹:[؛&(Eocܟw"0F^r>w1g?⼟G/Q~V$S'{G:F גG*8ja>KnߓM#ǻXlȓN|٧934pUMyݝw?ӏr0H[1Z3?ecF} 3=`d=nSޗ7w8pHh;.k>po}nڑ][gPb(";;RB^h(xWn"](NЮ=)w>_;ӕ'G_ĿB+K=unٿa }EUy^H&<a\uQnQDbmtiQӏcQvȗyy_u8w+#wT3fSD5ޡuY풮wJ;V_{zU9C6h#Qs/(V ꎘ:%GuyM#3˂ߡQet) `ȳ,I9Ose7Hzם9T튝rS#f]f7v]|n~_rֶ~oFy}#c};zc5GvO#^ .zڡCz_te :6;#W.te\i>/]g"/|(x6sc$jqF?t C'ƨkw } v(!ŵgdnX)U?G#pۇ_n q"bsZѻH25v@z7~j^ӸH/<qE:ꇍ}JvtGxhK<,~%ӥ{b煢#\?]*O9C䕶n-#& H1r94=pkZmy#Wc۽~8FqY3`!/@v}aY6V#nK"=萗GMZ랗X+bŚ[8dH9s"^})bIzY^aH|?/t)}g'<Ny:-W㜾w N0*Ϭw&'-y"t+l1d.uz0%NޕɦH r2қc E!m\5Z |Wz?>ύt^7߷ӥԪrF<9YhKy#Yvx?]H/=۶ӋOG^+D"t>]r(_~,O0*5[sϲ@xH! z;~9j,]E{_8<\ڍn7|N1#/3Ћ{+Xcrޖ7a!yR$]ury0gA)j+.c矌l1ҍh @ksD&Ɉigt9dvyW9ʝOwՔ3D5(q7AypEz}tϊt#}o񘈹jc3QmşD_#fcukFZxCz핈[.q/E,y6*.qz&"W(ˈoYO=kw$.%0FQt|%GOEqfr9Nxՙnv_zX[?߿'E(ݵSc>oX~ {6 {~f-hZ3#Hu'<>"(~M_1Kסar "o{Hok(-C7t /pWjg_yٲDz? 1ߌh<5r5w=DqnpQo\qț~wˎtZwzc"/rc#FM'wɣ_t)}=#<rF_ǻTu?m(۵q^5vig9[sx=4۽?`׻Oj+n0ҵg4tSyg=O>mK(.mĿ.hk{0j!ϺP*pY9txوgc<پ℈Sy"/ax(>Qw_|{~/O3KW|dT"j_w~ΪvQ}ǢjqVu6up\v:L6{_/>qu?3=>ĕ$<kgV/vDo#n$+Oip>q(w8n v_+G?ߊ~C۟u^oyH4s*"xsW .?>ҥEz?@<|(as׭"E̽t߭ЫUUgw}oȓO[XVnzPe{pV(}MQ~?{>]I"/v7i,ngt鑮:yZV{3ru#oVU\;|QvzZLWVDocGqVclg+G&Ȣtw_ڹؽ.8*](U\sZD5G#TՇVNW2V"om^W4Y8y)'.b"^Q{!ٜ7 IDATw{ͶV\wVD5NyOE^vO7ugFǟ"=qkl鑻(.%< ]Ot ڮg:] {űF>(~B suZd(>#H=,Ru$0;3?jy:CT@wtroG̲`S^wVˠM]W:]ǣg#"F{D^s_d+u\\zl&ݠ_koEׅsR.#]}j}^6 'jOÛG跑ӥ#nM<+B}GQm,ޑ& HQl ( ذaA R,`AQtS# =skA% ;3I8smg3>")#@gzW'VH*~@t(oY+m^+k{{"Cd{Dk 13c&ʼMN>v}dOz{l؈|OOj}1'ɶ>I Z*&}K  5 ^a0e3_;,vBR$QqZBȹGf):lr57gG/cmݗ}_9qύ=l /xyE?gIA؀=r\*a'dfE{tn#_L7Lx<{ ͸W>9.yIG9/(@f6"3%&ŽU?4'OvP e)@ZڰRˬ֟{I{JO3nLE8]f#2o=$cd5I6#[n5뛤 P<<μ4@RaGɔaG~pO.{VĊy_C⡲{߯/+畻e5x{Ka =om8"LI+Y05c@1)9qf9ʽcfjXuݲc S==g_m Z˿,Q{yٱU橫nq/~PpP )%ұo/&k&;>,;c:Nk@Q<xmoi(tde.9uY]I >-/Ox7D{mt9wIƿ@roge^Q-gOq*^υgՐBդeHuE2~ n0 (bSw;{V?ٓ{GEFu;v]h⌓ ˙?U/% {l2,1n&R*stTye E.m~TDzRcn*ֿlwG|̞] @@g`׆˽lTY~*M-l8nMUg޺\,&}+%+ʽhTQ / NGGˆ}ٗ;}ަ><氓dƣ$e[\Mq+ZZ̾!וysI>b "^{mYӀ/QF^S5Fp٣JBG.پٟ`#EFtm9NK2U09> ;}nXʽM2(E^TNp:lZU&'[^qZ<_ w܌SŸ<'{dRlb#fZ9[v(۬k!j62`\YHqq l٪R庲UJ)!("ؚqҪdϗٰBYʞųGn)2Ox,R͗&ǣRݓO䌹Tf@ȼǤ谅=S_;%Wr{>s$9ng/ [sRg߮c:V>4 Wx/fRtϧ5ߙ|5eϹ+˹jʙ6rcb?^:F:.f'zO0HAfE}{`?(E{J3LJ'׊l6CXٰ[mʹuN@ʡx˽,dП6d3ZxR8 ?&gܵ2n? 0+S䮖ݣ;v((r{J?"t ;J^|jPFLN@ʡxi'ʽm)LFm^i3[+O:3w>2 r.g+ ^;SHH5*r^-ykP5ɑ6h%U?bB|qIH)m,RrORO] j.`1h錢u| rcddLjP07v =|E(P]~l/39oܿ@b ;EN@ʡxH3]X벜7x|RJ6,'y\!ǓA[M H9)ř5e/~(˹@kʙtgv&{olr2 7mu +;ݰe~nd> 3/C&6YwIŽ@ʡxH97e.{Y۞Mj( TnC̛B|1]^e>|Ff㪰c̔<[]&ga P<$[95{]e= &uk6~nOٽ3,Es%a'e/g)'q?']zOeȽEE=3$9_.wЧRRaGݨҀS:fٗr&:S. ; )9 y'#+8v'*.YrY] `Tp[1V3ʙ2$$c=qޕJU 7̒rn\~GɽԹYb9%}KIHY9Bы_ 3D'|Z0̞vM=B׆4WI|0.NӤC i{79 p& flYu?,r~`If ;˗&Uk;?4;u^OK5;Ef|28^=sn Ķe_Ɔ_f KdcKlt)\9? s-VVǟ'+^QLDfWxHۚT%{-R̗swK + MμWt^%g& ;yY`˺c 'IrsBB|fג{Yw6kRnt*d~oΡLtD{у^93 ϓiN yWF[r5Ž㌌~6 ;9RGd=c?pP<v+`Qif>!g`JqθkY,{Yuy^~r>}1dc=BW=+r\xAosw+ d{CRfYO\<_Q=9$-~@FqA{r]dީysՋe=\{`jʙ2$dy ^=!];-нBv=n#d=q )D~ /1r/#sr&^9_]4㹢\{JOȘKJ=fE#{hS=uuO_?vdz↕r %$n3d6)6=_GÎ r8N3E2\.gh7=d~XmK;'fYY_qE1\sZzu1 Frv;{{R"fqJif "*#e>9\sd& ࣴ4u)nذ>kQ6mSլY3(P0H=CRPƎ')=/z_e~,(?P<xysNlrWwGuzcƆlJg ;ƿ])ҿY1xA9kn1Ş~'d歷V-Îgi#7_> ; %bvl 6lv'[̼)a'r<'W_Cv;>J)(@8yV ;X[v g˧^5k ;^VU ._|տM6iÆ {͛7uݰ"j]aG@aؗ=;NRܸq4a„cfK/m39;iԩI_7'صA  @։H g=qm]v}f `M}P }Ǝ'jJRYe@~@{ IDAT<'w.C%p`1?:+ nZ_@^pdÆjL_رc7j}d80)N$ZBdy9-e5{XXٰ̝"Žx]/W\:Hvu,Jއ4@ {rLtO= H y^VqdcHd/ڥ2o?&3gϷ|Ҹqc51}W 8fNi#%oT[f#J=يHEJf @[7H[ˬ^,lic WxA乡x4 vMr"]vQ=d.ymq̈́d})[&@lзgV} xA9mM+$H~5^pL'826"g3H2e~˗6pQ<BŲlZJUJB5}̝^5>j.L,*Y/~Ev עxAm;'lR$oikSf҄.Jed=qɂH% KqrxڶsxAٱ9 9{ٲ-E'va6[gֈeMڞ 6D52L _߭GdI9p-=;vܖ&'r5pΌ^'jN% |AR(1yۨCk>̺eZ{(ON$ 9 *H$UXQ*URɒ%|)\plqc77k׮?vm7o֪U/hw眣>:9ƞ={Z>]6z.AxAy*['yCd~fTYYg TREvu8իWO  ;Vҍ=JW^qE`uYݻl?$_ٳ5Vt8!լUKmڴQSO&˖= H9֦M5|x]Žuek]-~'ӧkuasJx۶o7G +˽I|.Z?w, *6F{6ia1bvlrTX1ا:)R48y^͚t݃cókN3Fwu֮Yr: >^aE+k@zy,sJ<'w侖6Vy&|;Y湾fߗql'9W3ИcU\F%*Dz_{n}?'T5z>cQꪫc3uQnZ|yɀT\E?dlٳ[C֠A䦧 %wnKV{RC0Ov!;$V7MSݺŽ ]tqlܹC^p&rؑuqzl(˗?(ȦN:YK{΁c`ȉyA?6֭[3ڶ9sŽRsJ<'sAk.@.\XΨfόRNr&צNUeŽI ԋ_=p@ ΐWׅIvG//regfk]vᅚaG!xA)ڶ#v%|}e=>$KOUd9TWB͚=[UT ; ?`n6oNz+8Iթsg=#رSlrMwْ?Z=jzÎBBb g}䰓x9=.U؅)ښ}.EiS<'ǍӅ&~y-xs5nHv ;~)Z+UZ-({߭A<9VRͷ^U3 ;sJ.jn ⇤Epך[=ڶRPR_bJaG-[ӼsÎ--ZԴyUx3/|≰֮]{YN_ ;sJ'Smƙ}eO95{j_q_ (b9H$OfE.g ;NB=0bd1ƌ}\խ>(@.]F+VG Y9%<ʽtLFDZ,gTVϺxQGYRpkN͊fY` }˖-ӦÎ.36<'QպukM4TøQZZZ9{Í7aߪO>$…/|rm߶-hIȯV? S 6q M{_檫ŽNlNmxcc!~X\r{ٳ[i>@N_{ߞ~ꩧތ]gw쨣>=6lX㢟ߟ~}"$ouTҁuiu߽>6.b=ēu_uxSE};;dPsQ]^&?=C}5iZ?:;ȑ#iߌ{)=s7;/{xs@͙3G/{ 8s4ax_u)k{Vnyn„ G`C8yB fލYZ s||Yȍvl߮]赩'}֭[LM6;~z|X_F|^:UÆݣnJMS-ZP$_>N"{,mʕ$}Ӣȝ(@ym2ݯmlr?,n؅^k7YO{Tg &rƠ"g?!BF|YK/e] 7{7i.B?--">L+K1jϞݺ꫓.wk~ٳJNY' j9I]/uue^zI_ۯ@(@)(e@_7e*}s*^Trar?"U-{؈]9/*e @9P-cbkjOdSO>1N8{֬(F#G~em$+:5h`D~(#(ozNÆ}:*R~ߺ5Q<ZGpTn~Ѳ4௏{EIwlX{=}Z|6Qd.=R3nNX{UZ5_֥)}o/VTɗuS!9e+0>⹧bŊ.sH[FtQ͎ޚZ'JmZ^~QevpG:%KeO>ėu`ڵڶw.\${vޥ/><"6_/B uSky̙3}YTR ES(뙌ןi== P`Cf9]vU.,YDuKꚩPx,CK&}MSPnq([-(@!ns+N9.qOfaG@ ;AޱmemtٲNo]}}#1g>5bcۏsٱUTBQR3[U^{ߋC,'P8uɴUf͚*W\(_^e˖U+/ts>a @'˞~]1K[7U[w;}+{E\ę1Vm 7Hn9Z%tgԬY3,Y*HJ$ ;G Ѳ  ;J~ȃ煝"e8Of'KYr@ƺ rii t 7v:a ǣx 0{ϔ{;!g̥aH9f*En=FvS= Ջ(: AfR9CN{WѴmZ9w"k[IRcEԗ-VV͓m23L itnE"5nz> 2  ;NR+Nc-qB#ۨ챝ZG'U dc1ȓ(oH[H٣6h%(^Ad~%3o/?/^N[(/ֲ6$5:CWex Ƿ7=.۲Txb)lB)/Z^y-\@ cO?Z2 fKŽ/P1=$i܅GbͫCcibD6B;2Xe>L0kzi޾c'xB|6m~$ ~1nF/yְSdfsγ]-2nkOܫu6* +G֭}YA*T8\M6jʕ$}@ xy3dvuVf͒+|RZ!VWȼP0,905ir/k/ZȗuاJ֙g)'>V9昤y椯 kgzz1#<җuy=ran0RscOBY IDATVOG߉b*9!-80?|"4G:LٖW#;5mᾭ=ujEF)LJ.O/ @<þ=(@Rt"d[=|)-m||*ɧ,m}̔!{N˓^ugMy*{}lJ*{mAw߭=zv<9ۺk|ϪRjիn6mټ9ik"~=z'eK=6z.$]0ax9O2##e~Aniٗ2˿V~'x̚قD?zTyv#2e6ĉرz1H*^o{=xA'4k];wթSu=db- .,{' .ik">{FO9_lY1 MaV~/}>M:uk6?ؿedD.WX1=#ܱcn/ᝆ}u:蠃}oޛuӪ+}{pzgU`!]?_ԘcUP᤮{݃sN=pI]{+_f͞4;ۦMݧ޽P1ed}ᇁGz浱6w\iTAGvyy&wxu݃m3Bٿghm-Zhű6A8y۳f}/}ԱS' ._G}WL l?~nzz소gŞݬiSmܰ!eJu8&3s{k _@`GݺuӰUt]f9A䋯eilroxE*}~G7w?%Ke˖:uk+Iwf;Fvt =M7zKoMӧk)]6m㣣]v*Qd(9]t eonwuj-{@׮]֮[Jgmri.b9RE}~wdGuE{YVh9Z4o{P<-ֲ=l+{YkOJ-.s_VZ *s=CuaLjˋ/ ' 5G%Թ9;9rŊc :y|,W,]{wގs{t-6jk{yXϦMUF ؾ==sjž^ QT)URE^-[?l߾Msէ~Y} #(;L 6GO>9~P3[Vj[#M|%qđa>xZy밣 xW,i45س}9t?'x>Ӱdw+}嗱S@]x&v ?P6]=H*TX'pbl \SLVŽd+ڹ3(@(ҠU|-}Jc/po?ٺ!(žsۤIE%K)SԮ] mٲY7h- ; }TK.kS'8릫Sǎޝ[5}}4ϩhP@%DBH322"DJRf%T *sݵ!9g{zoO@^ۢx5neۣ\Ž#cNJf*$_D0V^=]tر#gg0W3f˂P=\_A W ֛o_4I?M g+@AE wp-?,=>ax.OQb͗NYZ3f7|)~)ShڴiZn]ӧ== ҁ&MaFjܸׯ )ieᅴ:5_f갣Yd}`lLٲj޼>೩aÆw}eRҼ,͟?_Sf9aG?H';x\в!-sޭdoЮ3Q'~UR%@Bl{!:tyg+4tȐ`M@Î4iP^C;t;x݁-BENe̢דiq.Es$s(jپv̬qG^Rh) 5O킛7]fꇩ]9`F3Ҳ=a"]vvUkZ yHfl9dlO 3KE#SCO-_lo$:UfW2cߕY8ys(\"t]!3klS~[wk{Vϰ]xTeq)l S եJ&HˬY*Y&--aM PP58N*\44Z>W\nr~~aDy_&MP<|JIV4&aܞ^Tpd#eȧ|&9(@HKel \#e/{)_kSHޮMweEf%2[6n=<9D ;Le;⊕9B*wz{e/ӻ6@xPb-W:aMezS2FNIYS䩳B&P-'в]PŽSpe];@N; @Q<qlsRy+c{,ۺO (@?Z5UK3^Y䖫XS;L\G*_M&dmV/~"3w̬12g ײTx1vNLn32vKQ҆9c.RH.r'\*'v#K2cߑq;*2=\U W{KOorM(W#:l3F m(@> +($y^u wzPH^Tbro\y++:^+d~5+N!p%}( pe+^Tfzw$}gϓYgzFUo({[Rᢩ_Tkߑ6m2s&~M(]T+;e;@ZQ<|\6VOiHy/].4Ⱥe~/\lYy\aŸdO;@ZQ<<][.ШCw̲aɗ_)S#uyw'ycY & VKu ;v! (@j4>0Vw|)d~.4nPK@E.wTtQfPo> ;@Q<<ȕ"cP1eyWȬZv|ö8OV?۫RW}'$g-tCPB5& 5@X(@dϸ;E|"}. ;I<3;:Kxb%׿/K{t5m AWTذc$zrN)jk+%~uI Or{#{U?|/z/Q+ @1aW ;ΎV.@sc|lÎ#"{M_]fp3@ƭe{,!](62'.B(Yޝ-dyWySfLEn:8VDy‣d/Y+2<"cN)cv;~ɋ$~U=_ש xyX7@I{d>}/SP@E;]Ód;ޚB?{.9劖[Iį<. ֭M2ߍ}.3: A5=s B3;Ezԗ;sL~QefrFk&ӑm Ԯ|Uzͥ:GUWPC*Rd8[9 4g x@d֯vk)։^= n\VfԳAyn:D?GnTڽ/#5j%[N^EyXSsvybEs3ys c/v;۷mse):\rg;= .?u&ϕ('{RCR@==wƵPP<2-ݡd& J޺V?)U9 /^;>їKZ>/V4?lo^Dfư (P@3~iEf,@̸s.n6'n2"Tذ䌿C;ׯf̏_,_Fd>/U[{3_xw?!&,$(&dFihy_ :4Me@:E{d&}&/9&c.Qc\tSڴNZ2i(_Q+e=u2s@h޵e/y>9;bcu/Y8.`bOc:ͲnW}]_v.ix?k~unuhVJ^\t- ϕH'Rr {Rr%-eߦ}"-<,Β2_"r0+źmnٛ:dH ?O&1-2s@YnnS<%{{~x!֭I~rs_%{Ҟoᕋ=rZlW6j,W\RS@;!rw[~>zlѱYwzA J y c/PGɻ~ lr'L= v}!h|1'XSyrGt vJ'#NYb.yϓeC]պrZuHe Kw%wnN]\Gק>>t?]?=Eޒn|oel{d3j=Eŝo/_@A ҐDru7W#==M|?(ό/n\{ՒHש K)+ܩ# Lf\̟ }KUWavR5Sm|HږtEKT<4R%k̔e>93]iBwZ<6vH_PAç&9 \S8;A>Ϡ. ΙΎ;WriN^~ti<2_*3aaH};\RO.\T2(opY (Rʞ~{vE:y㺰d<3n\ m={no'4{,R(u¾_?5 vU !a s4stʗq(:4$Ŧ" aPdϼOY]KդOY: *)sk6;N}!OZ)-]2OOreعqx/] Dǿ= K]^po;5 ֌~#s8Bٰ [=_ϦrUd"ӷIIG:j3Rrlf$K?۸V>aG ثߒ0Hf}LPpnnC,s5?q$E^@T3:Qg?^zcG ӻ_I=UJ;`lgO2D`Z?C^zWjwrudlV ( d U˾0&(#0wrpnY83\ΌOqMwPM6ʇY?#93#E-|=Hr|y/^*?2?w]q(IYo7\r;_+בkw]0"d6OzyYyl&mR]d~&3i㟯y>v}A*S)H19)W YZnԨUlӎxn#׺[Vɻԇ*y<[k.$F*ǿүSe~\f~yY0C/#{R_dzw;OKKP0C,%uf0pV[@Q;˶^dxZˬZ9 4MM?Mc=Z*V:mX;vٕ[ۥ',`r 9'3̐e֭}o8sx\ӊ~;OܼQ޽'l\L1ӿ FA:+ygf~-eLL=k1 m[ ;&mz-?'Mrmc+{ԧ%o }Hn{7 ~rx_NO@s@0'#]H>]r\#w9;?'.DszocNf2RrsG.ifp=?EfҔAea%4׼Cfo6~xѱ8;a?JW;M<[?VZO*[s@e]'hsx$ӂ?w _qf RtTQK{_$=o&2ȵ$9iG)sρW\N2K~IYlY&VX޲I^ePIbV-Lmuc;l.-rBU>gϓu`o.D|TfnT[{LdKjLa/rCwz](wIe{}"%no 0gCC5g<)$-%`lecbFD?{3y=Ti8|;\+W' x_~[^ɞqOo;0J3~םdCIu%>WLc3!&Id[WM(1h+4$9;ey+^VCO;yrǜ޹-Omޕ`y{w&m`%T4%oभ n&{ 5{m#??R?TN,VZY`lgܠn&Eǒ^y#88 X'G̰Gzì[!Rt8o.; pќݨѲ}{gyޞgϵ5w+%𤠥rn:q}/;=GW v'Uk}FiX;os40> ȧS(3&3iEaWS?E*U!y V%׺[0V=Nf`yO);ճ!EgV?x@`6N=!W[CclWMu%U786nˌgZ;-;}+yK\z]sϕ w vk6Gԑ&%#&4{J)#R(2|mV= K ?olXDxTH9?_[Ok)kiƼ-{k񻚔.G\&#h({ƽ9Q\绂!%kUi] '^j)WUNo%L@޻w:~gvE9'#>z.k ]8*ϯЩ2>Tblѱ-WrX7<}$go~Β_Vfrt,NNwףMÂ)S\ʊ3׷OP< i]*sċwa0?~.39ZZH*wY?'4D<׺ş `yZ#:eEGp>{ #\#5O^J-e,K}0̚?n6޵zpϞ$o5Z_s& Y:Gt {R6^bF<)o[ Q< 5x ;\H_^/%^2s&&7l.qN29i'i7ޒx<}ȳs"[NtP}'##mmO%Ocnf>2f {T_+Qz?7<'﵏󇿫o}4on̕(I?u¥?CifH1y>):s1΢irmno^]!3yIzޜ0U\z9jem(Γi 9aڳlIlyUo(c`bmL/N}(Jpnѱo޸MN;}5Npڕ%,Uf޴zmN2;ѯ?8.tiBE4Kq۶vu:P=g7*QV,A]yCCg4{BA$tO۲ ]O\glvt&߉3vo^b>AGk>#0yVp%l?z#إޤT~XC.cB\+[f @AE_)rqk~gܓۊ;`%o]2+&9 N1U\˺ "3gb!_C5)$o~w He|]=2d n.otwtEp6mT+}@޿:"3ipb'_4[K|S]JZg|fͲA?~Vڴ! ?/&k4 j&hZA.<~Nvx@ 3fӒ?|j+=-KU;eElQD_"s.Y-o*זDxO%pksT"]v+%S" sB @<5.r]Q|@ BA<ƛ4XGyl{5= ˝|e0|a2Y8\]_Š2'$^A}L*;Rndo&-;IH}(_:aY]*C͡p}4o|5%C7E\CdL;37꙰#{c$}G[=SY#1f 's#/t ^\+<ɋF>]HH5%9mι.R'3e~gg5:Yjxb%4DOÎٖȝzSBsO 03FK+Ke*%e& ;@((Md+E3Fsb+lT*TD@\.2e]O}D;*Ք¥q[#Yz_"2 yw^l12ٲ)x@>矟m+#k..i}BN)]Ζ=JB$PݟoGe]T&nw)2>  3m#eצ7S9"wvȞqObE :Q{yJIF(T$8=?v̆5as\~,e>^fC(,\/J ;ND ;@(P?)x+Y^;-Tgܭȳ'^~a:r_XS{yRopS+rOcR-qThɿOPSymwTnQf)$s0kɼ~rڜ[}^r'].wD`'n۴Af2~eLP IDATxsM,ee|=sl-+s&&O[6WqXQ ;N' Q<qyhdPZO]jnwPj69P|UXip1i*ˤK_/?|ȍ劗iΑ gs͗BW3vw#u7f=DO\ђr-wag?V-N{%|;\냇`;fji>,9~n"O CJUeo&9'F<\y=XQ3owy8:AM-6oOfԳ2koV q:bAy ~Q+EsNTޘBlg}"kMYѡ~2+6lɺe~h'u,@j4mTO }XfONUD\O cj4" ۊE2ǎ&Alnj{W/҄ȌIL<,_mc^OV߉OٻFK[6ɼsi~=do!U5n> Ƿk}u(d|-IЇd4W1K$ xG t8H+m\aGƵ2vfcRtje/y^*W%(,wʊJ+Wɬ_ҼJIx*w+ͩ">;PJ=kkˌ|VyaOV/waGٹ \"+uWN_\HW{/S4M }z'ZgM}(d,Wl? !;i#Ny}n\/wTl/k{O TC RtG˞T<׾be/%ve"eS ӦmFZY/{'Bqkʵ!׸uGdgW><9%-rޗu)R(8Ҍ@ٰd Dn:Xh) \&)LʞzT,kLČxRf< G̴Ql!NFX397Y2ɌxBfܳ3+)Pmue*ѳ {潻o7oGɬ[v !3M:Žw/v`P<@1GP=YTf݊S䊫uXvL$Ž$s7Dž#!ft#^ٽNk7H(ϥdž%{|Sya'صEN$ s;Y+2ys0PCϰc(=sټQMeoyh#װ$YLfȃro;vGʬ_v i(`͊ul{v3c;햰$ڷt aG oX1x\޾Enc=J嫆bZyO)ᬟ/KCa>yQCsYT;)!{ѳRݣӳxsҳ^ȼwˍ~#Bϛ_Zޓ]dH@P<@Ҙky൫P=h:߾zYs&!Z >pT_!j iNqʆH,ɨGhp)w]pr?{w{?XKov9G?dxX4~+l=ꙙ_6˝YͺqcoE^w~p?ˏg]:kwIo O9zpwֵ3ao֟:hf7^?;'[ެ_κŬ{ߟSL<g:xf7 u$"IENDB`jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-black.psd000066400000000000000000030756231402514743400225300ustar00rootroot000000000000008BPS:8BIM++8BIM8BIM ^ ^   8BIMnormB8BIMluni0ST8BIMnorm0Layer 18BIMluniLayer 1    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ? $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$0,44320100.0..0.*.,.0.///0001/07:8;7::=C99:ECHHB???@?=>>@??AJHIFCMEDD?A=<=?<=>C=BFEDGCNLLKLPJKSWQWSSSUW]_QQSQQROUQ^Ye^WW[\URQNKSJOQLNLQPMJQZTMPVdfe\c_]ep[_[SOQZJJHEC@?>9???>:DDBJ?8??>=:==EJEMAIHCCHNHGC=J>>?8==956B:7669><:<<<:<<:><=>=<=I<B;>:<:><:=>?=B>4264626248><\WRbYSVRTTTUQQONNOPPON`STTQOOPSRQPQQQQRX`SWUSNPRRSSRRPNRVTTTummlkijgdgcdjigdhgj|}yyw|~}|{}}~}ztttryttsvtrttljlnpvt}}z{{y|{y}{{x~}w{{yvyzxuzzxxxzxxzxwxy{xx{}|{}{{|mnplnlpmopmoqwu{8:::88;<=<209::9658888426888CC><><<>8><::<>86:8898=>00.26647665:=@?=@>??9ICC=5998964;=EAB<<87489:;57;7659@A@JMNgb\SXY_bWOPNFUHMHKGGFFGKKGIGKJLTYZV[][`QIMPRQQLO`ZWWSSWPUVWPJLLKIKOLEHAAFFE;:==:??A?EIKMGJIFMBAB@>=@AACC?AMHDD?<;E==>;9;=9220/1/100.022022,220222232463,/$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ -? 'C]v#?C3e4?d%+u4?d%+4?d% _Ä4?d%q4?d%a4?d%)ʁ4?d%m4?d%4?d%Ӂ4?d%)4?d%-4?d%4?d% ́4?d%4?d%f4?d% 4?d%4?d%Z4?d%؁4?d%`4?d% 跈qaO=+ ? V Xe?Qa ?Q @?QZ?Q?Q%?Qk?Q?Q ?Q4+?Qbz?Q ?Qj?Q?Q?Q//?QI݁?Qc?Q}S?Q?Q?Q?Q?Q?Q?Qt?Qc?QR?QB?Q<?Q<?Q<?Q<?Q<?Q<?Q<?Q<?Q< &)HuriaYQI79IQYcmsusme]SI;!uqiaYQH3Q<-]ɪ}KMϲU#ţsAQ<gi!%oy/ߤ]Q<@׎5=۪H˂$Q<}gmӤ{UQ<<%u Q<,{ cQ<T_u:Q<OQ< <FZ%Q<ԹVd8Q<+l}IQ<;YQ<.izBQ<JXw*Q< ˴0<UQ< Q<N]Q<7GeQ< Q<1_p4Q<ǿݯi +=M_q ǿدúQ<7a22{evW) .]?aK^?Q<^ D^[l?(/Q<^ r gO?]Q<^ /?JQ<^ ́M?Q<^ yR7?Q<^ 48?NةQ<^  2ŞQ? Q<^ )b:Kqk?Q<^ ?4L]`C}?Q<^ HWTe2?q'Q<^ P/\m?J0Q<^ Xdu?08Q<^ ` l}?@Q<^ htڜ?HQ<^ mwɜ?KQ<W mفw?KQ<p mȁw?KQ<p mw}?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p m AWmw|?KQ<p m1ow|?KQ<p maw|?KQ<p mOw|?KQ<p meקw|?KQ<p mNޥw|?KQ<p mw|?KQ<p mEw|?KQ<p mxw|?KQ<p mw|?KQ<p mw|?KQ<p mhw|?KQ<p m6w|?KQ<p m w|?KQ<p mw|?KQ<p m)w|?KQ<p mw|?KQ<p m#ãyk_]Ew|?KQ<p m܊Y'w|?KQ<p mCw|?KQ<p mJw|?KQ<p mFw|?KQ<p mw|?KQ<p m! w|?KQ<p mWw|?KQ<p m(w|?KQ<p mw|?KQ<p mw|?KQ<p mw|?KQ<p m=w|?KQ<p m!w|?KQ<p m*w|?KQ<p m3pw|?KQ<p m=Rw|?KQBp m@>w?KQ+Rp m@*w?KQ<cp m@w?KQLtq m@u?KQ] m@n?KQn m@f?KQ~ m@^?KQ m@V ?KQ m@ N5?KQm@@bq?KQ,}S m@ =&W?KQqc> m@k =?KQpIݔym:,$?KQV/.m1>i ?KQh<m)O?KQ:m!bX*?KQAime?KQ mТ7h?KQSbzmf :?KQ[4+XmJ=?KQ  )mZ ?KQimU?KQhkFm\{@?KQL%=m, 4x?KQ URmt?KQBZ m, "R?KQ.E @Wmc?KQQa  _ym7%]P?KQWZa+gm c {-?KQ );M]o 㳇qaO=+ 1CUe~m[1 <?KQbSm9I5%- .5=EVs3?KQفmi #n~ϙ?KQZhmoO 3eϚ.?KQ m ųӏ\Ϛ?KQ< ^mlϛ ?KQfmބϜ9?KQ !mLRϜ?KQ ́gmwϝ?KQ1m ܈Ϟ ?KQI-m0ϟ?KQA)mDϠ?KQ0Ӂm[ϡ ?KQ" mOmϣ?KQmm7.ϤH?KQ=)ʉImϥ?KQu amg,ϧD?KQq2m<ϩW?KQo _Õ>m$9ϬM?KQ;+{muէmϮӀ?KQ5+uy1m6[ϲi ?KQ˙i9 3eӣuEmPⳃU%Ϸ껋]-?KQƯ }cG+ 'C_z oU;!K~ )C_y̯ oS9¯ sY?# ,4z8~ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$0088654324121210100110222111329;;==?@>FI??DNJPSFEFDFDEFDEEGESSPPKXNNLIFDCE@EBHIKJKLKMPXTRTSUUX`^`ba`aabon\[X[X[\[`kgqle_hlc\[ZWYW[VWUVXYZYZd[]Zfoqupkkmrig]]baQPPQKCGBBCBBDEHLLOKADABCBDHKQQQISNNLMSPNJKNCCDADV:;:HC:;9;S?@@B?B>BB@BBDCDGOHFDDAB@CABBCDCHB:99:8:9::8B?`^Xe]YYXZYXZSUSSRTSRSRbUTUTTTSVVWTTVWV[agYXWVVVVVUVVWVVUVVVVwsqqomlkjmkjmmkhlmm~}}~xwwwzvvv~wnonw}}}}}}|{||z|zz|zz{zzz{{{{|||||}|~|ppqnqrsoqrqttxx}<;;<<<>>?>44<>;;:::99998899=IGDBDBBC>?B?@?@?@R::;;C@222L99:;<:<ADGGCDCFICKHHDA==<<<;>FNIHEB<<<=;=>><<===>DFFUW[wshddfjqg[WUV^TTRTPPNNQPSSSOTTY^abakngk\WVV[YZYWnme`aa]^`c_ZTTUQTWYUJMJJMILGCAEADGFJKRSXPPQRSHFIFEEDHGIFHHVSLNH@@LJCBA@@A>943544533222332524436466768<:./$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$  (EW`f[OA! 8mh/KA3>GJ?4' 4y  8kyT*WJa~@ oZY9qe7ndOR)E4j8 f=lTW&!ߓ<ߓ`ޓPޓpޓeܓ16ܓ(&ۓ[ܓMfړ=ܓjړsܓOؓ ۓwؓKړ ؓoۓ.דۓE֓ Nۓ|R֓$Cٓe^֓-9ד@h֓4.zՓb֓-$rԓ~X֓$hғAK֓^ғ /ד SГHדyJmzד_?v(5!O֓u54W;Փ *x?`KtՓ5 nJ\I)ӓOeV A.oӓh Yarfӓ{ Pm3vs&>ғ&Ew)#S~Lc^?<5,>GNF;( j9,Z0|@@o &uK0 njWqzV0 `b4C;H=huL!Vn O %bAxY Kw)K^f_*yv9VA5y /.e" }D7@b%K87b-zK $c2RRv"qWwj `gb;Lr}Hv[n  \*.a Rx*R5ACޓDH6r*\dݓ >Ai}rܓS4L +\ jkۓ)vX}R3 ٓU ncBE8LOٓbn  ~!ezؓYy,Y.vyדH %AT^f`SG9 N7lb%;דp8mZ.ECoH=NדZ}99N 2U.V[דH-0|Y n i֓ ~Z%seI> ov֓.~jo zX- "FYfqri]C&q֓@ԓy,`&>F(e_)c֓2Փ7"e&`6{v-Uד%֓Cvzukd Gד דN 9Ng%*֓*\ؓYNݓ3Փb&ٓeP74ݓ1fԓ?vړp"sݓ ;ғu"ړ g sݓ  ғRbٓ;)^^ݓXHГ0 ٓp|Eߓϓg.ؓ@G,ߓcAΓDSד3 ޓmߓjy!wדOW0OݓF ~4.W דjm~ݓvKP5֓unܓ NWqk֓~0V3ۓ$>;H֓ Bۓ<3& \|%֓|G?RۓGvX%|[ ֓wz`ۓP(fG9֓R ^(^ۓMkuh iolӓ~K!fUۓD!$DR`n%1LOГwEtGۓ6aoAS)&Γp> 8O+ۓ+d]u`̓i6ܓVyHy>>Oɓb/N8vݓf 6-`sǓ[(uFݓ5K(}Q[ēS e!ߓA9J.ݓziL(_[ߓI@nUmfGޓ >qyFz LRq5Cjߓ>Fzq> ?HG45 7 Ww  }eOj7  udyW wW g$Wc0V1w *2A3}g,_[)nnawNck r/5hT!lv \i,GF  =pN.Wk_cl@Nz$kgFyzG ;.(%o[ [<Nr? E@ J?Yw *87 7[zm"Vk8 |/`X(3F Zo &21'nu*_e1\)(3<<3& Neb#zKL4g\) g>J}E~(2# Qs$2!Ts@M9{ToZrPM*^m9 v-Sv;,um3fe3d#r_8 \eB ;o^,&`"%|A=DwW$,'yfi7GvhyK~P!Q~I%yRiT  8Zq|wiZ8S!T|H N\t]n13 >xuD,()\uAD2 "*0A SiE?W 1en; s-4_j? Q~&uP| }c ;nf3&J7J ; .>ܓ78Cv_,(R$z<FJ`ۓR-7?9/GJ~X% J>[Fgf(~ܓH@ouHqr SP +Xr; KJޓ-ao(H([|v_} ~Q0 mޓ [m7 0cɓ[Dk0'5ߓm(;b} 9m̓*Tt\C jߓ'8S YAu͓^%q^vߓtB_'-J~ГJrifUz$ݓ%1OQRғ, ߓ1"9VݓR3xi'ZՓ[Lޓk^rݓ}u>0cؓ|ޓ-;ۓBaA 8kۓܓCT{zW.ۓ.kvۓ@ܓd 8_rDۓ=@ޓ_Oۓ`ܓICNۓKwޓ0%ۓ%nܓC(TۓWܓ5[ ۓ}ۓuL%> 3LۓO?ܓ__Vۓדe<NO=ۓA\ܓ|"4(iړӓ|S,3hk0ۓ3xܓK9yדozГkCwYMۓ ۓtnKՓOl̓Z3 (1ݓۓD^ӓ\ɓrJ  8YguynS6oc+[ݓ^ۓ;0pѓ|7œa9Ds G0ݓ3ۓe{ Cϓ@ “yP'Anrcߓۓ  UTΓsh@$*V~<ߓ>Kۓ**'g̓7@ޓjCkW.<y; ~9pٓiU 9yEGf,T~oGW[ $/0'^֓E{eKp0c ;_=e^5 T8?NPKӓ:^^r|%NwvN%?!|[hj9pғtE0pK s67``w uv '^ϓ1os Cy9_|.QGpœ^D_`K͓|KTg'=]Fi 0Yȓ)EG9p̓'4!'gS[q";WAi˓e8ij%[tY_ ~9ENK9(9y@OX&#=)R|ΓS#no%JyC^v b/Kn. @[r|zk\L&^q!0"Enۓ!07nT3DTb$Pۓ.L%[g0 Q\'gPZۓ<sg "-( JzC BvNyAy}>bۓIX;`nK7nT 4LcnupgV9fϓk,ZۓA< )h|>%[h0=ГYKۓ4!(&z=G~{EғG =ۓ'D Zq5kW Iӓs3+ۓ}_."Yi3 ֓` ݓ|b|*HG~|E3דNjݓSF3̓W `ٓ{<?ݓ8*  Γi3ܓi) ޓ;<ϓ|Eۓ;Kܓb XnѓW .ۓ? ܓ ks0ӓi3=ۓN>ۓDPՓ|ELۓ\^ړh 4@דW Qۓ^sړ%4yٓm3HۓR~ړJPؓ0=ۓE kh~oulPؓZ$ۓ2R>_-Yzؓܓ'sn8R>דsݓ|.vg"yu"-!֓.@ݓQ?ja;X4H1֓< ߓ#"1Z~d;֓J*ߓfs{cC֓V,ߓ Q<G <֓S/ޓU+b ,%0֓J1ޓn @$֓?4ޓJD\֓%7ޓ*%j mxؓ 9ޓ1 h&Q[ؓy<|CJ55ؓH?L\  o9ٓA`p b-TQړqDNyO<Svqۓ'H 2GS`cYN4{v[VܓlJZ4?   ޓN4Z$1DnkA.ޓBP~v{ M K}zGRiS|S=iFC`sW{-c e2,ku Yy q INGTG[w LD-mL%fb9^u 'j &Owqf[ar  j& SV j\'tcqFV/Vߓ@Lqeo!ݓJ(ߓ% 1N\gnfZO5R4hl dܓ\ݓw jj?ݓRHۓ3<Jlh}ݓ=ۓ}xoe]ޓ<ٓ6%`qc7ߓrsٓec" r`ߓ- ٓv u^ߓw Cד)N9 w[8ޓQ xYjޓd7OzVޓrt /LcktjZJ:|S&ݓ!e 8mvF#}P?ݓ_(DLSݓz)J\ݓzH=GGbݓl aD[ݓ^2T\ASۓP"$%&'()*,-._דBoLE>ۓ(%דj)^$ۓٓ~Z,)ݓ_ٓS }OiݓV ړCC ߓ=8ݓ%qۓ`~ Aޓuߓw(ܓ-Y~ݓHߓ5^ݓYkܓQr ߓ} o9ܓu.&T1\ܓT@9, nۓk[G=>Gzۓ"q^:(zۓ+bT$z(^ۓ%C1Nf {ۓcX @Tsmۓ  [SmAO6Rܓ 4VwqT*7 `irpbTG0  &1.%5ܓb{W3Y~m> 9L ݓ=.Kevgޓs_A^#c1ޓ`%v-a%}ߓ!ji$Gw.e$/RK;mbq B ;,-4Q"SXyl r3c5.Wg(YP~\){> >4ޓU{JlGiޓ E@Ts,Gݓ4  8ܓX/V(4?;0!PVܓ}m5rۓlyYۓW.,ۓ ~qbۓ%@E$ۓ|ikyۓ*[&eܓh_sDܓqq.#ܓOR3W{ݓ 8,Oޓz;JN>ߓ@w  &B\u" @>BX%`2d3)& F>c$1c"*t3 JoJ>c$7eu ,EVfvyq\?"*36u >c$\|M;)[^" ^Ä3'}R>c$mz,p3]>c$o7a `3ub>c$Zws%(ʁ3n>c$7K!,l3 |e>c$ Vg^13jD>c$UYӁ3D>c$XnZE|(3m>c$]OjZ,3\ߓ7>c$bK.3ߓ{>c$fGuߓ_ ́3Cݓ&>c$kB&ޓ3zݓW>c$p>XݓEe3ܓ>c$u9ݓo31ۓ>c$y5ܓ 3Nۓ*>c$~15ۓ'Y3\ۓ<>c$-Nۓ5؁3gۓD>c$([ۓC_3kۓI>c$$bۓP 跈p`N<* `ۓ@>U ^ۓPWdTۓ6>PVۓG` Eۓ!>PEۓ1?$ܓ>P +ۓYݓu>P ݓjݓC>P tݓg$0ޓ>PEݓ.jߓf>P  ߓ@>P$vߓJwW>P) mߓ3* >P-{ޓ,ayC!>P3 ޓ^V7>P70ޓni]9>P<zDޓy F+>P@vXݓm *n>PErj~Q..J7>PJnyJ)zy)H݁Cyp1>POi7E~8b =Zs~xkT0 >PSe,%.~P|R>PX`?~>8>P\\Tq O>PbXf__>PfTvJor>PkO6b>PoK*%Q>PtG<}As>PyCOo 0b>P~>c^. Q>P: sJ?A>P56O;>P1&$`;>P-8}pp;>P)Lo `;>P$`^O;>P  pJ?;>P}5.;>P##0;>P4}@ ; %(Gtqh`XPH68HPXblrtrld\RH: tph`XPG2PHo P;,\~ɪ|JL~ϲT"ţr@P$ \^`;fh $nx.ߤ\P( nJqn;?׎4<۪Gˁ#P-|4^;|f lӤzTP2!#N;;$tP70}=;+z bP;Do  ,;S^t9P@X^0;NPEjH@ ; ;EY$PJ{y4Q;ԹUc7PNv#a};*k|HPSr-}rm;:XPWnAo \;-hyAP\jT\L;IWv)PaegH;; ˴/;TPfav4!*;Pj\#1;M\PoX*|A ;6FdPtT<n R;PyPQ\b{;0^o3P}KdHrk;ǿݯh *;l8PC"J;R H L>dP?&|9;!ljFW\ `w>P:8n !);j TT>{P6L\1;F;>!ܦP1`HB;E\"k|?;>|?P - q3R;<$>ZP)~"cy;/!0>IѧP%$|si;&E:DU&u>bP 5n X;,xlnB5>]PH\H;]>P$\G7;]"T />6ШP( n3"';],=`J]>P-|!3;]C]Zk>'.P1 !|C;]q fN>\P61m S;].>IP;F\cw;]́L>P@ZF+Rvm;]x Q6>PD k2Ly9;]37>MةPI-y!J1;]1ŞP> PSo> |f;](a9Jpj> P0ob(.m 9;]>3K\_B|>P$vfAZ? ;]GVSd1>p&P[HVF:;]O.[l >I/Pzۓ2 z ;]Wct>/7Pۓ!Z;]_ k|>?P ۓ{R ;]gsڜ>GPۓl u;]lvɜ>JPvۓZWޓ$;Vlفv>JPKܓFޓ`;olȁv>JPܓ70ݓ ;olv|>JPdۓ@eܓ+;olv{>JPۓ )6CP^jwړN;olv{>JPXٓ4 )7EQ^kyϓm;olv{>JPٓl ,8ER`nzēz;olv{>JP)ؓ ,9GTan|;olv{>JPMד)  .JPjדcJP};olv{>JPi;olv{>JP K;olv{>JP~pbVJJP |obTG;. #ݓ;olv{>JP |n`SG9,ޓ^;olv{>JP ym`RE8,Dޓ;ol @Vlv{>JPmʓ yk^QE7)k;ol0nv{>JPHՓ wj^PC6)=;ol`v{>JP#ؓ$ oL;olNv{>JPٓon;oldקv{>JPTٓ: } ;olMޥv{>JPۓ )|;olv{>JPgۓM m ;olDv{>JPݓfI;olwv{>JPJݓ93}X;olv{>JP{ߓc5n;olv{>JPy9Tp;olgv{>JP H);ol5v{>JP*~"O;ol v{>JPt u;olv{>JP mQ!i ;ol(v{>JP3w>k`3 D-;olv{>JPDn/.sfS;ol"ãxj^\Dv{>JP!VrN!q^y;ol܊X&v{>JP ,@JSOD7'S7e ;olBv{>JP jQ@1;olIv{>JP}gW;olEv{>JPuW};ol v{>JP_=a;ol  v{>JP?<5;olVv{>JP n[;ol'v{>JPNߓ,;olv{>JPߓz];olv{>JPAݓ89;olv{>JPnݓL_;ol<v{>JP ݓz;ol v{>JP1ۓY;ol)v{>JPCۓ!4=;ol2ov{>JPRۓ0c;ol<Qv{>JPaۓ?{Aol?=v>JPkۓHU*Qol?)v>JPaۓ<0A;bol?v>JPVۓ. gKspl?t>JPFۓ w\~l?m>JP&ܓ Q ml?e>JPݓn,E}l?]>JPmݓDjl?U >JP4ޓsl? M4~>JPߓdN#l??ap>JPB(I+|Rl? <%V>JP|`npb= l?j <>JP& ooHݔxl9+#>JPE"J'U.-l0=h >JP`>$Mg;l(N>JP];r9l aW)>JPR/k@hld>JP+rE+lТ6g>JP R> QRay~le 9>JPmr8vZ3*WlI< >JPcqT9g (lY >JP|lA/hlT>JPTqV`f^S= gjEl[z?>JP;\(~K$<l+3w>JP2#c3 TQls~>JPK =rAY l+ !Q>JPci5-D?Vl~b>JP{mwOP`  ^xl6$\O>JPTLVY`*flb z,>JP<s; (:L\n 㳇p`N<*  0BTd}lZ0 ;>JP1$^aRl8H4$, -4JPJ )vفlh "m}ϙ>JPb|BYglnN2d Ϛ->JPzm>ߓ l ųӏ[Ϛ>JPUyޓ7;]lkϛ>JP<ݓtelބϜ8>JP1$Qݓ  lKQϜ>JPI wܓ1 ́flvϝ>JPaܓU0l ܈Ϟ>JPznۓhH,l/ϟ>JPV&ۓt@(lCϠ>JP=/ۓ/ӁlZϡ >JP0%+ۓ! lNlϣ>JPH ۓwll6-ϤG>JP`ۓk<(ʉHlϥ>JPynܓPt`lf+ϧC>JPVjܓ3p1l;ϩV>JP=Eݓ n ^Õ=l#8ϬL>JP/%ޓg:*zltէlϮ>JPH kޓ3~4*tx0l5Zϲh>JP`,ߓ˙h8 2dӣtDlOⳃT$Ϸ껋\,>JPxoq1Ư |bF* &B^y nT: J} (B^x̯ nR8¯ rX>"+3y7Wp>J.&c'Gp;_ j0woP#W*|V??`.&&ZpHF1@JLA5%^wpX+6@9-! ?4gsN-'Xp1EGr^yHvq'^X'k@_,( DDZ]%nuqsߓ/Y ޓr@_ޓ,(ݓJD !ܓu\=CfcAFܓ-a`%8vu9_ۓ>|;44kۓ))v lmwۓ3XP!"}ۓ4 sg&)tۓ(~w $jۓvsRܓ adin5ܓzFޓC26ݓUޓmݓ,eܓi7ޓ;5ޓ ۓ wޓyߓBmړ`ܓD ٓ@ܓCw4=ؓ<dܓeiiؓezܓ~ ؓ.-,*)('&%ܓܓ +ܓv9ܓ TGݓiLݓ@1aO}?ݓf; -DScrpgZ=1ݓD8#ݓ!5ޓy$3zؓz Dޓ;%0OؓN}ߓD(-$ؓ!1ޓf *,ړ|cޓ#,)?ړ: ޓH/&ܓݓl1%8ܓ4$ݓ)4"rޓhݓO7  fazr9!/w$ a/<504rf;V?.(VeswgK.Rh'K~9 PF4iΓe<yTAqg*%eW !3DPJ@* EH>O$ʓ|R*%m  Nz-7wE or~tmjjȓkC,} .fJr3J. RœY0 kQ"\` 5!@/6SqJ  ~Sc-0pN`~,*mxjߓ5(z+gWPC.Y|9n En~TBޓ̓E wj z=|S.W!E͓r3?Jޓ6" .c>g“M Г` @ޓq<r['PxƓx;ѓNjwݓ }W# 8Fdv|n`J7`ȓ qӓ{<PHܓ4argi Jq̓֓i)/&qܓ]E% 0Yϓ(.דWZ ܓq)\sCjӓ6NٓE ~`ܓ~20)R|֓8cۓo3!5ۓNQ}7.yNuA7 $.5,G}(/KDrsE>H!Tm8 bnkG k_|K7 fi  )[c0 w83=_fzVbdU10b[((; WsD,01o 7jSRn wR( o`EFl >q~J}0C0 %!OP.XFyvC.?fiAek?@~ Ln; .J.Fg\(%'EA Se1(5Qz$KwZv|([\) s[ 03PQ\*/bT!eޓ>9O h6i~`ܓH`q7 =OޓOr >pœ)OoSDޓ5QEwƓWL-SnH%ܓ% K~ɓoe89 PܓOL: R˓29C|Smܓlv'YΓg [y!..ܓc$.aѓ%|X wgYK5r^ړ%a5hӓH6[z^ ړ y =p֓7kmAiړ<J9֓G5K5%ۓ ;֓P W*esܓS3C֓X!yay0vܓsq>֓PD? Zܓ[j4֓DguR6ܓ--Z)֓80T |7 ޓ~ ֓%Rsߓ eޓ_DCؓuΓ~"ޓg}cؓn?ϓ<nޓu[,0ؓHbГޓ jړ*ѓCKݓ6qYړcNӓ|lܓO4S Nړ$qӓ zܓh  Cٓg9Փ@ tܓ{K<7ؓ ^֓ke'w,y֓?Kד86E?b% nՓ`LדVX ,Z$ccՓnY֓I{|F o wXԓv f֓ 6Sentj_R3 m;L Lcbk֓%V0 AG-G^֓;JR56R elP֓ $br*x[`w.Aדxvi ngJqO/ד|e"+\bq" .CO[hbYL1 ؓTK9}Wz-nؓ)2SBE K7Cٓk  ~@Aړ`q}Y.5K _ۓ\*l*xVܓ_ACp n`Xݓ)\3TbjޓP|s Wt&$k jJ=K|0<~ Q3y@<U8L`&5EH}!f#e)wP .j vyvmYwM`% !9CLMA5& 9Nae4~[H=HwuQ Vn ;v^.W.r7Jx*9cwO%n os?5   nN 4?X. m^(vJ>G4lS &` }" 5ALKC6`^zux6jh8  Thgғz TPJr$NӓTA@>z.4ԓ mq48ԓi(vCsԓ!kL ^Փ\ `WDדps Ta,ד.=ml8דRГu'KדoSѓ~3fדӓ<v֓?ԓG֓m֓P ֓ؓ[֓ %ؓevד;ړp"hדGۓ]LדnSۓZ/דJWۓX ؓ%NۓLiٓCۓ@3ٓT)ۓ-ۓ ܓ DۓgzݓwݓEݓL0ݓHߓYߓyfߓbqSPx( |k}Fg *(m|,%.$by=Di}QR[  .;FKC9& goR~O 5JVbeZO6 $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$0088654324121210100110222111329;;==?@>FI??DNJPSFEFDFDEFDEEGESSPPKXNNLIFDCE@EBHIKJKLKMPXTRTSUUX`^`ba`aabon\[X[X[\[`kgqle_hlc\[ZWYW[VWUVXYZYZd[]Zfoqupkkmrig]]baQPPQLCGBBCBBDEHLLOKADABCBDHKQQQISNNLMSPNJKNCCDADV:;:HC:;9;S?@@B?B>BB@BBDCDGOHFDDAB@CABBCDCHB:99:8:9::8B?`^Xe]YYXZYXZSUSSRTSRSRbUTUTTTSVVWTTVWV[agYXWVVVVVUVVWVVUVVVVwsqqomlkjmkjmmkhlmm~}}~xwwwzvvv~wnonw}}}}}}|{||z|zz|zz{zzz{{{{|||||}|~|ppqnqrsoqrqttxx}<;;<<<>>?>44<>;;:::99998899=IGDBDBBC>?B?@?@?@R::;;C@222L99:;<:<ADGGCDCFICKHHDA==<<<;>FNIHEB<<<=;=>><<===>DFFUW[wshddfjqg[WUV^TTRTPPNNQPSSSOTTY^abakngk\WVV[YZYWnme`aa]^`c_ZTTUQTWYUJMJJMIKGCAEADGFJKRSXPPQRSHFIFEEDHGIFHHVSLNH@@MJCBA@@A>943544533222332524436466768<:./$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ n)` VR v{<gMT\81!ZxIP;9s\v!p"&Dс*h  tz5dCV`ND(C55'ap Fс}l n^<O1vOMW).?^pO/o m<v#؀aD ܒ&$x,K9]9g=4}r#ڌ^YOA]ktiX<UBH`_%9q ,H(*؂!G(.ܓ MdXl[Ͱr1.w6b˅ q=pɎ?V׀bO 'FYlwk]D 3.fR`7qTQBq 5J{{3ق+ &ܓ!Wr"k.  >D{?zPbd!e lP!>]b.!-|Mr @ ,=ل{K/.ܕ!cgTrvŁ!/1 *ADk 6b}kU# tR)6X TÇEgd%l[tāVVt JE؁lCHم'% 6ܗ"m]%$E/C 3id9_A9]i<ю= JR39PCԁ6djt Ut 7>>م  tL9#xQNI^2 ./X+3 / { X=kHg* D`jBae|K-֠1wGviMDۂ (%pxO'( t'HK5]Wl#ƁcZK97k^zjل7܉x;"kV{/<s#q$1e*05e{7IrvgjPb }=9ܨ]Sw@(@+ٞP   l]]wՓFuTPC)#ω<iOq,;y }/1  aVoDr%;m` !j#]i q{Od]iܪ]]kjNPQ . 'vڠR ( .ۂ !5֕HI ?JbKAω=&(tF O1(!.,Aki[t'E `t5 ikWD!<6#܉)Y'tܬ^h` n^ >TRRڸ!3ڡT F„<Li#'8JI:+ ?֗I=:Q_dD YX  VVgk&Aاk,.5FH:gqHW[ɌOT^uxq*W,׌- )6t۱t6]1 VgHq +&PR  :I dV%Dy,kfqp ۚ:[ i H%=(gk V:N0:}2X%bS*,6ٮ ,VgtpV<#V` wɄ94[={!}4ܧ6nd  ՓFqD )`Ǹq80YdT˙W R,6ρ,։('A3quϪ,,6ٚH vTHn!er1O Lnd dR /BVh]H3*g0"G R,Ke# ړ5xEq6ٚH yً ":xZ 3B'dB/yYk Z}F@ B6GnI .`5]M,oe6R?6 VI9yYͺ*doP+Pk^(r܋.'Ub%'- Byf"uv'Y}:k Jk}tM!' сoM^%% )uM5I!eġbEcx ' s pk z}}[ id C ǓJB .mtkjreC¤r6 V:8v  9 }ځ :- iہF`#r 1n;6 ItvO{M  ^{kLYo)\  !ZP7 R3 !B/.,*('%#! d<tUTy  Rv ց FroW|81 Ti4x^Ձ;er}́=ol\kjeJ~b|x3568:<=?ABDc$qg]<6!*<5B<}v.(dd \S7a< B'lO)y $VE'9"I`UB"j[]k2 W<@5;7dIt /%ڄ_.} أbwPzʁ MϪ?R ֐kH '8ID6  OKϣ]Vr[Dqؘ-b܌4I7(#B615kD+5FzpW/c XBCNy2}  LPD.<x!=)] ]Mo(j h_AkM Yڷq ] TF$(8,f!E4vzL) S+wXnt= ` %A[t! `=A71c2,=8%i=b#I3)s2.nn=b#Q Bg^3 )߇2P=b# sW=֌3]„2:{=b## Bo2 =b#"Q_߁2=b#6'Ɂ2=b#Rq0Ak2 =b#ܚ*I2f=b#ȅ%&ҁ2f=b#ͥg. '߁2=b#w+2Q=b#pE!2=b#j ́2d9=b#c82=b#]hd2Á=b#V2I=b#P( 2u>=b#IO:X2Y=b#BtPׁ2f=b#<d^2m=b#5x 綇o_M;) _=T.xVcP=O(k_ g1=O !hI>5=O@~X=Oρd=O #G=O'gDi=O.`(=O5٣n=O<Ҥ 2)-=OC̥A`xd0=OKŦ.Q=ORGhV=OYe i@=O`?!=Og y--oR=Ooo<=G܁dܨI=Ov*QgTa ([ƴH=O}B6D۽x,{Q=O^!]S%=O~v=O ā=O~o=Ow(P=Oq>6y=OjZ ar=Odv-Ha=O]E.P=OWo^@=OP#Pw:=OI95:=OBS :=O<r:=O 5w:=O/o]:=O(O.E:=O "44G,:=O'N_: $'Fspg_WOG57GOWakqsqkc[QG9sog_WOF1O.lxց:+[}ȩ{IK}αS!Ģq?O5:eg#mw-ޣ[O< n:>֍3;ڪFʀ"OCN:{e kҤySOJ04t::#sOQH[:*y  aOXe.B:R]s8O_H):M Og l`: :DX#OnNyՁ:ӹTb6Ou,4:)j{GO|C:9~WOb:,gx@O~q:HVu(O lX: ʴ.:SON0?: O(4I':L[O>b :5EcO~Zzҁ:߁Oxy:/]n2Oql:ƾݮg );K]o ƾخºOkM:5_00yctU'  ,[=:k7Od#2n:Q G K=cO]9V: kiEV[_v=OWT1<:i SS=zOPrI#:E:= ۦO Ilc :D[!j{>:={>OCK{ρ:;#=YO<!2:. /=HЧO65:%D9CT%t=aO&/P:+w kmA4=\O-)lk:\=O5"kR:\!S .=5ϨO<K2::\+<_I\=OB1K!:\B\Yj=&-OI1d:\p eM=[OP I }́:\-=HOXi:\́K=O_i@{:\w P5=Of JrV:\26=LשOmB1nI:\ 0ĞO= O|Ч],*:\'`8Ioi= OH֓;D U!:\=2J[^A{=O5b]/:\FURc0=o%OliW:\N-Zk =H.O"J. :\Vbs=.6O,1 :\^ j{=>O.z/:\frٜ=FO :\kuȜ=IO5:Uk؁u=IOpi:nkǁu=IO%RG :nku{=IO_@:nkuz=IO% (=Pdxu:nkuz=ION *=Rgy:nkuz=IO ,ATg{:nkuz=IO< .AVkϸȁ:nkuz=IOs= /DYlϭց:nkuz=IOYnҢʁ:nkuz=IOʁ:nkuz=IO܁:nkuz=IOq:nkuz=IOҽnYA:nkuz=IO ϺkWD/4:nkuz=IO״ ͺ}kVA.:nkuz=IOſ ˶{gTA,e,:nk ?Ukuz=IO ɶygR=* :nk/muz=IOl ȳxdP=([&:nk_uz=IO45r:nkMuz=IO:nkc֧uz=IO~W/:nkLݥuz=IO  <:nkuz=IOs.:nkCuz=IO# m:nkvuz=IOoUKڄ:nkuz=IOO#:nkuz=IO  V :nkfuz=IO/k=:nk4uz=IO>2v:nk ߜuz=IO(:nkuz=IOy0, :nk'uz=IOK-]ÐK eC:nkuz=IOeFDי),|:nk!¢wi][Cuz=IO1٬t'1ی:nkۉW%uz=IO A_n|veR: |Q:nkAuz=IO y_I:nkHuz=IO&:nkDuz=IO:nk uz=IO[:nk uz=IO],YP:nkUuz=IO :nk&uz=IOuA:nkuz=IO:nkuz=IOa.SU:nkuz=IOr:nk;uz=IOǁ:nkuz=IOI":nk(uz=IOd1M[:nk1nuz=IO{H:nk;Puz=IO^́@nk><u=IOl()Pnk>(u=IOZGa:ank>u=IODJrok>s=IOi/[}k>l=IO8 y.lk>d=IOAg|k>\=IOe k>T=IOM!k> L3}=IOt4k>>`o=IOc';m*{Qk>;$U=IOoa<k>i ;=IO9nGܔwk8*"=IOh2n:T-,k/<g=IO]5sf:k'M=IOW8k`V(=IOzF ?gkc=IO@%h@kϢ 5f=IO{].yQ`x}kd 8=IOTY2)VkH; =IOتV"'kX =IObFgkS=IO~(}[/fiDkZy>=IO&Xҋ<}J#;k*2v=IOJ4K  SPkr}=IOp\)@~X  k*  P=IOȽ O,C>Uk}a=IOwO_  ]wk5#[N=IOqUX_)eka y+=IO%YX '9K[m Ⲇo_M;)  /ASc|kY/ :=IOI5+`Qk7G3#+ ,3;CTq1=IOn=؁kg !l|Ι=IOcXfkmM1c Κ,=IO] k IJӎZΚ=IOQ:\kjΛ=IO$Z)dk݄Μ7=IOI5ykJPΜ=IOmI ́ekuΝ=IO/k ۈΞ=IO(G+k.Ο=IO8?'߁kBΠ=IO"[F.ҁkYΡ =IOH6@  kMkΣ=IOl.kk5,ΤF=IO;'ɉGkΥ=IOxs_ߍke*ΧB=IOLo0k:ΩU=IO!\hm]•<k"7άK=IOF7"9)ߚyksԧkή~=IOkK}3)sw/k4Yβg=IO˾Aʘg7 1cҢsC kNᲂS#η麊[+=IOIƮ {aE) %A]x mS9I| 'A]w̮ ~mQ7® qW=!*2x6$!]nE8:jW̹ Hx4>ۀ ]^ؐ,D98רkiI`nrbO6́@P_VB0 ^Mڮt+B:Ihk%΁k::_(B;ef*ρ6F.`A<oe 1[' dŕb(iBϐ6TV(]WMM<<.Kx13M 9=<.5){OidJP%AQWO..c"f _dM\Z-DBA?=<:86 + .@ V"~k%#r(`Iv(|^+W Be}[-Ie.S4$1P5L !#%'(*,./eW7Gwtf<C51I>A4B<^WlF9'I6SM5<M2'vR/V-.0F5 FY)PHMW^'D<Y%!c$%Pf!܏ dL l0*;E(p:Ә3oy[uBϸ?@ y*0}q ng :dq\,G66 RΖA`9 qYbqP',׮ | B R(V} (Q${MnI#%]vW84B? (8(q l. Y̐RCA dY "h3qܳv:-s# ܞ=8E(&OӚ]P;@ӂ!xdEU(gcg \| D1hK%^oP2/Dܕ$]s ؐ._$ YȈ+:xXt+4 Siʺn.RYxlN/n&ܞ=F8g$6 H<EԂ" =!%dPug JG={TK0OӷtyY?Kn  W*W ַsj^PhȷIh4_V+<Y  xr$ex?`gYII@'ܧY=? gؚM~U8#JtӎAL5+.[+ˀ3(X7W(2%t'e A܋E )iWBg!~֜ܪ[q'a~`)WWٜO L2x 6S< bԐB -D Inu(l̈́5 qd:م(*xv(5I~e] >۬lW5ku,!`2lt 'UkxdI"?ܬ^6т)܅BMiל5KٞP "b 6tk8BR:ЙP  VՒD\'x#d< P3B8! `΅8(r/kx*{Y?x,.(vlC;NQx3`;`$+?  =ڠR q.UtUIՓF.R#ۀ O Tχ:kRMr2^y,Z _9kn!k5n/b'DR]RD)'tbR. 5DOA.k;Fpe֭g]l1ۣT ¥ԡk.pQ./=֕HTK[IHω<;W )eAHI% R}.{!.{ <gi)]o!HdH70*vxDidE^a^_%rۥW DoDi!;6:gb/}֗I;O y5q  <ы=-)܉GKxy?F1]V)w. Pl&R \wv!]=v|fPygrB}k76  $q#SVxvrW/{JVd(|:1DE̤5D7ۄqO7 O&lPܸ'[Q!bZoUjOqO6 Xx > ħ}Ld1G#]xe^/+Nf,zPBB<TH Q 6 {Ȯ/ed+2^YBGl(.,>dpP"tvM| t5&   dV_pYR:A^qTPg^7/qՅB5!mi$ . !P|zK(Xq rȕ6G bk Ck"Xn{OP{(x 5!?ډ)Db..&nv&F 3@!3 DdvrI ~qUB<J}cg qRd* `b"DOq '>*?؀bd$.ܐ<K *x!95 ,n\ qHYyK`Y$Tr9Ogl"1 4=xD ,م%s6 1UdrsbO8 Vtܗ#Mډl[lگy /Xύ#EDQ o?Vϴv6'.^O%.%t/M^D<n ]kN,}93(ObqqdP(ی& P֜T ' x n5t~b`]DN+MT.'*<d0 &+r ؂eܒAE[,T{:q}K Y^kx ډ6$W2kr}Fn6trd`L~=Ce!gqGl!)+}x;  #!(i >;A7E5[&(fܼy( {ۉ ,DWipdV8 -,zv' OovP# jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-blue.ai000066400000000000000000000433231402514743400221730ustar00rootroot00000000000000%PDF-1.5 4 0 obj << /Type /Page /Parent 2 0 R /Contents 5 0 R /PieceInfo << /Illustrator 6 0 R>> /MediaBox [-0.0000 -0.0000 479.9999 479.9999] /TrimBox [0.0000 0.0000 479.9999 479.9999] /CropBox [-0.0000 -0.0000 479.9999 479.9999] /Resources << /ProcSet [/PDF] >> >> endobj 6 0 obj << /Private 7 0 R>> endobj 7 0 obj << /AIPrivateData1 8 0 R /CreatorVersion 11 /ContainerVersion 9 /RoundtripVersion 11 /NumBlock 1 >> endobj 8 0 obj << /Filter [/ASCIIHexDecode /FlateDecode ] /Length 16451 >> stream 78daed5cd96e5cd7957d17a07fb8fd6020e986abcf3ce84da24cb401263162079d7e3218ba22b353 24058a72ec7c7dafb5f6b94391454f8f0d2bb07db5ea9e699f3def7df3c9bf7df1e5a7afbfb9fbdb fed3b8732f5f7cf2c9d9fdfef2e1eefe95c0e9f3c3e1e387877b22d3efbefac3ef27ef774e7fa6cf be7f7f77ffb0ff66fafbfdddcd747677bf3fbcfdf3ebffc6145f5d3f1cf6affef75dd85d5e2f535e dfddbebd7cd8bf9afe70773bfdf1eebbc98729b857b1bc72190fbebdc09b6fee3ede7e737dfbeecd ddf7afdce4a6d4f40f27797b77f5f1667ffbf0c5fdddd5fec387b3bbc3ddfd8757d3d90f97b7d31f 2edfe197cbe97ff687c3dd3fa73787cbab7f6cc77cf9f1fdfbc3f5fe9b3fef3fdc7dbcc7f057d37b 4cf361ff30e9985f1ff6dfed0fe1ebd79fe7c9efc2a405ffe3d13b0f3fbcbf7b777ff9fedb1fc67b 6ef2a7ded3ce3ebfc196f05ec17bfef47c1bd2fef8c2571fbec5a102d66ba77efef0ed254986399a 36c5395e7f1e6d1f7ff9807d804a7c1ef857fb9bf707dc04691c929b36ff8c375652efbfbbdefff3 15aeeb76afdff2d7afef1fbebcfe17a61c57330dfc8f1f6f2e2e7fd8f34adc80ceaf0ffbf3bbfb9b cb076e5e60fdfaab6f3fdefcedf6f2faf00a0cd0f48f4ef566ffee9a0c728999534ed37fedbf9fde fcf0b0ff801f9debe1ad3fd71ff70bff68f8affff3dbf0df86ff36fcb7e1bf0dffff311cafe590ce 523e7f7b7ee6dc2ffd3786ffba814f86d78add78e7cfdfba343f11d3df3db76a4f40939e8bf3e38c cb0f51cf5193143de779c2192f75c5397c5d35ae6b3d7a5993bbb6dd1ffedd399c2f65fdb51e6fdd 2de866ba6cbfda2f6338d7dd0c48c77b397f9bcf9f1c24730a916e9cd5567563a2b419fc76dd859e cee7f76cf88630f9b59edbf2aa56399e7ebd0b6d7efd71196edbaf9b7b38db0cf3f381e6d5d78bd2 cb7e73995b3c6f274c4767f746aa33bdd6c786377ce08dac6ff4fbb89d79f8b88ce397c7b1cab2dd 747c2fc675cb69fc67e3fccf5c9feb47cbbc9e57df9e7a598b2fdb4be76fc39b137cd117caafa7fa 65536d29df37ec68b4fdece4546fe7a7706e12f788cbb0caa389423d35786cfe88f6fef404eef52c e33357f217adae8dcff4ecc797c535660edf48c49be5de372bf423013d9a72bd2c239a4d8ee1e17c 2b0463f36f8e373f0697a3e39c6d2eee918a881b3a94ed6fe3487627af67d2bdfe79ab3d2267de9c fde7acf678290e8fdb41dbb957b23dc296c5169e7fb2def946d73ccb0d5cfd57ad6b8baeca6a5937 1e11f0592e2c1b79df2aa0ed7a2777b5d9c166f3cfef6019de56d4fe6ba4cb8fe19fbb8f2365f5dc 3e8e2c60dc3cb7f5ec6f7e84f6af8fc8b85964a88b93ebac56bd6ed0bea5c7f6eced91ccc79f3ed4 2c328b51f68b0df71b9aa7374fa7f28f54e5f9e6f5d7abc5f1e989f15adeb1e1671b23957e7ce5e3 3716892b5b359d8e6d2a7e4dab6edf1ae9d9fdf31b620d458141f1988d8e75df7c719b3b3d31c03f e338a599e717aa97b095f1e51079aba4d7c18b7781815b99afc7373cb8a26c3cb0beb1711b594a1b 4fae3d629d8d3358ea31d7a58ddbb5713f6a7862e7f3c6294d4f9dd2fac86fd434cf3bc54351ff5a 9ffa670def6f45a44f3ebbfd8619a6f1787677c324d78725fbf4c5fddde1ee1dfffaf9edd5e1e337 fb395df873b2853f31e4b9e4e14f0c7b2e97f813c39e4d2dfec4b84799461069a58948f4e5fee1e3 fb972f9e90e13faf6faf1fae2f0fd7ffda4fef30ddfefbfdd5fc9a4dfa636f3cdaeed7df5dde7f78 e62427a9f963733fa2e02fd8c633ef8a2c830e4a738a30ca7dbe7c31f9f13f26af3ff553f4532853 88d3c5df5ebef89d5e9afcefa78bdb972ffcf4d73fbf7ce1a63fe15f4aa94f6e976bc9f84f2ba54e 7fbd5c7e48b5ef7aef65ba79f962795e1e2e36e01870b199f311b01df6f7972ffefde3e99d9cf8cf 3ff066493b289538f9ec773d60b33704cbaef54030ec8a7e2d0d47a90d48de41a9052075d7420207 e7b64b3ee4e98c03eb2ee7865f73df2597ab5e73c1f1c1ed426e69e2e4d1732090ee7dd3c05a76a9 39f075c56ca0160fe36bdd451793c0d84ad0838736d643d636f0d04bb38178c5b6018a3457f96bdf 85d8fa04bb89696314d2622e427a6e5e0f39c7ce81c1955d6d992bfa5d0ea1721b025d27888bac9d 450e20396421816727d243c76ca563363b148e0d3a931a000342c08988c71fd1b3d7e48584988dc2 d56163dc7f6bbb507c37da363c901a0d639b77a29bcf15abb7bc73bd90b6d03e91fb6971e76a326a a7eec66c6443d7752998ac8b08340940b07a7046286c0733e4b4ab1ea41b0393d736028ec98be0fc 5eaf39bc1692f6903d2906fe890dd271665bade07c63aa16c136adef7ccf4ef3fb18f18033f6d6b0 d50c7ea8311bc5daae84dac524ada56ef4077f7b4786c9b8d60c6a7b0fb6247f028989950e1fb1d5 e4c63bd566f305a7034b73b65c7029c157b25cd28d80b08678716cc5197bacf3c0e412ef37efbcc3 e9347f13420ee91ce876a58a390b885f930d0458c20c2667fbc76e7d8d3a72abb91918300f3489ef 10c6e8b0848b908b46c4ef6a8964aa40c581453b56cfc19620e89c818562afa93c6845a447081d91 e27117be27cacba0066c4d822cf3575c99edcdc334f468a003957552dc16991c77572174c1277063 737a07e42c33896af536b0c782070fa6857301042c1173176de1de70870d67ec75bab2813dba2e02 e60411e3fc31b276c8cda45875bf2dd4a407f086b383f75d0c14190cac2e44c90eb8206b7eef42d2 43ea122beca1431eaf065395c013259c48454af05e1e4872dc3c278f3eeb163a05ffcc2e0e4c4b52 67bc9f8736c07d15eab15ea800c1db1dbaa824229450d7f4e0a43fbbf449e1367817a5751be81356 e77de130510f74cbec4e1bd5087820fa599aa0a92af91fb256a9fd2e4cc56108250b44ce54b0959a 2405b159c29607f796a87752c12d731be073483a4132394480baae409d49ba73a7d2e63bb17072f8 19d1571b18a96d9ae9d2d0f91a769ba1abb5994a8d547995a10a89ae98fe818989e003081deeb718 fffb42818d06c60a81d243e986a428250922c41e84c0f08cd9a0727941fc15a48d526ba191fe4002 fe26b5591c4f8497236449031388e0ba696328feac6d2458964e63542974a4275ecb662912151aa6 4d19d4e3c04adb979ccdc6bba3a682a477c83f5ec35aa119e20304d347a8d9a0811182df8cfe11ba 31e8763c751d2e3dd296c562262042d03855cd946858cf844dd88a204bf145da2ce588695382a025 d39608358840edb8e4351044b58150f259fb0759b01db3ad002385820f29677b80f24852c859f685 034d69e3e0d599a5c62458d37c8396fb207b912687bc5496809f381567a73c0df357249e3119f374 32c08d8120b781b9c26651d86b1331a1d3fc30a6a99180e44367169c1c5b03afa3d17501575c0c30 53b5ca7e91c242e8cf008932551ac8ed35ead5e297d96a33c35a786b174390337906462d14f0b040 c866e7a540d65aa15f012500597693196ee907309ed93efc04edaf250042264c90a15087c97b4c93 e1dbfde597fa77d86292b3052555e10191b8d02d49960b8a08b1b99f7020d836370369eae469b291 8d92e0f138d4dbdc7082a84c9d4ad751bb83f1711c02915e02f52684dfe40456014c1b05423a8cf9 00424b070dcd813ac577ca98d3ec8d56ca0778392ddb7a3104bb5d803dd27e73638de70ab8a1462d d0a9fa79a301179fa4b581a4a13542a5680573ec700e6d0320a4aa0bc486c13401acdfaa976307ab 168440450721b0c1631b70325a1dfe1f94d644a4f4d885802859480ed1d44d2dc906d2ff6b72d4e0 b1b514b50d5a47da60b26c932f42c3e6e4b6528393e8f02fa15caab4766ec3b1a3f5820f3b343edd 2c1019f62fac08b94b9ea48debe3265b106326e309019a1cc44c258d51a6d8bb37dfcceb5645ad61 e841179041da9eacc0f76176f18bd7c0e486c6089097144d2915ae776120d44892c3d63b4512083c f32ced01c295417bae08fddf4b1a37a91060840f0e720a2b42b1ca039146a521937fc881d9042d66 1a4a73239d540491d4cd1b8715a863a0afc7b74db0cd2c50bcc97b227b720912112acd406f574d30 d019a699c0deaa90a2c8024abb56ea6adc6d873764ba1d629e8751962743a3e37db08b0bb2504082 2931e8ff5eec5099914836cd862b1cba1d9e49a16702860a99d741ddce7b94414fe494cc2b13c293 265377b0a470209b2eb4046f46194e719d11bb9d6ade88062619351a204909576c0a491604db6e2d 6e07420838958c02e76f231ce036d63d0839deea187865f6aba468025114123296e97dd050410410 b043b4200556c4482d0a67cd8f7166201289ccf3ca1b6790056ab7c449a02db3421e5e5c61f4d4e1 3f3372b9323700019193a71d13d53e19a326f3bd933c04d877935d0c84296bb342f2e69432766bd5 8482562f3a79a18e7ccbd760828b1cdaa258187aa5ba3842ce329c8a10574f3b89cda0290b6266f3 bd158943c542379a7f0eed6e1747f5cc7089208e8a637aee96290e47ffc71912280bdc5586572d1d 82b7a2f61599e3903ea54120af7364e6009a0c78c69a3c31d8915dd1dcc5f4266782cbd2b245c28e 1c004b0ab1ce026066f3c4b0a6a7e102f8348f82830a0cdaded1e8d8fa50678619dbc3bc145abf10 18c248412e0883fa6c36ca40d8a840af1594912d817282ef4b573a724f4c62af0068876d45e7b62f 795aacb49946401d8b255dfc8a819d228f1be886e3b8b8439782ac31b8dc8e11639b4795aa00a732 96970a05abc2821461f02aa0eb199928f861f08c3b998dbf91db82fa262f2fea9220d47e1250bc6c 71a3cbfec465383be146983f42d55b5235b35a688f6f0c740a83009a998cccbe502b00e99dc10a48 8438270d242f86a23113c055207c162880df7d95d8316190ccb2c27f29f20c7ca69ea34cb07351be 02396e18e9ea154bc14dabc3e4c3fb86abed25e89df22110cc24d7810e5dce342954abc39b0b95b1 489412f2c76a9b602f66a75bb46b794a935febbb81330aef150a15f6d997e1bb35845ac0e04d3b04 61e042f833044075f028652c563218f4738ce685f53aa40518eb1072d52a2f1d806750441a75f22e 47f5610e1014a7c840bb30e710daecad7466360842d9e10ae9ac4a2359bcd68564bab2816928fc66 b341a898090cd0f0590e1a5fc3e50369438773f2c68150ddad9b8d60328c014748d4cd5dbb680aa2 aab084cb25177bc68c042a73023492141402dd99e343c3c0a04258c5705e5bed6c9455022d10c80d 1c25209becd1003323c60323c892a5e79e1c7e2656998da1ec1990981e080472761c55782f614871 0860fac06c2a734d4d413c36549969801623bb692d8a4e31c74b0e06dd6a44e508f7300a84f7bcf2 ca5de3ae70f5298566f3465bab513b30c185f5617f1d5f82f87043917603d360a781691d000e7369 147350dee88ab839e8ac706a98a124e6e955d1f027a6c0c0fe8da69d5e6283b5828e81409a5a57e6 95bc136918b949667b6990a82ea19e27ac552933213293e6e3a0754ec13431def6524685d9224a95 c3e13b33ae340691e6d5bc7b61140a62a158ba1436a71526a82265d34228bad1a1f29af0266c5a91 ab0df3ce6d05ca355413b83e30b54c4431c92c0a891929822ed86b857e1911f82b86c0f77686c468 0e9a0fcc38d2e080a254e117063a65d418e464fa2c9e4451568c31257526d37591b3c1fcfa364279 2f0f94c9b64c77d2ebb55e82e5ed5c939d07119865613a0d4adbdc252f67a129a356e50062a1ccc4 39078694cc6508ccd271f259f12be2f2b472f202e8a23254a3cae0e6b105283a4eee98f00bca9a87 794590da0904153990ee360d166336911a88136bf19d38ac31b61da44e40c652d28818a1e698ba0e 70c603634b1e333737188c6a9288e5d479e35457da061625239159216af61af492bae3b131fab6a4 21833d4abd8a0f67b662427403550407b68e8a8451260b3483c81c275d4b6a2c38e155488369313d 47961d7e1698354b61386595481f3a53d418a02737c6e49f371d026b6f9e9da7eaa5bda6fbac98cc 6ebcd86b3c261198071b08ae6e73b866e958c86f767d286f7a4bd42fd42195e68cc96066e8035323 ba5fa7bc4e3713d3fc3005918ccad9e82792687451ab0c4f21fd9306165669025d8ad8cc0aeb98e4 0dfa3971242f45345ab64ec780c98fc0e419c29700816f32f7f07ea036303f3d26aad83994ec61cc a62c389d01680f20d9f20b7c07912638b6d37f844cc941662a5d20542ee45c6c0c9b9fa6c81c4411 47d1a8682acae0c81a62ff2e30cb019319547e010ff8427d4c4bcbfb2292e43fc275cb348973b82c e346a7a28ec8cc5109905b744c4a2bd3c94e2752fa50b137160d02b0af307bbb4e0b302ddce41283 c04ef31458f9e989af7076c27f3047840b39da533a39d4a037a641990ba6068f8c071a6b8a5c0080 7c329a2a47ebcd688621f2ac8e12933b8838fd301704ab9744c053e201c89f8ec5222230bb4e480c c9840bb41facc26473b5d7a0284c5552cdd24da56c966a39782aedceec02413871d9f223c599e172 8d1925e9ff524cf0d308e5094612929e1ba97c71821ec7fe9afbd9fe1a7553eb54a099c6d7bce115 6444c95a24f37be0d17e041e0832b35d36200b28305da0486296920f0b8203480054529941a8b6c2 12c9a8ecd492f23360a47986c23d6c4144c150a01c2233ecd68370c38e9eee825c9d3aefc53320d3 bc700ee1d045e19da53a128765b84a076806e1cbe1b668cb133cf5c4cd2c486458853d73e9050cc5 aa1e872de89ba5c1d6f96722acdb9811ce96181ab3b23f839c2d519dc9b2ce63a39c91710b810cbd 200c30299822cb0256f3050f5b900e84489a5886a34a85b942dc4c078b59dfba41381b4b9c8c9416 f0e208a40be8ac328553f45c37a04e51684dfd0ac2d643f68af958e6deafc820a668b280506a3071 36db02d2f6b7289ec139b31c1744c3415729777241aeecf6614bd20a1eec14b06d6d33b63001e2cd 9380a3738430ae4976fb0b480f142ebdcd368349493c1a48baf7d44410a82e25529484ae2ba293b2 3e1efc0a5e6cc199332f4ef1f0e119100c1f999d2df4ca370c1fbd0cf066da4897a233ff3133fc8a 6c187e05370cbf8233c3aff3cf4cbb6e63cbf04d95a947ca61053b8b1df218e0a09b855e40aeeb98 e9a70b3583514a9f2a08865601e5061952a053cc20d5173b100e5b905a8ef9e475fe5913aedb9811 9e826571a7811b2dcab41e35dbaa4519c0a5dad7f95764de86683283f063a2ef83267ecec89d02e7 0b3a6cc14571352675dd8601e8b564b513ccc8d5293e393c0392a920b32a5988b70b04ea660b6e78 1581225321fd78da8c2df92d574786de4c66aeecb7205bf65bc00dc3c073679b417e06dc5ef1022e 579cfdf0f2972b4e74a7e2f115af47db58cf15dcae1bd288a94e8233b92e4ed1f0391004878d6564 6eca24d62c29de807828a67ea36a35f53970bb2bf62085c7745bc0ed7038f49915e467c0b1a58b53 fb7c0efcbbe5f9a353c2603dd40ab2ecc2f2c1610b52c3b07782b5e01cf286135831ef4746f6ca8a 2625f5e3836f407661353fe7dee12ff415d4baf4f65a5e41360ec1436d96dd0dea385a10ced09dd5 05667073e4a7e77d0e543d9a79dd7e7ce31b70bd0816b98a1a5166903b67c55691fc0cb29b22cbf6 2144ac4a5cae48a4f7e8e7c60c03e7e31c8ec0f9d4cbfc0b65966dcc88ba4de00b0509ec86aa8888 e07a6c4095a4b372d6b062a91e21830daeac486de06c705555672094ea3320243dd5b96786854a65 e74e81748659ab3c6c41f01b34a5b3fa88924b0bbf21ca0a6a3ddaf29bea53bd1eb970ccc02298d9 58439620f196dff0f3826ccfbb80b8a0164d79ae60619aa35a82dbe69f5965ddc68c5c9de2a85f9b 11660532339242a4c8e8db5bb766656806231c58e5ebd662e9e89250ffc53e904a231c99a26996ec 536f26d42e40c77ca4f59bc8ebc134ec120a2a8d060a2211d7ead203e27dc040c7e2d848adb3fe46 df2aaadf2d5a23891a79a2cad2dc06233b368d12f1a322a1964338938ac4ab24864d824c5910c19d b0b817875567f741ca75aea1c17f347f21d591f723c8fe1d501c4e3df3124412f41391aaea3e10c4 f75d4e4a4a75a9c865a6ebe58f24d59fd96012e8a3e19e0333d641d5932624f4b915a858ff9a68cb 3ce4c5e83662732be9d658d6f170cfa1fb7077ec7e60ca824a2e7b38d17c27bbee676a38e6d07829 9e496ad3b881f78b40ad76d37c2d7b5e1c931ed61828a2b1c24f36a8ea96e5fc6c74a271832e08da 8363f980fc53fc68ccc1fe5578175379a6c812c30b984dceaf209a4861492bb2d21846774c60809f c92d11fc90ea5cd82f6c71889039cf4a02732fdd3b438a8ad581be526be39de0e74e91c8b5385b8a 142f84a1058a453792554af5ec8712ff90e7739f072a414aa7a9a9fb81f33b32ad63b650d9a4883d 883999fd0ec6780419121998473108bb5591807927a8b6b9a6d37804fac85dbd5491e93506852c05 c4d1865003130e4a620fde26c8962b65c9a8e935157b7f883832211110db726b6d168aa0964f58a7 4a261c5950e6c4584026d81d73ce8c93d8fa04196553431e99344a4a65cbcb605105acde06ba32d2 8f91fd8338047b03ab657159726159a13301783532752a533392ceea7471ec4da6d9ac6c8ab44c5d 6580c808ac84913e2567b2cac081508851dd1c90d162b58d5eadb70652de6d0f239921a652d6aab2 4da359c51b3aa008c96a94d1e48cf0d8665bcbd2a611bb5288899eeca8f7d15762b4dd22cbf25da5 3d27c3c86e1b7a0aac0966a5b6d84331bc72f6eea9ad8f60535b9c724dc1d2694182a9d292b270f4 8ec7c1a9a9025bc269a6e2dc91c1ca66b00820b2466ddd76f453d85fd0dc689967e64a51023b6a65 88d8e4c84b619dafd7d10ba3f45a2f76f54b2324539495deee18086d630a33ab6da18c96262a5528 186f2df9cc491029ddda6cada5295037e27ea3f5a3d1d0f1d33381c53359cd766936d110c9d6800f eab15f83484b83f1d844599d293d9fd45609dd42d62552953007d25b355559428c730bb96fd122cb e487abc8025e975c53e846d39c739d08f3fc547a4db99c2e6d10fb68ff62de94869a928ed7bb3911 32016c62522f7ccfaca41261ff8837fab3c9b7c462216955bf36fb157a32135058c5e8f4119a05df 99a183560c2c482669b3cc9e2ab59664bd165926b50e94cc0ec1a892c7184809d5fe592571d5cf5f 4284e8cc0464156b846453c849f6858da81d8492d10f6df9ae42fd3e51bd6ece7a51f186b9104dbd 774f9c8ab3539ec6081d289eec4222c11d48250f04829ce800124c54b3449a1331d92a378ca967bb 16c91bfbb0e0ec212dccbdb2428dbf99b004fa5c591c8e03156b3535eb0f8b90d4ec132850d592f0 6e74a81154bf836ae06ec41cec27622401fac3f91e4abe59274eb06bca520b26ef9d93b0f526b20f c57993d9306e07faa433fb4230b4918e7e42935febdf31caf52a48b34f8bd518c61e709a3d3fd2a0 2a8a647646e635b5ba45d46cdac74067fdeb3c13fb5359d4cf142c224901216386a80ca694991990 c29fd4dccf44188b4e00abb703a9e468168949d5687542b088b51af83c12d0c9a9bec49a6eb5c67d d6b52816da1b3f3c0856d4eaa33625579ee187ac22909cdcfc350e149b33ff2e961136b05fd97981 851db7d6e144c701fe9dd379f519800b42247ce3db127d602337b0907780b4aeec12ab1dea70e20c dd7cc0300a924c60253f4a407db4fa315a3053cca67ff5b2d2be355283edb4bd3182e2673c294979 43878d4bc175b306628a9f3d18913a89d45b1172d9d0fa6a2d1ab7598bb89b8d80ca7e2e086bb3ca b970203d56a73288b78150cc460d7a28b490911937297e9a62a671a90caabc66671f4e8c2f2e7a76 a69f62f6e38b11c7727194efe6ace7acb3977a242e4b2d1bfa7b461879b9cd641ab1b3dfb559975b 16b58988fefaffa26b4703d5f8c372505481375b17524f4a777a6bf4b781be3cba71d69dc42a6cbc ae7deede4e72e554304ce6df51e8838c2a9b86a8143b1b40ba219155741a164bd06006c7cab6cc54 a742eaba293835f69abe64235259fe2512239b4a68c39d7d6244fddd54ece227223e2c1fbc557e42 40a682b3124ccdd76e575cd9a6a52fe5d8a32dc3ce369a61660b3f30e385c6aaa480a7af9d3648e1 fe29bff3c011e876de945654bfe98ab0ef44df9b2d03a974475d779e9f4ceb52b6ef25b40743e6ad 366d7e0cbc328bd453378aa9bd4d67ecaa6ed214b3a71648563d8697457d3f3e8a4bf24f65ccbba9 5e8051dd73e6985bdb684d6409beaf0fcc7471bca3a698ccda13246bcc8436aa35b57e923146891b 615390a90f6aa8a333c25aeef8a82fe9fb257d79e8c3fc8960e2073c74483b7b74d49dc93a397ddb a8ac01b58da8c10f69c647359426489b35fc644f2681b6ec6c6a931bae2216cbcb55de2e8b70b9d9 403a4dd9fcf7e658db50c305ed153f53f14a8514ebc7e2aee090b9598d044aabc060b1bbec02fb50 3836b1e752b6832a97f323d4ac6662c478d87f2de3a32c80aaf1124c0c31556e550248d5b86e9d17 96c3e2c14760444d55558929126a2b8bb23626eaa9bea5f233b525ed5419719b55add8f751986608 4345e3d203db5133abe72442647fb68ac985de7db77692c633aa0a9dad8410d5f6ea05baaed79854 509b1a3f8351969fdfe7646b5c73d9fa4f69829dbea6a35b9d831d137e73528b47773af8701ed514 96c7278e6a50e3fcf4a04b1dd5a6c002bf8158222a53d6d5b2c70c601e1970f30db6f44f7403d962 8b8b6bec8d36442d64fce45265c5c74ec5d9294f63ce7e62f3290f879739a491fdcc3e9821f6aa1f b07b945f4c4419df664875cc5b08b1ee2799061d96d5114ad9f89e53df6aaa6591df8e064bd514b9 0d2db15b84c292ca702452f2b305c795da6b0ab52f2ca799b35c0edaeef1ad2c5b91bb1c46e672b2 7d9bd7e2d04595de31a53bb12fe9489fd373ecc5b49f67f3d7c5299aacfeddc59bf1cdf867b7dfe8 63f04f3fe507e55f5cbedb7f757f797de027e4ef3e5c7eb79f2e6f6fef1e2e1ff6eff1d3f4ee7eff e1e1ee7e3ff14b7a221cb40c78e6e3f587fdfdcdf52da6f8c94fe29f7ff3f177f6cfbf393ef27ffe 85edff59c0a9b73ef9e4b33f9dbf7cf1f2c5ff017c5544da> endstream endobj 5 0 obj << /Filter [/ASCII85Decode /FlateDecode ] /Length 34 >> stream GhOt'1B7FZ"#S^CKb'rAF*Y:rk'%+Q~> endstream endobj 2 0 obj << /Type /Pages /Kids [4 0 R] /Count 1 >> endobj 1 0 obj << /Type /Catalog /Pages 2 0 R /OCProperties<< /D<< /Order[]/ON[]/OFF[]/RBGroups[]>>/OCGs[]>> >> endobj 3 0 obj << /CreationDate /ModDate /Producer /Creator /Title >> endobj xref 0 9 0000000000 65535 f 0000017212 00000 n 0000017148 00000 n 0000017331 00000 n 0000000010 00000 n 0000017018 00000 n 0000000291 00000 n 0000000331 00000 n 0000000467 00000 n trailer << /Size 9 /Root 1 0 R /Info 3 0 R /ID [<35fb597d36428342dc114047b9758f5f><35fb597d36428342dc114047b9758f5f>] >> startxref 17789 %%EOFjgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-blue.eps000066400000000000000000010322421402514743400223700ustar00rootroot00000000000000߫%!PS-Adobe-3.0 EPSF-3.0 %%BoundingBox: 0 0 480 480 %%LanguageLevel: 2 %%Creator: CorelDRAW %%Title: jg2.eps %%CreationDate: Mon Nov 12 20:35:43 2018 %%DocumentProcessColors: Cyan Magenta Yellow %%DocumentSuppliedResources: (atend) %%EndComments %%BeginProlog /AutoFlatness false def /AutoSteps 0 def /CMYKMarks true def /UseLevel 2 def %Build: CorelDRAW Version 14.0.0.567 %Color profile: Generic offset separations profile /CorelIsEPS true def %%BeginResource: procset wCorel14Dict 14.0 0 /wCorel14Dict 300 dict def wCorel14Dict begin % Copyright (c)1992-2007 Corel Corporation % All rights reserved. v14 r0.0 /bd{bind def}bind def/ld{load def}bd/xd{exch def}bd/_ null def/rp{{pop}repeat} bd/@cp/closepath ld/@gs/gsave ld/@gr/grestore ld/@np/newpath ld/Tl/translate ld /$sv 0 def/@sv{/$sv save def}bd/@rs{$sv restore}bd/spg/showpage ld/showpage{} bd currentscreen/@dsp xd/$dsp/@dsp def/$dsa xd/$dsf xd/$sdf false def/$SDF false def/$Scra 0 def/SetScr/setscreen ld/@ss{2 index 0 eq{$dsf 3 1 roll 4 -1 roll pop}if exch $Scra add exch load SetScr}bd/SepMode_5 where{pop}{/SepMode_5 0 def}ifelse/CorelIsSeps where{pop}{/CorelIsSeps false def}ifelse /CorelIsInRIPSeps where{pop}{/CorelIsInRIPSeps false def}ifelse/CorelIsEPS where{pop}{/CorelIsEPS false def}ifelse/CurrentInkName_5 where{pop} {/CurrentInkName_5(Composite)def}ifelse/$ink_5 where{pop}{/$ink_5 -1 def} ifelse/fill_color 6 array def/num_fill_inks 1 def/$o 0 def/$fil 0 def /outl_color 6 array def/num_outl_inks 1 def/$O 0 def/$PF false def/$bkg false def/$op false def matrix currentmatrix/$ctm xd/$ptm matrix def/$ttm matrix def /$stm matrix def/$ffpnt true def/CorelDrawReencodeVect[16#0/grave 16#5/breve 16#6/dotaccent 16#8/ring 16#A/hungarumlaut 16#B/ogonek 16#C/caron 16#D/dotlessi 16#27/quotesingle 16#60/grave 16#7C/bar 16#80/Euro 16#82/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl 16#88/circumflex/perthousand/Scaron/guilsinglleft/OE 16#91/quoteleft/quoteright/quotedblleft/quotedblright/bullet/endash/emdash 16#98/tilde/trademark/scaron/guilsinglright/oe 16#9F/Ydieresis 16#A1/exclamdown/cent/sterling/currency/yen/brokenbar/section 16#a8/dieresis/copyright/ordfeminine/guillemotleft/logicalnot/minus/registered/macron 16#b0/degree/plusminus/twosuperior/threesuperior/acute/mu/paragraph/periodcentered 16#b8/cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown 16#c0/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla 16#c8/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis 16#d0/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply 16#d8/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls 16#e0/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla 16#e8/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis 16#f0/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide 16#f8/oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]def /get_ps_level/languagelevel where{pop systemdict/languagelevel get exec}{1 }ifelse def/level2 get_ps_level 2 ge def/level3 get_ps_level 3 ge def /is_distilling{/product where{pop systemdict/setdistillerparams known product (Adobe PostScript Parser)ne and}{false}ifelse}bd/is_rip_separation{ is_distilling{false}{level2{currentpagedevice/Separations 2 copy known{get}{ pop pop false}ifelse}{false}ifelse}ifelse}bd/is_current_sep_color{ is_separation{gsave false setoverprint 1 1 1 1 5 -1 roll findcmykcustomcolor 1 setcustomcolor currentgray 0 eq grestore}{pop false}ifelse}bd /get_sep_color_index{dup length 1 sub 0 1 3 -1 roll{dup 3 -1 roll dup 3 -1 roll get is_current_sep_color{exit}{exch pop}ifelse}for pop -1}bd/is_separation{ /LumSepsDict where{pop false}{/AldusSepsDict where{pop false}{ is_rip_separation{true}{1 0 0 0 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne 0 1 0 0 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne 0 0 1 0 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne 0 0 0 1 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne and and and not}ifelse}ifelse}ifelse}bind def/is_composite{is_separation not is_distilling or}bd/is_sim_devicen{level2 level3 not and{is_distilling is_rip_separation or}{false}ifelse}bd/@PL{/LV where{pop LV 2 ge level2 not and {@np/Courier findfont 12 scalefont setfont 72 144 m (The PostScript level set in the Corel application is higher than)show 72 132 m (the PostScript level of this device. Change the PS Level in the Corel)show 72 120 m(application to Level 1 by selecting the PostScript tab in the print)show 72 108 m(dialog, and selecting Level 1 from the Compatibility drop down list.) show flush spg quit}if}if}bd/@BeginSysCorelDict{systemdict/Corel30Dict known {systemdict/Corel30Dict get exec}if systemdict/CorelLexDict known{1 systemdict /CorelLexDict get exec}if}bd/@EndSysCorelDict{systemdict/Corel30Dict known {end}if/EndCorelLexDict where{pop EndCorelLexDict}if}bd AutoFlatness{/@ifl{dup currentflat exch sub 10 gt{ ([Error: PathTooComplex; OffendingCommand: AnyPaintingOperator]\n)print flush @np exit}{currentflat 2 add setflat}ifelse}bd/@fill/fill ld/fill{currentflat{ {@fill}stopped{@ifl}{exit}ifelse}bind loop setflat}bd/@eofill/eofill ld/eofill {currentflat{{@eofill}stopped{@ifl}{exit}ifelse}bind loop setflat}bd/@clip /clip ld/clip{currentflat{{@clip}stopped{@ifl}{exit}ifelse}bind loop setflat} bd/@eoclip/eoclip ld/eoclip{currentflat{{@eoclip}stopped{@ifl}{exit}ifelse} bind loop setflat}bd/@stroke/stroke ld/stroke{currentflat{{@stroke}stopped {@ifl}{exit}ifelse}bind loop setflat}bd}if level2{/@ssa{true setstrokeadjust} bd}{/@ssa{}bd}ifelse/d/setdash ld/j/setlinejoin ld/J/setlinecap ld/M /setmiterlimit ld/w/setlinewidth ld/O{/$o xd}bd/R{/$O xd}bd/W/eoclip ld/c /curveto ld/C/c ld/l/lineto ld/L/l ld/rl/rlineto ld/m/moveto ld/n/newpath ld/N /newpath ld/P{11 rp}bd/u{}bd/U{}bd/A{pop}bd/q/@gs ld/Q/@gr ld/&{}bd/@j{@sv @np} bd/@J{@rs}bd/g{1 exch sub 0 0 0 1 null 1 set_fill_color/$fil 0 def}bd/G{1 sub neg 0 0 0 1 null 1 set_outline_color}bd/set_fill_color{/fill_color exch def /num_fill_inks fill_color length 6 idiv def/bFillDeviceN num_fill_inks 1 gt def /$fil 0 def}bd/set_outline_color{/outl_color exch def/num_outl_inks outl_color length 6 idiv def/bOutlDeviceN num_outl_inks 1 gt def}bd /get_devicen_color_names{dup length 6 idiv dup 5 mul exch getinterval}bd /create_colorarray_from_devicen_params{/ink_names_temp exch def /tint_params_temp exch def/ink_values_temp exch def[ink_values_temp aload pop tint_params_temp aload pop ink_names_temp aload pop]}bd /get_devicen_color_specs{dup length 6 idiv dup 4 mul getinterval}bd /get_devicen_color{dup length 6 idiv 0 exch getinterval}bd/mult_devicen_color{ /colorarray exch def/mult_vals exch def 0 1 mult_vals length 1 sub{colorarray exch dup mult_vals exch get exch dup colorarray exch get 3 -1 roll mul put}for colorarray}bd/combine_devicen_colors{/colorarray2 exch def/colorarray1 exch def /num_inks1 colorarray1 length 6 idiv def/num_inks2 colorarray2 length 6 idiv def/num3 0 def/colorarray3[num_inks1 num_inks2 add 6 mul{0}repeat]def 0 1 num_inks1 1 sub{colorarray1 exch get colorarray3 num3 3 -1 roll put/num3 num3 1 add def}for 0 1 num_inks2 1 sub{colorarray2 exch get colorarray3 num3 3 -1 roll put/num3 num3 1 add def}for colorarray1 num_inks1 dup 4 mul getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks1 4 mul add def colorarray2 num_inks2 dup 4 mul getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks2 4 mul add def colorarray1 num_inks1 dup 5 mul exch getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks1 add def colorarray2 num_inks2 dup 5 mul exch getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks2 add def colorarray3}bd/get_devicen_color_spec{ /colorant_index exch def/colorarray exch def/ncolorants colorarray length 6 idiv def[colorarray colorant_index get colorarray ncolorants colorant_index 4 mul add 4 getinterval aload pop colorarray ncolorants 5 mul colorant_index add get]}bd/set_devicen_color{level3{/colorarray exch def/numcolorants colorarray length 6 idiv def colorarray get_devicen_color_specs/tint_params exch def[ /DeviceN colorarray get_devicen_color_names/DeviceCMYK{tint_params CorelTintTransformFunction}]setcolorspace colorarray get_devicen_color aload pop setcolor}{/DeviceCMYK setcolorspace devicen_to_cmyk aload pop pop @tc_5 setprocesscolor_5}ifelse}bd/sf{/bmp_fill_fg_color xd}bd/i{dup 0 ne{setflat} {pop}ifelse}bd/v{4 -2 roll 2 copy 6 -2 roll c}bd/V/v ld/y{2 copy c}bd/Y/y ld /@w{matrix rotate/$ptm xd matrix scale $ptm dup concatmatrix/$ptm xd 1 eq{$ptm exch dup concatmatrix/$ptm xd}if 1 w}bd/@g{1 eq dup/$sdf xd{/$scp xd/$sca xd /$scf xd}if}bd/@G{1 eq dup/$SDF xd{/$SCP xd/$SCA xd/$SCF xd}if}bd/@D{2 index 0 eq{$dsf 3 1 roll 4 -1 roll pop}if 3 copy exch $Scra add exch load SetScr/$dsp xd/$dsa xd/$dsf xd}bd/$ngx{$SDF{$SCF SepMode_5 0 eq{$SCA}{$dsa}ifelse $SCP @ss }if}bd/@MN{2 copy le{pop}{exch pop}ifelse}bd/@MX{2 copy ge{pop}{exch pop} ifelse}bd/InRange{3 -1 roll @MN @MX}bd/@sqr{dup 0 rl dup 0 exch rl neg 0 rl @cp }bd/currentscale{1 0 dtransform matrix defaultmatrix idtransform dup mul exch dup mul add sqrt 0 1 dtransform matrix defaultmatrix idtransform dup mul exch dup mul add sqrt}bd/@unscale{}bd/wDstChck{2 1 roll dup 3 -1 roll eq{1 add}if} bd/@dot{dup mul exch dup mul add 1 exch sub}bd/@lin{exch pop abs 1 exch sub}bd /cmyk2rgb{3{dup 5 -1 roll add 1 exch sub dup 0 lt{pop 0}if exch}repeat pop}bd /rgb2cmyk{3{1 exch sub 3 1 roll}repeat 3 copy @MN @MN 3{dup 5 -1 roll sub neg exch}repeat}bd/rgb2g{2 index .299 mul 2 index .587 mul add 1 index .114 mul add 4 1 roll pop pop pop}bd/devicen_to_cmyk{/convertcolor exch def convertcolor get_devicen_color aload pop convertcolor get_devicen_color_specs CorelTintTransformFunction}bd/WaldoColor_5 where{pop}{/SetRgb/setrgbcolor ld /GetRgb/currentrgbcolor ld/SetGry/setgray ld/GetGry/currentgray ld/SetRgb2 systemdict/setrgbcolor get def/GetRgb2 systemdict/currentrgbcolor get def /SetHsb systemdict/sethsbcolor get def/GetHsb systemdict/currenthsbcolor get def/rgb2hsb{SetRgb2 GetHsb}bd/hsb2rgb{3 -1 roll dup floor sub 3 1 roll SetHsb GetRgb2}bd/setcmykcolor where{pop/LumSepsDict where{pop/SetCmyk_5{LumSepsDict /setcmykcolor get exec}def}{/AldusSepsDict where{pop/SetCmyk_5{AldusSepsDict /setcmykcolor get exec}def}{/SetCmyk_5/setcmykcolor ld}ifelse}ifelse}{ /SetCmyk_5{cmyk2rgb SetRgb}bd}ifelse/currentcmykcolor where{pop/GetCmyk /currentcmykcolor ld}{/GetCmyk{GetRgb rgb2cmyk}bd}ifelse/setoverprint where {pop}{/setoverprint{/$op xd}bd}ifelse/currentoverprint where{pop}{ /currentoverprint{$op}bd}ifelse/@tc_5{5 -1 roll dup 1 ge{pop}{4{dup 6 -1 roll mul exch}repeat pop}ifelse}bd/@trp{exch pop 5 1 roll @tc_5}bd /setprocesscolor_5{SepMode_5 0 eq{SetCmyk_5}{SepsColor not{4 1 roll pop pop pop 1 exch sub SetGry}{SetCmyk_5}ifelse}ifelse}bd/findcmykcustomcolor where{pop}{ /findcmykcustomcolor{5 array astore}bd}ifelse/Corelsetcustomcolor_exists false def/setcustomcolor where{pop/Corelsetcustomcolor_exists true def}if CorelIsSeps true eq CorelIsInRIPSeps false eq and{/Corelsetcustomcolor_exists false def}if Corelsetcustomcolor_exists false eq{/setcustomcolor{exch aload pop SepMode_5 0 eq{pop @tc_5 setprocesscolor_5}{CurrentInkName_5 eq{4 index}{0}ifelse 6 1 roll 5 rp 1 sub neg SetGry}ifelse}bd}if/@scc_5{dup type/booleantype eq{dup currentoverprint ne{setoverprint}{pop}ifelse}{1 eq setoverprint}ifelse dup _ eq {pop setprocesscolor_5 pop}{dup(CorelRegistrationColor)eq{5 rp 1 exch sub setregcolor}{findcmykcustomcolor exch setcustomcolor}ifelse}ifelse SepMode_5 0 eq{true}{GetGry 1 eq currentoverprint and not}ifelse}bd/colorimage where{pop /ColorImage{colorimage}def}{/ColorImage{/ncolors xd/$multi xd $multi true eq{ ncolors 3 eq{/daqB xd/daqG xd/daqR xd pop pop exch pop abs{daqR pop daqG pop daqB pop}repeat}{/daqK xd/daqY xd/daqM xd/daqC xd pop pop exch pop abs{daqC pop daqM pop daqY pop daqK pop}repeat}ifelse}{/dataaq xd{dataaq ncolors dup 3 eq{ /$dat xd 0 1 $dat length 3 div 1 sub{dup 3 mul $dat 1 index get 255 div $dat 2 index 1 add get 255 div $dat 3 index 2 add get 255 div rgb2g 255 mul cvi exch pop $dat 3 1 roll put}for $dat 0 $dat length 3 idiv getinterval pop}{4 eq{ /$dat xd 0 1 $dat length 4 div 1 sub{dup 4 mul $dat 1 index get 255 div $dat 2 index 1 add get 255 div $dat 3 index 2 add get 255 div $dat 4 index 3 add get 255 div cmyk2rgb rgb2g 255 mul cvi exch pop $dat 3 1 roll put}for $dat 0 $dat length ncolors idiv getinterval}if}ifelse}image}ifelse}bd}ifelse/setcmykcolor{ 1 5 1 roll null 6 array astore currentoverprint set_current_color/$ffpnt xd}bd /currentcmykcolor{GetCmyk}bd/setrgbcolor{rgb2cmyk setcmykcolor}bd /currentrgbcolor{currentcmykcolor cmyk2rgb}bd/sethsbcolor{hsb2rgb setrgbcolor} bd/currenthsbcolor{currentrgbcolor rgb2hsb}bd/setgray{dup dup setrgbcolor}bd /currentgray{currentrgbcolor rgb2g}bd/InsideDCS false def/IMAGE/image ld/image {InsideDCS{IMAGE}{/EPSDict where{pop SepMode_5 0 eq{IMAGE}{dup type/dicttype eq {dup/ImageType get 1 ne{IMAGE}{dup dup/BitsPerComponent get 8 eq exch /BitsPerComponent get 1 eq or currentcolorspace 0 get/DeviceGray eq and{ CurrentInkName_5(Black)eq{IMAGE}{dup/DataSource get/TCC xd/Height get abs{TCC pop}repeat}ifelse}{IMAGE}ifelse}ifelse}{2 index 1 ne{CurrentInkName_5(Black)eq {IMAGE}{/TCC xd pop pop exch pop abs{TCC pop}repeat}ifelse}{IMAGE}ifelse} ifelse}ifelse}{IMAGE}ifelse}ifelse}bd}ifelse/WaldoColor_5 true def /WaldoColor_13 where{pop}{/separate_color{SepMode_5 0 ne{[exch/colorarray_sep exch def/ink_num -1 def colorarray_sep length 6 idiv 1 gt{colorarray_sep get_devicen_color_names dup length 1 sub 0 1 3 -1 roll{exch dup 3 -1 roll dup 3 1 roll get CurrentInkName_5 eq{/ink_num exch def}{pop}ifelse}for pop ink_num -1 ne{colorarray_sep ink_num get_devicen_color_spec aload pop pop SepsColor not{ pop pop pop pop 1 0 0 0 5 -1 roll}if null}{0 0 0 0 0 null}ifelse}{ colorarray_sep 5 get $ink_5 4 eq{CurrentInkName_5 eq{colorarray_sep aload pop pop SepsColor not{pop pop pop pop 0 0 0 1}if null}{0 0 0 0 0 null}ifelse}{ colorarray_sep 0 get colorarray_sep $ink_5 1 add get 3 -1 roll null eq{0 0 0 4 -1 roll SepsColor{4 $ink_5 1 add roll}if null}{pop pop 0 0 0 0 0 null}ifelse }ifelse}ifelse]}if}bd/separate_cmyk_color{$ink_5 -1 ne{[exch aload pop 3 $ink5 sub index/colorarray_sep exch def/ink_num -1 def colorarray_sep get_devicen_color_names dup length 1 sub 0 1 3 -1 roll{exch dup 3 -1 roll dup 3 1 roll get CurrentInkName_5 eq{/ink_num exch def}{pop}ifelse}for pop ink_num -1 ne{[colorarray_sep ink_num get_devicen_color_spec aload pop]}{[0 0 0 0 0 null] }ifelse}if}bd/set_current_color{dup type/booleantype eq{dup currentoverprint ne {setoverprint}{pop}ifelse}{1 eq setoverprint}ifelse/cur_color exch def /nNumColors cur_color length 6 idiv def nNumColors 1 eq{cur_color 5 get (CorelRegistrationColor)eq{cur_color aload pop 5 rp 1 exch sub setregcolor}{ SepMode_5 0 eq{cur_color aload pop dup null eq{pop @tc_5 setprocesscolor_5}{ findcmykcustomcolor exch setcustomcolor}ifelse}{cur_color separate_color aload pop pop @tc_5 setprocesscolor_5}ifelse}ifelse}{SepMode_5 0 eq{is_distilling is_rip_separation or{cur_color set_devicen_color}{cur_color devicen_to_cmyk setprocesscolor_5}ifelse}{cur_color separate_color aload pop pop @tc_5 setprocesscolor_5}ifelse}ifelse SepMode_5 0 eq{true}{GetGry 1 eq currentoverprint and not}ifelse}bd}ifelse/WaldoColor_13 true def/$fm 0 def /wfill{1 $fm eq{fill}{eofill}ifelse}bd/@Pf{@sv SepMode_5 0 eq $Psc 0 ne or $ink_5 3 eq or{0 J 0 j[]0 d fill_color $o set_current_color pop $ctm setmatrix 72 1000 div dup matrix scale dup concat dup Bburx exch Bbury exch itransform ceiling cvi/Bbury xd ceiling cvi/Bburx xd Bbllx exch Bblly exch itransform floor cvi/Bblly xd floor cvi/Bbllx xd $Prm aload pop $Psn load exec}{1 SetGry wfill}ifelse @rs @np}bd/F{matrix currentmatrix $sdf{$scf $sca $scp @ss}if $fil 1 eq{CorelPtrnDoFill}{$fil 2 eq{@ff}{$fil 3 eq{@Pf}{level3{fill_color $o set_current_color{wfill}{@np}ifelse}{/overprint_flag $o def is_distilling is_rip_separation or{0 1 num_fill_inks 1 sub{dup 0 gt{/overprint_flag true def }if fill_color exch get_devicen_color_spec overprint_flag set_current_color{ @gs wfill @gr}{@np exit}ifelse}for}{fill_color overprint_flag set_current_color {@gs wfill @gr}{@np}ifelse}ifelse}ifelse}ifelse}ifelse}ifelse $sdf{$dsf $dsa $dsp @ss}if setmatrix}bd/f{@cp F}bd/S{matrix currentmatrix $ctm setmatrix $SDF {$SCF $SCA $SCP @ss}if level3{outl_color $O set_current_color{matrix currentmatrix $ptm concat stroke setmatrix}{@np}ifelse}{/overprint_flag $O def is_distilling is_rip_separation or{0 1 num_outl_inks 1 sub{dup 0 gt{ /overprint_flag true def}if outl_color exch get_devicen_color_spec overprint_flag set_current_color{matrix currentmatrix $ptm concat @gs stroke @gr setmatrix}{@np exit}ifelse}for}{outl_color overprint_flag set_current_color {matrix currentmatrix $ptm concat @gs stroke @gr setmatrix}{@np}ifelse}ifelse }ifelse $SDF{$dsf $dsa $dsp @ss}if setmatrix}bd/s{@cp S}bd/B{@gs F @gr S}bd/b{ @cp B}bd/_E{5 array astore exch cvlit xd}bd/@cc{currentfile $dat readhexstring pop}bd/@sm{/$ctm $ctm currentmatrix def}bd/@E{/Bbury xd/Bburx xd/Bblly xd /Bbllx xd}bd/@c{@cp}bd/@P{/$fil 3 def/$Psn xd/$Psc xd array astore/$Prm xd}bd /tcc{@cc}def/@B{@gs S @gr F}bd/@b{@cp @B}bd/@sep{CurrentInkName_5(Composite)eq {/$ink_5 -1 def}{CurrentInkName_5(Cyan)eq{/$ink_5 0 def}{CurrentInkName_5 (Magenta)eq{/$ink_5 1 def}{CurrentInkName_5(Yellow)eq{/$ink_5 2 def}{ CurrentInkName_5(Black)eq{/$ink_5 3 def}{/$ink_5 4 def}ifelse}ifelse}ifelse} ifelse}ifelse}bd/@whi{@gs -72000 dup m -72000 72000 l 72000 dup l 72000 -72000 l @cp 1 SetGry fill @gr}bd/@neg{[{1 exch sub}/exec cvx currenttransfer/exec cvx]cvx settransfer @whi}bd/deflevel 0 def/@sax{/deflevel deflevel 1 add def} bd/@eax{/deflevel deflevel dup 0 gt{1 sub}if def deflevel 0 gt{/eax load}{eax} ifelse}bd/eax{{exec}forall}bd/@rax{deflevel 0 eq{@rs @sv}if}bd systemdict /pdfmark known not{/pdfmark/cleartomark ld}if/wclip{1 $fm eq{clip}{eoclip} ifelse}bd level2{/setregcolor{/neg_flag exch def[/Separation/All/DeviceCMYK{ dup dup dup}]setcolorspace 1.0 neg_flag sub setcolor}bd}{/setregcolor{1 exch sub dup dup dup setcmykcolor}bd}ifelse/CorelTintTransformFunction{ /colorantSpecArray exch def/nColorants colorantSpecArray length 4 idiv def /inColor nColorants 1 add 1 roll nColorants array astore def/outColor 4 array def 0 1 3{/nOutInk exch def 1 0 1 nColorants 1 sub{dup inColor exch get exch 4 mul nOutInk add colorantSpecArray exch get mul 1 exch sub mul}for 1 exch sub outColor nOutInk 3 -1 roll put}for outColor aload pop}bind def % Copyright (c)1992-2007 Corel Corporation % All rights reserved. v14 r0.0 /@ii{concat 3 index 3 index m 3 index 1 index l 2 copy l 1 index 3 index l 3 index 3 index l clip pop pop pop pop}bd/@i{@sm @gs @ii 6 index 1 ne{/$frg true def pop pop}{1 eq{bmp_fill_fg_color $O set_current_color/$frg xd}{/$frg false def}ifelse 1 eq{@gs $ctm setmatrix F @gr}if}ifelse @np/$ury xd/$urx xd/$lly xd /$llx xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul 8 div ceiling cvi string def $bkg $frg or{$SDF{$SCF $SCA $SCP @ss}if $llx $lly Tl $urx $llx sub $ury $lly sub scale $bkg{fill_color set_current_color pop}if $wid $hei abs $bts 1 eq {$bkg}{$bts}ifelse[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]/tcc load $bts 1 eq{imagemask}{image}ifelse $SDF{$dsf $dsa $dsp @ss}if}{$hei abs{tcc pop} repeat}ifelse @gr $ctm setmatrix}bd/@I{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd /$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd $ngx $llx $lly Tl $urx $llx sub $ury $lly sub scale $wid $hei abs $bts[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse ]/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def $msimage false eq $ncl 1 eq or{/@cc load false $ncl ColorImage}{$wid $bts mul 8 div ceiling cvi $ncl 3 eq{dup dup/$dat1 exch string def/$dat2 exch string def/$dat3 exch string def/@cc1 load/@cc2 load/@cc3 load}{dup dup dup/$dat1 exch string def/$dat2 exch string def/$dat3 exch string def/$dat4 exch string def/@cc1 load/@cc2 load /@cc3 load/@cc4 load}ifelse true $ncl ColorImage}ifelse $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd/@cc1{currentfile $dat1 readhexstring pop}bd/@cc2{ currentfile $dat2 readhexstring pop}bd/@cc3{currentfile $dat3 readhexstring pop }bd/@cc4{currentfile $dat4 readhexstring pop}bd/$msimage false def/COMP 0 def /MaskedImage false def/bImgDeviceN false def/nNumInksDeviceN 0 def /sNamesDeviceN[]def/tint_params[]def level2{/@I_2{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def $ngx $ncl 1 eq{/DeviceGray}{$ncl 3 eq{/DeviceRGB} {/DeviceCMYK}ifelse}ifelse setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale 8 dict begin/ImageType 1 def/Width $wid def/Height $hei abs def /BitsPerComponent $bts def/Decode $ncl 1 eq{[0 1]}{$ncl 3 eq{[0 1 0 1 0 1]}{[0 1 0 1 0 1 0 1]}ifelse}ifelse def/ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt {$hei}{0}ifelse]def/DataSource currentfile/ASCII85Decode filter COMP 1 eq {/DCTDecode filter}{COMP 2 eq{/RunLengthDecode filter}{COMP 3 eq{/LZWDecode filter}if}ifelse}ifelse def currentdict end image $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd}{/@I_2{}bd}ifelse level2{/@I_2D{@sm @gs @ii @np/$ury xd /$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd $ngx/scanline $wid $bts mul $ncl mul 8 div ceiling cvi string def/readscanline{currentfile scanline readhexstring pop}bind def level3{[/DeviceN sNamesDeviceN/DeviceCMYK{ tint_params CorelTintTransformFunction}]setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale 8 dict begin/ImageType 1 def/Width $wid def/Height $hei abs def/BitsPerComponent $bts def/Decode[nNumInksDeviceN{0 1}repeat]def /ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]def/DataSource{ readscanline}def currentdict end image}{/scanline_height $lly $ury sub 1 sub $hei div def/plate_scanline $wid string def/cmyk_scanline $wid 4 mul string def is_distilling is_rip_separation or{/bSimDeviceN true def}{/bSimDeviceN false def}ifelse/scanline_img_dict 8 dict begin/ImageType 1 def/Width $wid def /Height 1 def/BitsPerComponent $bts def/Decode bSimDeviceN{[0 1]}{[0 1 0 1 0 1 0 1]}ifelse def/ImageMatrix[$wid 0 0 1 neg 0 1]def/DataSource bSimDeviceN{ plate_scanline}{cmyk_scanline}ifelse def currentdict end def 0 1 $hei 1 sub{ @gs/nScanIndex exch def readscanline pop/$t_lly $ury $lly scanline_height nScanIndex mul sub sub ceiling cvi def/$t_ury $t_lly scanline_height sub ceiling cvi def bSimDeviceN{0 1 $ncl 1 sub{@gs/nInkIndex exch def 0 1 plate_scanline length 1 sub{dup $ncl mul nInkIndex add scanline exch get plate_scanline 3 1 roll put}for[0 1 $ncl 1 sub{nInkIndex eq{1.0}{0.0}ifelse }for]/sepTintTransformParams exch def[/Separation sNamesDeviceN nInkIndex get /DeviceCMYK{sepTintTransformParams aload pop tint_params CorelTintTransformFunction @tc_5}]setcolorspace $llx $t_lly Tl $urx $llx sub $t_ury $t_lly sub scale nInkIndex 0 eq currentoverprint not and{false setoverprint}{true setoverprint}ifelse scanline_img_dict image @gr}for}{0 1 $wid 1 sub{dup $ncl mul scanline exch $ncl getinterval 0 1 $ncl 1 sub{2 copy get 255 div 3 1 roll pop}for pop tint_params CorelTintTransformFunction 5 -1 roll cmyk_scanline exch 0 1 3{3 1 roll 2 copy 5 -1 roll dup 8 exch sub index 255 mul cvi 3 1 roll exch 4 mul add exch put}for 6 rp}for/DeviceCMYK setcolorspace $llx $t_lly Tl $urx $llx sub $t_ury $t_lly sub scale scanline_img_dict image}ifelse @gr}for}ifelse $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd}{/@I_2D{}bd}ifelse/@I_3{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def $ngx bImgDeviceN{[/DeviceN sNamesDeviceN/DeviceCMYK{ tint_params CorelTintTransformFunction}]}{$ncl 1 eq{/DeviceGray}{$ncl 3 eq {/DeviceRGB}{/DeviceCMYK}ifelse}ifelse}ifelse setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale/ImageDataDict 8 dict def ImageDataDict begin /ImageType 1 def/Width $wid def/Height $hei abs def/BitsPerComponent $bts def /Decode[$ncl{0 1}repeat]def/ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0} ifelse]def/DataSource currentfile/ASCII85Decode filter COMP 1 eq{/DCTDecode filter}{COMP 2 eq{/RunLengthDecode filter}{COMP 3 eq{/LZWDecode filter}if} ifelse}ifelse def end/MaskedImageDict 7 dict def MaskedImageDict begin /ImageType 3 def/InterleaveType 3 def/MaskDict ImageMaskDict def/DataDict ImageDataDict def end MaskedImageDict image $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd/@SetMask{/$mbts xd/$mhei xd/$mwid xd/ImageMaskDict 8 dict def ImageMaskDict begin/ImageType 1 def/Width $mwid def/Height $mhei abs def /BitsPerComponent $mbts def/DataSource maskstream def/ImageMatrix[$mwid 0 0 $mhei neg 0 $mhei 0 gt{$mhei}{0}ifelse]def/Decode[1 0]def end}bd/@daq{dup type /arraytype eq{{}forall}if}bd/@BMP{/@cc xd UseLevel 3 eq MaskedImage true eq and {7 -2 roll pop pop @I_3}{12 index 1 gt UseLevel 2 eq UseLevel 3 eq or and{7 -2 roll pop pop bImgDeviceN{@I_2D}{@I_2}ifelse}{11 index 1 eq{12 -1 roll pop @i}{ 7 -2 roll pop pop @I}ifelse}ifelse}ifelse}bd/disable_raster_output{/@BMP load /old_raster_func exch bind def/@BMP{8 rp/$ury xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/scanline $wid $bts mul $ncl mul 8 div ceiling cvi string def 0 1 $hei 1 sub{currentfile scanline readhexstring pop pop pop}for }def}bd/enable_raster_output{/old_raster_func where{pop/old_raster_func load /@BMP exch bd}if}bd end %%EndResource %%EndProlog %%BeginSetup wCorel14Dict begin @BeginSysCorelDict @ssa 1.00 setflat /$fst 128 def %%EndSetup %%Page: 1 1 %%ViewingOrientation: 0 1 1 0 %LogicalPage: 1 %%BeginPageSetup @sv @sm @sv %%EndPageSetup @rax %Note: Object 0.00000 0.00000 479.99962 479.99962 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 0.00000 479.99962 m 479.99962 479.99962 L 479.99962 0.00000 L 0.00000 0.00000 L 0.00000 479.99962 L @c F @rax %Note: Object 138.39987 151.22324 217.76117 211.53288 @E /$fm 0 def 0 O 0 @g [ 1.00 0.00 0.00 0.00 0.00 null ] set_fill_color 164.08035 151.92652 m 166.89231 152.68025 168.57780 155.60022 167.82463 158.41247 C 167.55817 159.40573 167.02072 160.25839 166.31461 160.91178 C 176.48050 178.48658 L 177.30340 178.38624 178.16202 178.50019 178.96847 178.86246 C 179.80724 179.23890 180.48331 179.83559 180.95811 180.55389 C 206.78542 171.52271 L 206.70945 170.87924 206.75254 170.21254 206.92913 169.55178 C 207.00822 169.25698 207.11112 168.97408 207.23528 168.70564 C 188.26186 158.48192 L 187.18101 160.15748 185.09669 161.03254 183.07474 160.49055 C 180.57090 159.81987 179.06995 157.22022 179.74148 154.71723 C 180.41187 152.21339 183.01153 150.71244 185.51537 151.38312 C 187.75757 151.98378 189.19502 154.13301 188.98866 156.37351 C 208.62794 166.88494 L 209.91061 165.80551 211.68227 165.34403 213.41395 165.80750 C 216.22649 166.56180 217.91140 169.48176 217.15824 172.29373 C 216.40450 175.10542 213.48454 176.78976 210.67342 176.03745 C 210.66236 176.03405 L 201.61729 188.87584 L 202.78261 190.03039 203.31865 191.76350 202.86652 193.45238 C 202.80047 193.69956 202.71487 193.93569 202.61310 194.16246 C 211.14028 202.51502 L 212.09329 202.09096 213.18917 201.97616 214.26803 202.26557 C 216.77131 202.93625 218.27197 205.53591 217.60157 208.03975 c 216.93090 210.54274 214.33124 212.04369 211.82740 211.37301 C 209.32413 210.70233 207.82346 208.10239 208.49414 205.59912 c 208.66224 204.97124 208.95222 204.40630 209.33150 203.92271 C 201.19550 195.95565 L 200.06844 196.82476 198.56438 197.18079 197.09235 196.78564 c 194.68942 196.14217 193.21030 193.71997 193.68907 191.31307 C 180.97058 185.76198 L 179.72079 187.65071 177.23424 188.41946 175.10627 187.46334 c 172.74132 186.40176 171.67521 183.59575 172.73679 181.23165 c 173.09679 180.42973 173.65663 179.77748 174.33269 179.30665 C 164.36013 162.06576 L 163.41307 162.37984 162.36907 162.43115 161.33924 162.15562 C 160.22268 161.85628 159.28441 161.21594 158.60523 160.37631 C 147.40923 169.58154 L 148.39455 171.76082 147.58328 174.38343 145.46920 175.60403 C 143.21792 176.90372 140.32885 176.12957 139.02917 173.87830 c 137.72948 171.62702 138.50362 168.73795 140.75490 167.43827 C 142.46164 166.45323 144.53490 166.66016 146.00409 167.79827 C 157.56917 158.29257 L 157.36224 157.45521 157.35742 156.55748 157.59496 155.67080 C 158.34926 152.85940 161.26866 151.17279 164.08035 151.92652 C @c 208.63446 174.96907 m 208.17553 174.57987 207.78917 174.11698 207.48699 173.60391 C 181.72375 182.68441 L 181.75861 183.01805 181.75776 183.35764 181.72035 183.69609 C 181.77846 183.62948 L 194.65455 189.26306 L 195.79323 187.86813 197.68904 187.18441 199.53298 187.67820 C 199.65883 187.71392 L 208.63446 174.96907 L @c F @rax %Note: Object 87.87118 161.42117 166.14510 231.41934 @E /$fm 0 def 0 O 0 @g [ 1.00 0.00 0.00 0.00 0.00 null ] set_fill_color 89.41465 198.74835 m 91.47345 196.69011 94.84498 196.69039 96.90406 198.74863 C 97.63087 199.47600 98.10057 200.36778 98.31345 201.30605 C 118.61631 201.28961 L 118.94117 200.52680 119.46898 199.84025 120.18586 199.32293 C 120.93137 198.78463 121.78630 198.49776 122.64548 198.44561 C 127.73820 171.56296 L 127.14293 171.30671 126.58706 170.93622 126.10318 170.45291 C 125.88746 170.23691 125.69386 170.00646 125.52321 169.76494 C 107.18249 181.08425 L 108.09326 182.85817 107.80894 185.10094 106.32869 186.58063 C 104.49581 188.41380 101.49420 188.41380 99.66189 186.58091 C 97.82901 184.74831 97.82872 181.74643 99.66161 179.91354 C 101.30287 178.27200 103.88268 178.10164 105.72009 179.40076 C 124.64306 167.64831 L 124.34939 165.99798 124.83553 164.23257 126.10290 162.96463 C 128.16255 160.90611 131.53351 160.90696 133.59231 162.96520 C 135.65055 165.02372 135.64913 168.39468 133.59175 170.45291 C 133.58324 170.46113 L 140.18230 184.71515 L 141.76488 184.28315 143.53370 184.68539 144.77017 185.92157 C 144.95131 186.10243 145.11288 186.29461 145.25858 186.49616 C 156.75562 183.28762 L 156.86476 182.25014 157.31348 181.24384 158.10350 180.45411 C 159.93581 178.62180 162.93770 178.62151 164.77058 180.45439 c 166.60318 182.28671 166.60318 185.28831 164.77058 187.12148 C 162.93798 188.95408 159.93581 188.95380 158.10350 187.12148 c 157.64372 186.66198 157.29931 186.12794 157.07027 185.55789 C 146.10246 188.62044 L 146.29181 190.03096 145.84791 191.51150 144.76989 192.58866 c 143.01099 194.34813 140.17408 194.41786 138.32872 192.79984 C 127.16220 201.03874 L 128.17304 203.06523 127.59562 205.60337 125.70350 206.96825 c 123.60161 208.48535 120.63855 208.00573 119.12202 205.90441 c 118.60753 205.19150 118.32265 204.38050 118.25291 203.55987 C 98.33556 203.57603 L 98.13373 204.55285 97.65638 205.48261 96.90265 206.23691 C 96.08513 207.05414 95.06154 207.54652 93.99487 207.71490 C 96.36888 222.01342 L 98.74885 222.25011 100.61461 224.26384 100.61461 226.70504 C 100.61433 229.30441 98.49940 231.41934 95.90003 231.41934 c 93.30038 231.41934 91.18545 229.30441 91.18573 226.70476 C 91.18573 224.73411 92.40151 223.04211 94.12186 222.33883 C 91.67244 207.57033 L 90.84359 207.33080 90.06406 206.88605 89.41465 206.23720 C 87.35726 204.17811 87.35613 200.80658 89.41465 198.74835 C @c 131.64718 171.68457 m 131.08082 171.88753 130.48668 171.99071 129.89140 171.99581 C 124.87351 198.84784 L 125.17994 198.98447 125.47361 199.15483 125.74800 199.35638 C 125.71909 199.27304 L 137.03613 190.93861 L 136.39776 189.25512 136.75323 187.27115 138.10309 185.92157 C 138.19691 185.83030 L 131.64718 171.68457 L @c F @rax %Note: Object 79.80605 212.25458 124.79669 299.89587 @E /$fm 0 def 0 O 0 @g [ 1.00 0.00 0.00 0.00 0.00 null ] set_fill_color 92.63083 286.82164 m 91.87767 284.00967 93.56372 281.08998 96.37569 280.33597 C 97.36894 280.07008 98.37609 280.10920 99.29537 280.39408 C 109.43235 262.80283 L 108.93402 262.14038 108.60350 261.33987 108.51392 260.46028 C 108.42066 259.54554 108.59953 258.66198 108.98391 257.89153 C 88.24932 240.03978 L 87.72973 240.42728 87.13106 240.72350 86.47030 240.90094 C 86.17550 240.97975 85.87899 241.03219 85.58476 241.05883 C 86.21717 262.60214 L 88.20879 262.70050 90.00879 264.06822 90.55020 266.09017 C 91.22145 268.59373 89.72050 271.19339 87.21723 271.86350 C 84.71339 272.53474 82.11373 271.03408 81.44277 268.53024 C 80.84183 266.28803 81.98419 263.96872 84.02797 263.02706 C 83.31165 240.76318 L 81.73531 240.19228 80.44980 238.88835 79.98520 237.15694 C 79.23231 234.34413 80.91865 231.42501 83.73033 230.67128 C 86.54230 229.91811 89.46085 231.60472 90.21487 234.41556 C 90.21770 234.42690 L 105.86126 235.83912 L 106.27880 234.25257 107.51159 232.92170 109.20019 232.46901 C 109.44737 232.40268 109.69455 232.35874 109.94202 232.33323 C 112.91159 220.77241 L 112.06800 220.15928 111.42085 219.26750 111.13200 218.18835 C 110.46132 215.68507 111.96198 213.08570 114.46583 212.41446 c 116.96882 211.74378 119.56847 213.24444 120.23972 215.74828 C 120.91039 218.25128 119.40917 220.85121 116.90589 221.52217 c 116.27802 221.69027 115.64362 221.72173 115.03531 221.63499 C 112.20350 232.66460 L 113.51991 233.20602 114.58035 234.33080 114.97408 235.80283 c 115.61811 238.20576 114.26003 240.69770 111.93619 241.48658 C 113.48816 255.27657 L 115.74850 255.41461 117.65792 257.18372 117.89376 259.50472 c 118.15654 262.08340 116.25987 264.40980 113.68176 264.67228 c 112.80728 264.76157 111.96255 264.60283 111.21676 264.25276 C 101.27197 281.50980 L 102.01720 282.17310 102.58384 283.05128 102.85994 284.08110 C 103.15899 285.19767 103.07367 286.33039 102.68617 287.33839 C 116.25591 292.43197 L 117.65083 290.48882 120.32759 289.87994 122.44167 291.10054 C 124.69294 292.40050 125.46709 295.28957 124.16740 297.54085 c 122.86743 299.79213 119.97836 300.56598 117.72709 299.26630 C 116.02063 298.28098 115.16343 296.38176 115.41430 294.54038 C 101.39953 289.27757 L 100.77789 289.87569 100.00290 290.32838 99.11622 290.56620 C 96.30425 291.31852 93.38400 289.63361 92.63083 286.82164 C @c 90.30926 236.71559 m 90.20183 237.30746 89.99405 237.87354 89.70066 238.39172 C 110.44630 256.16324 L 110.71786 255.96624 111.01238 255.79701 111.32419 255.66038 C 111.23745 255.64337 L 109.67811 241.67565 L 107.90079 241.38652 106.36072 240.08683 105.86665 238.24290 C 105.83433 238.11619 L 90.30926 236.71559 L @c F @rax %Note: Object 133.64957 202.31915 400.19357 265.86142 @E /$fm 1 def 0 O 0 @g [ 1.00 0.00 0.00 0.00 0.00 null ] set_fill_color 233.89965 250.84460 m 233.89965 245.83890 L 224.99972 245.83890 l 220.22674 245.83890 217.83940 243.35943 217.83940 238.40050 c 217.83940 214.62406 L 212.76454 214.62406 L 212.76454 239.23502 l 212.76454 246.97446 216.54198 250.84460 224.09688 250.84460 c 233.89965 250.84460 L @c 261.91531 250.91405 m 269.47049 250.91405 273.24765 247.04447 273.24765 239.30504 c 273.24765 226.23420 l 273.24765 218.49420 269.47049 214.62406 261.91531 214.62406 c 249.12312 214.62406 l 242.68195 214.62406 239.46038 217.82239 239.46038 224.21849 c 239.46038 227.06816 l 239.46038 232.44463 242.51896 235.13329 248.63698 235.13329 c 265.39143 235.13329 L 265.39143 231.03071 L 249.95735 231.03071 l 246.20315 231.03071 244.32633 229.43225 244.32633 226.23420 c 244.32633 224.56573 l 244.32633 220.85830 246.22753 219.00416 250.02709 219.00416 c 261.08135 219.00416 l 265.80784 219.00416 268.17194 221.46123 268.17194 226.37395 c 268.17194 238.81748 l 268.17194 243.59159 265.87786 245.97808 261.28970 245.97808 c 240.57213 245.97808 L 240.57213 250.91405 L 261.91531 250.91405 l @c 304.46277 250.91405 m 312.01710 250.91405 315.79512 247.04447 315.79512 239.30504 c 315.79512 226.23420 l 315.79512 218.49420 312.01710 214.62406 304.46277 214.62406 c 289.02954 214.62406 L 289.02954 219.62976 L 303.48879 219.62976 l 308.30995 219.62976 310.71940 222.10923 310.71940 227.06816 c 310.71940 238.46995 l 310.71940 243.38268 308.30995 245.83890 303.48879 245.83890 c 293.40935 245.83890 l 288.63496 245.83890 286.24791 243.38268 286.24791 238.46995 c 286.24791 202.31915 L 281.17304 202.31915 L 281.17304 239.30504 l 281.17304 247.04447 284.95077 250.91405 292.50539 250.91405 c 304.46277 250.91405 l @c 329.14290 265.86142 m 329.14290 250.91405 L 347.35691 250.91405 l 354.91209 250.91405 358.68869 247.04447 358.68869 239.30504 c 358.68869 214.62406 L 353.61354 214.62406 L 353.61354 238.46995 l 353.61354 243.38268 351.20353 245.83890 346.38378 245.83890 c 329.14290 245.83890 L 329.14290 214.62406 L 324.06803 214.62406 L 324.06803 265.86142 L 329.14290 265.86142 L @c 400.19357 265.37499 m 400.19357 260.16066 L 385.10759 260.16066 L 385.10759 214.62406 L 379.82353 214.62406 L 379.82353 260.16066 L 364.52920 260.16066 L 364.52920 265.37499 L 400.19357 265.37499 L @c 159.30283 265.37499 m 159.30283 226.16476 l 159.30283 218.47124 155.52567 214.62406 147.97049 214.62406 c 133.64957 214.62406 L 133.64957 219.83811 L 146.99792 219.83811 l 151.67849 219.83811 154.01877 222.22545 154.01877 226.99899 c 154.01877 265.37499 L 159.30283 265.37499 L @c 203.30957 265.37499 m 203.30957 260.16066 L 179.60230 260.16066 l 174.92088 260.16066 172.58145 257.77361 172.58145 253.00006 c 172.58145 226.99899 l 172.58145 222.22545 174.92088 219.83811 179.60230 219.83811 c 193.22872 219.83811 l 197.44668 219.83811 199.55537 221.94709 199.55537 226.16476 c 199.55537 238.81748 L 185.16472 238.81748 L 185.16472 243.47565 L 204.49106 243.47565 L 204.49106 224.91383 l 204.49106 218.05398 201.08523 214.62406 194.27131 214.62406 c 178.62973 214.62406 l 171.07512 214.62406 167.29710 218.47124 167.29710 226.16476 c 167.29710 253.83402 l 167.29710 261.52781 171.07512 265.37499 178.62973 265.37499 c 203.30957 265.37499 L @c F @rax %Note: Object 116.83162 268.46674 196.19320 328.77638 @E /$fm 0 def 0 O 0 @g [ 1.00 0.00 0.00 0.00 0.00 null ] set_fill_color 170.51244 328.07310 m 167.70076 327.31937 166.01528 324.39940 166.76816 321.58715 C 167.03461 320.59389 167.57206 319.74123 168.27846 319.08784 C 158.11257 301.51304 L 157.28967 301.61339 156.43106 301.49943 155.62460 301.13717 C 154.78583 300.76072 154.10976 300.16403 153.63496 299.44573 C 127.80765 308.47691 L 127.88334 309.12038 127.84054 309.78709 127.66394 310.44784 C 127.58457 310.74265 127.48195 311.02554 127.35780 311.29398 C 146.33121 321.51770 L 147.41178 319.84214 149.49638 318.96709 151.51833 319.50907 C 154.02217 320.17975 155.52283 322.77940 154.85159 325.28239 C 154.18091 327.78624 151.58154 329.28718 149.07770 328.61650 C 146.83550 328.01584 145.39805 325.86661 145.60441 323.62611 C 125.96513 313.11468 L 124.68246 314.19411 122.91052 314.65559 121.17883 314.19213 C 118.36658 313.43783 116.68167 310.51786 117.43455 307.70589 C 118.18828 304.89420 121.10854 303.20986 123.91965 303.96217 C 123.93071 303.96557 L 132.97550 291.12378 L 131.81046 289.96923 131.27414 288.23613 131.72655 286.54724 C 131.79260 286.30006 131.87820 286.06394 131.97997 285.83717 C 123.45279 277.48460 L 122.49978 277.90866 121.40391 278.02346 120.32504 277.73405 C 117.82176 277.06337 116.32082 274.46372 116.99150 271.95987 c 117.66217 269.45688 120.26183 267.95594 122.76567 268.62661 C 125.26866 269.29729 126.76961 271.89723 126.09893 274.40050 c 125.93083 275.02838 125.64057 275.59332 125.26157 276.07691 C 133.39757 284.04397 L 134.52463 283.17487 136.02869 282.81883 137.50072 283.21398 c 139.90365 283.85745 141.38277 286.27965 140.90400 288.68655 C 153.62249 294.23764 L 154.87200 292.34891 157.35883 291.58016 159.48680 292.53628 c 161.85146 293.59786 162.91786 296.40387 161.85628 298.76797 c 161.49600 299.56989 160.93616 300.22214 160.26038 300.69298 C 170.23294 317.93386 L 171.18000 317.61978 172.22372 317.56847 173.25383 317.84400 C 174.37039 318.14334 175.30866 318.78369 175.98784 319.62331 C 187.18384 310.41808 L 186.19852 308.23880 187.00951 305.61619 189.12387 304.39559 C 191.37515 303.09591 194.26394 303.87005 195.56391 306.12132 c 196.86359 308.37260 196.08945 311.26167 193.83817 312.56135 C 192.13143 313.54639 190.05817 313.33946 188.58898 312.20135 C 177.02391 321.70706 L 177.23083 322.54441 177.23537 323.44214 176.99811 324.32882 C 176.24381 327.14022 173.32441 328.82683 170.51244 328.07310 C @c 125.95861 305.03055 m 126.41754 305.41975 126.80391 305.88265 127.10608 306.39572 C 152.86932 297.31521 L 152.83446 296.98157 152.83502 296.64198 152.87272 296.30353 C 152.81461 296.37014 L 139.93852 290.73657 L 138.79984 292.13150 136.90375 292.81521 135.06009 292.32142 C 134.93424 292.28570 L 125.95861 305.03055 L @c F @rax %Note: Object 168.44825 255.61984 246.72246 318.57874 @E /$fm 0 def 0 O 0 @g [ 1.00 0.00 0.00 0.00 0.00 null ] set_fill_color 245.17899 281.25128 m 243.12019 283.30951 239.74866 283.30951 237.68986 281.25071 C 236.96306 280.52334 236.49364 279.63241 236.27991 278.69357 C 215.97732 278.71058 L 215.65276 279.47367 215.12466 280.15937 214.40721 280.67698 C 213.66142 281.21499 212.80791 281.50129 211.94816 281.55402 C 206.85600 308.43666 L 207.45014 308.69320 208.00658 309.06312 208.49017 309.54643 C 208.70561 309.76271 208.89893 309.99288 209.06957 310.23496 C 227.41058 298.91509 L 226.49981 297.14117 226.78469 294.89839 228.26438 293.41871 C 230.09698 291.58611 233.09943 291.58611 234.93146 293.41928 C 236.76463 295.25187 236.76463 298.25348 234.93203 300.08608 C 233.29020 301.72762 230.71039 301.89827 228.87326 300.59915 C 209.95002 312.35102 L 210.24425 314.00164 209.75754 315.76762 208.49017 317.03499 C 206.43137 319.09380 203.05956 319.09294 201.00076 317.03499 C 198.94337 314.97562 198.94422 311.60523 201.00161 309.54643 C 201.01011 309.53792 L 194.41049 295.28476 L 192.82791 295.71676 191.05994 295.31452 189.82346 294.07805 C 189.64290 293.89748 189.48047 293.70472 189.33477 293.50375 C 177.83745 296.71257 L 177.72888 297.74920 177.27959 298.75606 176.49043 299.54551 C 174.65698 301.37811 171.65537 301.37811 169.82277 299.54551 c 167.99017 297.71291 167.99017 294.71102 169.82277 292.87843 C 171.65537 291.04526 174.65698 291.04611 176.48957 292.87843 c 176.94992 293.33877 177.29433 293.87225 177.52309 294.44202 C 188.49146 291.37975 L 188.30154 289.96980 188.74517 288.48869 189.82290 287.41124 c 191.58208 285.65178 194.41899 285.58120 196.26406 287.19950 C 207.43087 278.96117 L 206.42003 276.93468 206.99773 274.39710 208.88957 273.03165 c 210.99118 271.51483 213.95480 271.99417 215.47162 274.09578 c 215.98583 274.80813 216.27014 275.61969 216.33987 276.44003 C 236.25751 276.42302 L 236.45934 275.44649 236.93669 274.51672 237.69043 273.76299 C 237.96624 273.48718 238.26472 273.24907 238.58135 273.04724 C 234.78973 264.97587 L 232.43471 265.39398 230.09244 263.96220 229.43027 261.61200 C 228.72557 259.11014 230.18740 256.50085 232.68926 255.79587 c 235.19112 255.09090 237.80069 256.55301 238.50567 259.05487 C 239.04028 260.95209 238.32935 262.90913 236.86356 264.05348 C 240.72066 272.26715 L 242.30466 272.05427 243.96888 272.55288 245.17899 273.76299 C 247.23638 275.82151 247.23723 279.19332 245.17899 281.25128 C @c 202.94561 308.31534 m 203.51197 308.11209 204.10668 308.00806 204.70224 308.00324 C 209.72069 281.15178 L 209.41398 281.01543 209.12060 280.84592 208.84620 280.64409 C 208.87455 280.72658 L 197.55723 289.06186 L 198.19502 290.74535 197.83956 292.72876 196.49027 294.07805 C 196.39616 294.16904 L 202.94561 308.31534 L @c F %%PageTrailer @rs @rs %%Trailer @EndSysCorelDict end %%DocumentSuppliedResources: procset wCorel14Dict 14.0 0 %%EOF II*#  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~image descriptionmmsjql[]ZUm} e"686C)wV6mITŨI y]* )ZIgqdbim<., USR.C0}t +3&&| 0a&6a"IIToDyT]E"G3q.%c'im4?QO]/?w0o,4 k>>C9N1'_UG*Hss/z5S4L,QqG?1CG~%5V=gL5 S;d8 4a݅/)\ 5,U'G'zIo{"*a/wecAY3#}uwr|@z 1 CxP]e?B iH%%aQq+( BO(9HS1Ko(X BG{5Y};: go)$/w꥘{=AS#OaϔI OYz [m\+,703s_#k*[f;.7t,eJ<\yO_8{/o SQ/*'4j g(:3F$}a =5(< m*E2Tn-4om{J;w102L] 54-  8*a|_:]><&*å$7db!11mI:] {%p *cMЅ̡_XUF)b_0m8r,\5 W Es,t BSSSS&*=NSSSSSSSSSSSSSSSSSSSSSSSS~-sA &Y& -pv6v[ [p-3&pva :-tC&pv Xg0-g'&spvtW k u_-G&ypvi  SSSSSSSSSSSSSSL;(*=NSSSSSSSSSSSSSSSO=)SSSSI7ISSSSSSSSSSSSRG5&SSSSSSSSSSSSSSSO=+ SSSSO3SSSS$&SSSSB >-&5 i^&_AT&'&&jBv& naܰA~&$0&BT&cRK&ץ0M&4} ϙ~7+TEN:(=$$k2$B"F>`YE@ wQ+Wue' 6wP"Bel keK4eeo*;+;k?ՍG\"O[{)TE/oo'7h:{[QqcG;;-1rA[L-[.T \B1y7{ 8?..O ]5D ^]"ڀ'Y azs$SS;U+ec`-G!g!OtX#7W "y~ CIC=M BU.:cO;GCD}l_& [݅-#'cab<gy"_,]Y& u -F'%iEqesI YMT#:kk UťGW} Bp^,Q 9J5R oBkN[$qmK'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'*u-T |V7@gcD,Sz%pQ?f džk"(@jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-blue.jpg000066400000000000000000004743411402514743400223720ustar00rootroot00000000000000JFIF,,ExifMM*;JiP p >RESTU bRESTU ^http://ns.adobe.com/xap/1.0/ RESTU C     C   " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?O cnjnk)>0Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((((((((((߳~![u[ #pw5oe6U,~ιM3xqxZ۽q}K2Eg8P?Z?+]>lu*JjT8Sч$W5`' }#]QvdnWx3ᕴKhнu 1G\jA_WhFvFv,3/5%NB(6~@2$Aσ~k$Zm<ȔGpH9G5T1|p_dpѷ_.@8 pq۠%sTZ~l14xiMn֍zi3Ǩ(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((+cGM\WDеjPU eݿ_Z5m^|QհtL!z~T B+<>]FR4ӲM?}MlDcF\Ϣ]unZ0ivѬ6QFT`QE:6ۻ?IYQHg_~P}GEK!Cln/fk#&somHJH~yW)lsˏN9p7_(?7 (,tƥiaj[R̊Ma ^)(/A /ꢿN+5O?v~t|k^WuQ_cxš<3z"k+Z'sч ٢9)٭Ճѯ#[ou]i/n {=dW~ZzL@4v8PƼ^p5+ JS}a[ )%蝐QEyEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPnjnk\p~5-p9v/2pܟg\j7Q[Z%Ĭ8aBI~|ӎYdž{9>+φ|#. Pėze{U?/OWuX cSyAȯ1Wd[^Ot~G9o0Nz7/RO|0=7~ҡRln_wsgCτ>۰56)bK =؁_)|XE:‘!#_9y<+4Y䑋3Ԓzx39~ rjihE~ ]#Xve]H{: k#L/4]B l `Otaȯ tDĚM_E'>U-Fm5c7S./췪ouiSTsWi-%G<$HEwsP9$WWxFYx' Lxcuq7]>*ct yyZb_4MsPqeүB8o 'm6&y}Mk%(Ǐ߂+*~q-#2KRAUU0Nzv0XuOgن-sQ?t5]{Pކj̨olXB Pс 2:21_>fmWob4K{:;ȯho*b>+tIܯds95&EğW.kiiG\ٞV}oUKLѯ精1UчPAU4WG85QEQEQEQEQETi<ΐyIQGQ.h(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB(((((((((((((((((((((((((((((((+~Ͼ1*IXM,6{x˟e_K Vb '+m^\&G}]}cx? G].6qr^kooK}:@0r~.5/rQ^6ו?*_wpw”KXQΧ|~>oh^ҟQ;}2z4͂Trk]ʝZ{V>KWujkƧxw裢` ' ZX̦}d]?S^KKE5&iO~?n G jts:9 l`kcZ5y=t]T9f-&W#9f9uK~Q^Q@Q@Q@Q@Q@"I(bz+uL ~ZYMfZa&}q{K^,aݥh<_a=?jO#!Ppdnݘ>Of2VYU{uoeuca<")|O'wJ<̟7B pH_q^RljD iM7IY[Z'{}1 e ůpԦzGcTM%~gim%ԣ$^JC\}+ E|- $90Q^H^7Ts~nwcEuyYJGڴ3ތ9 (4k.le Mndzч?-k2<[ ;}^{;RyIWte>_||t^LI9x;=5|NU|Yz|du+V>-tuf??Y$>%%^{?+F3_5y:fJS=_Tל1Tj2٣|Nb"(QEQEQ@a^oɣ ^"8ɯ>~~wkIGڔbO?*Nl i |w.>c# '^_C_ZT0qy.exb1PR%}uD}#(#X#E r6;Vǭwڒ1$\7WgE|u:(˞}ӱl=D:RAzm_LMS|Ͷcdx gkX,*M-6hdWR0Akò1l20`6>Xg57 s̡,>!ޤuuk<9O(VZݭ{z5k?#+O͂((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((()%ĩHJQKkO؛ݾ$;խdG"y3{y>Y&w}]\ %-/A#lݖ={[:fBJʾmM=#Ķzw,BH U$s@x議\E˴ 9NTxZ/祟s珄<xڪἹWd?p+ߥGi%xlLr8^^O~ ,qWPB>HB䌴Px\d{o|XcjL!G[f;tsv$ׯs>!h=c d)SE_t}Wc<3XxB_>V3z`_&j_l5Lpl1{"=ɮZ3.0yb_>Gٷf˶"v#p+>d((((((+StM}}2v=OI3"A;)y[i㕏8Q#h ^>mC,K-K{=*s;o'+^l;῁,~/K \`!$t_!~"^ gΟ?zx/Wӿ*CN05Mz(;US\HO3$Y؜OrM~™tu5vY=?S42|&JK̊(B(( n4nf1WF 9WڟwqX@vW}_h3ʰ/g]j}W|Ww/gwLnsqj} *؎AW췩.}kC's67Kg&:xI@ 㗚v;d`W7>!⺴ >=,l9Pǡ*ަGU>hb>g䍝ơs\HvPgcס߳ĽVg,-|п|*.o.t N,[j0u|V/<,To;};_?x]/hH@?s5wgo[Iouw6WT PA|)c?#nbPS'd8WOUa1٭W.,V~զ]>UYŨ0Ϩ[m2(9z$ Y?}HBQҒH[&`0-dSV-֙Y8{[t9`>_d ƤT(¾ 좶LCi4u$K*¾6i_|I/$ :% ?ۑ|ǜ~~Ξ%?)sh,@:<ƿ;U?Rknm~ocçɧC(x(_b>ն> vl~qT&|-~-h)%R<7c#ߎ`N̟zWETV?P ,68LDdic/; O-+V|Eu~3S߇Ҳ 枀Ni*\~N:%%; j֋F+C((((((;⿏rA#ysSWhUe [YZDp0+do;5mVy: >j/+OfD I؜RGݯsML3 ػ/_/E/3tc&cV3oWv>^~.K9:Vk G׎E~SІbzcs LU'w^dQEuAEPEPEP_v~šm 5[당13Qv,[tWujPKxm/@:p&|WK,i-pc,9=+~ QE~HWTg'0A Պ)ӺI3{> 񾻡Xi۫)FXzԡվ2/c ôpU5a{q)bӧ4v>&S^"{w-{{.`,[#9x8gW5.٩n[p` zU?GR}nS!K@ fijJ+4gG[kwz?2\D^̦oݏoME|DZ򽴓Gqm+θ;vmǯjO]hXj6Mk5XᡃNC‹y/3su8_n8៊_|?(kI>I>GdNm9WG&}(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((((((((:<޹p еIq#a-g2ފ&u1*YXz?hƶU.5?S]C) dӫ]υ_W|MNha9ʞ%ٻ-Sꮬ?^o;yWّWUy2VƋzϺ`s_x/ϋʭkז SWО<[z˓68'_)S\N75x$ tyĹɥu/S+?2 o?<API_x/}^ѯ437Wu7_~ˏMwȗ>7 &:Vi:0 jNcҿ~8+)ܾ??"(ǿ_Meg//3MlGx+e_<$|ɴ)|Kj8j,帻'>Iv߉a?igV_׭giz]WpmGfFQ拺>&p98YEUQEQE߲Z?3A ,#'[ּ~}F Kh{XYَԓ_冎Q}e9 =s޾GsO7輗W^lUz7|~H5^ú=2XM+tDQ!_>#T @>L '$ȂZd-Φz/X?^־8/rxګޞQX:!e_Oou~- (B?) ( ( ( ( %oOjӈ4Mgj4pN3Ϣ'Jx%kgxq-MJu}ioDW|gW~jj:9fGS9?+Ƽmy[I 0:Ƈb[Z;ƾ9!kkqT^ʪ8U|q5jƮ: |;ĝ#B v?Gwȶ-ۖ&M~f -ά_-G72FJ^/,uU omxTP?@o[YW-bJ_Xk|/MfXqH_~zWpn_ -eoO> ך<>z/FuF6xF{յc?~kdzދMHR䶙.ZBvo*Jє?~i:Zg} 70e:ZNb#5;<;a8IlI4ۋYlHѺ08#+пh=. M*,~^{_aaY}ޮ~/𸊔m}Zu/ jZEu'1nX~]GCYVqg>&OZ&rq2í|i^%:}6≯ ?pHσߴ~-Gs #^#.7}ÿ|HM%$yr׉vOS깔/GW>1\>ܢj2}G?o? |E.Xī|{>:,XR~>eU[0qy]_c{1(<(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (^<6YWͼdg*'8Sݒ)ҝiSmJ/№a|gk~ּƿ _NvP$RW>ٮ*9.JU'4z5 i^-/孮e `ȇ AGfߍQ|_R|C!`#+r5״]c;.m~d?ڼ)kq#_3Y&1JOSK~>ozTdA]@9hrFZ|';ŅBXuGbA5?3|Fۡrm'#_%¹M嘝qEY ]4t~OuQ_q^o~:xcM'&?+goVRGTft5y˺m~Gv7FқrLnrяҾO/DD.asfo*Tutb!_ "7_ǙĥV>z?i~~KbzkZ7XnY>/6>n@o6 ?ρi^[}R?Uz1; +ݞּ7]x~ "]_':1cj{4QJoM||s%~ O"MgcmyFb?`Qo~x'S -cYS }OjkiW?<Ŵ ؂w6@n>q8aWatK39d <m^r}~Ke?:Iƥ1fg=؜@;+:j:}Γqey ]K cOCnO{ (>, Je&89|;Ŭb_^/m) '޾o2 Y/gQOկ^>(\9a?Mu+T a"txrXL궑isl1>pฯonk[O=,ǂ3L 'Zҿf*H#u#ࢊ((((m8=iyϗPO_>k6zmB ;HRc^-~{ȿyrMaGVsz׮Z|+gu<2o~;Ę湜2w)=LV翝;3WMB[y|+MAYT?m>}+hE~ B)cn.;SWm^[~~$|%t[sGf-[1?~dR&Q"ǃiƢ2_s~'f_ޚtߋ6z#tݛљ:toׅ_јD 3~_WuWEIvtRoQEDžQ@WE7dHee8 K홭xK^JV=X>>|Ey00Wzviʪl$_^l/Bǩ:Z#!Oךd/QfEм@o[ 3T7 zW ޽XMO@ԦӮׂc?,C헢x<\haVZL~Y{~v3 2ZZ˦u꺯|CKYd-=~$|N7⏄ڗٵ[wb!/o7%`I SIӼMKeZZ}aȧk'|7CРG/,6JkWh~'Rħ'V->\CmfXϚ|q됸+ 6嵝8hBPy׌Jgÿtֱm@FȘtG2pEM6|=i*w=8+ٿhΩ唒_jrRc8< # %xn ]^XlLye(8B(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y(((((((((((((((((((((((((((((TQ'ښKoXm{ӆr~Hpr{?ƕ " /FNa!v'ѵHΟ{Xϡb=%:WߟO?XsW*0V+~SF+~?}/gӿ%/|mwᎡ׺2D̵vGb2J?hςaL[&`m>cN>h`bkrk bx.!sH0;k 9$t~OK$5UYQEQ@Q@Y_\1Z\KkqܒO#^_n jk g~tW'GMIyǡ1y|\}_{!׭7tqx4 S-Od}~RZNCkvtw;Ꮘ|IMZygF|1O^}RJ$EӯSnl25HuV~(OŠ(n?~mlLF34D+7 |_`@/#vc}S|?2_T\&-Nw=7ĺ٨k`>5^-ǯ $&G,.W]gԬ[Ϟa+K'ЏӾCN4ȕ  4ǟӠ+IU7vl¥J)ƕ5hY.QY~ {oitp;.;zn7u|_i_7\qs ŮYpC!t.|O͎Ep }? !,/!CRӎU]~'LV59kekMC:+oּ5i}k+iVZéC_W~ ۫^51YgbXu}JLtrLC-<\Y>;7Zo3^^;ian'-rrO5o3v ֑<4~`/޾>|3/v}ON'覿0f&ydvGbrI=I5eZLƮzݟqe= YM it+QEW>oF-we҃z웯`tfm ^yCmxu_NW7T_^uwKb^'_uxzT7۬f?S_ _-Uh,//BQE)0QEQEQEikˋ}<4l 7ޯr0"jb8?ĮPk5c[K?Jqau&QEyGrkz΁tݖ&?(6FTE"6*z8"^܅F$kSQ_xVLrȘ鴹#x"گI*?5,5u>eꕚ2(Š(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((6MCfg .8:dxW?'7c};5R-Bhg6O8$Nyu/;)k~a14{;(#)6{\K{XyURI~GLn.er~7^M˫j F<vW'`jaTV;ϼRc));$%mx7ڗԙbN#fsϨ'D-2HeHl.*pGWJ fj-i/?0uiʅ}*)tȪ+s.~ wsoF9]}a_9_x|_ϢϮS4-(SR||[?,r"qyp%v@HQ}/e]TmFQt<4o/־m xbvq(DE^I'6uM2>Ǧ Ѥ#ҾfWu7?PpKX΢_ͤ~QzO'N[ JGT.ǫsX~?Pk|ˇk>޾+bxm*cMl.ʼnRO+3$Yֻ0\ZaR7pf}GeTmV?X#/>{@VEl/$5;r֙3nI1H;cA+/ ~^ :>|ye3|nxxm__Y?1XU:~io'g=om%V dO@xVk|9즿&v;{VLs&_?/cDM a~2U ǫz}vta)7J>.{/)_u[rv-,C Y;WQE~㇡O F4)+F*mjkYRmWArx&f:}s/W@~eW#~֤#V٫?*8ԃMzGխu*Rn,Yz20?'P4V[ 3XilHO%z3O{)wk,(,/U>_ϙM-ä59a3$Ow]oQQ\]Ci < 0UQI^xwW;GTH?c*Xx6?2elUEEfڗ |ku+[8Sމ&~J0 \G~xV̈́?[ce'ԺakŸYSPӑN|>QʟWxG񖏲=sOy›y rxxO2`ѸOFaeq^iN?7@/u.CȆ@FO~_1Kk}zyߧL ck~׶G%<q}25߇|ev:$ZήPr?g>ʴE͛>\dV ~dh UxQ藖ڽaav8P9&U"x?PϭZmh2Kxѻvtv3 g ?ǭ>_y*{.D5WQд8<4Xa |ajH67ῥcP L l˧7Njq;85e!A#iđd`r#A_X(;6^" y$dOJ nj`77ߪ\'|>8 \F]-G >o2RK+93\ۛZUN[<%mo_G$kO4$i}"ez~x5ԟܮmΣ]ck[(A(<Rַok''u׽[{"/EQX5FG ۼ嬟輑ęgJWwQ_D|QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE/Fǭݭ0=eKqF?WQZQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@v E13o%>\}\>չ=/Vx4[ ':`د{Nk6qXiɲ(!\*ORO$ks"VAsTz :ֱ2qVO˲]G:s$ Njoȣ̿ C/PC]q *6qNm~\~/#p?>i_~Sᗉ>jZ\26LR)@8?N~0z&w\gk>s_^(M-sNSf.@aЏC6r, m6>,h?IPJToSOPx㈵/KӢOyw'-?npMXW>˖ u[f>Hا(5O>#s^QlٮHz߷yЄC/S)X+}]Ac{W~#R_r>Hlgc\bT+Ǽ}Y|>7 z׏@Jɐ'ھſE_n'"~GFӍw:/#rE~~2'ooFOpG0?zׂ~!:%l_#<cj{\%G yu[?_tՒ|}xsJ󏈟Ek-; w1U"hJ3ПTk>9&k'$X{nnfV/$fv'$y$TQ_PGŶۻ (4|9>eَ9=~|3k?&/Ԟ\LycFpW_׆վ*s.;Ig.?ҿ ,|b#_ U߫%Fy)o (9?Z8O_t:xkkTy7u<e|8< ʚJ[I9q4F^_>~&vm7L,-P$ =I#%cma;;{/?|i /ͱt4ln~vյ KJOe繑cEq_~  \݅eYpIҿp)KK_߿gaD+#(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?Bc? Ez.井]h>L{y?챩EFbYeq׬Wm90)/ȩžW=#QEy'k'KW# eѱ9*3 5 Guc@g?25|ʂ|;ekE>yWgv.rYI4Q_'pQEQEQEQEQEQE[twRi//n\G v=QW{1rj1Wl:gGKJ9)#`w¾ ?f? |hsgջu/1w7;s=k."fY+k?OrPūJ=lEWd8u/z^ m6y?ub>.|\~bmSS^锭\2?+5Gzu48=G5h4$~Iٵ*xUri$WnFUQ_Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@>=nWnPI S# _l)9 (,`kF{YY <ZV3< *U[j%%E~b<ƭuDYyma'=GH'6(ȥYXpA??CBMS@[x,7]zĊ~8~:y/a xW"O\tYT}Q8 `L=4z3 왫ySe~[zTWOῈ>믥k/i8ɎQE:yw1_S U)GUhԡQҫ-ѣ?bemOkАE *}:o,@8>k|T[dp0:M~ {]ib'?vIϣk%w? Hq x]+魀>GJrU"!*Stv( ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((?aiwԧ%j<V=UB eWxdvQFAPAoj^7kIo~~UBr2{[}-m4[Q\$YѰn>Ƭ7HtQ0dE{фa+$|*s{QEDQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@]W/䱼$^є_x ?æj^^lcߺ>18&;ȲF̎2}AͲL6m+Mm%Ϻ?W|q- 6.G}h6U򯃾:~y&Դ&]zJÃW|.4^<d; @CQ=햽sk41I#a$r_SpgQ^_\>OokIK&4~FFJAA_k5u (nr?@־pZW[7H<:rVߓ@%1Ԯ1鎇xB><=uz5 -p7$O? \hա/}7tI|#2l$(c4%i[> U= u3~,%c3j1-u@IpXA_,|dYOV]LD%@;kN#TK V\I-vmi#,–&6NZnwϛ(AEPEPEPEPE?oO2 k\IPA], b+EV;8"\:]#< _nZC@H9c<{s}GCr3=Ĭ_r4}7KE T}*~K,eIEF>zO_J9uuVpl ;iSy? ়jx$6T%Ya^'C~Z_ŝ"QӠŐG.W 8rnWqQR$<|a NY&/[=5{?:詯on-.h.`'du8 PEC_&?ifQE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((ڕΑ[_Y]HC*2:E&VcM=O_m,>ӵȊ&Sم|o >`jjH?y{ӯ١kEm~X>͞t/WF3X`ƿ|3ޫ/u&DЪ{}~h?~xS,m34Gqϡ\~J+B5)ZL=IQ(AEV!EPE|/U|[:m=ԼEn*iЃVVݳj*jFJZ$gE~x 3'b}b|K7t8Af5=?-]&OiaoH?|5^2|GT<>S8E{Ju_wQfi6_jN#KRO ?g;ސ.Dw'L\(}OV>i' o q!O7h|:nj,l<M~rZo]kz]^x5;(?I&_,((((((((((((((((((+~|vOrOwj]hc?Ğjop gW=lzX?Vv=ӈdRk3uWY~ih?hO+\u&}: lo͹ #0;@8?:*B]gOЭ~թ_[iۂRieoY?5h0q>iFiB J:]ͦ~mܖ"uMQK^ɣc_?>) eCT :/ "⫯kd__cϊ_<@>.toEQ +?4hGכϨ20R2// ?_1?Ct|S__z 7Q #_4hGכϨ20R2// ?O1???)п'C>T ?Q? ?|"| O> >5-c{H;oC1 @+S/?f:/*'$HVSAA6ٕ8F)Dͳ2>rӭ;/[+ϳ (>~*ǪZı[V<;_qXϵqj cHnvFUBpYJ{si~U0b!Oki7QE} EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPnjnk=ɔeւVR~w<(B(E.T޿N~-QOJf#>G=~t47 5KT|ePJ[~aƸ4~~v?fNSh+ݿOZ()?o d=7aeQ ?U?ja0 )=O?Z,LCQ~C8O!Sy8gQ_QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVqTťh[a3E>4Q@hP3Ed2+`um2~m1 3_0(Tލ(ޣdg˸FgWIX[99U9+KӺ_:+}O<xXͦB8 ӑf R=ӳtF8ՃMz1iI=xHj-Ԫ Ŷ^cI2GR:HT'uܧUPw/&W~<ߊڅ휞fdtBr٘WQEAaQ {EXWbc15~)(8Š((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((+ς+}kķ:4<0"N)H$`X߲?k?^SOh+,-C1~W|K503vy?Y9^c۷}-oGIXÿnf~@V'?b n;w[YTI!9ֽc E^WozO ʪSRBQI_C?ec[>r~ͨ7,;29=߈^>$xKP5XFBɀZЃ姉?uOjZ5컰{y@XcsRpHN+Ց׍JOEWQEQE}_g>>o43i痀>NGz-z>-|:5:͞n=|9L8ίx)u2O.a*z0نA&Q +>p6J27O2<A0%'a*?Ć˺{>3y6*P }j~\~-K=sZյԌkVpF~a= DRI'W(ԣ7 iX^" )EN}|s|BK]5*O< kTxCὔXC wGge tF e5ķpnosvP8r:1b#-~ah3~*W6-#) YObI9'&*!J%jqKXw''QEhdQEQEQEQE|y7>,e܋/.sEq5/A.m|mÕC8?)^yfgY?yW[Ἡ:^E> }28bT"Q``uG5 #\,tgCp G-_o ? fi,X$/~xWp~Z +E|}J5)y[ޕעy^w.1O!vV9$nNQLAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPnjnk$WO_"LRQǸ ~Gz7/# CFԡ0_XʝQP{+78~-;Ջ(Ԣfx2}qڼ;AH<{jj'H?CWҿ1uL<'eԳWX=~@GQ|(Tv#`r8"\kz"f9&f_ȚE'h)%dŠ(HQEQEQEQEQEQEQEQ^O_ƿ<)c$Ob #i~#ze:Mhߧfqv=~ğcFbt(K?Mx $W^_\e̿t9k^aEWiQ@Q@bMnkˇb2@9Ff E{ ^Bޔ#[CZK;0,N-JSMG^o|KA{]:K$ ՟b޺G&b#갳1o]#G1o]#Ks- ?7g7gs-[?q1o]#G1o]#G[?,+b޺G&b޺G&̷o8YW?t ?M?t ?Mۙoq@诠?~#z?4kNfKv]K k:dUw)po,4켏 +>t(((((((GK_[ƝMb݆Vh(~ΥZtI%mJZ\o m8=iyϗPO_>kizmB ;8Rc^7c[QM2M?b"F0H#kiωj[꺏 ̬qo~AXf<GO+ߢ `5s,ZwEl[?7WK-4766x9V N6N}6WQ֊k (S*x]Lv&*6 (N0((((((((((((((((((((_>+Fֶ yATW՟>Xkx,Mޚl3 ⋯qNڴ}}O|)CE?;්t/M,[wh| ? nOɭ;⠔Uy%Q_D|QEQEQEQEQEQEQEQEkxO72&i_ozdI" Tz4W?t ?M?t ?Mxۙo},+b޺G&b޺G&̷o8YW?t ?M?t ?Mۙoq@诠?~#z?~#z?2?ռg?_@F?,4F?,4ne?xy>!Yh!Yh~V|E} CH CH ?7g7gs-[?q1o]#G1o]#G[?,+\Eiz7W>!uYT^*TG^\5IQ-QZQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@2[azO cTDm_QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEsF/|=jZuZȳC4g AJ M'UdҼTFnr"`9hbqL:/ق/;DŽ>; :Wk}} NxTSOm].sZ_t/mBMeF=^nc+WSFH .ǝ8.fi$E'SF.ڸ=#KSN+[}>jqe?"Gy0(C:'` (0 mC319!qR%.EWy!EPEPEP~˟O߈p{7jmo73'YCU*O.nhf ~'_,-obioaS]& g}1Е-*Z>>LkkK&_wᗄ'li>Ӭz2[sXKO~}ìx{NW/%GrAQ_ӝw~L?^<ns?AM%Ѻ9<~tYF=O[? ,jVqZmֈFG`=ָ_a 8gcI+D~*jz` gdǼIYU<L/ _c^=ӣ}^OޜKyQ_΁EPEPEPEPEPEPEPEP&On?Z/;MŸLݚ9L?WpQzU }u~Bo}7`QE~~,?TZnt-~r_iX5x}xYtκˉ+ 8yce*AO_?>r9*SW}]J(Oꀯ9 wCj^v:ydg #5~_Wâh.a/_Cy gKXřI'k.y'`KwK?/n1YUGEJݽn^{[kf]q@-,Q3?v|âh(1(UMYQOշU57?Q {uW>"cg?/|y@<zo?+9~}# {xy|E_#o?ǟǫ(Ch&_Hxy><?=_7QGW>"FsA2?GǟǪlxZ\iZ [[m#E$fR3_>U,RT2lqq&VaEW|QEQEQEQEQEW-2xG6@>+ak_WS)/1蚚 K'J=}縃 <?=_7QGW>"FsA2?Gǟǫ;_xĭچg}+G$ʲe#+f/.O\8-8QIɯ[387N"N2S]j}Q_gςnqX]=(H۵| {~|)_W\;`qYm:)IWx=YZz0\K)Hxy><?=_7Q_Iψ!g?/|y@<zo?+9~}# {xy|E_#{X˯Z͵ŵDGT*'s1X+٣FIZ+d|#WVU˚RݾEV8QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE/Fǭݭ0=eKqF?WQZQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@{Yѿepּ">3LK;vq>6q__lj|>3nҧ_w_?dO7/@_-K菵P_XQE!8QEQEQEp94W~5Noq=܎rT/sl%(K4ò}}a&[H5l;4V/K˲M٣hw{};MOӭSdpġQ^/+º?YYϭX'YI;lׇ~_]|#)-Il|R7,ڭg]ߦk.u'v徫;_c͸f'I=mhwsLύ~Hj'ӠVKXά:eWjx_~NRψoZ#_;5[Co}8 d5#oCᦥl]_^RWVn{~z_-ᬿ7VQq-W}[YMQRWM?ſtg%wyg?3|S_#X?:W-a'eaAݽXq\%ӗ8\cywzEG&kٖqgV_>Š(>T(((((((((((((((((((((GK?ߟIjSJ&x#UIW_mCF_r_mCF\<'#HzK=.9,(栢(((((((((((QG_E=yuz%?)̿k_=lFXoJG}Q_gw /} q|a_iS?G#+ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((?a[՟hfh}}9E ^T v/S˛:/~KEAkFHk9|G+v _|CIӵH(Т(u_kFe%pc!z:x2a);$\!*Pz$,ln5;m- g JY݉I5'4Zw%hP٤N~ZW8SerR,-+w@CuN[3\?Տlkf5>g]ܸhe4󆔣OhyeMƳ^cc囫ʣ1~}|yZind#GV;dy_:5yX[v=}k2.%_U#yqu\ټ.ײ"_X>?߉'ڲZ:dջ#z*j*T '+#Io,lGoÜGkدᮞ.-Hsx˟Re]'/Ev> !sJ s$NU|(ۃ*|,R/ Ws$z3?O"~lQ_ʟ ?T*|,R/ Q`?!g?iO͊+Sg Trÿ ,6xn;]Baq3a0VQ&˯YJ-QE}QE},W>Yjz߇5ywn >בft Xm>}{%gx%z[Q_Ϳ PhmgB_)Կ>!a?E~63Bo  }K0w~eQ_Ϳ PhmgBGR??7TW3o?,?&?~Сa7ԿyMGUk ɿƏf߆(X~M4//!a?E~42`B\&KE[mٹ;kɆ?~qL ~939yxՃ?8诵ShwH^KG·+?f|0[4_iPɖ%0=~9Ri>G>[0-j-umW#˨傊(((}S$0M#HRi֨!ŋA#!1S\Xv|LW#e~ Ml^x%w?aoijZ0]`@,?e/)r5Oux.)z/k>fU8Cն~mQ_lf?wT63Boo>0ɿʢM~Сa7 ɿƏ>;??o?2_f߆(X~M463Bouϩ~̪+ѿfn0|!co5fɿ o+vh.L~UG0-/"^fI{`rO+_~Dֵ-&sV%C·=^J߀bDn4ܳhdZ0E*ZO5L/J<]c~ _j(e*H :E}!!EPEP~?_)O%~W%ŸW5~3Ɵ~Ȳ|LBh 6J/(𗏯m\*'Rkr\žY&m$֞gqWW8儣$w{h+ Ϳ Pko>7CC&#*5mgBG3o?,?&?\a0ɿʢM~Сa7 ɿƏ>;??o?2_f߆(X~M463Bouϩ~̪+ [su [=d?֜%wJɿR^jb~ss$ O+s xFK jki}IS^2KVu_c8/9ESSK] |Eh<-˧jmGe{ gWJiJ.(qAES$((((+f/.O^]^1w|z/['pґ_EWY~| q|a_g|)_W<-"_>6oH((ႊ((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((+,2v%_+ֿY|g'a_ͤ $vkz/)a <ï7ؿl)|z~[e~tWg/2wn~yדts|+ +Š+~ ~3kmT8zOPn|F"Z\]N.6pxN[%~'?~[Q:i_"+toS5CA5m>1ụz<ɏ?k{Ý ᇇa+Aon42N۹?08j/)qxi)183gjW=OgUQKt<]|@^fBE5_x~ݚ~ipZyO붾ɖ4z\}Wfſ(p(((((((((NS-zy&On?Zna]+׸(=fp*ZzY?ʿ!_־ۿqo?J(R? E$k ~]W/#^ U+6u/kï_;~&7WKWM\&*UjhzS/?)OZ(=h((((((((((W8gvW1 A^Rv7薬f1$K=7%Zvw=Brx~ӴM;h!PDQ:"ƊUF:<Vu+=:.vS&%P][.ˠQE2IRgFK1c\s4;_Ÿ~YÑ3+]dR&pK0UHß _Qϩ/K@;xTHƏk W qW q?_~U??z k? k?x/q3(#UC\qUC\q<meOEy5¯;7G5¯;7G^?k,+>?>??_YgSgQ^_iM1ǗEH%x!2͎&%N?櫃WNQ^i롏b_- ѓ6)CV-w-~?Mku?ڥq1LcIC##GRU>_/W^aeI_Ƌ2$C=ZS|^ {?~+Ɯ1J7k|q[/|EWGEP[^ vz5}QݘP9&kH7E_AZfrA#c@?v 5Suw+8w$yT/h-d."FJURz@힧(8ʮysI<EaQ迭_QEPu3AԵ]>u2ƿ\.NWg\.i;"|tyl_ƚ!#Țß ְX)/%gQ^ ZG?|9HO8/i`?%gQ^ ZG?|9HGq_ K@O$^3 LdW]a{+n=$@XTV תh襉_SRiE(:O.hnu](t\LlWg|s-gFu6YZb Fu]+WZ΀ t+nϪR&W}EN2U2ӎK.n3:(_? (=kQOy+6e/~'I4HEbQ\ϊ>%[7qZq*yԡ$dL_ N˖[}jӣzQ]۲N_r?$Q ZG/}Kqi`?%gQ^ ZG?|9HGq_ K@ß sC"ԿAEy/?9i(-#EQϩ/K?/=VF_k|cA:ޝwCIk)WM5V|OɦlQEvď~جɺOh?oC_W1__i\$CÌ5}\gſzŏ_h7RG_2䌘&q {W+w]n&9 Uh??,ޏwbL{g34m]I~bW %%?%%f( (((QG_E=yuz%?)̿k_=lFXoJG}Q_gw /} q|a_iS?G#+ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((a[EJˢd|Wb|BcwKב~Z-cXFϛKwǞd{: L!ӴT1 >'ԗwzw4wK}7L,DqC++>xo%&YoG|X{ɥ,Ë1VN?t_ֈ0/z'_~џOxgYnc֢=;ϭ||һ;gcrIV˲>WGP^?ͳVs]tK=QE QEWKό}Q|l%~~we|%vyϿ+_U9rüӼ>ϚoSk<3WU4+r-)qW̵?gQ]{lxKAEWҟQEQEQEQEQEQEQEQEQE~ovJe]")^_ٯ1뼓Ex_%]Ǭ__W_7W+>؏wn?-#|' EWG_?x/V˪E$k ~sxu_+Gw\&*Uj髙$_ -_aUG)E:Q@Q@ Od)d*6L+/B<;@3 LNJ⧅t[+?Mx"ku;{Y4`e#ÿ??L8¼ޟqWW4`e#ÿ??L8ޟqWW4`e#ÿ??L8ޟqWW4`e#ÿ??L8ޟqWW(?o M<l4[##6{"p9kz,,1J<}_q<mmmQE0WTGmGG'`'д5oO[wq f ?~Xŧ[8`cD?!_q%…,2M׼;/$Er|so>"y5X~OJN4۲g5NU;F)ӾEySpce=\s(#=㟋^-tlr@0=w$/5MFvgNOQr VN}/c3it}Q_H|QEQEQEQEQEQEQEQEQEU7T"#B0Uj)4n.}w=rEm ǀv ۾y#@r2:W~~{ڞb03P:Xhn6W]8'+c&dۧߺ~W)Wq|B{-yl"QF߃5_R*5#R4ת?ZFRQ^2M?G,m WRUE6Xu eE| +jU^*M:-(;__|=ʛenF81F ?P~5B8W߰^/<{ Y0XTiQk*Wq.=Q,D P%$ng=ok%V dO@O_%ܟ&>1ݠ L{gT쯭|Ye_wuS쳌ΞQ.]kmfJ3/KBP'@կwzW^=ޣ{qt2RI5Rrj y~\̳|nmUToQ^Q@Q@jx:Dž/F˕9-fh=eS(iJNTf?d׷Z e"8uP7@&QwM}xC)Gz?cxQ˩h[#夶l m~Q,5?a#e^4~Q_W;.i5}=>oiwz}bk[^ c=&_&ؚRN/f~L3x3·9-&w%1+?b׺~Ѿ7\*]J \5u+>](󶿉a~[FM/KQ]z%ŸW5~l?_)O%~Wkz@7#Ftc?H{.^XhԎ-?RN*a0c&JNɽ\[^+ ;2xEl[̘yaX*cs_8W>56:2ވG+ļNYOx?v߅1sWYNnQE}IAEPEPEP^1w|zًKSם&z?(@^+ ?AJ¿yo1+zGIAEW QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQElxFoцc_7k?5YR}aEV!EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEsF.Ai1o&H!zmE6bb٣kuȴOP¢fcs__W+༾&YKe;"hcu3~+ Fo<Nzu$U,~T5科c+ZPV >d&MFdF8=y2+6މy~1Z.4ֲv+C֐tZެ2jbC9TԚEB uV#_7Rܟ/a4,55ϊ|'rݛX!lvOz0<0#ҿ?1> 隖.-&R30+=זXP̲F *?]Jk`k…Y^_揋xihFSw_j{{Ѣ+#(((((((((#)^^E'ɿSۏ)v_b?/)y'J"Y?ʿ!_ֿ^oW}~[FOl(ԏłQ~__U#Hׂo 獿i?ZWMU`W37IuZ(?q S֊Z+t4 ( (>^w}A}__h֫RMٴll5)GO5_gyc*סNvWEݟpe8 [qNO74}E|wtT?hxm>hE?Y'ؔW)GO5G< _Q&%< _U#Tl H2EL c6G2U}?DAȥ>  ~C_PO{0kۿDB(> YD+lz7Ԛ@?<G_K?{+U|y iı'OE?+(kA#?ZxjOܛϨ Gqݤ$z(~?(((((((((((5 ׈ʹӖ9G)?zV<Χ3xݩ/Qp_o5~EV|zF9^ם^_ѹ;]܏$QͱI<6QE{}*h1$ѓ!ٍ}g_+(.$wkߺQK G#åL+#on/G`"x?~($\~'˟V5\Mˏ_"blX~¾55_<۟DZcG= Uߚ,y/S_>RGKB>Y#+?2kS?b}}IkM+((((((?j1i6v X};{׺G$MIa[2d~l+fL"0Y^;1vRsKOo/MlC7qmd̻V~>Oˊ*ۯgrWtFχႼ%C?Ⴜ%C+wO:HHr+_0k? +_0k? l,?l##+?` /"` /"+wQ(Ⴜ%C?Ⴜ%C?̯Fο?>?b-'40__5O-͵,Enp*؇KIl+ͪƾQV6.< 5>(ԳN+?=m/.7oׄ׻~_\o?A ̗EJ/(O>OGGzc?k_~H/a= ~NGBF5~'|{#Lj!q?_?>/ae# (O((((s_k+g?-F3:S*qґsEWI|]~BJB`{O%|_7">JgQE})~?_)O%~W%ŸW5~3Ɵ~Ȳ|LBh 6Jh 6J?C_qg(g̔QE~5Q@Q@Q@Q@Q@/-kw"Ew͌y'GA]^R4kO_p_O3ORҤQE~~XQEQEQE?觯.QG_E=yٗKg 8H/+>NR0ox?n?+M/{JgQE}aEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPnjnkL3Ñ?bx#/MlnzM o]8eZG6V.tկ&7#,>aUo}OoO2Zf28;Mx۟L:|l05 OuO BV_{>YV S䚓%;_5\h6S f&OdeWܒ>ϊ/k7Zy-rSO8_dT27NzN!gnՠ_Q^>0} zi躅bl#:CWxò?6}:|} /_q ᰘi0Vjuf_xv_^;#h|ѬeʛS lB6u!<+?־%x[n*"A{(rI5_rߢnH#>[>>XyvtxϯVue(}`+B(((((((((?;MŸL_w?ݸҙkk5#rҙwȯ ^+}k=fp*Zn$o(HX+G*@u_?x/Vx֗Ck~h$_ -]5s?W_%?3_O=hO@(((((_ գ?Z7~Cίͨ>j- )kx>Eǧ߼P57=<9_E4)~wᆌvjlnvԟv(ªUir{LQOMR ho%+p[]mܶqIv$p=5_{ ~15Ნv2<(y,K^1kj@ߺNxM'7B'^ϗ/o!}# +o' O&o' O&o6/ߑBGWߺO 7MߺO 7Mm>_#K(to?1??to?1??ռ|?~GA QyowDu`W|X_!k>#좿]`v S~zC%66dR5fRGuXWhUUm?'&EWgiq;&y^v xMFd-~GW9~aEW| "??_S/(]}O_G#ZG? ȓ !kwǿd+C(((((H$wk]p/-klg_??<O_ߴ׺ԯcjG"ʗ +(((QG_E=yuz%?)̿k_=lFXoJG}Q_gw /} q|a_iS?G#+ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((W<Ҽ;lLbL0^]Aǩ\}GiQ\xĚm$'8y9.X \D7KOWYXY ,=}GhEiROrO$$ֵW)NְiB hV'wl67iSٔ`yV9Sي8UQ]=}QK+ oxvb&0$}kJN}uX_3vW7K-'Fu]ጝ7_ (C _|lӠw /Hޟ(kīO>,vb(?4=,88$ꇎ8/fH9m?YH8?~O@ RHأ+u_u:myneK,m`?/;¿|hE&7c@`X¿5CZYuI?xSǣq=Wh\<(((((((((#)^^I(!O 0vN;Mz7fJgK"/{oW׫}klGx+#`_F`W~5[9oZ_ּ:}go*DtJC'ïiw@)?3_O=hO@(((((_ գ?Z7~Cίͨ>j- )kx>Eǧ߼P57=<9_E4)JgQE})~?_)O%~W%ŸW5~3Ɵ~Ȳ|LBh 6Jh 6J?C_qg(g̔QE~5Q@Q@Q@Q@Q@/-kw"Ew͌y'GA]^R4kO_p_O3ORҤQE~~XQEQEQE?觯.QG_E=yٗKg 8H/+>NR0ox?n?+M/{JgQE}aEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPnjnkeX;ۺhy7t:eC5HB%:q{:bu5]uX~2F\+ho{Joh ZSdHznx:ʅݿBlr}.)5$=X^A%u .eFy\$r{j:acSh,W5g6ߧeAEWiQ@׊+#eXJ3yGj^{xW#߈Oar܉[?7Zr?xS>dJ+ KM7^;s O_$2= -p/yEWQEQEQEQEQEQEQEQEQEu uZ? ]G.k:#F7dߢ5JuF5yI?Eg-v㘏O3٫Ъ;H- p‹ M_؊޴꿴l-ӠKV*j }.Rple5xkOή_mt˂,Vy5Au}3'[ OR{_QE~~8?L'+8b_cW~>%Om'-~}ƐoNk4<<զh:SD,3qo$XASM~R_TQE~;Q@Q@Q@Q@} .jSäJsLʾ|#%Ō-̱[BGYq%h7[gpXꂏnO}gEWJ~wS~;j|ku>߻ׇצ~Һ]prضbEkG*$"gu\56QEz}ȟE "??_SֿO_$z?)z!jb= ~NGBF5}G6|_zFQ_Q@Q@Q@Q@{앤6ä.c\`Ěߌb*}0!a퐃>^}-&\d#oQ_'iG0oxZR>_ۋX]Gx!3;'Z꿡x~WA>gUsL[I~EW*z%ŸW5~l?_)O%~WgCE?qn&xJދ5֡EWaQEQ^5(>0Dn/-b^j,1=+*~C G⓲<C?h|C0O,/9XGW?&3G0+ e,#+aW?C?h[D?upN?0􊷴ᖕ2.u &5 *͚Sn^$ρ)kZfϩ^xǢr@g֑%Gyk q:r'_)qI@gv]˴cOZ\kfpt)G^/?XVYGEWşjx|=-ֳrG_-O?W{O_V?a6u9Y?}Xp߸s,_ZRofQZ{Q_N|hWO]/Ğ@$T By*$cJ~ |Gg}'^?wHExy  #Zk>st3~~U}VdeXUцAЃV+ݦ`|C0?<A_Lf,/?LcQ_xx!?&3Gݖw&s'S&*?cz?1ձ]'&קO(,OKo ֿO+x'{)gVڂ17Вίo ??$mKYzJGɴQE~1Q@Q@Q@Q@y|5W#]B(EW;~Wn: ~|?h:U c E~YWz8/_gXھ O]$'|l+>VMynKAosf=kʌ E~{ga}Ꙧ]O5Ioɟ4W??bM+ķ/cЮ%OI,G1l0Y>'32&5Lf WX>!˱RUT_ihlms'ji5EzY>&G"*g ^ G_x9kEzW3o/*f߉(_U8/ d?_y_Ϳ?P??~&СTh%a<Wu &=1}8KW#Bh>i-gL*rJ寝xN~NW;p;bO /VW} _W-4=s;|vB#?Hy6?{NQF yg>ğƨ6S῅:A~ 2NGw~A2puj5jqۻ}~3jU[%~|Kxcږx-lm丐UI?ʴsz/]dMx>Oxn Y.=^K3eXi/XkF]ެXJ(RKcRrnR݅QLG?/(]}O_,~'u=?qk%"L7Ҙ\Ư#x.?cWpGkGg 7$aQ^=|ַ_3yq6?_fpX/Vt+ԴD~k1N4%{>h=G;Y\?_fp?%V{>?hGW_fpÿuQ?%Ϗ?G;Y\?_fp9OeAg&$|oE}ii(xd"HY|!|95ƻp!IPG\ո,?%v;0 VF0]ܓn|KWK:uXp;xcZ?cP k (QF;ܞ䜒{M^;{hcBQ(U@:Ts湗,}/!5Io/.)bp&cVֵkKTT#IS}Ykac+Oy;n{e, SYWz#៌/;Mm̂>H\eWQN4RK+OZuo&nEV~?_)O%~W%ŸW5~3Ɵ~Ȳ|LBh 6Jh 6J?C_qg(g̔QE~5Q@Q@Q@Q@Q@/-kw"Ew͌y'GA]^R4kO_p_O3ORҤQE~~XQEQEQE?觯.QG_E=yٗKg 8H/+>NR0ox?n?+M/{JgQE}aEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPnjnk%K~-Aeygݴ5ȧ!>NM5tQE!ijnAO6 o,W}q#}}V\'*/_!-rvdz,B)&(?9 ( /o |_EŸX5Ma>egG8o#Z)3FkLњZ)3FhhUO\ӴX)FRN/X-D?g$e-i '0Y.?ҥIۻ}ed[Y7W-vGl{[bt?bQy[7P5j^7BC$x`W׵Gƥ^ͨ_ۤw,tBdr:YE6QEEĕ[v/(><(g?D.#U#IS5;#~Z]SZ3LQLpvױ1Nދz?>rطܵ>3BYɵrC`pS_(jasjm7T-AԒ{MfǚMjMWp ,kQ`+`Ȳ yL}5Wע_~ļQW(((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((?؛D+\F{_L ~{4 %hUS7^D{߄,oeiW5/ɭh]VilQvfs2USS9~h<ٸ?7f&|EWgEPEPEP׿-ei ]ڤ?&@8?`X2A5]~=x mx;4[KOEWhwqkU0rQէ29p9:)-ӵ~5߈] ][^AfkՏs_?sjֶd w<+Gc_kZ֩vRAq^>i%ޒ_CƛXMϼoOS^n50[(l3 OpQ_QON4i+F*b1qu^);QElsQ@\i2ѱ^2p8tRiIYN/.bx]_uzRE#UW KQ _2/\{ _ȾZ?·'e:_*X*W稣RE t?_~?,O˫tUOGGzc?k_~H/a= ~NGBF5~'|{#Lj!q?_?>/ae"SAi]\6[;HSZ_—/bx]_uz=/_rWgC _2/G,O˫tUsQ)"}j{7\aI07U}jZut?*J]DJYJm(1 ( ( ('.f;&:`GuzT7yE?+U 4W KV^j:.uJ^fu,OJQNOsV^mQZQ@Q@Q@Q@Q@;%V % Q^H:/,O˫tUsV.'б5Ҳٵ}ov[^kմ r:0'h#hN;ͷQEQEPEPEPSQKksM u>r* (i5f4wGC _2/G,O˫tUsV—/Z?[ĺ^ %N+:+X1V07'vQEQ!EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPnjnkw궚m s{w* (9wc+gGѡHb4 ΢˝HTtV{O9u G-Y3~M_m NJo$gZ7mFݯ6QE_ݤVnu2+ą@n@G+9nbh. vHaz+=l៌]۠H{XYT` 2@ݍ~YTx ׏^aIft#i^ҷ[o#(PH(((((((((((((((g?D.#U#IS5;ڿg0+S((((((((((((((((((((7Oq6uB?je%Kd\!*Pz#_[jj,E} 7ŝp.o{~ള+khcBGJQG@ ~lt>)1UʯmVq߻We}_2\,h^տP,X`S3ڛ_ne E0.BycQqՔqF0s~KFeaAkg 宅FF[kIP|5 g9aqs:N}8ӆ )MQ+̙3Q / }װ?3/wh_5jOPGɚ^K7>'8*+ g( g(~_ٸ?Q](_&k?G(_&k?Gװ?0ω/.5[k+HZE(eT{Ev"Йz7ĚOĞlt$Kr* qarbL6:ʤ[nZn%bT(4qz.nџ~i_? ߉#'q?2{tQ_ϘM\eYW+_qUPX %H;Rm5Qۿbu^;+:5a*i-5abJxFZ4ɏOP/Tʾ a EcWԟ!5ؐ$>DA>/+h`^Z/+`/OG (C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (>m]}1 0OQq}_5{A]nMKSqh" ǁ5$$`ҿ 7Ǫiz~'W=VMy4߽uZ(,+۫Y⮛ck1_ggv5_O:G? ]+xW ?< h;rO<_{ 7wYAEQ=~׻o#3*TqEsi/{z3(O(((((((((((((((`_Q_XOGGz~?_?KDoG1C_=?э_G\ƯϋoY~H¢+(((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( X֘eWխ+RF, WmfI bj3kGV)jOw?]s~"x7J{rɍ:u`G]3T*St֏ƥVƭ7x]>QY!`:TbM槨NV4JU&-~!^7ֵXʑo?"g? l#s)\[_Wt|wq%.;ÞSϮ޿~Di]w?S_Wc$G?v޿ȯ;/SG ;D'Oڟۇ=E~DRtC4ڷ??G#%"D'O]Qz־?o׾ˏG/`Spn"LJ-NP.Ic!#r8S.O鶔/E||`jķo&97q~oZL=IR%G|E,](ס.h]4QE`t~z7t`f@; Us~P`kU0Tp?0A>_kS:儺u' z0=/C J^V﫻~k]3chu1uMVVI$ges"(?? ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( 𷅵Ok֚>i%t#1z$G:uME{ڑ@;:zWW _A@''gy,ze/k4K^p}+kN\y,FO`-|t||v^*~ǧ~|CޕcBe.POyc0;8ju ?A;(:W=|o]Pϳ/ Y%'?~V-nxZxzM>][+O֙o[2,U~'UWvL?m#OğqxPkLml{"nz \WҥN:QJ+d>6ji9IޭQZQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@>b]cavaqllש?h|Ex-Օzԯ'F}N| a,_>?P)H?h|jz桨wq$^ss{5FeX j[p6ahۧxݎ:`ׁ%k>/|=[ߦ2+m/gC/+򚦴{Y䶝VX)tkOET~ϾxAaiKnz=tkbƚ45RLQp EXM~e'_|i?{kju ˋ\9I3evOxO7ߴ=?BYxb7˻^x,; <+ 7~Ef8,N*W +B(((( xY>j3G="/ծŕK+y)/-kvU$p_*P5cr`)?*\Wmr3_>5?nOvlW'6n_,`#ᖽ Gÿ ŷ=ޟ(75퓠bqg 5h.vY?f-j4:+ŭWgW׬$m$vѧgw>/Bso ӵ3sџ_q|OIϋZ!0 ?g =W֟i7^Y\Kiwx\5G& OdK[h&?=qf5_/2쵲u'sJPRNZ]2C>4~~#9|\u G\{by.[+{M/.cȢHB;7i?i|vC/6;'AdU Mv}%q_:{קu|E:H9!>Sk3򀢊((((((((((((((_Z>c%^;tUs8-4NuMݘTWڞkXnq{v@-k(o`YZobv8aԬ܌ a%+nYN|ych.sVq|_[~ fZ%]\61죹5a7o,fce"8ݙGr9f#CG1mjWPou\4<SY)[.r^/0d8Gw/g~:߁zG yܨnW?OS@itO-<!%xQ̓ňD? ͽHM9l裰`w#~#Լ[ڽܗڅӗiNI>y6K_:n ]QGq{%Q+i?O(Fᔑ{27FS?};>/A4*6:L~﷨5BizEɊeeWe7"<|[z>Lׇ2%)i>]38i#Fwc2IߎaqorXHXK/?~+ ?h4Թ5Kp>QL_ J%gʚ6E^7?#>^i|&Y ^,%>RCsu>_ ?9Xx|Kqᙤ$O{''OR A>Q3hM'=p^#)T')^v껵X(?8 ( ( ( ( ( ( ( ( ( ( (TPKz)>Z%r@o#"< 8i][b&r79D)q>l\xn|DW#eƧSs~KoW^'ri<1m yo&q(xq-$`SO1H_Myf+[h,w$~{( W,M*[/''|1XkZ6ZJaE!y@<q_GWH|6GgFlmyOq{١񥥵r@{؛C.cU6׋vZ'c_30ٿs 5Z_n䏰:6NLeFU]ϾҔ] _ G"Rf>WUIޫ>W<ÅpQ rվs՟لYT-%E<(栢(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( 5?DP}:V'$VE} |<n"gl>)xb/7\)y>[;Kev_d x jkAkmg?tp XR(aaQ*h1XF6]<]˩*8Zj)vwke[f yG5R-4r6۷+k{ &8 H|GE_jPb919G?{.{_-M{vk>oxk a(AFWRZ]}J*{)ۋK1z)+WGM;0(QEQE&{y6)"0eax5]eO"U }nK_u K>Uͱ4Z( s*m]v G8UR@_1צ~ w=+O iHAm8Bq.JmS t'Wi[OF4MU6Y"'^1kd|/$ճ)le-?XRn7+odR  8{s4oS۫ |7Iæ-8-4يm O~|9qD7` #|@#?^3W:\|c5bZQͳ“&?4կ,qoq'3| NGtcwQTcrM~A^Wo^G~_0aha^qnCW587`LS`^EcCW QV.Y.h)J"*Q韒-je)T#== VM}SzNs0Ik>эR}NEDx .T~T޾ʿi.w VZ#2ݿ^B|>+1/ 7@afezϠ.|5Zv-.d (O:sZi94ks/PqwQQ{kRR z Ê/ ٍN[KF7ܶ\`W/SN Td c{ca_.x&\Cn6[<K;_ϩE,O QЕea_-IuO JOzX}, E.Ϫ?ͲF.xJۭuѯ}(H(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((+{G)>dEPܫFDᯮaV>bi ]$c 8 Zɴ-qfWv&O7c⬐?nA{rGkܖ$嶚FfKrU7%FqH**qѫQ]'QEQEWۿNGfY_-ʘqξ"?a-5m-ŔznKCu{z>σ?VΨ7;~6=K>DՕw=!?:fKi*.]d'VF2M57͕U]~IQ_r~ja-ųQטL+nY/|o{"haE r+LQ-m*"=6,҆Yj#kZyE=i>~&%?KQXO+l2*෥|^A|I?~(jrӡoXJ82 ){^h|ofٝJw}.7vQE}aEPEP> x4=vK9KiQWWCOom#chs  (NBss_ U\ιai7S/At1ʰP\;;jMw>)y^"R\_Um?\h']'m:E[.e*Gp{Wm_5ԕ*Z4VaRj2挕 (xMwqXZY8ԞI8c)F*q9%>RH NNSߕ|]Ɵ jZ!1@t}NKv5WNKJ$[{CC3+b-z$;\+ӿfW?3`ژ x\W֏5ˏ nhnn#*1t|=J+yEX5JEax:+\KK؄g%q[8J&֌ħR`SwWOaETaExRisu2)L8;±#ۀs_>"ѵSr%lܝܟ~XCk.Wԭdq+Kp˜~#V-g閚Wp=G޾&Gԣt]8XXtEyh7^l|3`=~|Mic7Lmc#ϲ|GclNYv߉aY_yJ<{_*{ 2Kkymn#8xBЃȨ+M]N(QE߲#ş|5YKn/kh쟪К?m>1x}5Em>qIt?~Epc,.'{_Ϫ?xT8)7Hyj*֭]Z֝ ƪ +EŸY)(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((O cnjnk8#jşŠ(LB((((((((((((((((((((((( *y-eh#A54n.s߄PiWyៈǧkA<59ډ< !cؚM9s5p_WU"6Gg1HMl Ԭ&43aGWǟb| #Vc.L3n=\7ڻ L~?r=1־oo?hOx5pNzo?x)*uW-d$_HO -"bE*tayo?|)z՛R&j,~WßgO"IMMRIA3К쫈𙝩Sg c{Պt<(> (+?fύ-&#z5Lcq^gV}J]WkYt?+?sbE _M}f ibؒs *={At^[Inu*~J^[Iew5RXu=8"\ص=>T>ރOXؿN~G15kT4~-:lTz6EtuiϿbߌNTW'PsJR:Ӎ*jI/6}|3o"ުMaۡy8=k~&¿Ysim/yX qzZxkDҬcY@ʿC㛍NK\ H0v}qq"C J]䑂($޼mH [[[1u~\?>zo_#Zl;d9-skּ W|CGտ,dz?=j+L>*Σ?P+[di9E.}8c..*3̹_|{<&[UOzҏ*=ʁ^S$o:u8*lGxo4FSnZL/ |w4ofHO.qp߆q^S|[uZ?D_bgI>~j?4[|[ ]Rי" 8??뭵͞G٥~&Гxu ׇ™éC߷oO/C8 T^;toiz>z3l[yWc[I5tTt{}Oosc4Ai1] G _u ~;x :MMf6tKA'[ǾzWy Rz/;~^Vچfiz7}?.ҼI x]HN3_xׁtwNMs.7)+;%_Em :>v3[tq_|;či_QbH*~x.!e>A.}Wss_]&'oo4~Q_D|ey "W,bUK}}: 8 px>>O~+t?2^WWJ(0:I> xFK%c1_%~:_op]KFG׶~«52iڋ_HU=@t_ kJn&vޡ6'Ge'%~3Ruq{>2a_{cjwKcV8<\~gXy& ģZ˿MAO pq>}k+2~gyg*p~/uz葵>ZaTpwf=Iƾ7"xW/d[F}? -e>Oq:IWaGnQWҾ.!S,&\ng\'¸ZXXcS5t)wt9Fq~ђ c[鶼/?J L髶<#^E|e U?Cx]7J/GwoIkP|qxu?uW79 5=t@>m2=A{W~^yN:?7w%x~V (((((coͼOG4A5T|Uhl&{ٖc؜sR{ Ea /><=b-3Jyw?S_9p›i躿?EଗKtEkWR׵9<;LǢb@W忎|cjz2B/EA¾>/nk#M6Zs \@yoUE=>~8ξ[˯ݷQE}QEQEQExUi֙t&oA^og-LϏgQĩ5&9[MK=\kë́䞟5~ؿ]}u7/i+v^էvzŤwV7P^2[$Fpk&<-x&:yK٦*aAp] XJ/}#/NRS]=_5=+Y"lx{oO-z;=Mv>=0OWXQpxMx}ko~տ<>J>.! uKW>4Q%͟x'>H_/,bhپrr5oڂ+yW0B+ʼu,<ϙ+ˣ޾Ҵ 9)\ׯH?wJϼ< ]֏G5_JԼ-ym=֗/k~FR ǘ $b-Z궋 AwȁppF33+ᇄ "2-y\ H~IA]=|iO"oNM~epYm<2\+;1'KY+m,x#ˣ@JIiۢ't{χqYѪe8 m7NkU?*%0׾M#]塸&+7qw::߀ot։? VO#6z_DJH$BU#5nA,էשȱJ4X~kchsB((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y(((((((((((((((((((((((((((((tS=,NȄ2#3gk4,0~?{=r6cObauFzvkʪl$_UՏ_D0jVO?e< O*4?0'68 J|AXMO}p)(ᇱA񿑦xEivlZN~cJ-Oer]W?j+*_R`'o&ϑ>'OMC,]èAp}nᵴKG1)gv'9$nXxM Bv 7S߆.i'g]EpbZ3I p S qT%KKlOUzx/8#෉i߈s,,z6")#N׵z/: h {%gUB=u_ [o[-myVY|cM&-owwe6>%ҤhbtE"fާh9۸rOޮG885eLqyV;p<`k;=7σ.Hlkh&qGpH#x]:ܢOX Z9\$il[if˻f'$뚎|G^[^57S4pq;VuFQN;3&qd5N0!_^ cǴ*1jhLb1< I_m־΄ڴnS&Se+Rz8J|EW{$7~_&0mm5.5nN3_=u=gZ再,PjVrm,ąGe Fq׭~Yxº#ZPl<2ȃЃ؎ ~ W.l~':UͯKi~TQE~~,QEQEQEQ]O7?zJoQg;史g~VWQ{)m4 俉=M~5SgZEm_?pvE=hy?_.$xwR3+O&$n(QEQEQEQEQEQEu> χ59AɅ$ x*_Z~ߵ/~&xg$ B17ȹIf~mLl'T  ,bj:IQm;kt_ù/IVjM_K7E?Lh~?(/~>iqham eH"y$`,ԚTOQ{}4fXq_pLfUOYa0k-4sQE~~QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQElxFoцc_7k?5YR}aEV!EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP|YMkKLZ%vcv0`^aֿCJ3P)o_6wQ_ZCq!DuFA2RZjpKkV3ձ8z+TrPq}mȚ(ΏB(ohE+OOB:?k'Ūbt:(%.D|ٔ9_wONNEb}|c᷉unRT?ywS`kߵyi :~0϶oTlcֿ<(\ $Mm%Ϻ68 JxWg;}[q:jO y6PuhuDLq)->TϱW<'Z앿q>"bSqPvܾe.'Ahi+K`?q/|?D&P`?276[+wE<,UчpE}=~?V@Li$"!=\w͸~USk|[-kwZE8\nn4eRv/|"כN֭ mp_U<Ȟ&Y<͚5_ HW=P:c}>GĔb=ڿ<8+e-p>ןQ_n~pQEQE | ] ٭j߬iO?U˿#W$&VMSStْ&Y_DQ+/ݣQz9~x&WmZ+&oO+a>s[V2IpvrH]OW_kKwo*?`\_R}_Y+Ɯ-eM*](>L(((((((@c+.Nh7mRvc> Ⱦ{3NW5s ߈EoK~G*xJN7DgUw8E'3ǯxQk{> q)O 0 {n_h6MQQ>= =V'Q']^-W4W-|t[2WF]il+h|/i 2B_A_4>YS]_&^w)I/΃we{,-B~c$_ Kiie;1(O ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S((((((((((((((((((((((((((((((?cߍV0Ԯ11Hy)@ս'Wu+}CNđOta^&qͰΌ>GUr"L]#2\iku-wOntpWOWC[ص >r0GPGpy %Z>!|QX2O`0ݞds95˒q<`-I"U~p/gP4Bk imoas!WFAk[k1̲9*Z^ |OX{ix$k&0Px F\ͥjiʓjzUOvZS*RW0\({׉ۿa=[I1#q ٫:mS:6qH,AX׵~\xė0jRi~'`_3X*6y'UY#x1e0z9+zAio=L(_? ( ( ( ( ( +k~ N㫱}0 *ȉ}E5f,ײտAd8Vz߀>vOiS^%a>O`k/_-UoV3' Gt Xvu# b4Q?k|;un %[tVvA8^5E~J:TEF+zi9Iۻ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S((((((((((((((((((((((((((((((((((((((EuyXZt|eoFqB_j5ۈ62&cهqÃkKOՇFS,u!>xQ{?9~xW.ӼOϭ#z9j*5Ɠe-At#L#.v6Jg&tZ켼:TBzZ (:w~$xZA`.~'u`M~aCEßjn,($TwF_ %O⦾N/k}VSxsv &-xapE*cqgw \#E,2.UԌGpEw57Pf'T5N}%m|ѯdz?%4mP e>{1їv>Wԟ a'PٱUE/>xcᭋ[xwHOP K'cj gQY5 M6\?˟{W '/Sr [*)[gM?~ғNt}2cՏ$[w±-p4 tOǟc_&X]βҜgFlhϑۗ.cbKI]ԫ/oW;/x['j?oC־+~1fE|*}$~8Q_ᰴ0*Q/uSsNQ]GQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE/Fǭݭ0=eKqF?WQZQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ pE~x/[ĞuX![H2*覯==} -|^%_l5hO k(ξ[^=kKU|=J5Ѫ((#/IjI-K:WWS^I=į<1gF,ORIkkOϋעQ5:dafWWࣃ^ NJMu_,qNc<~iZr]ެ(CB(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( ݭ0=lxFoц*_Ï6Y((((((((((((((((((((((((((((((((((((((((Ni-aq$sD]kV9 5[>>GE.eLL/mU{Y^s:I_fP0YSvdRF _9n v(W>F=~E|83/)5GT4>X}&kK]?FA[osMyWQ1Xξ&nR{~EWAQETE__ؾkܛkTEoJJ&=CWjzwv7p^2[z85WwRј]XmhxCZ嵏Բ<`*~-/ѨjvzMWPF2\H{hO-:7Wsy "Lyڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6ڿg0+S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((c_7k?5[2[azʗ荫~ (1 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (6> /Properties << /Pr11 11 0 R >> >> >> endobj 10 0 obj << /Filter [/FlateDecode ] /Length 6434 >> stream xڕɎ4Gr}֡&*JI=5/dydVO,ZgDnf~%J?7_/_Ͽ+J~Ǘ?}}Ow?w^i{~2:oc.5ױ^}%FJE|҆j_8_/WK}T?ҫtkf9^mf[sj`]G%G2cH,CU_y%M[U0{ ,i1z)eLP7w2K7R;.[bSڶNXL_ YsP*YwO?ת^KK풇Y{z2d%9!0]u9K,O!5lZjKI]Jkl ;]5WNڝ_F9t@\`K~6-WEmjKk+R^,"TE"K0,ੲ dW)ȺrN#tBՕrN;m!亻)+IW3:qќ9:#Khg1Dg;RfK{7ךy2H6Rl|jAe4pR;s*?ڶZi {[FaGM;EBv?HK,k'娻 t_loqh hhZ̟|jkvKKq  |ij"ҵ }iCIZ5-H~ ݲT3kwߐ6Ys+lK1 #ML#5ظLLHt#?X?UbD&is$vt!`c2,Mc=,E4m:=Nl_K1wSIӷ_]e3Wl*_*n, +0UTѲM20^5mkRl94D;eԡ@|u,ZNVG=~< ۾00H[),&;7X}cfr,\NůHlu)^s}c7ᜅ_x)vXԦ4"*y [Pls \َx~!q5(5ܜj \6}6HO"`V謸s#5m\N$l(cKô\tk#CX4׆=4- b*%}ՠJ e`sYñA  8Ԉ6d#}k\ٯj%jL"]A{&e RG0f 'Pj պ)wIf(6& 1at8TO8.27r( '!(VؽLjՖa':CGXU{lS56uFv`2zFbG&3X=BڄNJE|1]i 'N_tXB,b6*@٨-9ɉ'i" Ce$NimWwZ56j]ר1LrS&T1 zB G1DqGtU_:ٶx{e/ m L]IL`!{a8(,w"E ^U(2!mPcgv$k.YqRotnY(ee@TNh=&Wza ei8^R#L^oR\$yg3-u"*bZßȽ0q[5>8H!!vũe]~&(h["F:.g!3lRsE\jV:l䋁"ᵃOnb9W1Lt"S0P<ο\ʔOHVKbaJIRa=٬)` Z@#b/'F },E"]HpYI/>B.c eWY"0g|jݕbZ99'_4񐔃U,J IsyiPxrOHe#Lx#+%^i%#A;TL$iD_zD#J%9JpΦAI& W eP 2")9p5IYaYeV#/d{A]\H <,yȋNV.$1:Q;$JU~0"l2yK8ُQ"-.Qr8>f95E\6Kka 3&(ܱS^ٞltA,^K2y"W.uɠN8Θ/dz쪖]L':Ɓ͑M.r Q"+&A"͍CWM/EVZ<&^JR9C6_MqH.n4V;yu^!y/pX%i^Ha~S-;LT84i38恃I9bБ 4,쓤((؇rцbp3ӘzQ(xd rcYU& KFS'BB&"">r_ ͞I_7\yb -&J7yG9K\~k .;:y|p4a;qW`߂-I/~Qc20K KM OFid'cgabV"BtG,xL\cF jF*-@1B +Ɖ46\رᶾA<*}%n y)O iÍ ?nDkGߠXjP8u9Zbi ba FBTHʁUM]S8)(KmoJDˈb+Nݽq!Dk<]csA9  b}V'('| FeX?@ܴb#8x KgFAႿs>S3zU- gsس*ȯq.dc ݐ@=fvl؜2È ҔmN7RV. ~?K2/I$9.Ck{<_4%o!o!o4J7kya?A/Xrۻ@gFH˾翸뽌 a>x\=6{7r-grr"jL#fEn4=~!7UiS! >KwVZ}o\CSO>5yw= >[ډT;1:ҫ.R?x 7.S,^-Y8W)$~(;e}ʔQF۟;z4|eGO i"_Y s#pݮ=Qn~%U/ ~P 4w{#.|5xso^ƅ)GXw%_v6ڝ^SrTK|X+FJڼ49G ."eP$6Jw$.OAr)erQyCLO几s7 Zw ٘2.$XOߔ&6PC옣olȊM;z[hߧ}nImQ^r\X;PSr/yo!ĥdRUl2wꙋ#dN3f`0IыאH~#?#GwBI4o'wX2(T*^`B֎TCWyPjRL)iX7tqucM J)UzQ𝺅YkѦ^_RНt]bw6\}+?JH7jo&ۣrg9!d* ~6Wԣv\L>RZB|"Mg`fVL;#历4xI:RX %CC);ar *޹iK.sPZb|{mS"M`&L]ddGɚAfQKޯS1b-: @2< x0gO%b1|9C8 3""s)lTȅ{#Сŕa٥ D$q=3rT2h-LSMdUj',EӦτ Զi\6} >g>JEuGnNhypD,*}56d[,*]lL6};J1 ͐汗Ȥ R+ Tu>j\P(sO~ Mts{9 ӡ{ind~9oFhH@(zt{ \K]^sH81KyE6l*MBK\l~>jv4mw ۱Î> G8h9*iUGw/!q*mvq^%W-&-ƧxVQIaX-&i[􌐈-7S]9R@,S4/J]CvbD[g.)hAm4v@̯qisu؈] t ib' JYl?>=p\-S`Q`/(uNC49pwt5,i9+ΏpH8͗=Roc%gKlSpsNݛob~\1rZM-@:9FqoslͶ\9ѩ:[?N*y칄έyH}]DH x_PQ?sH {џ^q|t{df|0IGZڙx(r惺qc??.fendstream endobj 11 0 obj << /Type/OCG/Name >> endobj 2 0 obj << /Type /Pages /Kids [4 0 R] /Count 1 >> endobj 6 0 obj << /Kids [7 0 R] >> endobj 7 0 obj << /Limits [ ] /Names [ 5 0 R] >> endobj 5 0 obj << /D [4 0 R /XYZ -4 484 0] >> endobj 1 0 obj << /Type /Catalog /Pages 2 0 R /Names << /Dests 6 0 R>> /Outlines 8 0 R /OCProperties<< /D<< /Order[11 0 R ]/ON[11 0 R ]/OFF[]/RBGroups[]>>/OCGs[11 0 R ]>> >> endobj 8 0 obj << /Count 1 /First 9 0 R /Last 9 0 R >> endobj 9 0 obj << /Title /Dest /Parent 8 0 R >> endobj 3 0 obj << /CreationDate /ModDate /Producer /Author /Creator /Title >> endobj 12 0 obj << /Type /ExtGState /op false/OP false/OPM 0 >> endobj xref 0 13 0000000000 65535 f 0000007218 00000 n 0000006918 00000 n 0000007580 00000 n 0000000017 00000 n 0000007167 00000 n 0000006982 00000 n 0000007022 00000 n 0000007401 00000 n 0000007463 00000 n 0000000324 00000 n 0000006842 00000 n 0000008074 00000 n trailer << /Size 13 /Root 1 0 R /Info 3 0 R /ID [<2256b77c4cf385d62d804cc485c0d0f8><2256b77c4cf385d62d804cc485c0d0f8>] >> startxref 8145 %%EOFjgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-blue.png000066400000000000000000004212521402514743400223670ustar00rootroot00000000000000PNG  IHDR pHYs.#.#x?v IDATxx%ɖ%z3%1%Kr)wi]r)Mxj CXreKbӻGݙݝ~gIVowɿ z3P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0N %ysYw+JO<~tآp+cz\u, =Ase"bGQ p*\ (tQ\+cNO8VNj DA`idGZC-ݽ& >,jzmBw9gA+}}|$Bahe*hq[t z5 H1Q5cbqh١JU[N*Spe۠N;{bG#Ց# }|vꔋ0Z!_I\B>8jB_~pjY"fw4mWT'.4jW+U+/ֵ /VZ:{9jobE=~rM,&ޫ\ǩ?#[gxGBK&cB7  ]Q-Enq&ˋ5FZa>&m 1R]?- ] Bdw+F:{z嶣F>l j"^sqT\;T{A`uCl*t{+;VAǎ44(/''BR =Y>L*rkN"]nie{WzR]PA/ߓ |xЭ}=t[M?Zz}lNș,9B$RJy}Ӗ9l+4]D @zP=]z4)#,>,L2Qysk{ζ4tE.Z!WV޷)LXOo/ } ݩ'ܺ`- =hWt]d.Γ\&cqk/7\&u7wJ5qKf&/DUut<Ϡ?[gL} I?24.aXL ǷCRԨM "w ~ppa =Y4t_) Tǫ[d[4D 1Â`oӦK<[o$em = Ş0!krɯ%cLέl<4嚮)K:YGL,+w&vfg&xmt&uʢ}C~ֆ}_l Tdkfc WkkKSZK;^Xy6Fڗzy_L | JFS3|#z&.Hctmr`"c&HE"ЅIc/:}|JǨIE,th|@!z4߹Bs^o=y$99f@e*MBw?}=-Oϝ$="ܓk峦ȤҼpgkSHE ]N|۲AE vJd[ "=iJkzbdf=S]eRP ;(e0G]e mZƸvo>AjN\R]'ȹj X7َvv}S7\R@4zWN/̏=L!@4G޼,r~}k{Nf6>D:v:M#ǏS47a9ҁB @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t/{3"P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0B @P(t:` 0BOBSC_?ZEvwee seӥU;Fz{{<` =AV+* /m쌻lϜלk@%Mx(t8q|}Auh  =FʧL }o%}lJh}l (,d U=cԤ]='onС$Px2"1dR) q_-,k9}}7zHnc O'kcu9*V7(V0Н qYsTݾܳ׈V厣Fz(# ݩȽ,. *r`d;I+R^OjsWRk"{. lnQ[4#<->UN"nߧX"w#V\"^s"UA Iz3{ۋLo=涣.c>`lۍen䇁U?_A~) T{O߾4ܞa7ԆΜmhېYFzf05}Ġe ~;"ioA{[B˩"WIo. =±u rs'0S`͗&xrh^iOvֺ[N} z,V!I>iW;XE:uh̞N%y2 `L_s4ղ=r?фxIՀ6uJyu=zh_豎=lxc6uF}aA}Z}{7Fm2o U7,ZH6wJId|zY'R53'*Y=ًIB?5-:x+t7X?3iW:Hy6vhJBA?L\ܜxK_)yWȈjz•mN@z"&BZ-Q+}BOv2քmWbYb!MBOF9f L';B[woy?~cӓKPj.,U3иJ[muӲבI~tl!sHˋ &0rz)쯽)DŽUEc-/[f d'b.?p>ti}͠>$ylir/5Ok0lۛMc,\6 ;h]N ~K3ӶUa}V0ryxUpbw,`W$qs sӻ9NI=Z7WB1)zT.VtBסkPxY%: VOt.t7#~K G[ds qLA_+ZQFyzPyℱqv}vN-Կ/.| $#ZnGMҼ'~':M{mzr=o2k^,'"Ptгn:-5Vzy)ZtD?t{N:}B*Msd/;MV`|h務em97,,lVr]mZ^gȼt$dBplw*c&'O+y9w.˺Bw;3֮&=_ }JXEJ}I]З5w:ޟWnenM"ѼwBWZ:{vpxVWWB/~7FzJҶ #BO.kt@t_Zk_8eH0/=ڝ& ..t>)֦hSk,sN# B4_<_ oslIQz5upל娙idާLLuϟc;ɷ6z<+g qduQO̴׹5Q(|HN gHyY\ί'dW1R |jy))c=44ѵMW֟%U屧Z%_?ck6mSIA\{x8#?ychJh/l{?GmMʪJL7g /t$xhB d~>ׯhQnyX+Ww*t]XE:_=B6œg'/꿊ۖ')51M@#{-vȕ&&oeC l;I&K]R="jkGB?\-5cnA͈ރPO(N[&lUy&'7/ _i)/Y\.7ͨlsC[w.˾lGuBp-^[CVo>AWK#/t/?~u$*(5~^vg-[SdӖ~CmZY@/m3_!tSŮƦ+ s3@/?oT .H FMDn) SWgĶ& dI\*'_Mٷ @??s8Mɝ_Syؑ, =Yַs'*YJױׇF{"xΥ?nPyzY0\ZI[fOy=oT3*_{l䱡xc?f{˓HV8'S k/shqnD߻dC[ŎhWh5ga2/Y䑬vU+o =RNyjd Ҵ'C-9j1S6v{ݾchxAe}״q֟7Fzz{o,A$V=xt&/8v8\e\-_Y(3lDxނF{Isj t rPw7f9)ٞF V }ԍosTѡeSlqv$@7!:u3I~{-Rˤۯf`m߼r;`-HC{mwx\KytcVP\0Ln;pv1(孡޳q*t\Rz|"}Ŭ1v&! 2C: zڤ^shȶOOnt_[{OMurRuݍgZiv^ۏh,\XeC||c&gN+_Dz,>npg/WNUrés@g)e=e#?ERan_GsWJ{{ >n u XMuwΘ}|΂m]_텧ҭs]X0GI+`!гS1`6WK薈}Ov1_IGt5PEhxQ"гc^\37$?,bu.ܩLn9rtq{u]85b!dRi^F^wMQswumGY5xP=+If8}+ݡ٧]:롣i t5,0祵ۣ_Xu5/'Glp!ݳޫ1ypo>vJujeÈH @"xyskv_^[MТf]AAhĢ[w_MwleүY6kL.ի9cB{5a6DVڷv. ,Ap6@Kg]_ )efLJ|ݾU&HnHgn_ XdΡ=HOL77m7f (-a~oW kb[7kE9e)=-Zz:a_]^ KgNJpۆXaq"yV+7-J+kІR$Blz}v$6b ^bOc ܾ\7w%:|4uF=M:}{ z}{^=>F1DD nSEvc܊EpenH}{y_Kz WP杍nN=sB#=l~z-5E;_F =#w|l{ç<-uRy ޳ue~L- t:Diە@ MF"3$2hjw]ADvɑE}]t^z =˷7/\{g#2ػ <zc#"OESN}z<7O +)y_|1`vYh کdvw.i7VK1@IGwh{L?:źv92Ap y 3O;cwĦUCrsQa@ϠコN$c/DgXndmVScng7>w7K)a,Ru5Y_o_"kHi^TׯiB-+Wȸ[cvȅ$z)X nJ̌H(tz E^0ۡDžlц_Cy﬉gE'늧K"@,; lvr =G_Y(3ѧV%֖lk lo" TkH7= ]g͢yonJiGi*yuU{^/Utcd接iy8dmb@6!гXgїO/s>ʍ4lv-e}`ACD{ݷ2M$tvoEִzw/ &=8U U H%&ܺ<3[/u-7ߌ}[Tz{Dkn.~њ:_^{\IV} x/=׼?=t+̭Sۻyp@׆n@V]p7\Z=pe{s0vH{?_K{biç%byB_{*]sjYi[ݽRDWVr* 퉪]d+q/hy Cr |tHnp_|nNԤ-]s7 i /%?`NEGEB׃B6y"sf9ĻR :*tV`޶E.|!xc+KwUZisL"zrx@G+s2 4zq,w%_+Wy2Б9^ڽ!?f00+p/j\DE =}YӴ{`o04nE;ـ@ 7|}Lmn= ]4/''thm_4oOBttCzHOomب6C E.ȍw>Lԡ nhai{VgY8e5=[ +8/'Sޣ S>^:?ڤ2cqzmhF\NEzC[)'ϙ~)/7tC˜[Ő~ݻ[Xysmܑ{(B?[fG# ?|m]fE7Bc+tar$;jc &g{uݠ2eҴ\ w* ݲl!Դ\ IDAT@Oϙs`ڸGFdh0~cnu _HyjeycO~_r$}Vsz<%-sp;M͔`R[s)w|ic${~5c9۱,ۖWo?*5-;޵"= NI;U/+ @/A]r]ܾкmtи!u&>'c?\{2;vm5 M_UCƆ~^tn+$Μ,;?\뻗4n5UȜ]ÏkrrgޝNfNEy9vE˚僖;4sRdmkh=8k4z^Z.?PSRۦA<1˕/ ݔΞ(* ?vˑC7EQO֕޸[䘏[W$ջ>ϋXwrHo ya30׮/:p*>}n%JӶ+6KiA1,n8plM*iN1խꋧHeq^ܼgD>rnĢAg?J>E rsx=j1sr0wd:S ߾ueBI0vH E:5b~Գ@gy oBz4wN:t@E.<~X*˗ N7ֻz#Y%:oR9]c+Hj$W]*Ob._G>UDp97v^-ux^8/hy+ƕӛ. hhuyalڵiwnVD?;rier[G T\q -,!zSkЙpՕ SԱlz#Թ#o1Ek:dze`(s5q[ S85@gG縟x0O#'sW{]f<*69?!@ϰ]F6K:I҆ڭCw+ڢN7OދWmЗەVPtĴeT)jrӨܜ?`P `Tԇ8 <5)_!3rѣ/{dXaF[dswt0zot)L!3%ͮmih6=e]R/S~ӌr {w?^j~/6y?ǻ_Y(w?=`!гgNs~>Mn蔯fSK6n{ww[2!6-O'j7Ggވیֆ7Vwȁſ@ ;Fv:2W[c?JmqKu@gȽ9uz֫jJ[mrЉ;~{aCG~v tqQ%}Ylm&˫|le>,g?on 꿞?Еd. ;YޅGɏ__kCgGMrK^T%*ZN WJsG{ K9c/(W/Zø( !m Q${5Xa΋`Zirφҋ@^~ȸE$=6MrցqZ}4.f&A tnmVɟ[#]['_/6wy㫆StۇMQ/kSv ݻ"(uN|/0wzGEa(ȯz9gu; 6/!#TCؾq+y!ϟhP~赜k@IN/hIy ]c{meŬQ= tg|۲k]ʖ=:y ur'MIgmda%`=N}z<|\eB`mG=''GZ.|,>)n4#3d1C݆m`S{n|)Fpw@ϐS?]>@. Y3c; 11 P8|֔ H#ҍ@5G#K;۾C3llYud? wY(31&$O+;:{hD]s<#Rw,nC}郵[ԧV{zt!ϘBi:|ua fyнB䒅ݷtWCϫ`yuRUf޿˪c>@} Qu+K~X|ӷ6dʣ_kd+9n,b'aTd a?I˽f Y]40#:gBMyf3UkOM$nRaN:(,hصw? >g{[dх1qCH[KkȞAr|7_^-CtXgʳ'9_I͹j^y 4XO?U'g;#;r&3&9aThW͓m4ȃ_dU2@O?ַ!W 3si @O?R//յWE@O)Γ5oZ+rrO^<zxGO٩{{KuDAxITv)PzZ#\ ~45SQ򣽒ifFn1~lC| C+ۻsX)wnz;W{C{η#4hJnfKWzߔ!s:LͪSӈO϶Xsq)΍` z_Ώ`þcpF !г= WW~vMOtypM{ڏdv&(2Y WZ. LM0$8zoإ\_& Ά1@"}\{xEg To&%`*W:,t[M0TF;eJGWdac_7BE&>r{|:^Li[U2rHcJѼpP/+gevCٍ@Ɍd=8=Ht/Ei7OQp򪶤h7h퍑Qzr6^ P.3q7=hJݘsu0VC*~sxԻ0K6#= 7V'Yϡ tlrW-9o|eٳeU̫ύwx~qmσy@ϠaN]%+v)'G^ b?R~zW뫺}^0)VA7L7@Y' w˚9 w2u9R!5 c'VgRO&q S/$1$$mq`?8uw{\" S9{OhgbAjd s zyU [/t6fhoN>7LA}Iw,i;:>(䶮Tk{hh rsBk:r\,}֜m +!3 }b7VwȁեwϜ({߿2%ڌz!sy0-I꧳7tCw둣} ]sq(=xF^ á7FzzgDRwqWKtw;ԣmM7'LLk~ud.yQE 5=X}^OZ"_\ܾ۽GoBOoq߯ h;-hRŕ= b+T*u2ϭ9_M/q\q= ?^UB8@,pfۻ]/۝Eܾ l댊4j٠+W,B]j#jw,Qզ•қevj-=Z6(9@"g>tC@U\^-%:Yx<jeS8}(޻R>^| ?-n Vo`!гHc[j{1o>;[yutЅ ^>V~O7,,̾Apo D"#KkcG|SpS/ r}IU{6EłX`bW,`MLרI4=7EAĂ]`{{efgf9s|L9y[LN.5 vSMpyZjt|~,.IDTu Z5o\~z>AI^\U] zp){X\lU{KÆ.oTu`+x%Ÿ_ =.=n;BhW.)=mrl FEA ׌.`` N%#]U,IQ#|Jth\cuȅ.lH㡠>ٶY`r"(t!}0-jDc.s尧-K#dž.E5=ƛ'뽼p]xg,z~INsdK҄{tS@A!7gP5ʎpCe% -wzp(~WrT?,KhGiGOΒޡg2mUEڰ,gnvFv= NFiAW}5PbY笮׃h73g++fɤq3iU^ߕZ9|p3_Þw5BԚjC=R{eou,c,FKwoG+=PkB8A^7|ܵu+-Uhg Գwb^B| ,Qhmin??k5{%wkX-@֔3%qbq4 yBf8&}PbI=^Jo04A7`(ۓ)~rrMT{)/+D]pe;K(4< ztiQVw^Q${&_.M!xəb (p*]4IIc(6^Q(| c$k@KQG}>f#&=8Cک)rq :CjCAǨJeNkW;A_$@2\:.r9_5 C}N55+7 1vZ\g>@(A@ǘmȍ4='#򵮤X"x󵲱&%<2]~yHmj U;86COi5;>eiD۾9qG@񿇦#1(]/+;eҪr8*S\.Ya]l[kf0b5,pIIϏȎumts}8ek붏s[iۀ.#pӻz5XQ/Í f%}༠,5-aG)/v}#7KʎVX0 QЃ*aҽ{;ӑ|]@Z,8+c7#o"-KGnzPP/x)?S-oϟoQ gp\`8@EoSMwg%)Qz1W-,I0ot_(XUչX'I%N8y %|{>|Չ悮>[hn >k«|iCxO)z~Q,yvoiŲ;,,a//H,p IDAT{ "Ȧ$JXW%0GC>bl/3RG?Es#2rbAܸV(.y!>T+G?xuuv 7Z ӑ_Wۡ^<˾sH!Ό>reUW ]V]##WK=_ٷ/ z5{.;vv~8y55_EAb*4'<b@+tf/#_A~np]\oՄCkVSDߏɐ7G(p. _B7+ߵ˾zv>ezwsUivIBfju}ŲONre 0# &9@LvӖCת`\V(9Zԏ΍ռ-|mu[}ܫ|Q| e<*O}\$7QE=R~6t4T殭 徙cF1/+77ߴKť/3ȊɳퟟOn[teQ cDW }#[?,׿#d}((`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t P0 :@A(`t oeK-n+ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ tt::@@@ ttP幹2'?˻u24<#zb"BW/ˋGTֳ#r$ sئ!Im@Ϋ%ޕkǀ)"- zNZL _[b|FV_4 D@zHtwPjI,צQNlo86|ݱ]@/RXX|D^B6'rBp>|t JgD n~FOλOjlkɹ_tA dJZT1>9 A@[8&C~<-amjsN/vj~NnV/d;}܏jmݳLsq?yKDEWҶ'}d^Ǿm/:QW_6I"&9qu{tZ8sLӷT{fo|ZOM;fk~GO4=Cq}^a]Y.}Cn{xxXf&\f$]`/Wt^I jSPJqeRpyz(]V"cDʑv>;{G{՛ImrאKCijsۑȉ0eg.>m/Εb}={t&G->} ?.?Iiu%%}\Q '<2pni~2W@Hf˜|`]1 ?~QVo/gg9}oj}l/ֆ_e>ub66]]<$EsV\Z(V1':``JL|MJz[d6q9,图N]V[_"v0}|\5%;0mÒr0!2sbdEȶ~y_K]Q&xPZl4~y䭬"czrI 6io4jdE"F{Ma=)>c}n;Zy:xy;!*R[ܩ`w/ϖE<sKj 3[rycqcʽedʥJ/$9aAUF_hIuhz^#jH&Xm@z"@-rZQ-i]Z${=R)[/;h?{7vuq~zpP6ɴ'c?x老%W#N w~&7^bV~5ߛ?$=fGXX켼Pc/LM߳ I`hnHuǀOΫ v^8?yMZ|i홫˻d^㶨BP[e_\ rdnacTo{5{^ǫk?`0AO~%kˡ#TGiAbT_Uds{eMMrw;m↽^{[[;)rt|u~׾(+tn-)XY.mW#ʻ1#LT>1|XRO_W׽Ă8yuazZu]rID][Y)7{P{$|ONo/,y #!.YbG.:`@ʾ ?؁ue]``rLn<7wd;$9R8S.|e;͐%|_on%ӆ>ޏTJgeC'QbmZGgaaҴȼŜӋGZ5({zo4|0GN* xnɴ;WN1+Aμv0xۇn/P{$+OgsWHMϏY^lt?ΊC?OU@KgOgʱVɉ 2Bc<'@8yM4BXES{y0NL!cv|dhKլ-99#U8+~spŞ=O2/mE@`n aj[gXϫV!/𸧲k`<}Yٱ21vjKgܾWN߬X!W |v//(鉿}k ݲ&wB\Nhϟ>j1ʴS#h`{t~<-En& {w8_7w 2zQ+ ݺ^9՚NXo;L~XY=od&:{|T1(M+m0k5yd}s>>S2f"͇{pZ:%Y~vpmj}ɏTxt׫lyo<#G Wyv>--3lEWù\GuARG@ oYyorGΜ`>FD(!7rI' GfǸ/l;^t+H;l]/ϖG퐋_LߐE=wT׎ˋ{\Q[wj{=+OJ_)Sա[;ze w59rhVGU= =CvMQ\Lϳ\|?m5b"䱓[i}=螆sK'*Jg 96GhpQյ=f+$;ކg^)QnEr/gxGdĆK)ď}vcvt\tgz *8)jzcqf_~$MV"å9ZPSRе)M9o0j[irZ5[^=lpEJepd8^jV *޸YI|"۴Z]/V}V xMЕ~Ԧcl(t|/SU]ףoצ~s_ꢄ8:o$}vzQS*.-q;k{M6&$@mM8v8'jY@@HXXy?r5VjkI˗XB۵=rs28<s,i&_h^91f߮Ћыlt]|c>"?re~ͮIn I= WFBTt\U`c!m𞳺CR/wz- }J$Ǧw^-YٛpnZ Чɶo(Ϸ=|d%:`r^ &ʻe:uc;`rAڮaްuVQ?whG&MZ|fЈ IDATjjN1qԪZ37swjzN PVB:^Y#'Ĺm6oܺrPzcq\.nں;.H+>#^qwJ8 -~OfdY -耢z F{kH2*_7 {>Q݅Vݻ{ڂoc6ϟ|,-YiyF![Εɦ#7nn>lc'n*XGI2ۅ\77p]r_O' ֶ ]a ؇Z([ƺaF\09QC~~c뵤pis1b<1|(A@, [ #TB{x9j_7/äŊ5=rs5~nBэ̇Rz$)Jre#c[La]`4xmx3zdqײ:C@a|f1<ڄ ^,1*\گ*{]x}_+~h4hQWrcUms-xsq\7횝xx'W3̩Eԛ`! Wڽork;"!֍| Z>1/^-wʵ]/!=9:\-scmS^y\;'r&VZpjJ9:z[@u_c=g[ʥwЯqu;%pnG3c vte,XW/:Ҏ/$%jΡwnl%'jrҶ)yv@=r4=ИvDvYNwy 4ogN\+ozpE 8}RoB|D[#f-{^egׅ?l6*j?'Q~wDUq请4ˊl.^HOMjyMxtǹ&z\P&p^p5@ t+$-q `C6p8_+]Rq$՛24gg&;|^Lf w//1 DܸY=@-Z"#(-.H p|P!yDZ1鲠8^NZ] m=/_⬧n9@-Z##%6 j.('DަF^U,)OjAVR-K_mct@.m}GZSgd_@"#Y]c!K %jwOU';1} Q)qBNqrN\sx:E|H߾>A+#eo[K?R=ބcj5l՟?b:k~n>elYXc5;}" Mfir.Uo kgmӷ|[R.֠E@/0 7pM3]X֥ɹsyChK]#z4l: =Jt%{^݂r{C 5ug}aa%WloFm[뀜r|C{sgl`B((z"M:A@aT|/32Z-MN^]jz{%t{dhaKSϠ͊ob3`Tt qڢ2-;?Smn{UJq6!ufk>W8o mt9} wpx~@`Ν(GcejzD9_5oty)V~zR9(j{d _^Bw篟u*&GO@'p'ԏhlRUFmxѾÞojb]vK)wӖf^&O耯}|\5%{Cϧe{ۀpඩ; N^]h`mJ`罼C_J3ʖ^9vh A(ɶHo>-5jx~,-hʃUIr;u* 'N2Ns3'Ƙ{Db1}"#(}jF|vnv I3)_M% hH?~"z8=}PtIßBBML _)}Rs匒xi5T 5bߞwH?myj^Ԁ$5O$%~ʤx9UY{'>h?=.2\._6ˎAi5h _v|?AQg*@歬?2Z801*LRM_i1`GOʔsI9!B@GPHz8,iKsʶݦ6nq8_AMi7ųlUەعc2'l))弗5iO7cE+__P yޫL XO+~Vjb|/ 08.AIՕ'FɷؽoGL|LsTV7Yz<-}C224NQ%;m}?aҫш,.}2r -6B(t빪'^P#@("#d:1K.'7nnTB}NmCRp%+nh*3}TtgU-wWׯt@=m, ȩ~z^LKvyjNE1wn&g_ƞO͗OVU􆀎uM9x5rQ=i tCMpS";!=֝T=ZpЅsK~k.+x~W/Ȗ>n|P+ +p c ԛ¤()ozRR_2}n'-Ñps^!hO$1gȼ>m9v*=o/tcĻ]E/RyyKvx`A9rIvK K'vBʯeAas!sMoܩOeoOw8{5>l uM.sbv.lKՙXң/-z^tݫмMM^8OLZ7l 1R*.ߛ=, rGﹾ[^l7NzTVχ{T/H >l ]!C״\o7v^sKth%P?Gz_pg ƹ-߼tvp{%gA25=Z`Xpcu]]Хalkclm3Wkwy+J+>- FB@GH\l푡^(XY.o+z|>l77i*F&Ϟ6Q)na @!C3-y[yvmTydŹkU=rjܜ8œ@7]S!}E^ǚxOtnk5G&>P6yNBjF\v@y_wKzm?Y:%Y9># C/3|mQadSuev{Jq> `>ƎhYZ$Q+ `|=J$ƻ s V=g QRc *=c2'Ӓ]zlUj.Yd1WTugEn`_"cuȅоAmt-|g^㱓ɉn='."lW -^_gӋBL`z:\+a'YWK|wrhҶ~vD<!#|/yuM@~ql֔v1:BSy\{i1YCoZ ]nS lIEN.\}>lы#ZBkLs/Sz#}t7egϐ+1w6,薎΍wjz| ,L߽DRp>6/Dߺ|Z$rxVL!W|0+ͣ竞s_slgҋ݄qU\:ɰgغ..RheVNL_[3j=`c{̻bZM^o7 #|9w@[cU~qHm@|#¤miˏ?kCc@%LNt@ ~@"Ɔs=YQVnu^~;./NXsCӶ~ n=g{};mڶn!?ٜ5zW~nK]iP<^%_5UrN[p~ȫr|[ t`)i&~3b"¤bsdvh TϫjW*LS"xӛͅ%"r+;485KK2?Oe>,-*e{ۀ_L!{Fya__-&`]8-$wt0ޞes]om)[75> l[Mh̀_̋˥9W׻~,` CV^by7:`25]n9Y9羬"gJإh"jxvagS#&HӚZ-MOj唁CbdG퐟(M=\75|.t<@X׾ᒧ %5UԖt/0[|@rL%C ttoҪNѨu-wyVoߨ=MǟJ릻ϱrs5NM[@KtdBHQrzqdE,1:B^)Δ??OO?7qY2[{eٛ N :BvS'Y3cfN~7G퐋^e!Ext1sזx M4AH7(t ԩY6tf̷$tx7_.ˣ'.{jE!cy湧L 7úK ==Oȏ?'w'd._6 >aC' j75>iѰeͰߗN67zwzt&sF!(Wjr&3b59i1'wokY>é)rtM}J>os|棎I}mQ/wun_=]1vt|('eX=vAC9jWF4T<\)m3guME~=&DHK|ß6Tۭ.+1˺zncH]2̢rcNA,,N܄_֔vG;l/y\\s`s&e{"E.t" (뮫?۪umt.]@Q&E@z{2΅L~gg&H9df<@ ~Z)-VGoѫl ?{DQ# [5/[c=Ik@ ~@fu.yqi}#ꞽ0Ԣʫ}E?%Ňv-XEEE~cǛQ ;Є7Qe jt$Z(<픱ѿ[oqWN\ڛܲz&=RƊu 01{v[86|ٔ?00kz‚uuW~hHO|W]?|jQp:UT}0qձo@{ С3~c͹o7̈gnKkn7cwMYQwȑqg築#0;wAqT#N G)syCc֊uEaq̖?-Z]/M">?Yq䷾w*;ǎ }Cffgcclw+[D"HC3=}| nj}>vT!;mT&DJݛ ٳK {㬒F5hZ츭zҒ>0'֡)o {˚Z-,~`ep|7|nqhk蛻hQoqֽWDQ]n|my|]<Jyf ۝{±lX7w_m@>k,G}Ң8ϝSVa/.XSo/T)o@&Ksn@?M").(klaNO* ˎRzoBVzӻKڏ -gp:{={ǏjZLY6.M4iɺFmSs7larz8V_}.~aIXq΄Qw6c߫6lR8qlaNK FA @geIckck55\:4ī֙e9Okݽ:VWp R_״BdIe>hr>_ڹv͑&s3׾,NwnSѶZ@:4C͑̿̋K_\R*_ZZ٪[_9;f9}kA3/̶tr_?gwLY{lݻpԔ]ŅϧR̦L=yldzw\||S/RLY<%f2'Vw^m:=G|پI˛}ζq?5޽V턫ƌe[5Wsn챣yW,NwNɏ<[a6)V]Q?0;m-UnuX{Rc7v_8M: -0sZqߨq}cBkg.L_YN+ =߹c:wzV{̘]˃Y=dU/yqiE-ƶ?%殬j90T}ꆟՅ6uv}׊'3'ִp DCFC8Ax7LC=0ceh WHqՇ=wҒ±_7S]m>eyM?w蘾*\1Ж:wϮ>IAve 9?͉`{_\gm7e90>e7,]ܖυÆQkwn!|ihÇ=g *5ͨlSsZ6L>{⡣GG^ I9s+K Gf~]=纏 -l\:/skz`/̕nk=b݆UR;bB 9ee7ujc&S785lmsVV^)؞q!CewsFUG*& ?M)`[fƀncj=^u^Non\F@[c/^Pѻk 5Wn!{z}KuѤzWviܶb?%YGRjl~{p1Em=yGo+>8[`8ꏳÆ5/?hH|; :U6DߍǎV{x!l(_OyX`>G9;n9vtTkK=Wx#ohtK ώwawNYj\X8j\q&S!j GMcv<:kUž G_u[t.NyËޛ)=ݡٞ׋?OG(K[εfoz齺veg/z%cɚ٪kv >.:oo{}ɺ v8rb>6gK~1Sj|K8>_WP,v?LF^@lr7qy5mѷ;xmtmkg+_J+egCoB3,n_=all0ѿ"kt|6#5WxaEseeyl%``n`Uls'xr8e\jUeyF65uez{>\e?7%Uĸlal:lj.uv}5=?~gƪoĐci}^CFX~kˣRóϾ^\’WӾM6~-5ʩ1mٺ):~iIhy?gPy#{u{ەfµ-3cw{ı5v˜?*tNzZL^7R˶rJ }sr&@\.vZq}G>Q{5}Gv獇^M~πx3\gTYsmآ>ui<,۪}3ٮ ;Z9|>|wegO^]jO^~?u'{,z\Xn5|ܸ{vQ+h[x,˂m]9YhR_ة_j},,;]oH~葱ψoz]Qy{ 3]\tl7.ڿe tJ,\][>p`8ػč\V.ukb7Օ+bbh*]956AEijcz}D 'VvM_8vy7^6׈1{xid S?]Т?G{q![?<[ Mi9p*5O: ek7Ĕs]}D 9^{k!ա1x|F12ٽ옑뗖ۊWհKglקΏ02¯Ok^V@@vaÛVV>2~+*by988~ϠwV*R@E=_٥_b2^U:5#7۵">71:2_sz1->ǬSEmU :UsNC/\ 9. Y}u6|e9UV IDATϐ-{ͯ-+HO>0lΞ.(rd oCFů9@G!΁vm^9j8^~έwW-yh{hT4ݫsNV/ܯ!߭3~^ڞ86{ٖjsLب9[OhWsz·g*9.1WarV9/48rmAq[5U @. 92.uj]I} 8rkBQ}sZ6D|牶}q|eؾۧF7(yh{ȍgY>F_1%֮P}'Eyz]Y_}d~@;}737F.+w>W=V4r˪ۅb9<F,>kBZQ4\%mMm= ؐ<(-z;&(yjCT/:wujFɟ]8k=-\ڈ8Zƫ']R9n /uAE;q}G!L%>7p=:g]Ęޝy9k΋GgJxeCoeqʸ]buMO[0*>R<',Ak}1{eUrܸsJ#.yqI3meq☢eg渏rjL[:WT'Ki'G~}KskhU(NYdU>+fEˆzon\2GL0(PNl$΁Nwn\J~U =gC[))l)wdž}ewzէkEv@1{<~wwvmw_vk[0{euw/ۓ͓Q=r̨JZ8eT.1ıѵʟ&w-ZcdEǶ}ӢDžO]T}O;l|߮e?^ 6l'.=pH~iiy~CSnWE[yy'Gd+m~-8vTϝ.hEܾO@{ Ρk̂mmקpL[VUX{>3&P,6_55f.7׷]:ž#~yaybaqs+;EaMZZvܭU;{6~rЁe+Xg e5V?vhwM]mx>7ό7mc `m,H_L5N(y}Iw=NڶOl72YM_?8V5cD\6En9.s֝7h(Own\nYRi  ;}| ^<]w71:}[ľ] S3>s[5Z8#G|.R/u.X3Oseo M=y\h_Y76ž Le٪9'ΡЭ?[._/.*r:qȥ/.RWK9;fdO[G1;,][io***)5\M៑)^mf'm̤%k l[9^~:@C9t _{tAyM;'u')+SwlQb_7WTkqe~??McW0 %yǂ3չ]^{}SbÆs@o>ՍuS}?ύIlѯkY+&,ϖ|pwwqFcWROͩMe?\29>4GSb8}& M<8u#ΡsԘYns`m:)mVn{rQ|ezy׾q'WBj}ɠxѷ8i+ C=rd{q~cUSK>@s`\.2[?i>/kuՔm?8u6r: 0EqСo:ssupRgwT#s^\T8VVmח71wu0||vwLdr?t\Ϣmxk^_3YcG}+75ov?`5-ws?9tP#.|rT|`x&'k} GC%bkS=:WV|tVۚ#*ga:VrJL:ilӴLbj,YSxyMr'oS2~3 ' u CDK&48l̈wW?[zZٜ܆,_>>Y_5~aà?oX'ښ-~vy7B: WM3P/1W4uغ籔g+o{vY+;|ScہK~gXsqFKs}T΢mX,egOM*z.پ;^ּܽGtC7{_Wg䝳=µ1ݥ/.)Gm+.opT_\?/޳Gqw7XY)ygE9`MeӪ_ nwZJf"ϛ+uot.1+Rs`V]뛫be >nq准F[K1=b eq}s}maKsޮ gpD%V^;e[}XT3}(%bkz@z5X']l}Qdgs#]>0QwRux0}q|{N?~Ȣ~tZ mVȗS[6$9pk&lj~] e! KZI'3p{ٝ?o>pd~^=]̿=4?~lFxCMA{RQQ{Жk5=0sU7-5=oŋ[.S3;z^8զ=rdaOr G.^=폝6msEskyG<զ}zyiQwTQVEf6.JZmoOqV]sP>0WM;ͫEXn}y[} J7$mmsW8;YhXst]VFN%^nC<\Jv!{}+i۶m?y7gZA);nFp>O//ܯs^}egIK\6(ifFvw0Sym{זw?޻q+gq_gCOwna~MϞ=&,=|c{żUcԵ_ۭxU[M[Vcz7<=5gug⺃q[*¼ }_:TGТų&NgNpMx:6~sU|pDs\qАeӋ Gg }_V|Fo#ΏI'mRTUNNRepNhq]om/1'{k7|Cܽx8qѐuM. ts@Uvg`gF~5es̒W3!*/H м‘E}dhE;`6.f)ٌ״E6Ք0SVGۤdo4FfP^)g1rvq{}xHßP}Gʼ[?:SSu}^?aL%K34|lKgynܲ䝳 ѫKam{(>E:|W7+zl\Se{rtHVU/: ݡodSzl[XeC\ӯ\M_UG?xh.G1,vZ}f.&{s9s3NsL?elã{^8>=:veC-/[ Yk1O5̀[k(lT@n,e%˛ƮΕ#2S)l^aߜߺҲy3O4?+1E} vlM|څ-?@Ўˎ-[q Svϯ/Y[5:Ze;ױ[n03[^=gN.i\>0sU\Vz?pd/<4?^lEħ_VSrڱmg%LS.ouʹ .)c񣿗i9\q{Zo~ cz ǿ }ba|E {9ipe%hm\ypd[lWn}jO]]s?P^ڱGeeGkoXGNY50~¾)ew9ntkklEȾDm[P82__xx~NT^cQq?u?2ku5o˫bt;/p5@shѿa>YQ,?gBtN41=h=0>őTwO]*޺K; {/|aIaz}>wb4Gf6骗y[mmUScZtIKވ:6qH;|xj&N&NJz,k^Q7٪vjڲϥF#Z?gE߮M9} Gfʪ +@cdsxsίDzkMsM9nMo<(wQUI^DE@QDET[uu]]uҤ@D)"4iIH=\@̝ޙs;<<;'$=ߟe}@48r[]od+J[N/hw=[=[XV|xɯKqC+ z? ~m9B)44VVJY-1bɥIEfT-\/zH=߻7F Ł] +D u|[5$죽pbkNdדzy [0zCȓW@edm}XG^kI?ƹJ\g?(KfR|[ybXM*3v^NN+4V%~غ|:\j_' gەoVlGؼ&&s׊cfDk#Zz-ŢQQɷ8+ ^n}(.6Qs]@]*{VRW7n*-AoQ,p,;d RJSi%Z>mvGM*ps-߳D-[}vG k`0+ ^ |⭓V|r@Zquնs'b, [~gE,Gt65XKY%}mMZDӓe=fdLĕٲ[rnU5:E7kVov`u!Ie:9%xo0Vo%Dzu9U&\hN:ޏ)7F2 ƳժXE&Ӿ)44 ou/'ZjI o7u ~dz4fRsu;B:YkΓvzpnmX"ǽ=#6SZ]eN+ lغ<  6{:zrw9e״o$_M :k:4/}^LkNwb5u3{?xj@86 Gqp~O3+o6Hph{@%,zbWSrdɺ9 95Fz*s` -,ϻ۷r9hSfu4xvEyX'[(fUn9P^+3љ*e5\$x0<.YEhVp=^9X)V zpnmg2XW7O-XSӁF.}xv9qJfWPJ\-xoYԾ-Lp&sT;$WX1^'^ny4{ ==D|V-*-Ϧ6\vz e,CO':eNXzъ \9~P=0zzK'+pi#u_90<":.6Ω5SzQ#Bh(93R?;KmCĊ!^ {|Wp~Gw}ɡs?\tejzd-IwChf` ])+*Q PXDeFHsI[h-WlWFD㗋Đ+-2w'h6!a53l;f +|9<іshj@^?$,Es<#?Wͥ 遴i #$чghʧEt&e8cpW Z;n }#4ʫ3Q{|Y4DoЮ [WDoZ3kA&5K'Owv ܖA~2lP}Չp2?v+,*ӝA фty'"/ \%RmA8hߖWe?*V`Ty\,]|5A؛YTe %t{-8G&}Ѯ]{x=La!8lƗ%v-p~tZ Y[8,;b@Za g'm-TeLmLKGG˾ +. C0}q(\0jp>sX)cm< iM m:Ͽۼ eVx%A8 kZ(COY,y\,=;ڀ`4=[>>tisnTH_ԫ=.u?Tr?&ekhvvA1nJ k?{k0ɟӾs=sfZvy's=uU8g<^; [F&a4o7rLtwt!~^5Z9gYN~uDSy7>Sf@Vhݟk龞|=q9p:jn%[ 4j}b;OcCW~xJ6m`ߧ\qDʻbGt(S 5 ]M_b>2Ol4)Z^Ni̐>xizB}nۑpvI5sT4hy3nw}4 ^s\Mv\,%CeՅȞB)A~x'T5+4*p9Bα 6X?_d|eV~\{[=n -a+KМgnk?FJTo-UxDs|}S-v}G ZYrj[0_Oz~XP4-*l}LJ,9]Gi^ky=6YByU7@A|< (zC벯rLZѤ~2pzsO'KoT p#񷪧(CնBO-Gnۄ8EZ}/zIlxs'% ˃BDRa49h]IWFD#KOTZsf(@3UR>>40ʇ=觲&Wh|cӒ{9@8{oc=7Xp XKGi!{^Ey[˛ Sz'vcI;@8!mߴ^YV m_)* l?8"p4L>juk "rjMd6kcOTrڙߠhԃpX =ױjgcVY=6}J٩[fleY G LjGб+[i>AG˚+¬Fz!dNjV{ .^p86+Cӓ]wO@s0;vkZ Kq~bkiʼnknUiL]I0@A,#.Q߂}Wp8>sfʫ3Qbg[秋ޟUmuo>B;%WZΌTwq zzO-}WcU!n4Lj+ZmIYurjv-C|k&[?J$̠B!~ g?"4 7#h~N9&|R褑9ݟk>3hǍ2w!ZY'U<$m;߶ %%9tV ot$NEcS:Ɨ膍hJe_0v~FC-+8!> t{a$%2[$f.9:-`sj/^>Y8*}S`:spU kp9=p;۷uE~!.\gc=Dc髜neLD>hnnM P{Q24[ ]K|)_”+ml[/ۜYOˎWSQ}ۀd|ZlyQbLL>YCS{8|;9^!HJ}Q'jQcps|=)OGwDfK7FFRH6 DCc}~=Uخ2G&݈x?ZyC G,vRiBzp$D.׫82k"Lf@6s,F1ٯ]O_>4V<G2#8 f3ušJ(zðKr'}6!N{jϋU40RXo4ф9&/Z1Td[۝\Ҋ114wÀNx›yu&JZp oV{{Ypy|w9vRḤO2Mcou A8pA&";hCIS{ ۻ׏(_N3S5\ Q,Fx ٪fp.QpΊ@pWmTdT%~i(w0NH;hOUNn%T"p_!ROX/x͜wV vy̕3ǖz|mYLq_y-jAQ+c:3<zs gRʈnTd\ROk,o@R:߹H5F{csis%U|ՖxyPѬT A*7Q[h]0魟^>d)y IDATZU+D%~r;[Skwk\{.Z+b|f¥s@8][ *^H:9hrolLGKk)^{$Cp]}e7U+8YrZr8CɫV]{p= lnN"@,m=7:3jVdgf G~{(ʬ>tԲêuZ=A}>5s<뇻e?V0W"{]sӓɠ5֙()кd4U peW0qc٣xV*0J汕C XRܩ@os{U4pv~F c]4Dݟkv`(H?oW2QJqD f}tK= Z36;JtKZmͪv< *3"M(< A&gp=޴˂hZ0-X..9(ڗK qP9@8M)o0gB?mSkFH چpA܏o ݏ i})X~2yp I7+rţoa5?7=4I[ s~,icM4 ʗ^?Uq94Hc[a\L?6ѱ&˟tk|`\Ʊ\9h|#Ns󵘉N7]ϒ&:j3ocLeVt9 Ff`/aP(M_i|4Ix-ϦuG[M 8mnpwI܏RՃ9fχ˧}S;=[cHpIV%~ˎUs;i%УeSQ }E(_; 9hm .ǚu %8G'Kaޝwɱ]{_h<iC,GʚhN#⯗kdf zѵ 59H`Oi+ͅmqv=LU \yTX~Zt`U Q)jp-bgSTY{*z|W]iѾ]Bu|۩u9տu^ŦlCiW-F/_}醍j6z3i-dkzSf=MR(xf%'١􇝥?7iIcg6QNm י,?G9 N=1 6sЕWV f|Z %/:QDf˞5O/==@Xr%I(nC&ˎ &3\Hs dA8ڒU/\Ey3|h=P&,vuQs2@+ 旰sТ靾L=uA^We`8[P@KmNs GF)ΟWnΟNBK5pLws4R|E9XiuZu``spH]N|![Z}S r{VRTHF%q;+*@/k> Mܟ jwQ#4<Η>:?m_U8piEԇp>>Bu E T{6kwl-; 8HrMf[ûuzx[R@+ZfR@'6(v;\E mwU~]"=W59zQ1| `@9'r~@׹Fħ(\5f? Qq~>[ChJc#W_IqR \K497Pt\=V#glN,H}ˌ&O1F&(EN2:ǁX\E9&NV4Sp[ 96erf2@ ok ,nt_9=18L}4m~/̏fsm/g owݘd*G DrU4Pp++65ϴ}L̇giX ]ϓ=ǧ%=YgP/sz^;`cLwl-/r9+ \NRz%K]m [%'3S{R}K48PpǴʘxtUbp0k.Ɏ1ppєօ>̴el <N:7=C5 U?;nOeY,^T;'n@rv4҈xiuJ=w }g Ƶ$yQ yY.in =@8VוPL̡s>/'#O6KSTUÿ9 tSEy6  E镅rTvp Зm9?*H ^ku-ԛܟ,qYը=I/Bj^A8%>Dn*ҾyZQu@ 9,na z=K[7R<-.:Η ϩ5 Uh3U.naBTP/CJ0| {Z%[lX/Nh 6!Ʀ?x߈ ?Y~/=2꾉z|{P 9M >DkQx2ΙH]tMX'rП+ J򤧮^I秲&/R;Ö+1W4ӐyXM!1`f;sksks5=6!cδF~WWؽq<XKE6wςLwpekN Eݤ>ףeM4}q!}~@7y﫫m6S7n_4p9+bEVfSUZpTMg'SZ{x~Zz ,U}fIN?K\:^ޤ0\ōtyjǧ?N Ԯn18_?T #땃\JXOPei͔y4x?M4q :CL&K7l,@Q2[C|Uj@Tr %*ӊoK@qC+LwG\I7&<*ǰiYFKsU_NO 'oVRa>(Z~.iQ{(nܞXb4"ޱ~wmsm O,вV:]L ,}e_M ]#̛ k)̶͵UY^{YB?7ҁFj =X']p*W(H(|<9%Vg(7R^m }Wt=J}Rr-g {3USGR_26~7en/j @ WȾSa_avi&oz[~h. 掯ov=Ÿ'/(se?MVF-? oUlUdmK9&JY~`spkCBX\7B鿝nj%<]<_![#5/ߑ*LD}"ڒ4ю<2+spkbm NFBo spkGiR;8˂h( o֯LJrG` \Caᢷy"L-lj4h9@ U)F~]bx/ jVxdC8J Mi#-4/]&sp[wv.J`vKgtP9-֊:}Tjf vl/n,N:m AvWM1\ͱs7I-f34={*{ؿ(qv= 􎍙6Vy:YG<_O3GFϧup_|pi7pn%J mO@%4ѐNO7$wz̿nN}k6;id+Tpn!ߓgz>Lz,mlr#m|1I~07e*8*m*7{ \`V#i8t;wf`~fQ,а%5u ˫UqG%zW0s3<埃Un49L6X[n Oik:^A޽1dVp }Mv[R;/%BWhv<K8}( p.k^Pǜ/~\^X/ I[ @ W~Gߕh?n&Օׁp.k"%K-ޓjxv7)wGҎ)Vn|;R֎%VEG[V5!U!omG \L$;(i!kԇpB_вS).`gu.(G=s'j@T4󰺞VZpm4&ɟR>ͮGklm_ g@8N=AQu0odnٜY'\\ֱfsr̉[W.|S6:B']2þ) ]<4DZ4t `}y!˚M)]BgS)wm+8|NH~nhGL2Uಾo~̽FTʺ3WIvVjA 3tAEy23-F0}C86Z9&˱}^8Δ2I`%rxYnNc?.ϣ@8_%{V {3 \ڪ5gZiͩZNr%i|MHtg}6!6҈xS-3Hd%;7R,@ ь.J<琜-ahxy.t}us):]myz-ʤFl}fzh ܂)=T[U|:GQ9Wsn3X{gқ>3M_;'k0=vHL^I8Jmx< wJk16xm*iTxTYv*O~ ͪӿxޟ,zoQw]h`UBowhpne_ o/zHN/hlէj]en\j nΙsUkV<; Tll4)6^pfA-շҜ%~5!vѸeYYELCҁJ:IӀ(6V O=+m1ph?7TST_AJ +A\&zۂŸs)%a &%~?Wqh9ۑo ~Sf4 m؋%':O- ::4-E.|m[خ!B8pc+XQtBxެ>!lt93R?;Ks&19;y4j}s7 ୺!u3zwIRx5s 5%;rtr-oqp9 1齂-L1&}Dku{̽\:VxA۾CZ®lpnǺ@ܪ5aqU7u<ֻ{98>nOΊe6P^m ?c>98TYK'4^~+r=vy%c 9[)پ7T9t܉[ ip/;Qo&>iI4RZ|ڡ ܎%+@8psuֽgv<3Kbq5S:w;,E_4Йf.^srE/ɤ7FF|%,v4yWKʁi\ 9Pm̶-(WUQKaҁJ.<74[0g֜w0@8zd 4ݙj~K_p"Ovޑ@{;j j B8(\p>>-dի4"Ъ^>݉(+:pCz#/BGE)<+Zp&w ~`-(ֺ-"lcD)ͪ̕I?NMDwVO~ gd?U `Sf=ݞ0d1?!v<z };N5-4R4[nKcI~_I_nut4yZx?DYhV>K=hV"h^8w~ZD)<<@(߆RDdKپ _I*nT@_4tzUB/>Цq=Ji]B'='V5~Wpg 熆vy3Aߖт#U,lpG3zӓ{ʸ>ϸMcr[|=)wf O}̕ [hsf'|,/Ծ)c8_BQEj˛;EvLN]|t4u{ 8t 6 uK`ӲzqS IDAT7|KV2\f'vSV 旌NofyxHzLc)d}68ڗvߙ@~W/9]G>+>-1ޘOc ؝y\,-Z%}fA~mcѫ#"$c7hGlx(uޖҥ顯K~@8fΖ%=)Fo왇hHeΎ`KG&~l5T*K1ߓ^&+σV-\YBa9_zǗo+컶 /7_k G%ӹf-MZf3 ~[g3l+O_0O0= Q9L!>3̻;vD?0UcjYQ!1 -ИȺ_)Z䭤Dӿ(.By?XdgwA/nwAoҕtG~bw=: ĪP`)s+=-( DZ–w/>+HmwD7mXqKXEc:m{[Akre?J^VhjX)$@odJ xdb=|ku[,!OLfaf]~ܟKH P?|Džydi9u߬Pk`Zx ͙63U-V=ѿd Pzԛ(^TebוU &5؛^&f_6Y1=]0$a=ŖWWZЕv?[ޗ/TiTB8 GY9n6SI{Uy?)++M½Q+giùz~ #?AjC8vX0^A\QEqy)gwq;#&\W6RE*4yk!Ͱ{_;'<\9LJ Ҏb3zqW b*nwĂb^mCUl^יh)NKm~VD[^ \I s]ƿi|ݾPA(@+Q؅0e4YRO)K̿ouZZ>:W-}r]VM M~*kR9`'6SN/_ #̛NXH7sf3;SGS;V{x*3RlZTV]5؞#PBLR|7Zj}R5X­@{ё{.hfˌXxS%s;ܵgQcݨ#-g'vkco2 u CEKq$f~Ubױ(BJ½(fy648lm)#bkm(aP(.N̈x_**}:zbjnUR,ѓ$gP_%vYEJb|>֟..o5ޑ`u;}{yPȺU'j"-pL?}y{!YkAf H&==- 8^:XEޣIзۿ5{}xF=:pp%w-s>SBQ}Б&mͰ3f=k{U2Ӡ(_ åM*0Sn ho+zus* v0Њ1tOi[*\X![F;⼺ ;@I]ݾq1䱠Ns[CB8pЁF!p&6jח9tI&ݚHo=ހHa /KЧI-Nelqi^Jwd_ZZ4bIXU;b Yxvo1Ӵϋiӹ:Ym!ksEI#E"sNXБ:?ɪ)4,V|Yih 3]w/sg11꺏*\X? YiZenT [Юow 2[.hʩ5 4r[[q+vyHOy{ h< dlVUgEipk A2vIOuݾ)'8XA%j~s .ar'E4qKPTo%j葾g5Pa*ca͂>74R5kƖ]O9uMg%][8*#ꡭ!z3+.0 )݃ SK}QLM2Ntfzw0yw8_P6;@4o˅Kg-OIKOXxL",9gݟy˝(_\Lpa~3 HKS_(hڏU8~24ߺH;Z_#{!h 6ߪm%,U?,g.zĸ2c/_>48ž|VR2k͑{׵iOsVNtUjTH#~\fP4fhx?2֗/jRTSҤjv hkŖ~v{5꺫>āŰjn*tƌZ YmyߎBk`o|kj<>P$f˫G6F%Q { @ tC%{F G/F籿Td{F`gx5 Ij<ۅ֥mwvM3 X+m8>V]"ڇrg\\=Mj@޽@]wMI Cr! ' ""B)h+PX r9dzڭ.t \"(U V\V@+grLv422w&9Jf9{>?aWj<$aFgί}_mӈ[GR>p}!f'Mh㖯<.'Gk6U 8ߟ6pM'# pμ+srwѢKf9\qCPgL)T|B/ag[9 4c\sw_OvB}Gs;4߯>n\>3Ǎ~1#ʴe/OehXY9F(6 ~ŖwB/^8ï}V^~^Ҏڶ~2\GOP>=^~ur x#qCֶܶr enj-763zo5O޷C_jhh(Ӛ:C?8w{q>glc^ߴo O~ k{uénj98ͿT,Gwli_ 尛;zlgt^8O8XZX^cUT_[wH=kGΞZ6l+˨+Ϣ~܎{~㥖:Oҡw>ïU}W7'gNUus8)cn\X>zDS3aX[}~\ק0?fiž^sqY}YGzîzм< r>TO y=ʴe٥9nD9hxCپǏkYy}~ۮ[NYU靓G|t[o_nu;ʄQ{YkA@CB˷VW/8|yܚmuySN+_'?YO?;#Mdh`cTVzKCbuՏ5S&TrΞ_Qjtr}oWΞ; D&˚~'Cϰu?U}=_%΁2EwߊޝYV.}ۘ?Z4;1Ϋ救w(@BǧZm9)E!΁M虇(K[zv-/n~|=˟O/ gta7/. C*D֩N=|9ѿC|79PUK7q^__Y_Xv]5+ʷ_ҫPoO'}ݣjqOٓ9e=5r\u /qT=fgݙ}?ʳ+ܭrX{=sJ+[yҙ{OV([[8G=ۯŖ~ܼ̟4L/#9+#_U 5@OWVA6a]R>EZΙ7=lvz/3xrZ9 {:3KwWl/wyr͎ߟTNzP9vbcYewyhɶWZGU_]/+yo9}r}#[Xv};'?]7`0;rXYvrHcsͭ[VYƭP"΁nz>>c螗[˶]W-ok>}?{0khh(\4jٵLeqٴM{KT/0U[wWCu xr_=ӿ|1O79gZ9ǢURۖW77Lmm:3:܃3;|+'M(;{%:m3IcvuEy\9&S+e+[}bmQ߽펥+'O,e~~kʨ効醅uz<ݔ߼Ǣ]ؚr  >r9@ߟvU/lr+[O>6Fh@b̍JˮۼWlЃ袝1kty?r`a@q4=П9F?idϗ~A| yqqoXT^17zN9zG5yc/,6o+36*'gpĸ닺,ZYtGˈa swyRK߶ֶ>=9rcuO|lzz}/y;P8,^ٵ;;s/qtmOyR^]F[l=t/{9)5N 1Y#o]_҉yhWB[mAX/Yh.3oYRv ݿ7ER|q 8ƕ{_*Zy u YeNzW쏇>rxm̸eqY{9um,.ZJew\k6j=xr7_v??\]6axI3uXl.qWy֫<=?3_0z{We٣ˆO)v)㿶uq|gW#_U`~_9@=AZ9ez~;VkwՋgy Ɔ7^VymǛ>w~Ǣ]ĺWlد 8xʽѷ>[kpvf+7L/s,޼ǏE Uٿ7 8WpwRȼr\};v{zzW]2znpr{5HE[u+D_;]-8@koVcI88| u=撲 8P?n}y_t8@?^>"_m@tbW[)=C}z}}_ܺ;- :qC˝gLk>l[]_=jG[HAtzx}nW9vBc]^ os.}ʽgNk}WuswWiiFtۯn)OQN:|T^gޭKZv>/{ -qЍ]V~yrđOΊpӮ:vӢSs/Z~E;5|axig[9gOV.w@g9@W?)#ګ_Szg=xkyǝ f,Z s'zWUsxCC9kr̄e=(6GvgigM-g׾e]A{,R?-njo^Zʞ? q9@"ZZ˵O~C@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9cCU IDATL@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@0q9L@מ07ڧ]F Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9Hx@zs3g< =9{{m. (DTMDI>5͘Xh,wQD%( lキoK0Xؙݝ]׹ySY{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{Gx{> 1^,RYժ6Vm5inw 9Y隒N`{u"w_5ec􏓓o'&Yx/Q?XQ-N;#<&%Fvw@zVfpN d]rEVs0e'=gjRp]~GJFx!ңRx@+r<2!<pfy<8?GǪC?Sq`m@OO 3+] u# <pctѐ(OO+~66NݜQ}jVJ4pFs`&zz8vm:|zs ԍ1q}QqRTdžhJzV~| <p2S`DyvV՞B KW <p2"5OeZֶyz ]kz;snCt_MK]+^v^Wi8*h4!806گۨ iaqcuז}>W-ӾVq{S%jl?_< *7c{#Vr=+U<%YWwV_[|UםF_i8deF^]{HkD<࿎O -?%3ǏeڡeCYj[ڏx+յ_h3flPڤjwxz*ˌ Қ?~V?*m_CxVftn9.A ($gewkwUKޡ[cTRI}z6,[~PygsɜLEֆ7֊}߅~ހ@];&V7WBXrͺyuw\֬^S2uh;(R}足U/3_j[ykQՋgٞ(ΥCzzmG&hlf?6Vv@v(61QBzX c]\6 ׯrzKs4 {wFUeK)QZrN rp.#OJOIC g /sЫM ӝ5oD]zdk~RN--2HОwb׊Fݴ\k{`(Sq,DQCV?|Z&Ǫ>?ZYj3#)ᎽCx]uZn[ۻ= 8:Vјn=FIcӝ*=9)AV/'(3{[5zZutP1[ yY Ʋf}>OTJ FY#:$PV}VܤZu"Xנԇ)vNOչ#Q0o}qiz VY10Z;gNy|89z;&%ZfOP?Q>'eDhy:kCMepvfu{ΐh=:-EQFZD>8?úm?\m,qXP@s'$Z㳒fzЩ1}J wHL<:o݃id}]F/쬵ٖ>af7 |ɯ&$'xRBu`A_+DrhQ#cuX9Viۙ˚ur-_W=%I78X9<=3깢6M~)Ϻm?MNm!d<ܽ63Ϻ kNO?t<]H`~0*V>A=~vݱBmVsIY"gz'O\?)'30kqxOnyr5V6YM(Ƨi͜Lzz"_*T9p}#V²ZڥGֵ*c`fblP\ҡaO{ƴcn{tgn~kKhg gꝎO dz3<=#~L$@p7s::pS;kZO)jt£7HjUG=ӫ:utʬ1O^9+NQz~F5wUm?2-SGc/ ^*64PcS¬J! TLiq}JTPߪ  ӵc`״5zjG;zO 8&9L.TowstOʃ>*lTOKQwH\_%횽H+\8SQ:*ѱvvII:|OO =ro=>AqpEw}V'רȡi`Dn9.A=߷܄JRUQ3$Zf:t콛u}kUdݞ50JOǮ )zt}Uej& fQG%j}48ι_fOM+D3-OΌNHĴKk{E+~s\n G2ݷ3^)Jg kFZcwufP[НIfv#io%=/ <ǘ=1);⊑1pe34 IDATVXƾz<˳3tڑO[\]QЦ_9m׏)I q2(6Xfl5s]==;R?iWWtuzj{puaOu0W}Q#<{viXOdXBFgs 3=ߌAq!Zrv6;&&X&fmC&kzvDyCq+{u7uqd}I.~Td^iZ0<.=FP^-蹘@}qID=6MÞα@vWhԳ9„'&cf1!5L ln ~BN{Wf5xOJRU/ȯ Jxuf9Tq) ur 8Ӑm0R5EMR::( |̘W( txnFS_-=Ƙ?LNMv g(kó}bHxz{-9nFHkGPeSk& B46ٻ۵ԬOOs3tJfZ;qjSYf忎NӍ<=ombX?Vyc,<(@[ek@M˛55RMje{S ?wh\ǻpHۃvyHm8@dy픈 59IGwrl]^/ڷ/ihS`@;g]󲭖l)o1粟OZ; t#@Ny,{M˵{uv /+2gpBt o鑭YBz7AH;cCv̀/ڻZNkm|I}UѾ^3(t?\owf;t줗qaZOo' SzdDד3 oXnpu-}]5ك. Izdk4KjhyK|_Dxs]τ_78.XoCLaA ;k~w-Ǧ8t>ҍ\<#|XOO[-;гr}L=߅VpnzU~5Y}tNgJqW5q)az`j&ts@X0]oXUzx) o\*pk{Rҗ^ڡ7빝֯mc%zl[goYIƥzz*_al,m.Β#/}zy_<$Z?O\|Iǽpp d۹8>IW(Ն</fm#uOOe~GYpmViCzVͲ2SQ!'zP.PGzAyS]Gjۼl ezjc 7^[g-1OԺdX[/]G.+ ),(@7OЍԵ}ҏO Vmյiɞ:WLrW,/V>1SB7[UsD[UڿfZpʚv'  giTbck[:4.rlHkynVWg+klנ'ۏ;80@[lϷ=( S}c:p{Ù߮)1oX<9ѺvE`->+ͺm>][a=Wmt pǺ sSx+^YmF'ZCYqa Z;9 V{Tܽp /.v([}0m}]բi]GY^]w?^~1Eo֯?.?nx>9=LCl𝕞_̎kǥ[MZ+7|dRWk0Qʦ6 {:GbC}4!EIn*oZ]fCB4o.L5q|zuwջ9 TFzP}vaղڞ5|O ]a}trJW !,r zօ`Ź::~0hW~^iyi֗6 M/>z8Jk5\vft91I>1գbam,-tՂ pC; ia *ͻCo쫷M(+T.mÙ@V Fw5=I)S`=yzNώpy}6Tul~L)v{7AUԣscOLo W۪ |"m][[lRopte*A\Wa 0];&j"T|E?]^={tjkδg7EC5 y?Ǻxkb"5MVn*Ջ{HMI{2ygַjYTs1qvIҗ6Yک-:5š jS1~ϴnˉI.2{/Ro(n~qrRy^u]?\8}=Ex@/I//yJD$~5M6U4kGeU`b@u$늑1:|+w%S0u0~7QФ>aI5AS}o{q;RuhڜZGfum5QJ TCʦvka*otxk={[5aQSixmOf 8BBxum1%#\b{ ?-iXWxsz9f^j ֢vsBj`OA~/_oÙ > ~7UL ma{oLML { F?[}{Wywll9l:ܚj{ṩ T{ϰ/&Ztά0m0Y*UQ=?g/0TCn(niyzrG.{M3<2ݴzK;4ln'kÙ}MnT]?pTHv\̨ 5iS9nTu5-t՚ܙS&fglocخ`MfQh6~{|=:N!]\t(k58"s&>)Qpra:~.Wy2frk}#* 8t#~/ _]gvMĆh˼,E?-iքn}w@b8Sm*\:mnLl,EL5>({#cI]'tpQu{_M~LW)||_~T' 3y(}na=!`wp9;<δpk<|j^ҡ%{hghppjVJwXӒy"j34Ǚ:/YҮbZ ϏM Pm*kv<<,זjkdF蟧$kDBI9ښoהW;>AxhGEثg/ۻ''jhB=H CJqTmmբuVϴ,6Q9 Jceg EnY[ 3;mtq+pX֬B;=TAKMkg>0.D3Et1u/Saf,6CCQfP%Zì[*oSa]ZaA_!s`9QZrN55WЌ%z7-sm͚rkbZ}ot~>6שι-t1qx%uqb;.\Vv:1ɼi'2Hi+h+oӞkTφ}^ Y;< x;ÙYXcCuѱ]Zczv3tRnQٮ9\12+wԃ[ C^1gejX|-wMtkoVF]X<>,HGYIknLH \Sz+T}o}[vpH4r\vGeZla Uq"c>o[eˑW(IZY2k`シMJa>Wm|]2U{.v̢d]e{/=Gx2B4(. _L^}kʛtU˚]6ß/'X=RՕ4\TP+P&49 +klK~?{pǥkŹI/v%k+2D8+tSzlA?oLY{Gx2{i?vZC89#\'c6 z\EӢ34gpw|.qTBkTX`Ȍ˗M+jSoBS2t,Ԉ@,cNgk`<~?}c{Խ'E>P%k3)ʞzx+4+Ee-ηn⌾ymiK -E0㗼]ܣǻ"=g۝fQҦHLU#[q8cUJ{K 2d(noe~a=ڣc5>5L_<|ћIqΞk=.ѱF'z<8?ܻ<t|o+a ?}y݃ez~gmTEܿWkdjLvKNӛt>?2U}jV.G9>pJ._󶉉x3' L_QO3LW6in}uMH 'EMn{zz LMs<= x6{rG`Z*caӆc)Iv?_/WlOfOɗeYnoOyâumަBŅxMp~gdk{Vhnc7isyF%ve>'PoU3t>v3UȦkEa}f}OlOώPlw^So{'һĔ6]lJ?\JD;bOB?45S3pM {gH]wLNLtzTSslxޙ ]12_Wخws΁u^yeCѱ:oPƧ]Ox)__T:g+N-'`ݾMMfa5+KiW6'!ymVT&`זjN/R@iEwSH!!qZ0"F1vB7_Ucu9;Wi; Iၺpp5+ Q'fD.Ɲz5b'mA3 :]^8,we <bB5gp.^7siC2ݯ sts}{c?P۪nVp@nwk{5BWs~_Xbڍ|6w@M ׌:_ĹGuSmڈ[+quG_VVlw@>Q߬wǶjzY^k7<l{U nzpsm xEzy7 pAq!lx.~15{6nG3*Θ*++JjM uc4gH砫ڥvWi]7[cgkRUa-.h+ |6] !Gmomu!:!V>&9ԥ qBm~rF5„J}֗hЇjTqR gM GDQ:+JlW0{ϴn'5YsF;}Np"m)w^[hmЈgr4s`g U]6y G_oz4.%TgԌ~&:v>3n>6u'rְ =VSѹ8m5 3wX\Qbτ^h:]D .=p?sa-q =Ny% ;LL St>{q~Ng[:ݻJwt IDATz?.F`>)A *{⃂Fݸ\ 5ZNONvz=@ԴS߶WQ7BgGpE>?,Z1!:MlOl<pY8tz66%LOLԙ"ئ/?.>g"<0bCX~Mn*k}zGVn4[@׵x/jv՟_e彩 iUZdUmؙ뱟Y7m69PQzTp'+6wi{V]fclKQBXUE_ԮVhSNmk}}C[;'%Z 9Â&2.0fuC7[/gtӈ 50R?Wzz*2{ Flr_|WLgIJ]n7߂f{ |g6 l9?~JLIu^Ol|ִR-9eoaS;*[tk[6T5t8;m{8]7~XǺNX!utNmz`sT6?~W=G;նZC%m' f{=?.j*½=ٌo M7FXi{;-ֵě{b`\q+,Z0^!<zQ!`P.cUкT jӖ;]Mi='%95jh-k*t*iuӦBi)ȩmөk !!qVY^Ww*-ѹͺˌϼUJ̎k|ݟ&'c/pq7ajVT:®:=<7Q+nJf__'ݷjۓvT/;PǶvvݕ*s _$9>|Q{6TYM ]{v8Ӻ^xӳ#NNf2wMN‘1Nkw>"!Dd ヂF]D_!yhMVju'·q 6|o:{oK|>N6Z(Ňtz܂фnBxVgԢ3\Z}Ȕp$[k+d6sה7j?Vu.=3{JpnhKZwWe4o|^5|sί{slTlj8.-\+KW+˻;?<5m9`fܞk聩ino=WY4b[Y9GX$kKӺ^xl{z/6 s3453葊v=OusVHŇ:kc2ccx6e= Гý&[cOu.^V^]`ӠٝW/Cxhl ڮS<=#s<])ӓjhOO+wښ7k%*mhT ϏO Pm-ovӬo$X;'幍V%{H^Ǜ[7z_xK)֍˵ E~6V;;vi]>4+}p&'POOî-ZӀ޶k<='ԵjS`nIOs9[;u*ńS5wf[&[]//{>Ϯh;j1Ez1ۆ*mem* MkbobxE@0ʬaLHи/j7VjuW4k0-͟]X fi4f]HZP횟m~dk}L]Gw}XШ]UliNx:׀/G?J/UCU?0!vW{!ribd5LxS=jHV&&tz̬ ReS[o|+58՞j  zS!Tu@ԶjԳVKhC?Qֵfjf T7cOu.^VEMOlKeãuU.?#<?IQ%.{]6A uê2+&|+9q˄xk6%z}o]Z<y[#b\\}k-^Bp-)Ю*ڟjB~|{~]Nm^^sӁ`"=R#tJVą(v;52 +Uf糒f}Q٢'U]u[EP<FFvErx^;;ͺm7ٮS;+ޡg2nU4'#<h_uO[e+ {ZS4,hҩ@zdk5 1)Qǥ:^mUf܅}5<+fsofZv:Bհ0@/'+6(.DR46%j@k҆66kSYV4jou7On>o[{;^J"tws4Eo%4;uttuֈ #Rt@ǯ{U4kb_elAvw#b4nQ='IZ.ǎ2l$=߮),ZrNNˊ_,Rmbʦ6۵к}Lr?]Y`x5YYnUV7{/VZŴ>/K#U:@xp;ӚvGEVn[&[ˊ03whJzx:UvQPڤOX/{ONVtHgeZ'iްhM~)exep~PU/s9#k'su^UՙDiH|&@QwH 35>5K{jG[v^#'hZ,k ' ge׮t- 1k`&$n0k`== 9c)׎Kyֱ)_WCQM3{gBJDޙ1I] FW(eaر>^269T4!W%<kUҮXx\jZ}"T|E?z6g_'2XtlJ%(.4PqahlWnmrڬ_4X Ɉ Yе=yy~EAS"4o|,_Ex8S| yzzz X}l@4i'<\7&NotJr|Qn_@x]- v}gfYYnr̼/'Vcgxל ݱ'^$/m:O*Ef=׽[?vZ *?L_<=#k}; ܿ'(ct>كvKou^Y-dN kZ{&fFjwx%8'xuwi|{7V@M+^]UJOOCxJK~}1?gK5\wit8ahMN(sѹ/{?=_m&8wV!/.К~T`iW"<x6<_[ekH\_YCT~TJ5?&!cyB>7 ;_.Kw[[ԨK Lu9vgs?^T}ꀖLΫCy?MX }IQ;GbbƘo1bLb%&v`bÂR׹3A۽cf>|;m0Fd믇:X?c2? Lק!l K5$ٷbK+;u^֦lѺ fć ݚ>\ ?<[zqiչ4gQbStN[WETk|{fk-k9v41;leh~*-X j[wxy3 7۷m,$:n%HS P i7,sb{lh%/cZl+g#̉~vչ*xwK/Zp1 6D:9h; 16=(ݫCn~~,rzRCYՕ/׆m7H1(-N/bIǧiiPsWo@#y;/nki(|ј4m'X_߭Xحuu]NU 0L+LU2tT3ի{>h?kG VҳJB87rK;psتm-={SspܤX3Yϻz}jUYK>nl_uD?pRN4O/LT٥C5mfA.Mr:xtc۽Nﭪs\W-!֣X5u5<֜W!i߆߶IzֆHRƄ $5Qa6s﷫;-0HKުgҔ>+j܃e nh6 Ot(3ν-t>UQaqa sB,66'AW0)04ut՚u|>|L,9R69֣M_#Ыmp`|Jk/5[λOd bTpgps#Sf*=}R_V7ף2EU0=G?pWN/֕/ll`{}fO+1p75I00, CnFEN_aCD`OH@ƙmč6v?N_X봡˜ȿӘx] >U%^eMYBXꎣuv8vtǷ sSG ;[I}mJcq8X'n-;k_{@d7@K+:t':np_F+ݹY~֬~CrumnGH$ D'MsxU;FgN g6\8Xͳ kd ڔy&%z)Y!F]׾^wk;CC:Inty߃-H@cݑ8fC8#*Z{Zw`n\v.}ߦ^".O7.3!=!FcJu'9wɏ[uf}mWDopnHWӟSngt2KpFvs÷^Նn`OX-i!2+IمAm;,K<Э N[Ht˚Fm=(cV_R p_^^98m~B+k:- s68WQѩ-T6jі6`=T ֩lld%Ƿ+#29d, rY1#y!UWZtKެ/wR- Ѿצ=Tq^RrM f TnXZBO؇9@ϧ,V|hw5Kvhu-mAWwm18\1Tcئm-GdH_:&?m鲗CHAi*_SJRT%yc_ZMMttVrlVm _ǔ&kpZh'ߴ1C:~~sBa |ZUy\9G rV}x`;8{Qmlq4p`LzpU3GmjG//6EX'!֣9>bGGtvh!:$L#mr:XjZaf M6VO/Lߪ-kHg{6DթJ$ Bm=ʸc9@fwkڼj@l~;#G?vFS,ѕ/jksG(+4ocXDSF"mwuXqK N֭Gis IDAT˷C_``K^ַ^3+[[zt+u'괡[1Ec̱#kU_b<8=[ҹu"V:e[֠8sN XWG>Qn~~\eYOzEsʆ}aC8Dv-]ΨTst*-fӑeK6 }؛siWG S]n>p;`{7#tČ~ͫtF_GUެ0׮_`ٸv}JG fIk~ƀJR"M̵~89˜FôCeۑ@pH2>F]t `w}t]I+տ5k6s%K;sl.7-2^7^4XXcvDlD򼱫׆H;o=HS CWKWrG>_t(@JѫghJ>@nR]{lZzPn<eQHwNfo7ֶZWНG9 IM8Ӫ/wlu: $fAUw$fTӨL&Koq1pFfͫU5]ZSi~Ǧ.z}>CsKk{o: = -Ny u򰔐w΢jNA (zzHޤ{nG^[0Nkmت~;Թ.,hy^ݽ>><e':7<3[\Ꮲw9 ?ػ^]j-Q?S*;sQVs*m6UݩiNq}t@_N˶xokcUuy}j=>P>;yAa= y6N j;*ϝqJmdcQV k>$ז{A>MkjDFO瀳2Ħ&HEl0b=52_]8pk= ܰRHIc&$Fm;cspvQfߓ`It,BlMdl8*_]x2816˫:Xe;psIZ|]Vn{r`ABP*;,X9$hMOsLb(߅њ{z熥$ؓXGIq5ӿ5tzUxIێ>{|0.k2<)=)NUIj9y&1%>Ɯ q5'iuyۧ&_zUN\9S50r_TO;ݸDlvԶ=>ƣ;kjޗ`77(18-N3hjaKЄ3in_۬oRcǥv:WqA0IYQO- qͱӶ~NoaY'``瑸޹Go=z]ask= ܰHSuy*Lne%|!y-Kj;Y#Soc_9 /o/Tk[ U/bu10.`ClLHJkj{28zm=>%ygO)ք-``.fjFwZq|ų\y:jP^2ƷKq ,stjJUE9)`U\qik=Q@ve'j*I gωWUuc۵*<-EQm2b5ѯX\kr:`N+i2U1hBlG7kVa8fK0띯VC׆hyZy X@HoX7;N|wLrXa}t9p:`rb!v MHMJ0%VO.Ҵ ޕњjPfAA$ϗѲ2S w$΍[7+;Skj̱GL<ۭ+טx417AhdT=V|%x¦zIYd[7h ZUӥwwti[{ho7&Kퟗ:8QɱfWZt(: -)ow:bL/HxMÓt 9NfGs 4\˫x@4 y61ZKج *\[IiZѲ&2KЭdV6Ns>.ƣOϧf(ž%ݭ=za[mG98_Qݩ[4鱍-aV@4kwt _X|'߷'fV6~l[5=LLg[s*)BJ4ޭ*k;9`Ib}p7$sP~r`V-2[I0- K+pzӋmMEz2zd}ےHB kk;5 N l;7X^q=`tw}9 De)Yv9zcIQ $Yg1QcuxIp%}V[Clkڶ%tHqZ{}=QfߓE' ]ˆh#V߷l D-GO/ѸlZ PYOWXv1oVG3߃#ÕS ?}F%S5w;nDyc duA8tH~kEldjKĉ 2SfˌؗVвutK]9q~^zbxnEQb3$zewgJMv}=fTFesiPu. MҟWѾЇ-tl:&c}Rn^ը6ڷH lWϦfnhix`Ah L]8&]gH7ޭf-j(+-.1O[z iwlsK-Ì\MVfɾ[?mw_-ŴlU~uPnY5'a?h6yRϱZly}{!#!Ff:5(ln(Nӷ'fұ*IߛR}MƹC 35Ƥ:psTt-%zN*H$+}U&qM mz`^OzVg”|kH l ia~no7*ml -TҿXܚ}1 4g7/ר2&;A{P3^Vְ< 0wkyHc7+MjY-Fuϫգ͇d:v|cBEׇʩBX"˂^'ɦMڬD+ϭH~F7{3swFzӜS D&0@ -;kЀ[[-[#uՄt0[UGII/UvWU/7lPKwn="8z)i.G-3!FH#SuXIɀRt <~];)|(fw'ʝ5n{IlnK*C=U!oMLJ1x <~4uiI[O.t: ܭlCRB[\_߭PEkaPZyDNt(DO5tkCeZunͫ}f)=MwlRaJuTN nnZՠ߮h'g3r D0u)Mf}KZd>qz0† ?Џxt>i|\/IwWvz.C?]شv{byn?2md1z`CMQpgt)N @a*s!yQ3r)YƐ#} ^P?Xۤi}?l hMdw߬^dD"E9 .Ҵy۵*|*Nwh:<#NK]H-N >o^ݨ^H@MM8Ӫ/wSs9FY?}zzsxemyBW?ZZ} H@tl>>.7ݒw";$ eUVtSq&-=v/nk׫: StF+tHe~Ӳ}q?2PepVi% Ց`ucBƮ^mtD5Diq~w/Cv=tBjq$w_ۡh17}W'zөYɲ:v:qheȌ8\}IY!gUsz $yܤX57VuwGG>a\9D{Cr̊Яꈭ\Rޮ緵}'$iPspuQy{WK+:,-uз6|Э;6Y֝G+7)r+HbaYЉ8=My.n9:[Uաt>fLvEY#6ztf挲~\VmsGNg]{k$hOVe0x]=!yXN8v&x(Q}(yn<~zv9 ')Vq5vWof{`okZ٦}Wi[^nXV[{0 QW7Y%'yZ^թ Q:a ǭ'-5gݫ߿]b~}P~z`%^Β}Fwj׽9 #yF-{2ޏ}ܪ<>8G?^VGXAJO?EcB.vOڽ>Fq-sO(0G[OW\7k]i8oBn~7#'`~O듯 T1I*dNhi-4gT~sPmf5Sв O#9vŸtx:HEhE:0,.ЯWA{/Crda{/jEU$_\XsFћsJ44ڗU7Y d%ɚ54?^!Ʒ6w'v>_N(3şXav=M?VQ9 v(IN(ʞ}0Z'_?P3^ۿ-Hh͎׎+]ulKǦo385Nǥkdsy\N$_`l&1 lmtмZYiP~P Y1$VxT=Q<|G_`j'r+ yV-m˪F=ͲE)fLOm]L ~VFf+7L[Q͟]$Dg'}݉]<| ZW{Zl7z~gL ϧe;: "<nU/jU`$xt`Zmm CuN0_W7ϫDk2ѧ?Q9@e{\d޿Tnz>`sZ=kQuZV0oYh$q:cd*kU8\4[zt50'k9G:5ݿIiK2+ˍLD+4'5z%[﴾K,ԂENbܣr: 8g:j:xÃ{}Pei]x4!7A^Oи&iMd0&7յ]ZSeV fWb5^ݼJ%I&ퟗקq FF=^cjkMyr>;ONg7Aٲ}DJhE 7i2<4}@ܹlTrC3'2?m4' ^=@$p!  bۜtJCD)NXOhU꺔|&=:@gf|(ՖWmt H@3*pҷW/ϧ9V)3!F,!ɖEW@(<(clb1jլ+gM_XkMaY`ŌAlgXǣ#%}tڈT&@zgVUڮ5ǎz$ N^-emݍƸbq= yQh~F0-Y[1MUvbZ}n?u>FIRGvAqj~1-[퓦߻C_Ӥ?n5;H@3*py]g^Fv0"3^OX !aI0_H@۲}#-1b%Xw c߿mRsB' MQ*-pX{~JScm=q:ݴc sBsF:ՋlG JӋeay@:8G'MQO;sH@1ZLi&Ӌt$-=k~lӡ؎9D&ײ#X-.ƣNsG|Eqzn\^t("yQQiQYqlKںn|>"Fg:$Ir4,=N̝ڽZ^١7*:Ckj; &g)->6hJ~:=$ %z~ruFr#w믫u%J퓦?!iH*v^]=at}84Yoms:ې<OWL=6;^:*FuH~it(J'8% (I aTLY!}K; ws@ NKht=v:IX]ۥ[v-Ƨ&<%5Var^?Dm=>|RK-?&DMM=tA]!؊9DgQ7y |Mg=[6xblqDkg:rۏ -9y k뺵0lE9Ԭw3FaP67Qf>xN5z.oS{O@8{pC.cJݵp:ۑ<050I攄|]Ѧ yzCmكO4Ǜspʜ|~uZ0lGx-St}2vV8Jt__nW58 Oywn 2BqW[)$ zd!#tvMK7y8 յ]N|kD-:X9'{|?y[HC̲C>.ݴ0+Iւd( z#v!yaf{KQ?t(xYaDG.(՘xC1=qSIJ >i+ljadE8%Ph}}~&CӾlӼY:kdckK.xZfK_ |X?g6 #@_H@|vVmpR^֘l&&3Jݏs2$-NK^,kEר^sF ЏrsO'/ԫۭ Aq_.O#ۦ,ьD-m=0{;sf3"L84Y ՟W5o֩"ի*:v;h}c50Ѭ@NG{P3Tެ$ `7#>ޭ秏L_$WvCKەsF*8i^]FO~"7&d#ԟ5:zӲ00$ TmtTw\2KIsUmFrzx0$YG&kLvFjy|?7u?*||NY 9AǕ'fҹUkC}Wc"ߟTyIfs UwlRNR}tNbqz[t+jMݺc1>&eI#JĻ_~]&f~~4~T2y ZA}Z-yy~65[ݯOkcWp::;~d}Lx [}zq[AoTЖ=ݳ9`ptݸm1β3^Lz;QRQt+euŵVEATRD =$!NfL|#eL23gf}_{qH9睙sN&|y֗ة"]]/<d_9:>ߘ>}qu+cq`;6ՔŤ.-=6ut L5q㢦8q;sEfm]K1i%)9ǧkQ}{ux`@v}{Uc׵W4Ю(sѕV-NL>Skx]eA>4^/^K%-'A?>y>5}˒xkbmsG|9qΕqĴȤJθkyK߼qza]+׵ׯ~0  ]|co1*?>[J>vO_9d\q3Kc"i\}[gUIPՃcjl e5;Ƽڶ+ticnXuÝrԥ4ziG|N;צmٷ7ېʢQq>TIEgxkyO74??~r:x@Yqlo\+Ax9*i[xTcE\6'A9I׮EqձcqEQ&3o]ohCC~^^\ZK/#&5[:ۅ#r5/k[c =I Ϸ45rYz*⒣J~KXϝ.3 IDAT7#y{E@ ]3i@iqSzatH%<gc z%1 Է^~\C FŗO;:]keڻc\?zz8GN/?4%xu [VG{?`h@R'=T7({JOi]Ϗ*+ȋK7YVocKSK虥qLڽ(=vE<-DiQSܻ_CK'_$8sakkv(w|K+ׯHo6$8fb̬Ǘ9UӦ)/l|tQU*)k(t籅ktKn67 mC_}qk(>׍|x\sqί5!]#9;VdIUv7eR֟p㪸mIӀ?& gVe{ iloA <svU3f&ϽmMv[W717$c^][sղh:_?J5g{ iᆶloA < IK3V iW}35rYz9&Du,v[Ο;\4Ck[:cBynM@xĂӪMq{.C7<ҵ7`T^,{^[)ec˖d{+uL*UPEC//Ir_6s g{ 44U!^yKZ9dTĿ3&loxqU1bx_]+{fx0`~>>(+ Φч^w3;,>8vVi ^Κ HttU6y;Ux;wy~^^Z]mFDeѨo5ofeAӞN9*8xYDxg{ .CğV ҮNGWWvvEi~^#Fᨼ8}nE|tbcƢ'6Z;#///~xxﮕXE B?9?,k1C=ߘc@u>-sq%1,?M=Hd`c{& .&ʾIƫھ >{U^nusGܶ9n^I)VEVv^_z.~2>;sl$IF {ʴ{2Z=5ƸbA}:&sg̭xݘk\fHur\)NyU?C!$?*|n1z+$lLBo?!gz q_iW[7/4͋YJL*OZ ĭKIWlo_Uן0) szy~\|t%?5"85r꜊8xJqd_?xrcnV>>4>8q(bdf u{bz4 ftd{&t:nSg<-nO$|^eW-:#9Y[uQ\~pL/46]?'ߴ:V4o}$M٣x]Һ}n\uJ;sx%9\rt\R-i--'EVVg{Vk8W+nЬQb|A'nW~BS|சX gI^u~զ5fC?YR,T_xiiGT͈.gsF:9.?[{XixmkFK'atR]-`97+[-K2rx`S3JC3ϫ7K_XhͦʐsLq~i1wZ{d1QYǓJA}גW71NS>`˺~ [vTɨ=%+>x_[3~ޓcyKŕNJ;&qNizR$<`<(?mj~) }XTrMIEV+^A<}5qCui;j~ZjyZ$t=hrq;,UN(%IuSIxMk:g= דטI`p^ůM3Jc+mWo̠=^r&-~N=sƇܥ2OsiUy2*`9qӥqۦK48qBF"9_J]eA`#C] ]+{.i>\ĺχ'GMǺsyK~yIkV_I~蔒3']3(]W6!oduݫ ͈/_:ziPQΞ> =Ƹ!ݹ9.~!`u|nIzkʐw3;-'ă[iW&I=PyRu1f?^)]_{ sFqGN+MԁggZ޿9UqAcSc&3_8fI{Ms[m?@I:-sղtȷ_?~5~` <`@%JpqBܴ)j[rrkieK,XߖΙI*z8M-돟uI~kyIYQiָׂyqS<5 m'6}34U>_2Cޞ$mӿA{=68Wy|C\6!~5W5gM v 鳧w?uC7^ /⛯߾&[~L}|tSE|ΞͮEq)S3V˛xGgec{l~}^.~߂j}fQp v8lJɀ?Nhl˺y|c̯Skhŏ}#}Fsc+iu0R1`HN.6^!Gmf'-߾CyنAUL(GϜ :8͆δMw^$?/VIK]q`(^18٩bPgkܳ%UcdNyq)S7 J{gŹ+ 0\ 0sFUCFE{':q{ΕC2<SO3#2HwgM\*rh[4c ؉qY]dPuzVD^{>5a=!޻[eb(w 0Ϯo掮(z ʦy7wXaHѵ׵>lTZZ?>Q$O-nt_ @0~9Nܮ,x1󍱱]*~vճddgψe⫏ }eh_7C-@A{WYsٷ_?.]=5kCԷgqg#Om6gM=xii+=xQ['sfF˦ΈO=Pm ]]]g{Vx;PxiSc3 z/:5q]7 } qU㘡۾}^WWf}I[$PaaS4wfqӺ7H/)=\kPg^OFe ivsʨoť绎-'CۦwoO}6o^hw߱fv4r$ْ%Y˅wVgu%QѨH;ږ/V߷%K^>zOM߼xiiyOk.￳&.1`H0z!:oŊcI&mj֞seV31|Ľ+[m7L_"N]~Q~<6dLZs(JFŇJזm_!~!׷eiwC5/'M#~G vC";aAqӢ(xQ؁ sEkm<5o6kޱse|uiQ>gNJ^{-pAȖ~.^Td]9!ܛ;ۏm.yU[:O(a|Kry_T+iLy?FmKIOZ'aиX7_o蹕|"`劥Q9ٹM߽߂MO/mzdCܳ_3Tu]5/zixeq܊x(UƗϯ9 ԯ{1FX6*iQ^29t]3Kj]s7-khSn^.+?pl|l}ќ\]+ӕuEK|x6BcԴt^*Vnxt嘙ecz1T4uXݔX?;zbc}צɬXؑVg`Gs>~!][R^mXQ&!}Mo^3y1Tkn>5*.~!~{俵6.}<Ӄed]֥+ia*c8%1?=>|wMn:>{&iG<~ַNC~"]?E=6-`9&0j][k|q9}̍fVéfoqUuI^縢hq +|T5JiFi| 1wtxSU7b'6F0ɥ{+}'_־30 ,3qϫ[}PQ8*;#>}USG36Mֽkyy{h<< c Mfow$-lCkEb1=^mSW5鷬 9MRKۻTVE}ҕt޽kcvj^mkrOM=fR̹34\W,dž͎^w4%k6ܶfw4%ɨdm)9p܊8sNJؽjS4*.ص2][Z-RO_8Ku75U裋z%y.8z#}ǚtʋwL׍>P=4=oFz; o>twͰk{{"M}u7ntݟ>gz:7_|h}|e3vUi5Ic E=-]XMk\1~lC{}øuq5-p]t䒴J1iLa|,r5^`%MJSzTgX=}CZww^Ɂ-c+Fx}s+⛯S$soMjMk/mww\/gN'7KL~ 3 ^.i}Фt}~څMjbUS{u鷬_=0;_Ւ-V$+Ko߲}YF'2߯{)RcY~Of'socA* ՛Vt mLG۸kMxgҕ8`RI\|g|Qcl7U mC{Nu/?6ul}Ԍ:_lc&_>O-3u53<2y8)饽^~zy$*xqNӵmq]5qϊ3'3[I+*xdmks>fߔ'Κޯ![_|c{=\C@.\V ΛU_ז~;؅lT^^qzi<ǹ}~?rZI6u#+P=.xYN_G~ ;:p͊xx s`Iw]=Y} ov.OyqN?Oew~{_ԽkZuol<}gsiXs}ْXݤr9T6-67-nܸjĶI'2;ᦓ甧ߓ}=ZC\-== Ϝ[?JN !$&<AmY;b|cQƧ3⛏m/.ryᆶ{EKKkwYxoX ٛ5͝/3SI%j2g8fex1KҠ8wʸ ֙` IDAT]]r(+9ttmiVeacy1"_od @6|1 >xlo k@uISpْ.Eq3񽥤u ҆~﫹+X}]kU|^kh]XpXݏ39-_.i僫f×ON ΚY\_{d}tऒ8c>{eܹ9~lCs"mï]W}8ZGo)~'ֵWn qͷ.65'u~RIߛ~_,ږmko]zo Z)<ς_yR:'VoveqE-ͳy'nWv~ qs[`8#}ǚt%̿;&ucٴ}縢ۧW7w'}E{mW,5InڶMqNWuW/%VU?4?m}?Wf}gNK_+ij>YZ;҃A/?:}ny:% ~?0%Eե+qNCr|Ri~\ _/?\-}.ˋwq^0iteVƓZ6KھUm]_ϛ28H=vm@nIAɍznU#{1TaHxLR坬K#gT5g5.V4\Q]UaIY VN{yNGֶ~^@Mѩ]ۥ(;szeP|ʖx5+~SX}@v`Kfuʂǧ3wӕxxMk|வȚM/U2(Ik}7UC͗^-x'n_O{vwHloOn{Hx#D2]ޮ(_}7NwRѵ'vuv S׼9GK@nҕ8nVY|1jpoCUQ׭/Xlbq,̴w֤l{ON齃Bkgi[MJNh^}wpdïxCu@ 5-beKۻ+Q8|nac,ܰ?`mi{ua?nە3ٓuS[$٥O FsgČ;(m/_7u ݯst|r11o?ۍ߭6Jx9uq5Jv2#DZ7J=~4 [8fB~c{C bu-qƭ㎥dVea;gz4ki^vjmqve僫cqEJ>rϺ29Z}M,oOnh0͛fFQ~^|ަtzbZEA~cݻV[] jcM߲߫]y ϢW2*~)ϫ 憎NayҸ)]VqqLa<'<p]:]ߖ}ֶtě_N>>2,F* 3E] +aѵ޾6XGF8wc۶j<1٢EbRY~ e^e/:h9]i=ouFJuIWM_ֶI7 =W478%<zu,`;`RI|8bZ zr㢦Sz~J:W9 e{* eqł^qHM?zzV /o\V~ေd\XΙ3»֦ӑpT^R\P]P6Oם[߭u-uRq|q[W%\}ӕWo~|pT^|`ccb鶧 7g_tO Q s߬llۘ.˖DwcV5m]qk./T 'sɏꇶ}qxG~Q_=:=G7ݠ}&<AI\/J2,̋돟޾xk_ԩӘk:yvy:$b%$+J+K#)k;≚֘Wma¸8cnyc^KG|F6se1NjhT\q8q?6{l(̋{OOնW/k=$?>wx.ck$?|jc| i'PQ8*e}DVkKڔ-gEs?Nx@3Uw5! =LWF^Y)pI`t 6I}F+qʖxVovV'.SX{6~6!:-` xvuO'g{KKZ?rƴ8+U#1qۧń [oæĪwϊu-qu+uzݾӹG(llo=!/cc 9)i}Я?Ց?L1'cjភw2%^?*35dT@m̫mV`u1jx# æ  qk _=2'*62'7f{ lZE~ZڇwU?lIx@\|cܼ9Uhc[W:s{{~Н׮E;#>{VNJwό1EW9e~yCWHo6Θ1CnyKGW}xnC[/6w'﯍<<>6 y/ ]eK⒣&{wvROk^mÿ|K_{d}<$޶И53f{Яlam>pڴخ2;?^ַu׮ݔ 'ݴ*ѽ?!>qߺm#ْ,׼yR=tP⤛W3x?6uohï]׌ 693aez{K1kK+oZԯ;\,k,#u'L+λ}M\q@Mxx~C[/ʂ8snE6$* zqQS\>Aܵ9 GE5: zf+yrHa]KG) qsŒO3>~t% /3qiCG qłxr]nT˽dNx@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@<99Ox@@@ F{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs IDAT9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓsTIENDB`jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-blue.psd000066400000000000000000031051471402514743400223760ustar00rootroot000000000000008BPS:8BIM++8BIM8BIM m m   8BIMnormB8BIMluni08BIMnorm0Layer 18BIMluniLayer 1    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ? $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$0099654324121210110120222121429;==>?@?FJ??ENJPSGEFDFDFFEEFGESSPPKXNNMIGDDE@EBHIKJLLKMPXURWSVUX`_`ba`aabom\[Y[Y]][algqle_ilc\\ZWYW[XWUVXYZY\d]][fprupmkmrjh]]bcQQPQKCGBBCBBDEHLLOKADABCBDHKQQSJSNNMOTPNKKOCCDADW:<:IC:;9;S?@@B?B>CB@BBDCDGOHFEDAB@CACBCDCHC:99:8:9::9B?`^Xf]YYXZYX[SUSSRTSRSRbUTUTTTSVVWTTVWV[agYXWVVVWVVVVWVVUWVVVwsqqomlkjmkjmmkhlmn~}}~xwwwzvvv~wnonw}}}}}}|{||z|zz|zz{{zz{{{{|||||}|~|ppqnqrsoqrqttxx}<;<<<<>>@>44<><;::;99998999=JGDBDBBC??B?@?@?@R::;;CA222L99;;<:<ADGGCDCFICMJHDA==<<<;>FNIHEB<<<=<=>><<===>DEFUY[xshddfjqg[XUV_TTRTQPNNQQSSSOUUZ`acalogk]WWV[Y[YWnmeaaa]^`c_[TTUQUWYUJNJJMIKGCAEADGFJLRSXPQQRSIFIEEEDHGIHHIVTLOI@@LJCCA@@A?943544534222332524436466768<:.0$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ Exq9aQ p )Wk{mYCZ7aҒH0o c_%FwY a#jA9&h U]DBj"ȁ&(,O1x%=sNcoZO.N>>-p" Q}с mE[9[Ye06Im[7 ~F)pO#,#*#3WBkB wG<ڄ)mg[Kk{yfEbLSo n+B 3S.1&R/6#%Ysf}i˄96 ? q!GI!cq[ -Qg}{kO ;6v_o@a^ M>V; 2,&e(| 6 HOI\qs&u }]'Hkq5&4YJ3GW76&rwa&79 1KO! | ?q{b)_0 ?f aPws+}iccVP }NS -+ ?(~k+*P۷7N ;yƶsB"nKBkyEG V_;B ]N?s"#z b @HH  XB)^ZUm:%57f2;7 fG|Sw1 OozLpuW4"9RyYO.+ [ˁ-.-$ہSW>ke})rhWB@ց{mz@ D({c7Eʴ!)ۂ*9u18=u@U w$z]q GBk`J."J2]   }kkQa]N0)E#y[3D 79  pcOӶ݄+D~o &z)ky#[skykl|zZ\^6-_/%6'=STI#VqW!KG,.#Q [9/'63K{yiކ-P o =%y{"eO&E?)0g-mxomHa__Խ&;aQEXy) -AVUC2 IUGEWggWA< YG6 kwDV<g;%t P w/]~ ;Qg*{# ak /=V9oc/ Ic!NeL WW <a'reKB;.1-@qi.u=5LC3^{5)19~)@" %aζa9}# UWku LDGqvV ;gkI Sq!wmUe NYmB 36 eW B_ fPk^4`sKD=gy+N_mcQ3{"'?6kyE|o}(73JfN,E }ES&)"^6SvS#C[Df c+IsCaq 5@)ɁrCN&ŴԵ=@U5KU.;c.XC45؁,kSNe"#rp)a#tOP *naƷuin2ݹ~t S?? tD($[.ʄ?k Xm 'gjp:YE֐K XRW cs+16WE&U/$v457 aҿ]%J4e0S_c v6{RX$soҊCq %ȿo'=I۷gk$IHC^nsޖO gf ccw{, K{36=QSCw Se"i[a m##1d34 0#?̆?l9 cw$S2,]_εCUsc +O3{v #%Ci yS+G/w{ cCZ8C$:f+q`13? 3cwcE) co B<iG'<?!s QO 0oԺA8gsae _3?3/-K;33?S !aS'uЄ9[X" s s_7McxkS;1w8(R _3Wu)$%=P?S% (Ch%;ME7! sr݆# qk ~eツ- _3 Y²c/KqhGk?Si+Z9EBj{w {jC u& [7~WJ2P;W7 ցI|{wWy X7g l`I76WGehw4e7Om6ȁSWikuwZ*o7@*ZW|/Rix=Vk M C+ak +ցO:*#;M8On!meY }Ug" #<;# U fH!-ށtL7g{hQJL?RU 6o=lY3u?_I?ЁcTBҐg1s\2\|m/6-b $q+-4MĒv(-gÁ C|$ V{Y'-%" Ym$++0Y >U'uq$Pr%-{ i ys NVL6~{zuNང?  cCA $BC4 yQo)Ĝ 9D? #U[!Y   m|Xg 0j  'h]@ _ū;'M!7531/-+)'%#sFba  _ Ł# Qɸd!Aށ9ay<mDuGԁ}j{zuVq;=?ACEGIKMOr*wkE?'1F>ہL F ޭ 6/tsj`@pˁΫE%M-}[0" *cˁP-B(TobL(zik{: dEJ >$D@sT 7+n$6%q]YĒI_ ª{S-AUO?#  [ WkciO!4 q<U@.)M?9={O2=Qeā7r f$LNZ:ҁ%X\O5E&G0kkY.z xn$K {Y gӂ%kaQ*EYleS8 ,\ -PL%3?ow=20H.A3ȁvļ'ā"P<X0 `+ӹf=%o %A[t!o=A@1c23GA+y =b#U;)s26=b#^  LwĠm; )߇2]=b# eG;]„2C=b# ) Lo2 =b# (^_߁2=b#?'Ɂ2#=b#_8Kk2=b#1U2v=b#+,ҁ2v#=b#쾜w5 '߁2=b#+2^=b#P'2Ձ=b#z ́2tB=b#rA$2=b#kxd2=b#c2T=b#\/2H=b#T[CX2g=b#M\ׁ2v=b#Et^2~=b#> 綇o_M;) n=T6Vc]=O/{_ w9=O 'xU>==O J#~Xʁ=Os=O%#R=O- wOi=O6o.=O>=OF 2)4=ON K`xt8=OW6^=O_ܧRhc=OgԨuyJ=OoͪI&=Owū -- _=OFGG܁sU=O1^waa .iкS=OL?Oڊ3{Q=Om&k`+=O=O =OƁ=O.]=OH?=Ozh%pr=Os4Sa=OkP6P=Odm@=O\)]:=OUB=߁:=OM`%:=OF:=O >:=O7l:=O/$[5P:=O%(<<R3:=O- Z$n: $'Fspg_WOG57GOWakqsqkc[QG9sog_WOF1O5}:+[}ȩ{IK}αS!Ģq?O=܁:eg#mw-ޣ[OE :>֍3;ڪFʀ"ON!Z:{e kҤySOV8<::#sO^S$i:*y  aOfu6L:R]s8OnS0:M Ow }o: :DX#OZ:ӹTb6O3<ف:)j{GON$:9~WOq:,gx@O:HVu(O }f: ʴ.:SOZ8I: O.<T-:L[OH"q:5EcOh:߁OՁ:/]n2O }:ƾݮg );K]o ƾخºO{Y:5_00yctU'  ,[=:k7Os)::Q G K=cOlB"c: kiEV[_v=Oda9F:i SS=zO]U):E:= ۦO U}r :D[!j{>:={>ONW:;#=YOF&:ҁ:. /=HЧO$?>":%D9CT%t=aO,7\:+w kmA4=\O40}|:\=O=({_:\!S .=5ϨOE!W:C:\+<_I\=OM"9W&:\B\Yj=&-OU9"s :\p eM=[O] U:\-=HOfyρ:\́K=OnyJ:\w P5=OvVc:\26=LשO~M9 U:\ 0ĞO= Ok3"1:\'`8Ioi= OSDOb':\=2J[^A{=O> ql7:\FURc0=o%O }yd!:\N-Zk =H.O(V6:\Vbs=.6O39 :\^ j{=>O6 7:\frٜ=FOʁ:\kuȜ=IO>:Uk؁u=IOy:nkǁu=IO+_R:nku{=IOnJ:nkuz=IO+ /G]s:nkuz=IOZ 1G_w:nkuz=IO 3KawӁ:nkuz=IOF 5Kc{:nkuz=IOG 7Og}:nkuz=IOg:nkuz=IO؁:nkuz=IO:nkuz=IO:nkuz=IOªgK:nkuz=IO {eO7<:nkuz=IO ־{cK5:nkuz=IO ҼwaK3u3:nk ?Ukuz=IO Һw_G1 :nk/muz=IO} θs]G/i,:nk_uz=IO<=:nkMuz=IO:nkc֧uz=IOd7:nkLݥuz=IO%F:nkuz=IO6:nkCuz=IO) ~:nkvuz=IObW:nkuz=IO[):nkuz=IO% c :nkfuz=IO7 |G:nk4uz=IOH#::nk ߜuz=IO.ʁ:nkuz=IO 83:nk'uz=IOW4kWuN:nkuz=IOuQO03:nk!¢wi][Cuz=IO9Ɔ-9 с:nkۉW%uz=IO Knu_C ^:nkAuz=IOnU:nkHuz=IO,:nkDuz=IO؁:nk uz=IOi:nk uz=IOl3g\:nkUuz=IO%:nk&uz=IOKށ:nkuz=IO!:nkuz=IOp5`b:nkuz=IO:nk;uz=IO:nkuz=IOU(:nk(uz=IOt9Yi:nk1nuz=IOS:nk;Puz=IOm@nk><u=IO}/)Pnk>(u=IOhRp:ank>u=IOOJrok>s=IOy7[}k>l=IOA6lk>d=IO Kw|k>\=IOu k>T=IOY'k> L3}=IO<k>>`o=IOr-D~*{Qk>;$U=IO oa<k>i ;=IOB nGܔwk8*"=IOx:CT-,k/<g=IOk=f:k'M=IOeƁ8k`V(=IOQ ?gkc=IOJ+xJkϢ 5f=IOk6Q`x}kd 8=IOa ́Y2)VkH; =IOĒc('kX =IOqQgkS=IO/Ôi7fiDkZy>=IO,fE}J#;k*2v=IOV<W  SPkr}=IOj0@~X  k*  P=IO [,C>Uk}a=IOO_  ]wk5#[N=IOUX_)eka y+=IO+gf '9K[m Ⲇo_M;)  /ASc|kY/ :=IOU=2`Qk7G3#+ ,3;CTq1=IOG؁kg !l|Ι=IOrXfkmM1c Κ,=IOk k IJӎZΚ=IO^:\kjΛ=IO*h0dk݄Μ7=IOT>kJPΜ=IO~T ́ekuΝ=IO/k ۈΞ=IO.G+k.Ο=IOA?'߁kBΠ=IO(iQ.ҁkYΡ =IOS?J  kMkΣ=IO}5kk5,ΤF=IO ;'ɉGkΥ=IOs_ߍke*ΧB=IOXo0k:ΩU=IO'jxm]•<k"7άK=IOQ@(9)ߚyksԧkή~=IO|W}3)sw/k4Yβg=IOKʘg7 1cҢsC kNᲂS#η麊[+=IOUƮ {aE) %A]x mS9I| 'A]w̮ ~mQ7® qW=!*2x6*&k"PACzeS<H $lm3OBA|y!Uoq[? #J]ncM8#m YȆ2MCTx {+"|ÁCC"n.LDuv1?āQ6Ɓ!o$KEu 9ˁi-%sq/yM?ac /keYYFF6W 9;Y BGE5=0  [ӁysV]+  K^e[66сr#(vnsYjh4ہ OMKIGECA?%2"5J%c( {+)݁/oU.|m2e Mu²i4Tu5`<*9\с=X#%')+-/1357ue@RvEN>9UHK<LFmd}QB-U?`Y=FY:-_7c458Q=Qg0\SYem-OE g+'r*+# ]v& t X }$81DP"/!C;i MԔIJ18w Ctj3R?? _K!oB gq]-3 M _.c  /^*# YU ) +leA<MI/A/ }5E׌A݁}q}f:< {z?$)J=mLفl2h F2a$/(فW,l'[R  wg܁6 (Wȁdgtwq4K( zہGd g /r ;RB: [s}j{SޖQ0G_voW?#,4xF{3eIM#)9'N[g ocWA΄9O) ̆? S kβO !C*by Yg!qI?' 9WunIx" }k>֎I @M_w+s+LOWO72S):26\8nQ]7NS K0,H g_NKsg (x;ΈC4)%GAP.,[k#\DJ'sPb/wڒr wj  Oݾ9xW+m]:7O*#k6n*ø g2Cf2< `y־5_g }Z7,GQAw*?SEP(øG'+s]w ۸VRGaW8[gIW%e1dzm\$xUx<nc2$Fg ބ* uIowgTTJ-gGI wY bA)V KX>25i2;/f@e/:+߆-u%KñP0yeMw&i -po0ee[ X:  ?$`E%qM 4OT#.#}= sC.1/=Tul H}#e={"3&o:} -b{sT(Im?0MYy> W] (q ?̱{A L_C]cOj- )sF%];LA' oA/ 7#{1gI36$/}#NDZ^;oDo*2#I  G_ 6bсbUQ5_)["aC|!_Y:m3$h nB"{&!|=7"q -O_k_O0-q_5 #=O[K5!{D Q uw!k}9a{6#^57GSaWiUSEDe0uK!SU+_5&6 Ewy0kڀ'SsS@81OysPm!pmn+eOOy'D?Cwq7UD[ >  EG40RWIQ9 kc"05 ]ۧ},_j'kG v \w M|@? *)`cd7Vcs/ C9OP=O@ݲ[@ [,}]Ԣ-i^$'qhbz[[?  fH Xs9 RǨ)Ákum72Zv3!]MMFaS^? 7ut2:mgLR}.63Hs]( Y=," scn#g_ CKma]wm@7L>&~y* 5 &]ɸW.f ?R q{ N{(f[].> 'ŁI0Oq56,#,Q ;J&; OsU#bMFVrw_t1oq$(O[-H1I#qs*6##FW%1& B>3j SgWog*aB[w }(9 <GO!"3+ ? 9bsq[Ac#)Y}i"}ʌ 7f)PO^ Icψ?-5ȑm[+6+7YmOE k{Z3B ; /[qs]/# #, ]a-  >qokOZ2Ya5-1Es8%,2#u$KPi3aCW g m{ %?*e:{Q?soXGN $u΁'wR}!&02D  )'/yHD#K@P=i,.vٌ/  3OeyscA43#- []) $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$0098654323121210110120222121429;===>??FI>?EKJPSFEFCFDFDEEDFESRPPJXMMMIFBDD@D@HHKILKJMPVUQWSVTX_^`ba````nm[ZY[Y][Zakgold_ikc\\YUWW[XWUUXYYY\e\][cortomkk~qjh\]aaQQOQKCFBBCBBDEHKKNKABABCBCGIPOSJSMMMNTPNKIOCCDACS:<:IB:;9;P?@@B?B>CB@BBDCCGOIFEAAB@C@CBCDCHC:99:8:8::9B?_^Wf]WYXYXX[SUSSRTSRSRbUTUTTTSVVWTTVWV[_fYXWVUVWVVVUWVVUWUVVvrpqomljjlkjllkhmml}}||xwwwzuvv~wnnnw}}}}}}|{||z|zz|zzz{zz{{{{|{|||{|}|pponqrsopqqttxw}<;<<<<>>@>44<><;::;99988989=JGDBCBBB??B???@?@P::;:BA222H99;;<:<ACEGCCCEICLJHDA=<<<<;=FMIHDB<<<=<=>=<<=<=>CEFTX[wrhdbfjng[XTU^RSRTQPNNQQPRROTTY`abaknfk]VVV[Y[WVnmd`a^]^`c^ZSTUQUVYRJNJJMILGCAEADGFJLPSXPOQRRHFIEEDCHGIGFITSJOG@@KJCB@@@@>943544534222332524436466768;:.0$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ëඔÕ 쪻Ѳɾ™Ք“ݔē䣁Ɠȓʓʓ͓̓恓Γϓ񖁓ϓГ聓ГГʁғ偓ғѓѓѓѓѓѓГГϓ쁓Γٖ갓͓鞁̓ʓʘɓ͔ҜǓۢޢƓÓÓ˥œǾՕƓ–圁ȓʙ񧁓ʓӝۣ̓ĖȦ˫Γ䪐͚ۗաГ뱎ٲ꟮Ҟғ񺔎 씓Łԓ–ߓۗՓʙܓϔꟁדӝۓᚷۣٓٓ魯ꗁۓ䪇ؓہݓ첅ՓĽŁߓ򻔅ӓזÖғЁ˙Г“Ԟϓēҁܣ̓˔Ɠ􁓁䪁˓ݘǓ쳁ɓɓȁ ǽ򻔁Ǔʓ恓յĖœ˓󁓁u̙Óҕ͓՞“Γ՘ݤϓ媁픿 į쳁ٱ򻔁ݺڗĖ韠ݜ̷̚񲁓՟۞ݥ欁蔲騁Ϙخ恓ߠǪŕ񖪓쫁́Ӛၓ㨓道⢁ҨȖԝ֛딁핧ޤϪ奁ʥޥ˗Ơ񱁓ͤڝ໘۶见ְ锢ϘÓЪ󵁓ē˥ٟޠƓǠߓĔȓȝߓ몁ɓộ񖝓ݓә˓ߖܶܓ̓ߖֱ䙓ړ⢁ΓѫؓǕГ̦Ӗ٘דѓǢ֛֓ӓ’ԓԓ⼙԰ӓ奁֓ ݷѓʖؓױ߁Г𰁓ٓߓҬϓڝۓݓͧ“΁͓ݓړǢƓ̓见ޓؓžɓʓΗד㽚͓ꔁɓ򳁓ՓݸГȓݟғسԓځƓÔГӭݓœꪁΓΨɁÓљ ռ˓Ȣ򗁓ŘʓÞғ ࡓȓ侚Փ񪁓œ޸ؓəÓٳړʼρԮܓ֖޺ȁȤ甿Ωߓ睯Ƚ┲こѲĵДնش道紓ρఓⶓߓߓĸޓӷꕪݹݓ緓ͩݓʁܓӁ򙫓ˮܓ́٭ݿÓړ͸ÁгǓړ⸓ĥʓٓ䔷ոϓٓŹɪޓлؓ؁۽ؓǸΰܸ֓“¤֓œӵՓ񕁓ɓǩՓ˓涔غ֓΁Γةֶ̮֓ᔁғʞߓד뵓ꙁՓ콖ݓדڗדؘ߰ۓؓǁړТؓӕؓᩁ 𘕩ʽ™ԯՓؓа宁ĝ۶䵔 ˯ғۓؓ宓޾ѩޓқ֧֓ؓ߶ܓɜ˫ѓٓÓĝۓ껖ʓٓƓѩŔۭٓՓٓˁȓ߶ףדΠӓؓ߁ĝ̢֓ѓؓ􁓁ʦѩ ҽԓⳔϓؓඖե̓ؓƞǛʓؓŁӪ踕ȓדفถڪœ֓ƞ̟“֓Ӫ֓ถుדƞۙؓԁӪٓ聓ถ́ړƞׁܓӪ؁ݓ㸖ρʔߓ΁ƁߓこՊϕ߬ρ۾ȁ՗݁Ɋށ񁓁ҋЌҁۙɍ󙁓ׁ쁓쐓򨁓ʔȑל橁с撓ͦЖ恓 ̹㓓򢁓񠁓ݓ՗âĔ🁓ۓ̐ǙٓĖ處֓ܚ陖윁ԓǁǖ뛁ғŔˮۗ隁ϓ͠斁֙癁͓ҙׁ晁˓朓嘁ȓɥ ͺ㗁Ɠק딁󗠓ᖁÓϨʡݕڤܕ竓݁ڔꔨؔߓ֔ρߓՔ݁ޓͬҁ灓ޓ ʾЁ񁓁ݓݿǭ΁ܓ“ˁЁܓēʁׁ큓ۓœǁہこۓȓŁց؁ړȓÁāٓʓƁٓ˓؁͓ؓЁؓΓ񗁓ҭדϓځדГ道뫓֓ГՁՓғρ甪Փғ道ԓӓ¦ԓԓ֤ӓԓؔӓՓєғՓ̙ѓ֓Ԝѝѓ֓УßГדѲ ǶؓہӁؓݩؓف؟ؓځٓٓ݁ؓ恓“ٓ“ؓЁ쁓“ؓܔ“דՕ򕁓“ד“ד㧁“Փ賁ǁ“ԓʣѓ΁こՁ𖁓ہƁ⁓ց聓漓恓ͻ󗁓Ɂ󕁓¹Γֻ ؚ񖸓ځΓϓēɁݵ ޓǓجʓῤ󤱓ۜΓۖ럯ϓ䖁ʕГݔߨғŁեӓƪԓこ͢ՁՓʣ֓ǣفדĤדށӁƁؓ󁓁偓ٓٓړۓŁāۓɁ΁ܓ »–Ö΁ܓ۽ǁܓ绗ݓ筁ݓ道ݓ㟁ā߁ޓוޓЁށޓ۔Ɂߓ閁Ӂ񗁓ߓߓƁ؁ߓ䖁ߓ왁ߓꪓ㛁ρ䣁竓ϔ󻁓䬓ʓ챁񁓌淕୓𼔁Ӂ ѶݮΩߓگܓױړԲٖٓ偓ѳ灓ٓفʹہٓҁʵρٓʁǶÁٓāĸٓؖٓٓٓؓ恓ؓځؓؖ́ؓؓ“ؓÓؓēؓ٤ÿҶ¿œדաDzʶîǓؖד՚ݿƦ“غȓ䁓דՖϩձƓʢɓ؁דÓՓ龘ǝȓⶕɓ́דœՑΛ٢ʓĖʓדǓ޿ՏǗќ̓󼔗˓דɓՍĔΓ͓ؖדʓՋ֙ܔϓʔΓޓד̓ԺՊÓГ袛ϓݓ͓֓ՉœѓГܓ֓ΓՈǓnjӔӓѓۓこ֓ϓՆɓϊ۔ԓғړׁ֓ГՅ˓ƈԓԓוړˁ֓ѓՅ͓ՓՓؓ֓ғՄϓ֓񜡓֓ד֓ғ׳Ճѓדޡד֓֓ӓՂѓԂדؓՓ֓ԓՂӓؓٓԓՓԓرՁՓۣٓۓוӓՓՓՁՓٓܓѓ⁓ՓՓדړߤݓГՁՓ֓ϻܓד̷̓ړޓГɁՓ֓׶ӓٓб“ۓߥߓϓՓדΓٓ麖ۓΓՓדʓٓۓǦו͓ՓؓٙƓۓ̕ܓ˓ՓؓÓۓܓʓԓؓۓ𢮓ܓΧɓ쁓ԓؓݓ𝬓ݓ짓ȓԓٓݓݓǓԁԓΓޓݓݓוǓȁԓΓޓこݓѦޓרœԓΓޓߓ񗦓ޓ먓ēԓΓߓ́ߓޓÓԓΓߓߓޓ“ԓΓߓЁߓޓӓΓߓߓޓ̩ו끓ӓΓߓꁓߓؠߓ٩“こӓΓߓƁߓ䩓Ó콖֓ΓߓߓēדΓ枓ߓēޝٓΓށΞߓ۰ē񤁓ړΓˁߓޡՕēۓΓߓÓܓΓߓÓ홁ݓΓߓÓՁݓΓߓ“ޓΓߓ“道ޓΓ蜓ߓՕߓΓᜓߓځߓ聓ڜߓၓ՜ߓԜߓ͓ ́Ԝߓؓ こԜߓ 큓Ԝߓ Ԝߓ ԜߓܿԜߓԜߓԜߓʁԜߓɿԜߓ ǾԜߓ ǽ؁ߓԜߓ Ƽߓ Ԝߓ ƻؓ⁓ޓԜߓ Ļ̓ޓԜߓˁݓԜߓ当䔁ݓԜߓܓԜߓۓޣԜߓ̹㛁ړԜߓɔٓԜߓԝד̟ԜߓܳՓࠓ͞Ԝߓ옰ՓࡓԜߓՓ࢓Ԝߓ𢪓́Փ࣓Ԝߓ霩道Փ࣓͛Ԝߓϗɓ֓ओԜߓڸ͓֓ओܚԜߓ䶔ާϓЁ֓॓ºԜߓ̦ءѓ쁓֓॓͸Ԝߓ ŻѓדওٯԜߓϔӓדও՚ԜߓߕԓӁדও򰦓ԜߓӓדওᘥԜߓԓؓ৓藤ԜߓՓؓ৓ԜߓՓցؓ৓ԜߓՓؓ৓ԡԜߓՓٓ৓Ԝߓ֓ٓ৓ϠԜߓ֓فٓਓԜߓՓٓਓԜߓ֓ړਓޟԜߓ֓ړਓŸԜߓ֓܁ړਓԜߓՓۓਓԜߓՓۓਓԜߓՓÁۓਓԜߓՓ߁ۓĒਓԜߓԓܓʒਓԜߓԓܓҒਓԜߓӓƁܓْਓԜߓӓၓܓ咓ਓԝߓғݓਓԝߓѓޓਓԝߓѓɁޓਓԝߓГ䁓ޓӔਓԝߓϓߓߓਓԞߓΓߓਓԞߓ͓́ϕߓਓԞޓ̓灓ߓߓਓԟޓʓޓߓਓѡԟޓɓޓ󖙓ߓਓ뢓Ԡޓȓρޓƙߓ৓Ԡޓ缗Ɠꁓޓߓ৓ߓԡޓѽ“ޓ閜ߓ৓ФߓԢݓ㾓ܓڝߓ৓ߓԢݓѾܓϟޓ৓Ǧߓԣݓװܓ֔ޓ৓ޓԤݓ񸁓ۓ㟤ޓ৓ĩޓԦܓ私ړޓওޓԧܓړ筪ݓওݓԩܓ㾓́ٓ绗ݓও虯ݓԫۓѿ˄ؓۻݓও젱ݓԭۓƓɖؓ »ɖܓ॓ܓԱړǓ֓ܓ॓ⱸܓƥ ړȓ֓ܓओۓٓȓԓۓओ̴Ǔۓٓɓӓۓओړؓɓғړ࣓ٓؓ“ɓГړ࣓ٓד“ʓϓށٓ࢓ؓ֓“ʓΓؓࡓԆדՓ“ʓ̓דࡓ֓Փ“ʓʓדࠓߕ֓ԓ“ʓȓ֓ؕՓӓ“ʓƓՓӓߗғ“ʓēԓғДѓ“ʓ˕Ҕӓѓϓʓ񬾓ѓ͖ГܛΓʓĖؚГޥΓ̓ʓʙΓ̓ʓʓޭ̓ժʓ޳ɓɓիǞʓȓɟƓɓȩƧǓعœ޿ÓɓԿİēʶκ従ȓ Ǽ ڿ ȉ øӽ恓ԁۓԁۓԁۓߓԁۓޓԁۓ帓󭁓ݓԁۓӶҙܓԁۓکړԁۓȞؓԁۓúՓԁۓԁۓ恓ԁۓԁ½ԁۓ̨ԁۓ淔ԁۓ祁ԁۓȁԁۓ恓ؕԁۓԁ▁ԁۓفԁۓŁԁۓԁۓ䁓ԁۓ恓ԁۓՁ灓ԁۓԁۓʁԁۓ道ԁۓçԁۓڮ齗ԁۓԁۓꪞ㟁ԁۓΔԁۓߘԁۓ뜥ԁۓ藧ԁۓݔ䁓큓ԁۓĩҁԁۓԁۓ૓񔁓ԁۓ쁓āԁۓڭԁۓāԁۓ݁ԁۓݮ񜁓ԁۓԁۓԁۓꟁԁۓїԁۓǁुԁۓˁ񁓁·ͧρۓށ Ł쁓̭Łޛ󭁓Ȕ㝁Óœ͕॓ȓ码ʓ뭓̓ޔҖϓ뤁ѓӓؘٙ֓󢁓਒療ؓ򡁓ݬړ񠁓Ҳܚݓ򫁓ߓƔ 흁ᜁݳ윁ᅯ뛁ʔ馔隁埁癁晁Ж嘁颁㗁ᖁ՗ݕݕۙˁϯ񁓁͛ߛɘǁ𥁓݁ߓޓݓۓۓړ偓ؓفדƁ֓񁓿֓Ó聓דāœÁד⁓Ǔדɓד̓ؓϓٓѓؓԓęȁؓՓ涔ЁؓӶʓ¸ةؓǩѓҲʞ֓ٓغ֓콖݁ۓ˫̮ٓܓѓ ʵݓǗؓݿє߲֓Ӥ ²ߓؓϲٓƚۓ甁鞳דĥۓޞ縔ؓɁדնޓڪԓדɪߓ̟ѓ𘁓ד옠ڻϓՁ򜷓֓Ͱ˓䶓ՓӤȓՓƚƓၓ픸דЕ縔Óדܥڪ渓ד̟֨땁ؓ ɪޓ΁ؓϓ౶ظړ˓Ӥ󚁓渓ړƓƚځۓÓ帔ۓܓ唁ܓƁޓƹ©巓ޓ˥ѷߓηƠҁߓ忛ߓֶ߹ݵٳށ޳Ҭ̦ל汓Ǡ镁ˁ榹픯ߓຘΘݓڴ񙁓󵼓̕ۓڔԭēׁޠĩ՟ؓהͧœŕȢՓǢǓޓᣓ̣ ķғʓ㔁ܓӧ՛“໘̓Áړ ǯÓԁ޻۵ΓÓēܓկГ얁˗Ɠ܁ӓΨғρǓГȢԓ۞ɓ䁓̓֓”˓ʓộٓہ̓쁓Ɠ۶ۓҙ͓ÓհݓΓ򕁓ϩߓ攁ГٻɣȁɖғÞ ӓơȴ㼙Ǫٝԓ઒򖲓ܶ뼖֓ݝְד媓Ъ䣁ϘٓǗʤړΙԣğۓ›侚ʁǕݓÜݷޓ񘞓ױ֜Ϟѫ򗁓ȁ͗͠ϋӁݟͣĔ㤓ꔂ߁Ԛʺہʁǁʖ󗁓΁ԁ۞訓恓΁֨ŁՁ٩āߖ䁓鞬ȁၓ܁͔Ё߁딁ڔԜՕ܁䖁Ǜ圽ꙁ ϸ򧾓ˁۗГǘǁΓϜ㧁͓ۗē灓֠뵁˓œߦͦʓǓׁ ˷ɓɓǓД˓Ɓ򻔁Ɠ͓𖁓ÖēΓʙÓГこҝѓڢזԓфᨁν֓鯁┼ד𚺓ٓ씉˔ۓƗ蘆ޘݓ݊Κᚳߓ՟̔ ú̍ݥ֠䪁إӕ첁ͮ葓񺔁ؓɘМڗ Ļء༙ঁēΞ筁Ɠ”ȓ󼔁ɓĖߓ˓˙ݓ̓ӞۓΓ蕜ۢٓΓ㩁דϓ鰁Փϓ𸁓ӓГғѓǗГѓΛΓғ֟̓ғݥʓғ欁ȓѓ؁ƓѓՁƓѓԁƓѓˁƓѓƓГƓГƓΓ끓œΓˁœ͓œ̓ہē˓ēɓ΁Óȓ񗁓ÓƓ“ߚē񧁓ϧ֛ Ľ婁ͦ ͻ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$0/98544313011110000010222121319:===<>?CI>>DIIOSEEECDBEDEDDFDRONOJYKLMIEBCB@B@GGIHKKIMPUSPVRTSW_]^``^__`lmZWYZW[ZY`jfmjb^hibYYWUVWXVVUTWYXX[aZ\[amqpnjgg|ngg[ZaaOPNOIBEBBABBCEHJJNJ@A@A@@AEHOOSJPMMMMQPLHGOCCDABI:<:IA:;8;D>?@A?A>B?>AADBAEOHDCAAA@B?AAACCFC988:8:8989B?^^Wg]WYXXVX[SSSRRTRRSR^STSTSSSUTVTTUUV[_eXWVVUUVUVVUUVSUVUVVvqplnkkjikjhjkkhlll|~||}z}~~|uwrwzutv~~wnnnw}}~}~|~}}}|{{{z|zz{zzz{zzz{zzx{|z|z{|~|~nonnprqnpppsrtvz;;;;<<==?>44<=<;9::99888989=FGDBBBBB?>A????>@C::::B@111=89;9;9:@CDGCCCEICJIHB@<<<;;;=FJGHDA;9<=;=><<<=;==AEDSWZtqhc`chleZVST]PSRQQPNMQQOPQNRRW\`b_indj[UUUYWYWVmkd`]\[\\b\YRSTQUTWRJLIILIKFCAB@CGFJJPRXONNPREFIDB@BGFIGFHTNJNG@=KICB@@@?<94354443322223232423446666799.0$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁ݔ ށ݁݁݁݁݁݁݁݁݁ݪ݁݁݁ݗށ݁݁݁݁݁݁݁݁݁ݭ݁݁݁ݙ޷ ݁݁݁݁݁݁݁݁݃݁݁݁ݚށ݁݁݁݁݁݁݁݁݇݁݁݁ݜ߁݁݁݁݁݁݁݁݁݉݁݁݁ݝ݁݁݁݁݁݁݁݁݊݁݁݁ݞ݁݁݁݁݁݁݁݁݌݁݁݁ݟ݁݁݁݁݁݁݁݁ݍ݁݁݁ݟ݁݁݁݁݁݁݁݁ݎ݁݁݁ݠ݁݁݁݁݁݁݁݁ݏ݁݁݁ݠ݁݁݁݁݁݁݁݁ݐ݁݁݁ݡ݁݁݁݁݁݁݁݁ݑ݁݁݁ݡ݁݁݁݁݁݁݁݁ݑ݁݁݁ݢ݁݁݁݁݁݁݁݁ݒ݁݁݁ݢށ݁݁݁݁݁݁݁݁ݓ݁݁݁ݢ݁݁݁݁݁݁݁݁ݓ݁݁݁ݢ݁݁݁݁݁݁݁݁ݓ݁݁݁ݣ݁݁݁݁݁݁݁݁ݔ݁݁݁ݣ݁݁݁݁݁݁݁݁ݔ݁݁݁ݣ݁݁݁݁݁݁݁݁ݔ݁݁݁ݣށ݁݁݁݁݁݁݁݁ݕ݁݁݁ݣ݁݁݁݁݁݁݁݁ݕ݁݁݁ݥ݁݁݁݁݁݁݁݁ݕ݁݁݁ݧ݁݁݁݁݁݁݁݁ݕ݁݁݁ݩ݁݁݁݁݁݁݁݁ݕ݁݁݁ݪ݁݁݁݁݁݁݁݁ݕ݁݁݁ݬ݁݁݁݁݁݁݁݁ݕ݁݁݁ݮ݁݁݁݁݁݁݁݁ݕ݁݁݁ݰށ݁݁݁݁݁݁݁݁ݕ݁݁݁ݲ݁݁݁݁݁݁݁݁ݔ݁݁݁ݴށ݁݁݁݁݁݁݁݁ݖ݁݁݁ݶ݁݁݁݁݁݁݁݁ݗ݁݁݁ݸ݁݁݁݁݁݁݁݁ݘ݁݁݁ݹ݁݁݁݁݁݁݁݁ݙ݁݁݁ݻށ݁݁݁݁݁݁݁݁ݛ݁݁݁ݽ߁݁݁݁݁݁݁݁݁ݜ݁݁݁ݿ݁݁݁݁݁݁݁݁ݝ݁݁݁݁݁݁݁݁݁݁݁ݞ݁݁݁݁݁݁݁݁݁݁݁ݟ݁݁݁ށ݁݁݁݁݁݁݁݁ݡ݁݁݁ޖ݁݁݁݁݁݁݁݁ݢ݁݁݁ߔ݁݁݁݁݁݁݁݁ݣ݁݁݁ށ݁݁݁݁݁݁݁݊݁݁݁ށ݁݁݁݁݁݁݁ݏ݁݁݁߫݁݁݁݁݁݁݁ݑ݁݁݁݁݁݁݁݁݁݁ݓ݁݁݁ގ ݁݁݁݁݁݁݁ݔ݁݁݁ތ߁݁݁݁݁݁݁݁ݖ݁݁݁ߋ޶݁݁݁݁݁݁݁ݗ݁݁݁݁݁݁݁݁݁݁ݘ݁݁݁߁݁݁݁݁݁݁݁ݙ݁݁݁݁݁݁݁݁݁݁ݙ݁݁݁݁݁݁݁݁݁݁ݚ݁݁݁ޅ޿݁݁݁݁݁݁݁ݛ݁݁݁ރ݁݁݁݁݁݁݁ݛ݁݁݁߂݁݁݁݁݁݁݁ݜ݁݁݁݁݁݁݁݁݁݁ݜ݁݁݁݁݁݁݁݁݁݁ݜ݁݁݁݁݁݁݁݁݁݁ݝ݁݁݁݁݁݁݁݁݁݁ݝ݈݁݁ ށ݁݁݁݁݁݁݁ݝ݁݁݋ށ݁݁݁݁݁݁݁ݝ݁݁ݍ߁݁݁݁݁݁݁݁ݝ݁݁ݏ݁݁݁݁݁݁݁ݞ݁݁ݐ݁݁݁݁݁݁݁ݞ݁݁ݑ޿݁݁݁݁݁݁݁ݞ݁݁ݒ߁݁݁݁݁݁݁݁ݞ݁݁ݓށ݁݁݁݁݁݁݁ݝ݁݁ݔށ߁݁݁݁݁݁݁݁ݞ݁݁ݕ݁݁݁݁݁݁݁ݟ݁݁ݕ߶݁݁݁݁݁݁݁ݡ݁݁ݖށ݁݁݁݁݁݁݁ݣ݁݁ݖ޲݁݁݁݁݁݁݁ݤ݁݁ݗ߁݁݁݁݁݁݁݁ݦ݁݁ݗ݁݁݁݁݁݁݁ݧ݁݁ݘ݁݁݁݁݁݁݁ݩ݁݁ݘށ݁݁݁݁݁݁݁ݫ݁݁ݘު݁݁݁݁݁݁݁ݬ݁݁ݘ݁݁݁݁݁݁݁ݮ݁݁ݘ݁݁݁݁݁݁݁ݯ݁݁ݙ݁݁݁݁݁݁݁ݱ݁݁ݙށ݁݁݁݁݁݁݁ݳ݁݁ݙ݁݁݁݁݁݁݁ݴ݁݁ݙ݁݁݁݁݁݁݁ݶ݁݁ݙށާ݁݁݁݁݁݁݁ݷ݁݁ݘ݁݁݁݁݁݁݁ݹ݁݁ݘ߁݁݁݁݁݁݁݁ݻ݁݁ݘ݁݁݁݁݁݁݁ݼ݁݁ݘ݁݁݁݁݁݁݁ݾ݁݁ݘ߅ߤށ݁݁݁݁݁݁݁݁݁ݗވ݁݁݁݁݁݁݁݁݁ݗތޢ߁݁݁݁݁݁݁݁݁݁ݖ݁݁݁݁݁݁݁݁݁ݖ݁݁݁݁݁݁݁݁݁ݕށ݁݁݁݁݁݁݁݁݁ݕ݁݁݁݁݁݁݁݁݁ݔߝޝ߁݁݁݁݁݁݁݁݁݁ݓޠ݁݁݁݁݁݁݁݁݁ݒޤ݁݁݁݁݁݁݁݁݁ݑށ݁݁݁݁݁݁݁݁݁ݐ݁݁݁݁݁݁݁݁݁ݎ݁݁݁݁݁݁݁݁݁݌݁݁݁݁݁݁݁݁݁݊ߵސ݆݁݁݁݁݁݁݁݁݁޸ށ݁݁݁݁݁݁݁݁݁݁޼݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ށ݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁ށ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ށ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁߁߁݁݁݁݁݁݁݁݁݁݁ ߤށ݂݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݃݁݁݁ߪ݄݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݅݁݁ݑޮ݆݁݁݁݁݁݁݁݁ݔ޿݁݁݁݁݁݁݇݁݁ݖ݈݁݁݁݁݁݁݁݁ݘ޲݈݁݁݁݁݁݁݁݁ݙ߹݁݁݁݁݁݁݉݁݁ݚ݁݁݁݁݁݁݉݁݁ݛ݁݁݁݁݁݁݊݁݁ݜ޶݁݁݁݁݁݁݊݁݁ݝ݁݁݁݁݁݁݊݁݁ݞ݁݁݁݁݁݁݋݁݁ݞ݁݁݁݁݁݁݋݁݁ݟު݁݁݁݁݁݁݋݁݁ݟ޸݁݁݁݁݁݁݋݁݁ݟ݁݁݁݁݁݁݋݁݁ݠ߫޿݁݁݁݁݁݁݋݁݁ݠ݁݁݁݁݁݁݋݁݁ݠ݁݁݁݁݁݁݋݁݁ݠ݁݁݁݁݁݁݋݁݁ݠ޷ށ݁݁݁݁݁݁݋݁݁ݠ݁݁݁݁݁݁݊݁݁ݡ݁݁݁݁݁݁݊݁݁ݠ߁݁݁݁݁݁݁݊݁݁ݡ݁݁݁݁݁݁݉݁݁ݣ߸ށ݁݁݁݁݁݁݉݁݁ݦ݈݁݁݁݁݁݁݁݁ݩ݁݁݁݁݁݁݇݁݁ݫށ݁݁݁݁݁݁݇݁݁ݮ߁݆݁݁݁݁݁݁݁݁ݱ޵߁݁݁݁݁݁݁݅݁݁ݳ݁݁݁݁݁݁݃݁݁ݶ݂݁݁݁݁݁݁݁݁ݸ݁݁݁݁݁݁݁݁݁ݻ ݁݁݁݁݁݁݁݁݁ݾށ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݞ݁݁ޜ݁݁݁݁݁݁݁ݞ݁݁݁݁݁݁݁݁݁ݞ݁݁݁݁݁݁݁݁݁ݞ݁݁߁݁݁݁݁݁݁݁ݞ݁݁ ށ݁݁݁݁݁݁݁ݟ݁݁ީ݁݁݁݁݁݁݁ݟ݁݁݁݁݁݁݁݁݁ݟ݁݁ށ݁݁݁݁݁݁݁ݟ݁݁ޣ݁݁݁݁݁݁݁ݟ݁݁߁݁݁݁݁݁݁݁ݠ݁݁߁݁݁݁݁݁݁݁ݠ݁݁ޜݽ݁݁݁݁݁݁݁ݠ݁݁ݺ݁݁݁݁݁݁݁ݠ݁݁ݺ݁݁݁݁݁݁݁ݠ݁݁ޔݹށ݁݁݁݁݁݁݁ݡ݁݁ݸ݁݁݁݁݁݁݁ݡ݁݁ݸ݁݁݁݁݁݁݁ݡ݁݁ތݷ݁݁݁݁݁݁݁ݡ݁݁ݶ݁݁݁݁݁݁݁ݡ݁݁ݵ݁݁݁݁݁݁݁ݡ݁݁ݳ݁݁݁݁݁݁݁ݢ݁݁ݰ݁݁݁݁݁݁݁ݢ݁݁ݮ݁݁݁݁݁݁݁ݢ݁݁ݪ݁݁݁݁݁݁݁ݢ݁݁ݒ݁݁݁݁݁݁݁ݢ݁݁ݑ݁݁݁݁݁݁݁ݣ݁݁ݎ݁݁݁݁݁݁݁ݣ݁݁߁ݍ݁݁݁݁݁݁݁ݣ݁݁݋݁݁݁݁݁݁݁ݣ݈݁݁݁݁݁݁݁݁݁ݣ݁݁ߐ݆ށ݁݁݁݁݁݁݁ݤ݁݁݃݁݁݁݁݁݁݁ݤ݁݁݁݁݁݁݁݁݁݁ݤ݁݁݁݁݁݁݁݁݁݁ݤ݁݁݁݁݁݁݁݁݁݁ݤ݁݁ށ݁ށ݁݁݁݁݁݁݁ݥ݁݁݁݁݁݁݁݁݁ݢ݁݁݁݁݁݁݁݁݁ݧ݁݁݁߁݁݁݁݁݁݁ݪ݁݁݁ށ݁݁݁݁݁݁ݬ݁݁ߕ݁݁݁݁݁݁݁ݭ݁݁ߖ݁݁݁݁݁݁݁ݮ݁݁݁ޜ߁݁݁݁݁݁݁ݰ݁݁݁ށ݁݁݁݁݁݁ݱ݁݁߁݁ߢ݁݁݁݁݁݁ݱ݁݁߁݁݁݁݁݁݁݁ݲ݁݁߁݁݁݁݁݁݁݁ݳ݁݁ ߁݁ށ݁݁݁݁݁݁ݴ݁݁ߠށ݁݁݁݁݁݁݁ݴ݁݁ށ݁ށ݁݁݁݁݁݁ݵ݁݁ށ݁ݿ݁݁݁݁݁݁ݵ݁݁ށ݁ݼ݁݁݁݁݁݁ݵ݁݁ށ݁ݺށ݁݁݁݁݁݁ݶ݁݁ިށ݁ݹ݁݁݁݁݁݁ݶ݁݁ށ݁ݸ݁݁݁݁݁݁ݶ݁݁߫ށ݁ݷ݁݁݁݁݁݁ݶ݁݁݁ݶ݁݁݁݁݁݁ݶ݁݁ ݁ݵ݁݁݁݁݁݁ݶ݁݁݁ݴ݁݁݁݁݁݁ݶ݁݁݁ݳ݁݁݁݁݁݁ݶ݁݁݁ݲ݁݁݁݁݁݁ݶ݁݁݁ݱ݁݁݁݁݁݁ݶ݁݁݁ݰ݁݁݁݁݁݁ݶ݁݁݁ݯ݁݁݁݁݁݁ݶ݁݁݁ݮ݁݁݁݁݁݁ݶ݁݁݁ݮ݁݁݁݁݁݁ݵ݁݁݁ݯ݁݁݁݁݁݁ݵ݁݁߁݁ݯ݁݁݁݁݁݁ݵ݁݁݁ݯ݁݁݁݁݁݁ݴ݁݁݁ݯ݁݁݁݁݁݁ݴ݁݁݁ݯ݁݁݁݁݁݁ݳ݁݁݁ݯު݁݁݁݁݁݁ݳ݁݁݁ݮ݁݁݁݁݁݁ݲ݁݁ށ݁ݮ݁݁݁݁݁݁ݱ݁݁݁ݮ݁݁݁݁݁݁ݰ݁݁݁ݭޣ݁݁݁݁݁݁ݯ݁݁݁ݬޡ݁݁݁݁݁݁ݮ݁݁݁ݪ߁݁݁݁݁݁݁ݭ݁݁݁ݩ݁݁݁݁݁݁ݫ݁݁݁ݧ݁݁݁݁݁݁ݩ݁݁݁ݤޚ ߁݁݁݁݁݁݁ݦ݁ݞ݁ݟߖ݁݁݁݁݁݁ݝ݁ݢ݁݁ݰ݁݁݁݁݁݁ݝ݁ݤ݁݁ݰ݁݁݁݁݁݁ݜ݁ݦ݁݁ݰ݁݁݁݁݁݁ݜ݁ݧ݁݁ݰ݁݁݁݁݁݁ݛ݁ݨ݁݁ݰ݁݁݁݁݁݁ݛ݁ݩ݁݁ݰ݁݁݁݁݁݁ݚ݁ݪ݁݁ݰ݁݁݁݁݁݁ݚ݁ݫ݁݁ݯ݁݁݁݁݁݁ݙ݁ݫށ݁݁ݰ݁݁݁݁݁݁ݙ݁ݬށ݁݁ݯށ݁݁݁݁݁݁ݙ݁ݭ݁݁ݮ݁݁݁݁݁݁ݘ݁ݭ݁݁ݭ߁݁݁݁݁݁݁ݘ݁ݭ݁݁ݬ݁݁݁݁݁݁ݗ݁ݮ݁݁ݪ݁݁݁݁݁݁ݗ݁ݮ߁݁݁ݨ݁݁݁݁݁݁ݖ݁ݮ݁݁݁݁݁݁݁݁݁ݖ݁ݮ݁݁݁݁݁݁݁݁݁ݕ݁ݮ݁݁݁݁݁݁݁݁݁ݕ݁ݮށ݁݁݁݁݁݁݁݁݁ݔ݁ݮ݁݁݁݁݁݁݁݁݁ݔ݁ݮ݁݁݁݁݁݁݁݁݁ݓ݁ݮ݁݁݁݁݁݁݁݁݁ݓ݁ݮ޽݁݁݁݁݁݁݁݁݁ݒ݁ݮ݁݁݁݁݁݁݁݁݁ݒ݁ݮ݁݁݁ށ݁݁݁݁݁݁ݒ݁ݮ߁݁݁݁݁ݚ݁݁݁ݎ݁ݭ݁݁݁ށ݁ݚ݁݁݁ݎ݁ݭ ݁ݙ݁ݶ݁ݬ޸߁݁ݙ݁ݶ݁ݬ݁ݘ݁ݶ݁ݫ߁݁ݘ݁ݶ݁ݪߴ݁ݗ݁ݶ݁ݪ݁ݗ݁ݶ݁ݩށ݁ݘ݁ݶ݁ݨށ݁ݙ݁ݶ݁ݦޮށ݁ݚ݁ݶ݁ݥ݁ݚ݁ݶ݁ݣ݁ݛ݁ݶ݁ݣާ߁݁ݛ݁ݶ݁ݣ݁ݜ݁ݶ݁ݣ݁ݜ݁ݶ݁ݣ݁ݝ݁ݶ݁ݣ݁ݝ݁ݶ݁ݣ݁ݝ݁ݶ݁ݣ݁ݞ݁ݶ݁ݣ݁ݞ݁ݶ݁ݣ݁ݞ݁ݶ݁ݣ݁ݞ݁ݶ݁ݣ݁ݞ݁ݶ݁ݣ ߞޖ݁ݞށ݁ݵ݁ݣݩ݁ݞݯ݁݁݁ݤ߁ݦ݁ݞݯ݁݁݁ݤݣށ݁ݞݯ݁݁݁ݤݡ݁ݝݯ݁݁݁ݤݟ݁ݝݯ݁݁݁ݤށݞ݁ݝݯ݁݁݁ݤߤݜ݁ݜݯ݁݁݁ݤޤށށݛ݁ݜݯ݁݁݁ݤށݚ݁ݛݯ݁݁݁ݤ߁ݘ݁ݛݯ݁݁݁ݤݖ݁ݚݯ݁݁݁ݤށݕ݁ݙݯ݁݁݁ݤށݔ݁ݘݯ݁݁݁ݤ߁ށݒ݁ݗݯ݁݁݁ݤݐ݁ݖݯ݁݁݁ݤށݏ݁ݔݯ݁݁݁ݤ݌ށ݁ݓݯ݁݁݁ݤށ݉ ݁ݐݯ݁݁݁ݤ݁݁݁݌ݯ݁݁݁ݤށ݁݁݁݌ݯ݁݁݁ݤށ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݤށ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݤ݁݁݁݋ݯ݁݁݁ݥ݁݁݁݋ݯ݁݁݁ݥ݁݁݁݋ݯ݁݁݁ݥ݁݁݁݋ݯ݁݁݁ݥ݁݁݁݋ݯ݁݁݁ݥ݁݁݁݋ݯ݁݁݁ݥݰޓ޸݁݁݁ݥݹ݁݁݁ݥݽ݁݁݁ݥ݁݁݁ݥ߳ޓ݁݁݁ݥޕ݁݁݁ݥ߻ޗ݁݁݁ݥ݁݁݁ݥޚ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ݁݁݁ݥ߁݁݁݁ݥ߁޼݁݁݁ݥށ݁݁݁ݦ߁޴݁݁݁ݦ߁݁݁݁ݦ݁݁݁ݦݿ݁݁݁ݦݽ݁݁݁ݦݻ݁݁݁ݦݺ݁݁݁ݦݹߦ݁݁݁ݦݸީ݁݁݁ݦݷޣ݁݁݁ݦ߁ݶ݁݁݁ݦݵߢ݁݁݁ݦݴ݁݁݁ݦݴ݁݁݁ݦށݳߠ݁݁݁ݦށݳ݁݁݁ݦݲߪ݁݁݁ݩݲ݁݁݁ݪݲ݁݁݁ݬݲߝ݁݁݁ݭ߁ݱ݁݁݁ݮ߁ݱ݁݁݁ݯݰ݁݁݁ݰݰ݁݁݁ݱݰ݁݁݁ݱݰ݁݁݁ݲ߁ݰ݁݁݁ݲݰ݁݁݁ݳ ݰ݁݁݁ݳ ݰ݁݁݁ݴ ݰ݁݁݁ݴ ݰ݁݁݁ݴ ݰ݁݁݁ݴݰ݁݁݁ݴݰ݁݁݁ݵށݰ݁݁݁ݵݰ݁݁݁ݵݰ݁݁݁ݵߧ ށݰ݁݁݁ݴݲ ݰ݁݁݁ݴ ݁݁݁ݴ ݁݁݁ݴ ݁݁݁ݴ݁݁݁ݴށ݁݁݁ݳ݁݁݁ݳ߻݁݁݁ݲ݁݁݁ݲ߸ށ݁݁݁ݱ݁݁݁ݱ݁݁݁ݰ߰߁݁݁݁ݯ݁݁݁ݮ݁݁݁ݭ݁݁݁ݬ߁݁݁݁ݪ݁݁݁ݩ݁݁݁ݦޯ݁݁݁ݣ ݁݁݁݁݁݁݁݁݁݁݁݁ߥ݁݁݁݁ߤ݂݄݄݄݁݁݁݁݁݁݃݁݁݁݃݁݁݁݁݁݁݁݁݁݁݁݁݅ޠ݁݁݁݅݁݁݁݅݁݁݁݅݁݁݁݅ށ݁݁݁݅݁݁݁݅݁݁݁݅ޟ݁݁݁݅ށޟ݁݁݁݅ޟ݁݁݁݅ޟ݄݁݁݁ޟ݄݁݁݁ށޠ݄݁݁݁ޠ݁݁݁݃ޠ݂݁݁݁݃݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁ޗ݁݁݁݁߁݁݁݁݁ޙ݁݁݁݁ߣ݁݁݁݁݁݁݁݁ޜ݁݁݁݁݁݁݁݁ށ݁݁݁݁ށޢި݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁߰߯݁݁݁݁݁݁݁݁ ݁݁݁݁݁݁݁݁߾݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁޼݁݁݁݁߸݁݁݁݁݁݁݁݁݁݁݁݁ީ݁݁݁݁ߢ݁݁݁݁ ߖ ޿ ޼ ޺݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݘ߁݁݁݁݁݁݁݁݁݁݁ݜ݁݁݁݁݁݁݁݁݁݁ݞށ݁݁݁݁݁݁݁݁݁݁ݠ݁݁݁݁݁݁݁݁݁݁ݠ݁݁݁݁݁݁݁݁݁݁ݡށ݂݁݁݁݁݁݁݁݁݁ݢށ݂݁݁݁݁݁݁݁݁݁ݣ݂݁݁݁݁݁݁݁݁݁ݤ݂݁݁݁݁݁݁݁݁݁ݤ݂݁݁݁݁݁݁݁݁݁ݤ݂݁݁݁݁݁݁݁݁݁ݤ݁݁݁݁݁݁݁݃݁݁ݥ݁݁݁݁݁݁݁݃݁݁ݥ݁݁݁݁݁݁݁݃݁݁ݦ݁݁݁݁݁݁݁݃߁݁ݟ݁݁݁݁݁݁݁݃ޒ݁ݤށ݆݁݁݁݁݁݁݁߁݁ݧ݈݁݁݁݁݁݁݁ޛ݁ݨ݁݁݁݁݁݁݁݉݁ݪ݁݁݁݁݁݁݁݋ޡ݁ݫ݁݁݁݁݁݁݁݌ߣ݁ݬ݁݁݁݁݁݁݁ݍ݁ݭ݁݁݁݁݁݁݁ݎߧ݁ݮ݁݁݁݁݁݁݁ݎި݁ݮ݁݁݁݁݁݁݁ݏ݁ݯ݁݁݁݁݁݁݁ݐ߁݁ݯ݁݁݁݁݁݁݁ݐ݁ݯށ݁݁݁݁݁݁݁ݑ݁ݯ݁݁݁݁݁݁݁ݑ݁ݯ߁݁݁݁݁݁݁݁ݒ݁ݯ݁݁݁݁݁݁݁ݒ݁ݮ݁݁݁݁݁݁݁ݒ݁ݮ݁݁݁݁݁݁݁ݓށ݁ݰ݁݁݁݁݁݁݁ݓ݁ݱ݁݁݁݁݁݁݁ݓ݁ݲ݁݁݁݁݁݁݁ݓ݁ݳ߁݁݁݁݁݁݁݁ݓށ݁ݴ݁݁݁݁݁݁݁ݓ݁ݴ݁݁݁݁݁݁݁ݓ݁ݵ ށ݁݁݁݁݁݁݁ݰ݁ݓ݁ݶ݁݁݁݁݁݁݁ݠ݁ݓ݁ݷ݁݁݁݁݁݁݁ݟ݁ݓ݁ݷ݁݁݁݁݁݁݁ݞ݁ݒ݁ݸ݁݁݁݁݁݁݁ݝ݁ݒ݁ݹ݁݁݁݁݁݁݁ݜ݁ݒ݁ݼ݁݁݁݁݁݁݁ݛ݁ݒޫ݁ݾ݁݁݁݁݁݁݁ݚ݁ݑށ݁݁݁݁݁݁݁݁ݙ݁ݑީ݁݁݁݁݁݁݁݁ݘ݁ݐ݁݁݁݁݁݁݁݁ݗ݁ݐށ݁݁݁݁݁݁݁݁ݖ݁ݏ݁݁݁݁݁݁݁݁ݕ݁ݎ݁݁݁݁݁݁݁݁ݔ݁ݍށ݁݁݁݁݁݁݁݁ݓ݁݌݁݁݁݁݁݁݁݁ݒ݁݋݁݁݁݁݁݁݁݁ݑ݁݊ߔ߁݁݁݁݁݁݁݁݁ݐ݈݁݁݁݁݁݁݁݁݁ݏ݆݁݁݁݁݁݁݁݁݁ݎ݄݁ލ݁݁݁݁݁݁݁݁ݍ݁݁ވ݁݁݁݁݁݁݁݁݌݁݁݁ށ݁ ݁݁݁݁݁݁݁݋݁݁݁݁݁݁݁݁݁݁݁݊݁݁݁݁݁݁݁݁݁݁݁݉݁݁݁ށ݈݄݁݁݁݁݁݁݁݁݁݁݁߁݆݁݁݁݁݁݁݁݇݁݁݁߁݆݁݁݁݁݁݁݁݁݁݁ށ݉߁݁݁݁݁݁݁݁݅݁݁݁݋߁݄݁݁݁݁݁݁݁݁݁݁݌ށ݁݁݁݁݁݁݁݃݁݁݁߁ݏށ݂݁݁݁݁݁݁݁݁݁݁ݐށ݁݁݁݁݁݁݁݁݁݁݁ݒށ݁݁݁݁݁݁݁݁݁݁݁ށݪ݁݁݁݁݁݁݁݁݁݁݁ށݯ݁݁݁݁݁݁݁݁݁݁݁ݱ݁݁݁݁݁݁݁݁݁݁݁߁ݳ݁݁݁݁݁݁݁݁݁݁݁ݴ݁݁݁݁݁݁݁݁݁݁݁ݶ݁݁݁݁݁݁݁݁݁݁݁ݷ݁݁݁݁݁݁݁݁݁݁݁ݸ݁݁݁݁݁݁݁݁݁݁݁ݹ݁݁݁݁݁݁݁݁݁݁݁ށݺ݁݁݁݁݁݁݁݁݁݁݁ݺ݁݁݁݁݁݁݁݁݁݁݁߁ݻ݁݁݁݁݁݁݁݁݁݁݁ݽ݁݁݁݁݁݁݁݁݁݁݁ݿ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ށ݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ޢ݁݁݁݁݁݁݁݁݁݁ߪަ݁݁݁݁݁݁݁݁݁݁ޯ ݁݁݁݁݁݁݁݁݁݁߱ ݁݁݁݁݁݁݁݁݁݁޲ށ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁߁݂݂݄݄݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݃݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁޸݆݁݁݁݁݁݁݁݅݁݁݁݁݁݁݁݁݁݁݁ށ݁݁݁݁݁݁݁݇݁݁ ߺ݈݁݁݁݁݁݁݁݇݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݉݁݁݁݁݁݁݁݁݁݉݁݁ޮ݁݁݁݁݁݁݁݊݁݁޿݁݁݁݁݁݁݁݋݁݁޸ށ݁݁݁݁݁݁݁݌݁݁޺݁݁݁݁݁݁݁݌݁݁݁݁݁݁݁݁݁ݍ݁݁߹߁݁݁݁݁݁݁݁ݎ݁݁݁݁݁݁݁݁݁ݎ݁݁݁݁݁݁݁݁݁ݏ݁݁߶߲݁݁݁݁݁݁݁ݐ݁݁޵݁݁݁݁݁݁݁ݐ݁݁݁݁݁݁݁݁݁ݑ݁݁݁݁݁݁݁݁݁ݒ݁݁ށ݁݁݁݁݁݁݁ݓ݁݁޸݁݁݁݁݁݁݁ݓ݁݁ޯ߿݁݁݁݁݁݁݁ݔ݁݁߻߁݁݁݁݁݁݁݁ݕ݁݁ެ݁݁݁݁݁݁݁ݕ݁݁݁݁݁݁݁݁݁ݖ݁݁޿ߧ݁݁݁݁݁݁݁ݗ݁݁ ށ݁݁݁݁݁݁݁ݘ݁݁݁݁݁݁݁݁݁ݘ݁݁ ݁݁݁݁݁݁݁ݙ݁݁ށ݁݁݁݁݁݁݁ݚ݁݁݁݁݁݁݁݁݁ݚ݁݁݁݁݁݁݁݁݁ݛ݁݁ݾ݁݁݁݁݁݁݁ݜ݁݁ݼ݁݁݁݁݁݁݁ݜ݁݁ݻ݁݁݁݁݁݁݁ݝ݁݁ݹ݁݁݁݁݁݁݁ݞ݁݁ݷށށ݁݁݁݁݁݁݁ݟ݁݁ݶ݁݁݁݁݁݁݁ݟ݁݁ݴތ ށ݁݁݁݁݁݁݁ݪ݁݁ݳ݁݁݁݁݁݁݁ݭ݁݁ݱ޲ށ݁݁݁݁݁݁݁ݰ݁݁ݯ݁݁݁݁݁݁݁ݱ݁݁ݮ݁݁݁݁݁݁݁ݳ݁݁ݬ݁݁݁݁݁݁݁ݴ݁݁ݫ݁݁݁݁݁݁݁ݵ݁݁ݩ݁݁݁݁݁݁݁ݶ݁݁ݧ݁݁݁݁݁݁݁ݷ݁݁ݦߞޚ݁݁݁݁݁݁݁ݸ݁݁ݤ߁݁݁݁݁݁݁݁ݹ݁݁ݣ݁݁݁݁݁݁݁ݹ݁݁ݡ݁݁݁݁݁݁݁ݺ݁݁ݟޢ݁݁݁݁݁݁݁ݺ݁݁ݞ߈ށ݁݁݁݁݁݁݁ݻ݁݁ݜ݁݁݁݁݁݁݁ݻ݁݁ݚނ݁݁݁݁݁݁݁ݻ݁݁ݙށ݁݁݁݁݁݁݁ݼ݁݁ݗަ݁݁݁݁݁݁݁ݼ݁݁ݖ݁݁݁݁݁݁݁ݼ݁݁ݔ݁݁݁݁݁݁݁ݼ݁݁ݒި߁݁݁݁݁݁݁݁ݼ݁݁ݑ݁݁݁݁݁݁݁ݼ݁݁ݏ݁݁݁݁݁݁݁ݼ݁݁ݎ݁݁݁݁݁݁݁ݼ݁݁݌݁݁݁݁݁݁݁ݼ݁݁݊ު݁݁݁݁݁݁݁ݼ݁݁݉ށ݁݁݁݁݁݁݁ݼ݁݁݇ޫށ݁݁݁݁݁݁݁ݻ݆݁݁݁݁݁݁݁݁݁ݻ݄݁݁݁݁݁݁݁݁݁ݻ݂݁݁݁݁݁݁݁݁݁ݺ݁݁݁޲݁݁݁݁݁݁݁ݺ݁݁݁ߴ݁݁݁݁݁݁݁ݹ݁݁݁݁݁݁݁݁݁݁ݹ݁݁݁ށ݁݁݁݁݁݁݁ݸ݁݁݁ށ݁݁݁݁݁݁݁ݸ݁݁݁޻ށ݁݁݁݁݁݁݁ݷ݁݁݁߁݁݁݁݁݁݁݁ݶ݁݁݁ ߁݁݁݁݁݁݁݁ݵ݁݁݁߁݁݁݁݁݁݁݁ݳ݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁ݰ݁݁݁݁݁݁݁݁݁݁ݮ݁݁݁ ݁݁݁݁݁݁݁ݫ݁݁݁݁݁݁݁݁݁݁ݍ݁݁݁ށ݁݁݁݁݁݁݁݌݁݁݁ށށ݁݁݁݁݁݁݁݊݁݁݁߁݈݆݄݂݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁޼݁݁݁݁݁݁݁݁݁݁݁މށ݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ޱ݁݁݁݁݁݁݁݁݁݁݁ߏ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ާށ݁݁݁݁݁݁݁݁݁݁݁ޡށ݁݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ށ݁݁݁݁݁݁݁݁݁݁݁݁ށ݁݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ށ݁݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁߁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁޿݁݁݁݁݁݁݁݁ݿ݁݁݁݁݁݁݁݁݁݁݁݁ݾ݁݁݁݁ ߶݁݁݁݁݁݁݁݁ݼ݁݁݁݁݉݁݁݁݁݁݁݁݁ݺ݆݁݁݁݁ ށ݁݁݁݁݁݁݁݁ݸ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݲ jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-transparent-cropped-javadocheader.png000066400000000000000000002405171402514743400302140ustar00rootroot00000000000000PNG  IHDR8+Z!bKGD F pHYs.#.#x?vtIME 04H IDATx#y'B@taCֲ-yZ{~c^ ~buwpdZ$˖-˲a 9r龠drn.t$sNUNG?RDD'D6p9Жl(L@ZG HЪSH D4=C@D&|Ǽe@T(":u$2 zg%,2`YA]5yQ3}?#86ut:@n@۬]!ΠX2PDDDDDD4V$?`hƔ߱ ^Y//b?d?G+e7,K:凶_mWp%PE}r)HdA$""""""pN"&V ec bewH;+ұ.)[ ,D44ɀxq$$uEi6gxsN,e|=ĢGq"~CANj""""""R-C@D uM& hfCkhr<7P^x.T N"8h=y|p/^<[q˸au~$<ɠݘ! A/c4q"~43gACm)2zN_nGhvT N"(͌'ڇA6Ou59g^HResDDDDDDt$^r x ˧:ΖGeW"\F0'"""""' L (8_˻OmAdǩ\ODDDDDD'MXH[?1~e~=!DDDDDD4RLpQ$Q$gnk2xDXyCH1ID?H4}܋Uj:.WNE ADDDDDD#'MըonC Ng2OS>DDDDDD4RLpѱFKƫGh}kZXh2 """""3DtN ߹|ˤ`J}1wY#Uu7  D3F,}>ZYDX23c af7 D.;\xkS0b>Vʼhd$*YM^G4UB*u*Pc2{frHDDDDDD42Lp@M0N- n%,{z(v:^.dҀ}<:kDDDDDD4ZZl3X<ﺞ[)̹+!$MN,1Kcךb87DgN^9r_Ǔi\Oބ~Gyb׵/*ZuӰ*o4"""""":&8Πiܛ~/p}Sw|`w{'rm{XƪɉWOu#"""""~gfΎds2c3\/K7>#+G3wht Uapgfx$}ę xMDaN mrcFZJDDDDDD0yz Sb"qLf853[JǰҸTɹym"~u̎ D3U >T&" LxPrڽQ5} Dt̏~ 7Mhe5:ٍx/"r70 N1j=BP澏VE&"pc hitзеTKH'(UkiQ{Qs"_UG& W%\T'""""""" N1 %,:fkJg bi\=>C+`i5V*;?U-Gr(T"""""":2=C@4^sؖ}+,! qڭxPl9R%aTWʯ#){J/u\OmPfYۡtmTht>9 w_EI4B6}@ n;4G[^D֐+r """""=&8TU߬+}㳝mn0ݢ1ê^c1?~(+`DCgDKӄՀ^E>J2(8(|Gl&Sj`0«myIA6k7SN4F\-y)*p>x j#Vc19$x?JG9 DDDDD4TLp "Z5?MA,zg*f|-Hb?dyf6jcRܥjtmM=X؈qϾԘ8tZ-n{jM.0nǫ;GdkG053u&žVmf24DDDDD4pLpBe^r=~w ʛݻ\qs󀹷o{jδ;$(S.37g 媍77 DG[ ,=?^VKyIA6k7`"dOm[bOe[[ֻfG܊W,KOhПyvá>6 |nor"""""(&8zdU$X0M`_Ac1oAx$L< go{P|f}jcwo@.&B*~8 Xux@7Qd:UFM Jc}j&x:$EMы)Oǫy."[v ہ?˺_#d %s*6LG{ .S#=>IfDDDDD4@LpX0x|ǼeO'@ŚǓ<8"R\]7~xL L]C2S00)^qGٴ/cY>7exj{ ?6ZON0j5ggO$5&KJ uDku*})(zj$k3j/Xsx~J=q[I-Yk0"ճ%3x:GqGE7OScl5^eId NR$^D"\h͘63cz*I<^Z 'fzH l2h*#xwGg䵏]qtZ-|`qQȵhCGh4[#k4FcalcTcr~_*NxHt+QX&=s(*L{K?U@a~n#w64in)۸Ϧ^,?qO ӖY%Wa vM1bKDDDDD'ȓP]DfΉ!dXvo{z!1"{>Cs^`K oԪ˩ alMGWUȀELv+EÌ،nF N:qA֘%7"~մ­6y+r(*R 'l%d֌~g8ZKxQt_EفsR=Ձ_,-6n[|0IMF|(Ne,(sHchUaC,+=]EZقhϕ aMIh,-7cUrBqǶIvX(qhRG>_%""""i:Q3ఛU͍xkJUGVQ_Lלm![@v֣j[XmG]}6i7/5053="2CłCXzVE%""""0I'&Yyz\82lV~^Z{Ul}_ l'uκevfJqh4?h4DDDDDt |Om#*6@V{G,>4LůҙL.,aXz vKDDDDDШ<^,x_5/4Ps h+ϢVCq(xE$s9VĿ7읚6ywXXV*DDDDDDऑ+IW@( :SF>6DjEUEξi5X$Tsmۉ2g<񊴈u|nN^"""""7ऑ[mYǦFZۊϴj3hCrU՟e78c;8wF+;}jau܊Rk;pU߳cw{Gp92nc#~"19LLs"&8i:-Nelm9hԷm {wv=^t6d^:Vg*(؉]#0?Vo4X{[ rǶh5lNj""""3 N)Y4U- !&27-IĒmLfu&V"Qhr}o˸^\E;&*/E"]z ѽ#}XO{Cl\(dȉNDDDDt0I#UǪFtp謊.V[_b}mD Vnhdp%1>V;՛ݻ|rD12NxEV"7C;NDDDDtF0I#Uo4cQ(v;\K8\ǤK6?|*yvr SX_ŝwHVUZu/v(a38DDDDDKD]TR7̸301óBdTI+sXMj 6B&4G6>^ʯc0Oi5]فֈƀl[ VCp8P?hҹ"t7)|Y)=!~gh4i'ܤ}xƲ: L&a|K?y +صfKH2d#%vԻשιy*I&|rr w~G!d+ácwg!y7=ґ1f 9B}>o""""S SqeَW|E³"8g\,ro{9d6ϠSmߪV;f|;+m _]OÍIm˩㏻Ŋ\ø8,=ADDDDD N!J(xIY=ӘgѠ߷{jߺǠl:~m'HKp&z~6 IDATzZh(fumtOpn6 k\/⠏=C׌~| &y3Lp҉x:׵=xr uMMBvXga T KE%9_0*6H&|[1kHOJծ #}E6ӯbvW'2^RV>7јcօNF|bS}n  ܰ_K,=4i䴙񵂷r -=UΧST&0z#1 L6  n}**ݻɛZNv/GZUDDDDDc N:t_N8eo %98uq"" f_]Up9΀Ae=8܋FAoWzK;~+E3ED`ae\lb+q<nN9B9Ɛn @'AfVdPUbV28ea7mCHH~x̐QGRS ܱ,y!}U \[Tџ"l,,nQ89!uSoJ"UM%^7Ht>Zk{jR FHǟtn[:8&3y_çr")8&/4 KrO""""tyr>>LMiS<ᜱm8s "‚ZÜ݄N4Zl+]$4TxoBDph?~~M j um(c&vE®᯿w0WǾl=-F0e[9J|d@Xpf`A(P*!""""R1&8)8F74FӉ\>UkuRIXһXj0YA0""H5Rˏ)3V"6'&#t}z.ev_UiJ$fɬ@ ycs]FiGSkL=E2`vrqmZ&CJkF?L5 "WvI 44o"""""bN\q$~o|*(%pgwpASF/n" AYrD (7{jButASN%uvoEh2ٮe\μJ~sT5NeOT5RL^[pÓꮑ$(Wk8N`+91o+P(PM"TEY jaB$, 7cWvxM E09z^_mT[NMj.sr凊Sq9~J+|Pa3$c@R2ȧS>Y֛uɈ O+{d1 Z.zL9N\>*a\0VЁoeCn -ؕ2q~ BčJ: 6[v2>]zrASR+4]ɩҸ\9pϱS(>յ⼶р6U {l(~]+v ^<7IOLDDDDD' NRF)y7"CH;U󍃁ie$òRSҚcF8V&83CArjHBS QZ#h,W5-iSWb?iR%vɈގe %ŋ% CJR^M#B,UmԛMnLMR\VZY hR6ϖ"Z|5ӏ&"/u>I|hhT!ZCi!A5=ф\A(hI`T\A`ݠp\V>LFVbZ*d/}'00S- lY51/v1K1_b^୼؉r6kmLfpE3Ѳ:Jv}f`ׄ" c3ΥJ Tn%HџnE@\o=O95e2F NRbzZX*tz _Ob+@_xLDZ5}Tݕd:=֗^|W 7^j0cP/|YПn6=jZ()P`K!j.4q,d!F>@.B=`E|S>T6XmZh{B]'D Tr:`bS±D#v^.;y*5DDDDDD#_i,) $B0>nѬב~deEwO4Sl:dDZ ?\b5W%|\d(_쁼>0zz"}*",6݇0MVL*Ґ$,]ěٮ<_D{@Yvғx`=B{7z|)/؏}DY9@=>[F N:lfl S*[}6zL5VKr>9M^T{+NWcЬ ^> ׋lع'5ŵ:,fn}Z &rS}.:"N^""""!+tU k.w/>*v02zXMؕ-*LDDDD4$LpҙwOPd-+&+{أJTOjqDEsH%шae}f5M˕3;fgs(0o:l*`0gA.1W:q`nER+uj~@222cKx>\hZ̎~_b }!=:FR]{(Vk RIӻXl0ihcskXTˢL44a%TzX1>H5zf-\i\:<S± F~8m6XkYTUcZ1IgNT'ךnzXXd\Tj|z}rQꨴxkHX 80!iP(2 pǺS2ysP|[UTաEkLu},6bCZM4xRBc08;^\ (fp t&5\kpŬb@CcH- ?V68*W8j5X E,񖲂VɊd p M!xYҵݣ ʑ&ګNv-s9233FBl-^ΊcżE2 lp1'Q,9z j/=]bSۯ Z$^iX~J.)RQ&NrFu;6=ұuhhĊDtp}ڟ{5p>^Ar39fNqEϱq/VXlEB`^ۄL[TC M 4udb(XN&8L3"^t= SǪ;H/[߯8`1xJD=F*[d~PuAYrj.Khx3pRJyIZ~ wU_;c75=Мb=- 7N4 pMN&&PU=^'e\FMMhDDDD4:iFOY})2leS~QY 3J } Z7"42j~j5/; Z0;gNgv̵ > H =A4]!H.Nv+lJVƈY ,T rH嵛6A9LW2ôcn4cbu̢RC?o/"c yĭF܊ !,)G@9A߻h|q'#c Զ+੃WQN}\`R0c-|b|Rb+d4rVWC c5:&7n,p;(Rl;L4bʇ.ikv!GV+e]=HH'޷-dzh7Yo(qj81vKz.O~,""""D:$R0v/*c0aUd{>.Ub$pŊXGv(݊ cnLJG^A MU-VSގ&S ՏFfg2")$fbosyAƮw4xrBD=UѪd4;VPm5溶[@\ X@NqQi㍂1V$N?MFTuywL""""Ӄ N"v+B?VތrLĨ1`DE}H^ɇ;N% :]M'"&]]qVDm&1Վ]`ƒU2OA+ɀ~Ou#M%s Z'fōf &?.s)X$G)N|vEDRoVU첀]W3""""'Vinբ@2 VkC"_,U߄+L*H\hx#An/Oń]q$vTOhu$9݊V'ŹLf+_Yd\噤&""""24*Y|}m8#NdPùtMVc8wEVG_o^s5˩Su E܉d "=҉]WrLx]]ۭ*c'R=WSZ3tSܑ8(DDDDzLph/,K9, i YkXV8]$B GbpmwkM Sqm/ڠ׍jY~ e5'1gzjK<铿WBrl4$&<()'/;80DDDDjLpMMOF?LLOmv39}8%\{w뉛T UHKM6*j1|y\OIG9ø*(dYHp9#xfS]n58DDDDnLp&F v+>K+*6dv*as}t>\A[X{3`zx$;ߪ[Fz7NU] lfvo{Kj]tEl i5CZ1NS%""""b?| >N,JW,+8l vO$/芄8mfto^$'o4'wyֵ]^`bWƾ4^or6x1_G[Kiy Z@xk +dԹW[~{B18Z8aE#UнN+y{28VDDDDD ovuY(xʸ{bOmK΅ C/UЂFSlשs:[XBstuS)ΎluNc݋VPm:#T N[ &C_s2~]s@Tm7sFzmP߯[$^|v[uנ)纶rpٹeG/PoDDDDDLplTuU\qK|x7[ DcH,j֨GrXK ,{r)G|ӳp=*!@אIgzj'YY4۷NDDDDDghDOvr{h[J2Y-2p'V4}(PNhu*WD"ހg+9`3B|L_FmKj*X+@>=<6➞V ֊ Xq #o6aAK:}6PzCjcsf `鄕.u. Δ b j0@^1"a&a CDDDD'uY%ߤ]Իj[ȸ%,׊-0,<#OV9|3(A7$UJgp50b:~~EOr'QMvDžbS2&~{ Py$`?ETIY).mGDDDD+Ja1alDBa+E<^xZQcLFNtFh&܄ Ma^SD3a$x0-=),au{ײ)g-+h46S5l""""=3 ubAmb,#e@A#i ޯiWOE\hZgtzxl`]ų'Qv7(q9;ģƣpZH}B*كj%Zשs(RUo4NgQ`KaĢu;iLouK5";)꼘cܨERBCږ/~ouM VN&^**&~/QIA,Yr+]no!h 0I%,F,9D&XWJϯh4e'lRoRm6PɌV<.;Sg  f7D{+lnE xFbT|Hh50%ط&rHfNw]6DJtSn| 4cpVĸDD_0jcZǜ V\3x]ʑ?Ev?sODS{t35ʨڦB<Ŋ4M"( """'}$VY8os([\Ps+S>KPij|{[_IxTCkRn[FTmj+ۮD2g Zz!*emLLmSofXRzQԹL6|,CbA,k Pпp4hIeKQ`S`FRgB\Cboa$*}F%?DDDDD'}NA?uSݰ!Acv ƗREAko4 tZ/<,3(tK.-XQͷVo e2L2Dz7gmX3*.criOtrhz,ozeS$jO%.QY0f3Qg-ęA{ҝZs3X.aܹ2wM'"""')EMCwT$p醡X4"j⃱j~fѮ e@޵pW <I4%D&j .x=+|m22BNg0U dJk._D6.V9X5 N$S8Eb_Y&iT W(ϳs&%x_ R:~#l{"~~SZT){{0dz2_woA{_ϣjT@<)q1umd'ot"C :Tx#[Hm.݅.K|%oQyzt!=,UØSzR X$ Ӡ^ȚZs߯)(5+(Fs;8`""""XSw#ewrVL:]g#R%d;{P#֩\|k3i=xZ.u5fZrBL/i*xg‹;2i|LuJ=\ob';t&S%Y1A 4PwDžoijh_<ԨUshp͚ ehENɴzkc0YkJo]xmo+n>1a#f6@76U4$vCK1e>"ĕK &^ZbNƟ^K G'1 Oj(4y|䂽 bKE?~?$ߘʼnʀ@C@BUU[]UQ%1! f*u(B ˙ CQY޳d5/ 0Ȟ)`Y[~~2b +|c$ uu~/\[[e!ZP """ipONa]3f Md%8oj'`!kHhĸԼе?uR{OeG>ofXpvwӠhbLߕ"86L0\U澕 /Q)0z>^(+dI19~:y+&콝 SS GiH]}i<{tNvmΗ'U|;;T(015$S>!FP 9fhl(x9+$>EUtACTSOyUYszv?6_hYOgb$""""`sHvUJg.ngusbp8^Q)?d\&CZ&z9h]vI.5u} =j1A*-2rFB˜Ս-/$,vyr<zUKl3-= >7!""""P3S4c @ӣYD/8ȬgFcvGp>2N _`I2SX*wXMhͼ])ES0!_rplc&h/,?똕N/XƒiyL"#""""}_b|\Ab}SHti>! zy7")˅ hZ?#E ?%MDv`z7NO&Lt1g#$9rSRh[9&PFG?aݍ DNJEQGZET?&3t51й$WFPz`'*o6q7.\ moTd6K=ăY?jv>;:-F|y /pƮnl*vDUjd[J;1U6lG[pݍ j2CXcōFGO>X,2hǒ]ˇMAWx q6:``;QW67T)0*k*0V2dE`=Z实^rX(  .ۺべE5/$ Q7#"""".`]7L&d-yBҒpwDk z_4e1)ǕXL??|ϠZXUV)*;& QmT@İKm Rڬ鈈F>9k%-N6ma' ;9_VlXIK5vʦ*={4OQ)Xr,> \b@6+ !M]IFe0xk"VZ,9$""""B5scF?*K.Fn#TF$6+e7Rb`8_6`Ⱥ[ q\!h1i)ʅF\v0~V(WkX+x+pDDDDD#8{z#V{)mLpM ;:|Bw̅uY1n~MzGS?naD G@}U`B:oD 9{bp lFP;<朒Ȧ&gl|{TM4u%8&s`ҟ/ _ KFw 7TRxR>S5> 4u&rxQR1j 8^jRsا0a(Ro/(IƇ`Nh$f܀LnSa.~ַg3OPoLeS83+6wnX:\d6 5hNn$!0%SF/M D{눍kK Ue?~ݭI }cjz !9u_0Ux:CnrA˶jC*~U#υ4&~3˲WkYVABGDDDDD'2 NJQr)<]of^j5VoZIĜB":kg/񲸄0!E,gZqplDDDDDt"C=E3M4uTJuk49E[0 HKHw#7$~|V̤6rĹEPg85d_GO1oq 6 Z7Ȧe[Gو/<>ecK0ҒO@5e;ǒ}DzM=^YPPV޶в' ||Fo'0D* .pՍVgW3ײVIQB""""":Lp* M!45=/W0/clVo4 Z}dDg"BrBb5Wlvn4DDDDDD3 NTU]mʞ8dg2YΠsC4$|~>:ƊۜgC-Sw;zZL F%""""cgRLpQ匬<~>J5a n'[X:>^c2+=ܳ:yĒ'TF6(P&8@շO'9ayQ6;F/` 8h5]| |SjO {RnD6&P&8S<*@^x<8d܏{u6ѐ;DUL:ޯwUדun?<.V)ِDDDDDԱ&~0eNtS QJ}J~јl<"""""&8g%|^8&LGT2OB(@@x6~AlDNtbM￁\"ap /LXGEo{SS'K4N{rNcbQTK| \7"ȶB {YMdSY|<&o,/PqYƔDK$:w!`2tUr"zX u\M`4/ Z|7cFT4cգc^TGT֠pDDDDD0\pKV5n0/eac >zGOMf@6E=l:7: e9YE!D4FMBDGmms8ndBno`Ҭ[%۰YVAl."""""P'8>&dWb!{,+BGǔ<|tc{̻:%~ xGV/XZ]kȚK)7$AI|07_("_,3(DDDDtla=H-:Bǵ|:: 39NUZCx~6?-kl!]q-Q޾J|hrqZ:=RذS!PUc5ue5(EmC,ds Wln=p IDAT_Ж5lG4i\@2gH6wkt7:>TS\Np,1xŖhh:-L3gjՎ]?. -ӄg@N@Z:c((U *}d -zYl84U 6{ހ/_ LG(i<tJe3񃸮 㥲ϹUH'ZhWT+k(clӨ1!iB?:=;c֡Nh2DDDD#B,1 1`'`߻Z7@,+-tg/l CבD$cfX`+㍢ x }6j~dyۍpd\.gMLƇ5wܗ" :4wHРP(xG?qb>5KDTBH`ɬt:-O$Qu5 qL8,h69=YnZ jWpG?q9A u:zbhlX~`}=Q`Y:  ,_DG P0D4>f.v^vIS!]֍>i@!p'M`]Je>g@`o DEG߯W R*DDDD#o?N&qE^/\L.IJ쐽NƔ 4KW8k!:MDC arhBF#u~Z–ʍKRlڋYإ-j+~\%lEt:J WVFĸEBF&8;"C P =ոϊ)6l).cC8G1uJDCRA?9?H '&uz>g ~_B:vr}\Iڌmkp攘Ɔ0~Mxdq6 Oe9 *!Lj.ֵ?-/b?[oiРBP*3WarU{  ᔍf{OBj3DAz:|7cs^=r(&'@~M6Rh*ܛzjYVrIm DDDD4 0d^5$Y-]ڏjkZa (F n_GmHD#P3Ja sNI6uboz9`]+gg2шR2]`]_[e ei5j@,9] Cغ5$Py ȦeUL.,zHVۥ1Gi( 6b_%*a1J )iH=`R "P4e8[Ȫ^Rjfgf6fɂ6bMra3NE*v Z b!"8::ntl\G&QZCa < L8El9ϳqz-:.6b6|=mwIiy ˥ 7hpO."7PIgdQhˌeEjoF 7=L-7`Xu x;g( 7Ζ(&sDD*!cR!aw2M'}7l6O~fQM]Ŋ(Ⱦ/N%xc$"""!Lp@"?:ZI6<:?Cc(H֎;_:@~M&7dM} HOaFT'(I!ʶUYG [Γ~3IDʶ l$cF =R)^[e-VLs Z$kP2t|Cf-F$d#2DDDD#S{ȷӢ_ͺ,{9s$eqBSlCCM\,D:W4;ޥo1DDmp-ל&PU3@DDD4R챇G>za9qH]c: Ilh+/a<C>%aWJ @D&hbHV5"""gBQJzmp8R{ VtikNePtxNj;f%"@A%2$+ {؏*~Jb$491g񲸄q{w}&1[)ɌGScF }yQU*~jSWzDDPG  ~+}ҙ~:Q?;* ha+n=vP"cҡ XIDDD4j8O:^hǸزj^XECay~{"aDD'|IWJЦB]@(N,BNps*(Nzw'Jc>iod޿ ! =&8ho3eqD⩡98kKLnX+ȶn\P5 r4#"""1g 5-˝q:W6xM?jO$vF".h2[.auI0A }=44h0ghiU֯wajjbbըڥS]DD'Ʊ(J:d&F_8|Lbc &8O]㹶^,@f̯@c2z=AZHDtj rz&͖ܕuEh>6bd %h ™wA_ݲd;$ ²>dtY0m>kN"MFpkl{S8Oa./o*NXR>8|An4 g ;Fc*&8OIU*KPN}_mxdUdae$":jOPR4^b]~G,MF;m_s~f#FUwL3Pl>O0 qAzH-L ;l""")FK.۶WeMDD'TBcT)L.r>[hXm6 cE)|(Έ6k9fA?t;߾O_|ǒl>8Ea-\&l"""Ǭ !hoШUyVZpYDD]|W|Mn2O-~f?%m|e)u6_܀'"""aS&&(7a!::pV8:;!QU5T׮:c?M$ 6OA0'z6s?,_E,a}!Jה n@ݟE%$b\b$"ꁃL5,3n=RŲ=,#&\ݝa->^L(U 4}!{h ߓLXpa-N~KsoNu!֭XdWR-qX#FTk^]8fg y,4x8sD ёX*/q -˦%@ۤ"W"E'Q1 /pjǑ[V5b𾲇< E(07DŶԲn_2Phm`SV`DfRCUpt""""jWOAl4ШUP,OeYCjhYY\Qd_R9 W8ፈ wa uP QC@\l&wlܯbH݄ ݿ脺TNM"fe<.,֣H|lw*UzWqeYv۲x("SMZL@ I--mD x4ɀQu%iے 6}.Ħm/3p'pUaXGN,~ݼD:'{KEcOs3Ztn*+FDDDD]w] `bq ߶>ׄyجcg)b[?/r Ėp1{֣Yf"2Ձ.-/SH lDDD01>hoY^sl#"""8ve3# ^1NBr-΋o[7N0ϚSm{01=hfR#ǧ1g*9p,ɀQO+-åOv _b|iuH ^oKP(}?FvdQRPO""c88\mJ0`DDDD3'8' p =؊[+"FR<>ohoZw`S$"":&ńom=WgNp ^|so^RᓊݶڬFH+T6Ooc+$>|[耈踪c Xr[K;`NpFa0On:3#{ewB_ͺచdOP_nbI!+_϶u?FDDDD=Vsjj6ّn}--8ED݃A{_Bُ|vb=L=zeSTGyYTi@Lp aioލɉ/!~TTOο-=dB૞DȷǕh4 \F,*PwϏtphWtP*q_hs{/VW/F[7䫝,3 [L$0`DDDD-;1YUov:6X:W@[eF sozw5jΠQ_G$k#S ڋݽm:cИZOZy/b zjMPmkE֏j=R9Kjb ||}ulNy"[ ͑ Nۃ3&QvNlFj~xeY ܿ9pYTzW @ѡBl j"IT5&"":B'mg FDDDD}sd35˲7+F̍xO"$8[m"ڑx @Z"">̑h\58,+0YQFfkXސg%Xfo'""cyU5VϚH Ց PMD7^8̱'㉈#SS-˭G І&ߑ xU)ۊLp@mo 6wjjA#""OUEsd#""";2Un7Rzr~Co/f&3K q!.:*m3`DDDDt*`:uVVހ2;Y$ 3a4΀ѩ82iSd[r{Wv,%4V4""":aߥMm0`DDDDtjLpN˲?G*c7& KUΠGJ;n=ÜO b4PEfOk،Z[q X,3`DDDf#[FDDDD]\KiA:?L4?7F*aЈ Xr-˩) #""02`}cvج컘Z#"""Qe>H~6rdW|YDdg .kOSK"M>B\g5>\ٖe 22pDDD'0o=z cd͵~7ǟ0D$[V=lnicR?<ނ0?ڲW|ƸT6PihyY!jL P6 WJW*U*U+UdXD@Ȼѷ.&QG&k7ܣdl,q Z&M\ &p ,;P4|АP~I\7ұU)pEbA`uӠW;Gds =Z* RsĒ9 &uLY+k=Fwio 6&ҩVدs@;!,,r̅@(L"4v߹7f4viqc 0lD 'Ƚp0~&BwPo4:] Ed߯;*gDHi~ RB4, dfEhc,.?B ɵxr9\(whETmNYa{oW !Y\Ɓ:Y~v ADDDDJݭ5zR4y SF:p E) (Oi&ٌSĵ,e^FZcc$??&2IW<:8E O!"""SӵgUz0("A@ﻘ!xҩ]i4Cp7D}2ٻ89:>{&$BB "r"sUA[PTVˈA[k–z/.4"^ATŞ5j"fv l d N!hxz-05ca؃w3DU"msk&\""""* EFɸ~pC$3͌95y\"""""ɗqyv5w\["XLFvv ~$ATfs9'[3Q"K߆.æLC<;HAR.c" """"R,icX\4YM4QvTJ-m±b2MMcЯo54vQȓWu5w\3Q̅ADU'/b(5)aM{SWaCDDDDe .Kl\h>xAu7|\_H %4u<}S1dž1DDDDT$8Ŏ̲P9.hoDEGlC4֜zS?ɛqw#3D1:U*,@١ """"*+1T[ӆ޶<9 }HgXlh>B1H&fAHY=D,>FDDDD%O)Q@jj6Ou]9q |E'm{rBt~v, μP8iNttv!Lv76"Qv:UCpZR}S1l Q4A'O+ELQTC뒥3]팾2Fz "{qbmuvanXgt&k0|NDDDDNNQW#e˼f)ǒOID2R> `c}aq?"o.7CDDDD"K3'nSԕc69M]8qM ڷ xD$ITKA&p#c8}ilc?ba6B݊@&""""͑r1 N#Gp*e,$ͥ:ノ1Lُl5-FFDN8;HfJ>,:m, 3YL8x>׈]}1H`dx9>&""""%-& ˭S?Vf+P?H=V`dt/v,?)p1rt#n"EYFDu%0DU%#w8o&W9 Y=̺<3hbg28DC !Uy(Aȁc8ц喢i-i;lWax'"%%7豤pa&f0̮!RT&QmG"渤QMc1kfUY"Fp6ˣ}DOciҳ . E)Y, !.!RMBusCi&"""""#KLp3"ϛ uKWi벒 ixbgT G153JDeJi h&"""""#T#8EADrYa8;)  ry&%Rf,iggp"23]N} SIZ%&7GEg_jLC(T&QE%*j:69XJڮuaL/zLGbQ*4r`p"ɔ,\B%kooH*ђJ^g(rRj NQEe d#8uY~ KJ,&VJ?UBY!!*BSE&""""" $K3j\ Vv41(=bHե *3?Ȕ.䲌vQ|"GxW iʼz='ӹ m-i|hwal.1^DyN`:::cNfr[c-!dQ "ttDSYISE~8%$"r9 E>A>C :W:sXzKY a =uk?!Mf$"Ϳ:'sbW*i;*HB_89bAkpQ&  RHGLajlC<^yߓ6:5Ped6ڹ օ0̫sYpLx#bb(ƆH Nt.DS{]at4 c6%tnfddXо-]Աu 7;0K#L3@D%SW\tvz%܊6<1T2St2ZffuIqKBA*d]KpѺLaF ‡jKd#ٿ;C IWwg{>i "^4"·gr$u$8ړFp4Ɉo.m_taw<7|M'HDDThuYg_=B5g5}byClErju[nSk0QktZ0Y|wxNc| Lyfwf?њZVru!N$"e!fb"LMN-YKx]ZCc Fp?ŀhP47x߄фAR k6 pdM6 7a:Asj:`i7;:xo1{q;=rpFB-/| m5F2 *t NAꖮ3֥%WJ_6*$ /j3IDD氘[M2DrV#;]F5| m\*\4/ixLrRw3]uTNAD0w˕ң#vX-XXΘzBDD]FQ~iD'ӯ^gnu[א{thc.\$C$"*kfHz5{KX>Li9>BDDe?>@iԴ ;^Wl܊P(Ȏ!T?p[ U/D9Ҽ2RM*h g#j+i@XOD47](J!""Mp;ne 4n֧Qa@=Mbzx=x7:"AUg d NB-xTZQvB8[)DrpW idw} ?ܩn ?7ؑ5=N;S{@PUQ%gGbܓ= UJo|l+fљ(bI.MDD#Lý Q{&ؽ*0?$uu7 ?`6>: ͶEy1?X2Ab4y4,xa *-aͦ4}H&a0T~hv:Uy]$r(XfHf ^%mwcSL_ιӚD&8H{ɹ /;׿V՟? oV?:?Vch›*X]8_L6Iu zo^q>>z߇Ѳ&_#KN*K"By]+éKbhã!,y4k3GqRU\& %8Z+ZWJ܋w,)bQC&4&w?aY}Yg_oqwch.dG2Igm8G=t.Vu^ډv8E e/{1mb8G&6b8Ԃ Wpe9h$ G6:%sy`:wC[r,xGpYxNpƲurژ ѱp!JxkRX-&OyfSX`8C<Ť&U\.ᨀuO|7 W߫ />>Wۀou.P0SIIg epp-aպ <̎y D;C_\:UH&aMT BQǵ0۳+SbhEWJ?\\(""Ҍ'Uw滯7 ST$LqUm4~A1q[c?QmF~kXp1T64BEǵ:&jVYK1 oMS}?+iŽ0Wt-_܁lf AEiC[B=ro܃g),C5d$܈_- An"ϒE*߹ʽ Y>L9%\y'h2>f#qXO>ohT!о/zpLp7%_)6ɌEq\Zu3bwv|BW891E"uDD ^Umn?}0Tt8V:^Q}}9a?﮹թe2aU3>W=t]>-ЗfUea*ڼb#8ev`w94vC}ٗ0tt;~nt85'>qƘh%qנc"E Qx+ka;L)I`[ڍ)#hrq)t~ӸwbQۻI*blk ZΒJ' Id!]Cx _/&ple9Hrusshv4P|zzK[;"KdS@4KTecI1Á8wέᰘpX*/2˪\]Wc1h[! "@N V U d/z~#"zH IDAT@ Gjf i}ύV}рkr?s&k ry%pRP(KUlLnPJpꊜNSizsi#Ԋ^V$G8 *,&0ԣDT$pMg __S Cmx/L6^Ǵg}.v=E ~Χ૊4*nVt[~k\ ,E fcӊcvp-b"kYGpu:XRwI8΍J'YXxN !""Mxifvмb26jͼ_d@߾kg"lm㉥ɼghK 4)YT}hXഘ,>̭%mr5KqUI>U&"򳙍8zX eK) ÚJ[ HgL'r<^>Eabx)4Jp &8vh݀ISv*J'"J@cQ^!DDTvnI\Zd"{{k[BZ3SR1d*@]6ڏdRmjMFp42U(U_My{iu5YCqX$Š5|Gc"ǫܔfghjT+srYw%8'z`R?[hdTm]GpvvuUsKڮ)Dzm k:sR ""*bm=] 0QR,.~UۯH.wI=O2K{"m"LiZVs~*Ҽ"Gpe\cu2ܑV$Ga;bI>X54""QEp9[ΎeAͼDZi\x)w]H#\T32e)vxf4Pxީ,0t:ekeIɕ7jRkb'hoE3_&TxZSϛ/""҆1eFpt KT"_$"{cCG4džyҰpO )S>J!`RE>l읊=;5SEW3'i?) H,^=Ҿcԋ5ٹNoJ<‹.Dui,|S:)y0޽  r* Qef%7KTd:p76 Q+K&b$3,.Zib1kEU2,y@GG)}=NR`UJ7D4_sB]Sj'isa}mBgW'""R(bfZZ;11Dk*Lg2KƲ7I%'+SlH 0jޮR6gJ uks~=碽{1,70͍xllJM:o߈R7sxʵ ҳaJjH5vAvמKt,uTR7(E+ѐʬ%T%$qdow?Ad| сv57mK Sa "#vt\:]%~ԏlL X'aâH&-8@W)ΤW`yƗTtʬ3Gp 9e֡^ }r+l6`a|I5 Hdu9lkbe lu=V>s3&؏`~H'yK-xzb;/KHWINVKM """e P;lR 0)$+5pS& ԙpY +Ϲ B.g2zLFcxƤLŹsmS8:#1u`Y 7>Rń:-R% 7;_x%)MDDA] NSSj-|G8Jpђh". n6XԼORh4^`*On^QD m]5v033"y9 B:A? Fd1DxgCr%\_iz~GY;wtLpF ͥ_fo#FJ*De0"Y.$щ,JAX"z~i~Dgr󸖶64kC7a$yFD1IL>h^ Pu|(QE~?f. {>S.}& dH'%%<xIp:g"Ӈm5jA N   !N!J"L"L _k]S*LILV읊ѐאGK%~%iO%N&'(G.e[5쇿:[o~LLj""""4,08ELSt?e"""H",DTԁNl̢ɐA. . FZRI Dcq,őK,ala $k$ h40lM3(DDDDxSR/k` ㉪KZj0{1%o:lz:)m>p8 ^CiD49B$f-kx @,nN """Yz:FC~Õ<$""""[:ds{P\d@($`Яp1LDDD2pqwv=!"""t=A M a?FO<"ODDDpYЍ7\/d@h$8!\ i@cDnqwЋ$K˼Gmn\Yߏၣ<<+jYQ n6:nI9N\~*,csiivQsP0G027f1ZF,7q:.u`%yf8fWBɋ"""*IÄ cEOX2[ ]5)͂}1*5*]2mJz]PBDDD% *+_A{'bI$"""5U4ziY Rmf/=*2^pJ9yQɞ~Eۿw\9DDDD5[U.S:WdΑ(dwi!""mf )ҍHY UU}4=.47W\ܚ2E!""9Ĝbm^-L&"""qULFK::Z+*vӁbsۑp!""M (>&S NM&V㢊Zp!Ӂ:W eB]|c7KDDDD]Ih6TT RŭikhUCDDDE<|H6q'&\""""zEU$8%s把a_:~c="""*H2MޮkT|*tĢڑuWT2`=!""mzMrM""""zOp6xӨgfX1.4dws!""uf{ .F'8FIfY+*DqpJu\NN]fk ,Op&t&؛e`ʇӾE7""":}'VA μ\YƱR*6IZASQ%|d=KDVe"*R H^etp6mh!M2{*y%QED2f QMh T)uëy1yjr'"틧2 F,D-Q5US*Qqpu8G\Ip r$PјhO1!XTF.B([Mt ,QT'%$b&xFI֨ :ބ 9eF$m0Yw8`Y&FpРvfdoS4 R#KDT&Ma `E4}nWioF!$Uhi7HRM:#"ugOA'gzrT6E#DDR Ωi4w\1ē9.updʌ6[2ӬHH'OKD jM NRȠm9l*>JgF,Ltjr }SƃWiN,̵g2k#iPhH=pA "ULM(/$z ޮ^40DDeRwKS:8Rg":[Dx#J2ܻOvhT&6<,27 j3LpJ&$_cl.blx3Lϻ9G,>+ HS^zYEڕ6mTh ΉQf@jn/{볘I't?S8^DDTXUEd94c9苣apG[3:N 3LQw5x5vbm; LhU"Q+J59 V2{+4hapʤj'CdowT|<''0/<$0+csk=r#ICGxU&шkQ3LpXL"2߇)T?)nFgQTM3aczWQ= ASLg8u8E%DV$m޷C6&c^ԧO'Q%P Hg-J6MshA/) ?wHynߢHLDT>U5gtlWS ? '&ݘ++$ >,&g2'(;urjU+d24oY~9C]YE ,.$k;(d"rıXP_WK#T\ъ< ss)GdFsӃ<*̋O^6 ,͛t f5]o{K23VZ99xw;NKStw*X0蛚t5zsN^ITv{3xD 흊N U!Z.GR 2 $8l-3J]\RaS"m/;sLDTFU܎Aπ?k 0_?L9l^ʦmӿW.h2Dڙrb@GUAe~61$}-3^Eڵ543OmOcʨW2>>"Gh.,{u7C#U},.^тFIetm5~[-S?xD=:ɀ'~|"m;9E/OA̲y- l"~i;`Rjƣb<- 2QU/1ۊA _a7#{S3sUŭrN*N *=zVG>^}'9~Li[r`EyܶQd`ˤє\`VvF#IC8Vʩ&D Lz o{MS1`Sh/8TUy"S3|݁Wj6w}U]ҙCQ*ńyblLp<;KrYriwg IDAT!aqRNۂ~OcIzVP'"*a  9쏡>65!l ú{H35uyk-\hh{TlEJ`Y޷/ۤ{qO?Tu xڏ 2ɢ]7AӀ[V36b[~]Eclݦ ߉DDZSs\ӅG$mEۂC1|>Iڲ\mf]{Yn{.Ƌު~'dӘU}8Mk`կGqI#}8ۺdR<=۟Wt@YM%8E7ؽZfip/;ek,/]h6 rDɣO՛.d ZRe??In }u3$[%>SpC <9~a2:}?ۊGt53DDeVS zK e"<3^e?@LK i!S ojvs'6ۖcKs.= mƕNFU?,NM!So}{Y8G[Vn ~v SdI6q(|~Khey?7#L3$6S~?Ddʭ dK8Vo>s+nXsA'K_Fø tF5'&5cuGᲚy2Aˈn~ߤ7/dI6ӡ8.GUnQʲcIv3n8 j*4ًz\ 34ggp8sK6SI-w}S^4]ޭ~;nDkߩ(Qu-B!忛, |1V2&y>W2?htZd "6?{ۇA070$ՌT37g3DDPS iMph.h#^sk1WsRנsAM@ڎ;nR} K=U7.hT78>:>u5B;67 Jt=_Pm7]DU`I^=gmTvk> "8]_|лXo_PDD%t˅JWx٠=mm9Qq=[8]H/h0h”o|>|_Cq_1vn8>N `ЬY/pR۬ǀIC2~?>t?ŏQV[j@<#lF|J|cGZR!Z^OÕ}>c "a3cX(*%|;!m=c&rW\)UAԨU)BjR|+;qϞ~-9x? 䤿]Bm;p sߌMmFoR-Ҡ%J _ _,Z[R V AFQV*%r.=cŅ/|vW bָojyA+rUUc?>wwT枿ZI@-٣ㇿ?7O~o`p$k:ScRXA;r/g6W K+_,^I'!}:c/Jҹ3Hfh-sjj Tϱ8?xߊ/?ƽZH\yϵ P`28L:^?xj!|*DxW:3'x}pQq5,yTtM(jz_/~)6n=N>"]$=.O^O0~>UŶ]7@Zp e,v?cGnq?,U(rt0V38;?{/mz&7_й#' ;t;$"Ipm42^T*pD~dvc>K^wV5j[^w?G{?(Qk~z#iZK0e'DrUdQXRi!TrHp_;nHp}P擂:(x'` N""I} ):{O72+F$Bv.\Jm=~g.Q^w~/@Қl޾c Nj`Q ^jxwFoh6 VfzJՑc[)Q*Q.ˤ^^%,=kk (LnRcrH"T*p.Řivb]>!W2}ڌU ?3g*(_bs?w.O!Zmo~jb; &^Ao;:{nK ""QvE:m*鮞 =.;FtBg)=XG'7  vo`rj1.~|:(w2""h27Ԯ eGxZ^3Zj#uZ^uTrûejJhjFj5x>d}oM'"HpSC~b\=:n>SeF'1{dkfd7V y?Ǜ'.rGřܤ~|9DD HcҹV)q2+Ho)g*m0y= 26Ll Q{ßbQ!89Łob@PǺW@I\W$8goq{}J7Bt& ogFl72;^7@І*ʸWHY8Աvܸ=wqM" R\Y2A#H7hLrx?yJdq;&Ơ`n1*UޤLdi ٍ*_oC|0#/r0:TP/uwuZIĕ P Mi,t&+v鏰kA|^ %I|,(n 9ßD>&uWs?dr>35V8+.HnvxGLtWDMwm]ЛCϾI\?%sәSΤQP B\u'8Kb]ATneK.ڈw}DݹEPr^S IE_զ(,,7|``xCExG?F4<6wWzPq1Q'X lw*{6%m޾}XY F|䐹MSx_~/j,88DO?7t$}{a>`u('8gSB'B<|8#ɾi>Siab_m%}!_f"j5xb=ʾO"Sbd_Hܫ}ėTYe:ߣ轎mu8Y'8 u*Vk 3fs81,5j5`.Y{?ypPdo~;ɯ"cEńS5{^H̋|!Tw Uԁް` $" Ynz98jq,@Z%..P]//B (rkF+Ug} ~tNK_w/zwc9dC< xß}ozA|,F9qp/Ա}[a0M""9uSg67ԮMq&(8+cڣLn7z^C#8p0 N{WGR\0l} CEOH;ql r}wO|G/ͣ(aq>d$_k;o;}싈g݂Hd,7XA=MX&4!Pa s =mw`r-]-$/[MJf x!h ̞:#?6R) Qz=F߇ΆMwEE#M!+" |CPBG\ f 4`s8pנojR=F*2| "3Y'8#JC6RYhn:$RH&gP0z=(kßG\A E(f|?^('#]ƅp([V$vA8f'˟  $}C#زk|{i<"n8/dw䍶U۸sa.<™,;7Obo3ȥF PkPkP=G3P(*cVB4*<*"ʿS,T,>]ݨuh Pju(+ȖH,ǛzL8e3AYRA1F\DRX@UIPJ >BP "Z=J r*'s 䇛z{G74U^Aly+U$a5`2CѢ@Qb8r0i0Hr N"""ШUUG3Fx/Ke yLPFXZp+#b􉈈6Ũ>IMc&z$8ml.u!%8+vD'""  Ml/|;pXm|㑧=ȮPLe&M#fu""ఘpu&<Cд/qz'9DDDDD]Dv ιr Ӻ<#ODD}^E%un ]P)p"""". OgrqSp&Qe7\g^BVqdNV NńAg[̕4#ODDF ƨK߶s.\PɜKsNDD.zQal O2DDDDD2&g FMT`5oG@ɔTPF2H<QzOqdža­njm """")Y%8 UPg!""v9môGg@dHV Χbz=N0Ƞɐlv rQ'""j}XRy$s%""""֐Mp$NDDJ~HO4ަNDDDD$rVPP@/ja1!j5" u;vjDDDDDr˅~.Áxp`NJ .ubYE,.i.fSI_JGD.f$u"+j1"""""NpJzw Vo?0#ep+B ș UD~aVE`9[K0a&k;,P,9DD-U%ٯQ?zHG&8*%Ûr.?S,`X\1<À^(q܌E_ZB y!R;Îo3ʁ%"jDUۅh"""""Kp' \2o1F+ ے93$d!ܷ5V6x'z8x*Á&"jhE1LDDDD$'SdHV2_lp V6&BhnD@nt.ܾ7r5)<tDӨއ'~L{t-=הG'aT'BЪ9C6HPojjyl6RmE_/ʊdV- """""|j2w/{[{U`n`Lzn^4|#.m˕DDbQI['H:QpѳJeCΟ30ZřҮ/F~ߊ!rX: zdsHqe"""""YtS1}C0Qə&Qv- 9f²z $͐G&DDDDDr"o| D$rYo|mm:G "k\0'~]N18DDDDD2#aŗS׳}[1-R*=H/QDD(6sZr2eC H2vM`#xpƴohY}1ZLq'ѵ($՟b"EHf$8݄E=89kZJZ+vA!"Fy,ESEj5ƅHn$c#J;W5jB`T F+\v CDt J ^ƊX,,BDDDD$CJpF,\$9Mwy%/epҥХcޏ3  I*itK?4ۗ)J_J$"VZ 'B(a}ȅ07{ """")I%81h j;gN3^: r<ܚʆL:A """"1T:ըÔ â!"00ЇNcx ͫ+YMq~"ZBR j4^q 9kZH;tB:InŌ*;Mʔc 㫕)ss.[q  RjZB2)JS\eV xqd4Q(7l1HDu$Fװrs!=XX.DDDDDZKp+Uܮ`R̠\rִX0kʧ $'tn<} u!MqckHn"it ZN)NH ^>٫1mksj^̈́/U=yJ޳ND602/0n<uœX\q-$Հo$c4}4gK1Z~ ֬/AqS#@, lڄ/F1f(k '9DDDDD6Jv,)̮d7WW8T8x*ٲRќ՜L x /.Υ3Yt. XX\L`D=I?y$5ug$ G|&a]TQ /b #3N9#6=ʱEDl_ cz h,;3' 8DDDDDvNp@"NjE<5ì׶h 9K$V!C)qMBLd懦&<,m!U+/-ő,"H}ajM?*ciܑ>P$$"""" 8/tĒ3ÊG-ۡ2[Z~.u*S!D;Dm" FnlN |!=+e@ƧpD?Ǥ)5±$6S:ı-6[v?frW: [/rsq0qHz}sy+S7@Ш"މkNnfIcr6:P=7-?JS=_#gFRd 3Zmu^+O]_3eg`Hz&QSH&qsq&7h);Ӂ.<[V`>J 6嗱1(?(gDE+nQ $R\~Wq6[{L|]c I{:<۴ǔI>h"$"""""I=8WըAƩ K < F2iKتM”!XBR,3CBo]=)k:@[9ʹ~嗠}BA""IpNn1qhM%رkS I,GVA(HeLfvɽ=6)$Śm1ێek]]+$-+m(vZ\TۂO rHRd[T sݦ,VOp^iCЮy5e"7ń[ f36o_s եp8,*EjkJeEh*Q,DPkJ0 86/?D&Ǡ3Ɋ6t`Wؐ |Z Xp4 V??ilA#!h ࢦO1fBb)_ij'N}kꏘal $zEDDDDD'J:I`\yAő:?yэ3}t AШa73=7?ǺF\E/\U ߚ0x\""""""RrHN4"_Tk>W&W9sW־'jTgb GGqw mBpm3Ypظ+cqڠT( NSڒL#&Ij>a 9{hV3]kZ@)kz˨w1|uӛR 8j M=vQ,^,kz-rkܫh#p'΀~qSY*eR(- `*XWNľ} Q0DFoIeu4g.G$""""'ɎjKniy@)̅3k~fJpLnZŧ&i5jv!wJ_\٣\MNDDDDDYb*P;lmW_C~-by&8&Ho  n@b++[7( u,J$Pџܲ>"q ]} z|`wL18<ĀɌg|WEԗ x :$;jtkT q8Ɵls:xز ɝj , _z\v :(;D0 J?ndP"""""x؏d),r~zIg־q1ez,\k~ыO[2Oa)n1 ==n.*[q"#byB(I] 7hbSA,- lf4cBN~Yta!"""""PM|9 $7J*O?R@-eh󘲨Әam+2_8УA2{{-~ VXTP2> ڇz@:$D?'-!0@DDDDDx:d7G,]8wN"H.=~eDmr/ :wMTgb ~F^Gصމ\MAMY u<&8I\t6skZ-.kQG=8fG1&)0Q(n"Zc9$T"""""xLplm=:*/v(nɜo5X 8nڍT*5+M q5HVdfRQQcd+6$0Z%KqOĚ;؇&|!#X'Q :Q} u>&8IbTvqVV-D0=ӅdvʣdˉFTNn3Z"""""|fCvf\ 2r@4Ҭp<_SpZ͜hD&پ5:$k>EBCJ$k cnqPRBB2d֧-1@DDDDD Yuی p٬hGd*&+#lD/ bHINQcd-7N4[$}Js'p vhZrR+!z> ME}s( u<&8IDC{XfG?GaF$/0=ŨC<;$>1:$kMnfk&Ұ_~ p GnjŲ6I6y6Cby#""""{JIDX7t5YM$4Q-p-X&8(  द_o\K9:${RWo㰙`Hg;⚌IKpz&8E-lRXa!$"""""`d ,vm ׌-u݂gJ907@aO'R_f H'^8PeYgk+엿&[_/*7=8! `iS9R€,p'^\f}%Wm0v5-Ԑ@QQN9F! >i]^* KDDDDD5Wp/;IP XoNDdf65afTInvpQ`""""" म`궹bZFXh5jLy|LKtԱ|=nC&0r:EW 2 2 ԹCS5;C|ٕ,]\q/9ߨ㨔J 7>&&6 $^^B$43ϑmS;pRo5o^&"""""aB,\)!_O+UCY s&uNy`?*;l.sn O;j8! Z nADDDDD'uL|" b^]RmhJI_n=.b92r&<*%v+4VDE-@YBYΣR#"O (Æ{J:0].d*s ,Bf2~~({z1 """""y}?P ΙAe1Tk1̀0,sPDQt<u0viUI0CPTd@}גFL : wH,' C-^d Ϳ;H%eWKs+H݄lKtT&nr;8ލEc/LzmS)..xa%:8zHkVO(J%" d*Ŏ Xմ"j+8𡯷Dɵv΁>|Nc˯31 $PZ60Џv jڄ״V9]/lV, 'M#8Ak?JZ]Sݖ`o"ī'H6F)pi'cʢB:­P1قD75[aiѪVKJ7RHe5Wx.]71ӧG9\Ff3h8hB@ŨAR>RyI'݂lŐcA`&)S*pLlWCMj#.)!fJg\v6V#m遨6_VIS(#OS8 Ewp+~q!T'DDDDDԹlq|)?~DH$9k2qUXN'`(UepcnҶB4 ML.5B# -yH׋!fu 5zAxvl5}S6YAI-B.[@D?9pXLͰ7±4nS,`b$2)jFX]6AU3nBDDDDD &KqLytm9͠/3fHgs @D kQ$Q| BzߒLǰmR䠮Vq&mAFy\0,V̯rND e@m6mfP*POEۖ ,mŭSF'hѳi a:?"Sq\He ï@# -Q /1.ن|.^+a  8Ͱ~d;j,nmMSa2"(jE b3Hj8UzNڄ9_{a06.f_׋#~%6}pO^@[i7hp$c.u? .9mfCSv q*yl0%sO!ZOR y(hhNfl+:HVT 9Z[3 S:H*@\W\*`[WL$q[ID⿟ H3HGBGiuERʦX˨AIâچ9'U}uߪZBYհc}!X˪Vm"ϋ6mHע":c&N"x>A2vƟBnrǬ^d*}},*0jm~u֨U8w iID Jl˝Z~䌍%9!?7 """""> {~z'D:z6ŸFQJ_esK[yWJ:jxT^$ N}X:A;Rmx x %ܦR[ orTي]'6xx>WBIhNοB_l-r 3hDDDDDtӵH_'(nsP*>mm8E$G衮L|(qgcz.>yc~&xJ[,e tGDDDDDG ar}їrA9lE^mq^h,&M3M]5裍#yTjr1ǯ`,AD^*mߩs;8""""":2k/ah)[ as Ņ)7rjӂF K` 5MjzY9 8=)ië6UHto5wd/52~ш;lYPNOP̤arz! WwZ 'l(MƎWpv(aA>3&ss2x' ׏Аa =9>(=LdJ 刓m{,ϳ3M!27ViPzkP۰m-$'h!%4Gxu!G@J>G '&K1{CDDDDD .Q\2xPwh$* w#j%GZWSGV'-T)/~1 ihm-EتEQ߸r˕2 yܮy{r[\ f3d"8qXGE`I_\z; քI, ](ӈзMg4bnA5&8H*)X% (v_}A56Or]A-W9`Uu4! )}x z~f9Ҫ6QH8=(D`^OxI;c}]$$7ths-4ZV eRpe8YO!hhaIG?,Y靈ئkԸvM 3Pq,'lH8gR!"""""]LpvYRId?m&Yzީpu܀R.Êǐg V&sh}G1oDn 1(P[i)Pqr>;a+CIVRvQ`kx8]HG;E@N'o&gG&#b+KqPS8a(J%\"w=َzb`: 6LqĘvc_kZMb7N7jzTE h6+06*VddIHx0sΨ +HeO`j2m?\}FABsvgIcNs$k7AADDDDD=! pUɁ|َeJxٖ)^*[eBN3ܾR2Œzbr$^,tZS2>k/"$na'y)DIю&;ד2>gph_)lj<_kVZ$""""")&8VAp| U0#0 Y#Sk"H$R^70v*+?ɱq٭3}۽znw(W(E=ᴙ0*xFV JX m MM>XS2>o/`\E<6\UŧOmVD:z !ap-:>1dvC;Yܶ'&ag}Ƀ nڷJBo!POXN^}dKm?#~\`@^CYVP,P F Dc [w";b;g6ZjkmsgXdhH 7!mm) ZՠB}( O^foQM{ڏH+Ӑv69'|Bij{(J% "ĢY-mB\Af`FpBcXîChY$NDS85CSJ%-tLs!|'2wV,_c#0pVEA,D&}+:p0ŊdKmO>@$sSOpÔgrw!ZA13z :tЛz_`1+u:X+uV6[jӌuEuCd}nikh&-A9pi7flSi%ĒF7+[ˡ3ȴ0^9 5Xkvމ }KR: ~Oh^Q 7NM ]hǰ5e`'*Ds[wjbǬDDDDDDTk8UAˌގ[q<Pe6^Fs{bUR-Փ^8u-#90<-`$Eo يE5)qg"ZNAG66p>`N*mRHZb0YHQW19DQBł]W&xZH}b@C6yAʭъZ4\縦BPuo'z7 )xKm?_[C&x4btsrg 2tn4p[Yacb >LY(IRVwh`P-m8g3F#hB$qEȠjf~<=퀦E><[ (OlkPݭl=I[H\SP9N;-%,Bf:S(*j -iZZy}P.ގySX0EαJ2^-)Yq^R#""""`sĔ2 (Y-[:[ow0g͡J|'W`D'QrN-0GɹLW8={P(JU@!.Ņz*Ě*b@B@:ypjkJQ]ʦs b@)CGDDDDD]*rJFh dĶiVza`(ˆ6BVhǦS_5R}LnjrwK-}ABn{A{RAf`DpEĬ}V`2"w!j!oF?'j9j9`Sb& DDDDDDcsUkuTһx^DiG"DE׫cx.h9q ZD'[ 8Ļr<2RY ޝ7zWtD`n٠ѡh`"-$N@TO+(fRpdX%1ecJu 34JHyac?˙q"Bz|eadAEZg}*@i/[$-m_)q@ }X{r n#|{Vt{k~ [%3f0.ZUFJ"lA3OmW]Ϡk?Ux"IEDDDDDh6(fRKAc(XۺhB2CZ8 i4 O^ )ggsm1xN|(M?7桸ǡ}]q8LUc1CٖHP "۷hw*J2&mыphxki;AmǕFq%vt(cgR`haժd$Xb@Q"*xPA.DDDDD'&E@Y6Uv|q\ӡT Ɛ?yP靎lK߅ӕ[h]]2rz[GQ Q{>v%% 8 od9MXD%rཛྷn}\n'nL@ػZleI%X&Qw1I/`Vaa/2, ~4cIde =q "fԀ?Btn-)l ڽ]YVo=MW >@b·XSҐ}ȠuQDP@#r%ezCwsĞ;Lb8Nt_M( q1`t0bwDDDDDtpLpҧP{n- fE~<;iG]̠^o}–'ћoZ>Y>KAmw|_ ')s8$nV?(8bЀb l PkI/>)B.]fq,+jjz d6{:ScV&+qvQ,xuO d!""""5LpҾ%\3m ] lFľYmn ^l|nlj3i>ѳ ^, ><},=lj~NLdA;$j b6 c&g k+k[rJ11cXY)(W:+N4O+Vw*G /M f3HDDDDD# NjII<0mm]`2"l冯ʶ'j5L} d@jKti r-pRIAwt? xӲp18ko2Ч:-\uaB-WP̤ȆXM`hU fD~Q bj29Ւj`Ciu} ~ˡo6Cʤ8Ft/9gZBhQAo[7BelW\I\Gj:~k\K)/C/2 ,|'qފJ۫\w&旰cl77Np5n?()*J$mjfxPda%vF'&+OOMoˇuQ2 <'`R(w0k7-. ) X#xئaˆ͞Fù[)(2^ۑgQczO8f|O8G GMh7o~ |ttCP :-Ov6ln'(Sf6gίEсώ%""""vLpRtϛ((y>\h$/.N`(PVẌ́ymV M,i3ii!!|UY"g4Gc2bay}DTץ{DωJzm1`r:zp*o85G_GQ{%6o_mp!(E]uJDDDDD N$+0g#8kkୢD~ށq\iQh }bUb-y@Pcݯv ՝͜g<ñL|rk#̨ + i4(D41,#8mThhnNӓB?fz"id,a諘MF,ڵ$Glg"nǾ6R2߇U7P|=%ce5=9Wk4ko8*</-C@$W wQ^[:?{9gefz Lxl OT?4>Gifv9Kk[fO"+0hCBUDn:^z바r#s>+0ȉ@DDDD4Ęञıy *xۺƼ߂M5BS~!E iuZ/P8\n[_ˎɉ#bW̶6k($ IDAT+AR9QBl}x&*(P?^ @DDDD4ञH}?Vށ_N}ÁW=]z.o_\Jc_MP?>ٚq>(p? C)s+(Hf[7𔺍b8 )&8_=RydW0!w}VKJ{mrkiwZ,O^}? wpE~HDDDDD=Lpҡv/b6\gMqǹREuu~lPQOǰTAAk~\VJ%""""2zN|'tIOķ`+~ɀw1\/z?%|'l3@Up6PƢ-$<[}NHMwIfr0h4;*God\,GaOQچ9n-X>Nq@ ӑ]TzJv8 e*ngWi15\m~9`F}dK)"KN/=Lͣlkm~GN,1hԒRF<3h1IG.aX{)|{f?my㋧!um_gf5)3#vgpрVJ)H?ZeШ=ψW7[xn5\(Gf~ n t hC ^q8v؉'VV2x!1.붷Փ3li440d""""'\A~yF)u'nxai8mqڰGCpWwҟg\ UNC0q;EKm}Jmڦi Vr܀h1I}) `j~ņ^v>*XGDDDDDC Nh"7W+x׮ܸ[,Ɩ@1d,=TQWEO_3P` 1&8iTku$V?ğ+fܦ ޕk5M߃ ꪷMQ_|fPV4N%L1m_gbIcR"c \/C*;xE,c]Rb)lgUx]vN_h":a:^,uw2%br4"8U Ti[GULlĜj5[v~xӓ-r;lZRG4X(4u81})|go ;&8iI y=\Q LW]fZCcm\?n_9 ?+x^^JB9y e[Zj{Y^GɘQ26{I#$""""Lp6Ɋz]o&~/&.#vJy5Ro  9 cܰ%h4S֍ޜWwcШ->WڋHX|q%-"ш-^{  s P+՞\\BM|sMU-FS뱔xA(qŽTBa\ztv/_u.'cs'isk]$Z1 p[?1Z09 O0 ]h·0IDDDD4RऑQn:7ΉfI-òq1tS>H82l4<=/"ONne > 3He:]6xϊoV0xjud ?lq{.d,\k%ܶ*)uOHdIĜ,@֊&42\>bz7j>4/dn/N 6Q1BFcķ Z?f#g><=Qhzc~ zk%eKL.(*NW541`fFVcC,J+(mGIV9DDDDD#[idVJى';l^7M:s =?-clٷJB5Vǝ>E0C"^RUY5gkHvV^* c% 7% ,-) MjL<&8ih+2`7Uvrcʛ]#^[y3b{ɑIjn˗]쮤o(W-ίT@l/D@>h_@dEE+@"рYSQ,Bgp0I##zUnyQ((Wk۸x#h=v_Sgsy9sVU|Kmcey!& mh *``)MD";uJH$L ""C42rl@*{ߍk߂Wg]:F381؝-9A'(`|4CSE\?Dv`"""""+LpHY@)awwod$.߆R8:L3Pb4 ]\lQnm], h8Abz~_M}ˑIVZlnd0I#EMƱUnPVT$߂W>1&qUL3˘𶖜zh cx0s7K]+./whFI:/?r}ۿWHM&NUp[tp(b1U*:FKhz'^+sq=(*LDDDDD} N9Ul,!΍ VnZikɃф׊aý-^ZOʘNQU Z q|r+wPyDDDDD4@ऑe{Źa]X_h#9X1 ^,ºtz`~ m<'̈_i/bzSJV{w`"""""8zF ^<SMlO crᗢ;q"y4 ל 3[UY\#5e8fhCKüɂeS+eqNNl{ / 0ⵗ0Ш)Wjx*g^pTR &Y-cBhsBV Z,qP4 I2vm-Cd)N`2Xwuː?4+n~(1DDDDD4EFNBY8,;zxrb Rh OLVSA$_+s񴺅R7[_AVgh( N"R&y*xSva1$# %K$^ 8V@)ϪGLp9{N11Ez;tV;:B]zEE&RBssx~k?n"!x]vO%6$|Y/d`ϵǠ;r?rd!אY&O'6R2ܼÍuaN݋e#,·6K"~xnb7P8Cn1A?s 0t|Ɖ4KDDDDD# NOj0u|4xG2*)Ek:WTPhfj,b{*wh0Ƈ^tn?`8A"Qhk~I#D1FD-pp ^Z?qU"S1cg9${ɶgtp7ap0˸/r %RE,Ù,<qNEpW*:TEBxk%""""'Qf#3z=E$+,cDǚXsOjcg&zb$E6cΏaGQo4\""""""0IDCFxw~N?֬!ֿ[LHN_ĞsvMOMJ6O׊eJe٭5JDDDDD\C@Dähr/H)g$˿]v+rM\9h&Ƽ(/^ '7d\V]&' N"ZS)=vI6LL< ؝Grt _}^kZ ƼnmFtt2Rn&/ywG$+ S|ق'"""""z&8hy]v^D:k,M THa7S—[Hdr#=FAi<2FцO(6S%hpD*\Ilj}Ԋ5`"""""0IDCd-S``Ar~c$ YYFgN{>k[׈eJ5 ҕ~ zxfO&֎38-m`'CKDDDDDtLp,nn];2h<VB(US;lNE_ u'}1窛X܊'cyV k 8YFCzVɧܹbI9d"~OFtkd+k/a QQq"bdm4#靡ύ}OAȂ[:? "$y$]cZN$|mY>DDDDDD2D4jvi< giKSqЎY/@p8z^7_@E&^Ti:L%R$""""""&8hd (yy + e` n>SrA6lVbg<,V R=H(XTwȯZv""""""MJDDDDDD! "Ȟfplw3 _"hE`g&""""""#'cɚ00}Z!P:Yzs7SYH!&8+4th1Q䁎7PJ=98&8+ֵpPa?:-nJV̚kI₸Tć_2DD_eWa9uϔא\!!"""""CLp=! H_ג2fNpj-O`I\Gl/$"""""'c^]u`cB}]o1!ǂi"L4l%)k7k6@ &83sfσk4}Ǒ N"""""A'cT Iy +>fofjV@ &8dsL {[hoШ """""Lp彾hMVo |5ؿ}+0 &8~M<n[+ ڽ͡u.I> DDDDDD N"_SeN"yq(b-*)wdS!ܢNDDDDD4($"ģuBk3U?2T~Fge &8>Vou.RPźJB'e->DDDDDD N"O Gp+C|?pOeg"  &8>~^9}$IF8{+HGޏ o!'>рaS Ln,]QL /E m9_YH`/7 DDOP,)x&6LGP;}2Cgy>4ţ۪^E='"""""@Lp ɘĤrx b_*HX)WTv3e<#TkDDDDDD N"*L7񼼎9&p:vlab/||ŢBpCg2=*/A|7 MDDDDD44_o6"VXCh\xSbk}b 'e%FcCQ`$N, 6FRve糚y7Qvu:%0UiM,3! #1,O!(CD0y~*h?AbG~kz{l?+tZpMNٝc;L)d]/>/Nt$G| Q6ͺ\f O!IvA_,j+_{e||$Έ+N}9fk6#uYE(pǨ-LУ/y7~g[kx鈅7K7mXrT+:{qXkP0ӬW ]:myޥѢ {N}1A;UݛM Y դYϔldq BG]y5UNp)sg^O~fH lYѠ#EGW+>VFꑢ?7`/+VZ{yn`!GlBl` C$){gzJ n_I2oWqylmU7 f_V>oUU NpEgrM}|!_jN8P/oWu6>QUNp+nZu57Oxt+V?T8-t9XI~Ob̋ek˭L/tuOa$ (p[5,ߗrۣzU1>gu00sM< m(p[\7żUCp/嫹ekm}Ox }1;%a|:Gr['IoC\Bg勹=RYT1^H;fY/xܾ\ Np당)͌:+t8<M(pJMtf3"3 y|&(̚^3ބ'\_͍C4"m< |+` Sނ'ư<_-HH9$A%WeoA*.^u_e_1T)q.53o!0wDr 伧>޾63AxJ[P,ٜzQ9]qW`WocxZ[P6G*kʻg𼿏G)_)oA^RT\/2w2)}ڟ ( 8=PZ}r]Az3ג\9o'N_ oxK,_#O6XE*S2n6$8 (33!_VCm)_)07w%xyu5 lz+]r[֜5gZ#:tUyOM>n-Q/=\S,OaY!.8j(C~LվWwkN⛪Z+#sVSC"_9;4!3꾸i+t"liFח\?Ս붿R“F,$IX w.^Unj{uy_t|L}5e?{zs}lnvڢμs` Pau,]m+L)mЗo7O%Ɲ,پ~oaK-~O[vK6 6.AO x 5bKoj[|JA?j)V#/wڹ t=yI2-YV:\+緖/c'8 Nj,Mmk WCu電|iDaa۠a""׮=rfGn6qHm0=^o/|lu9| P^rKhsnayE$3dW?+fV*[J_/2wt}#9]Ňomʿm3b̿q0W#פ9BQ[h>)UΈ\mwNCwܑK èw4RJ-*Npev~BrwQ5uPL4+w4:Z ~1~q"}J ÈYa9:{r޳Q5noKs>DɞU"I OciTZEھDُx(p1;Kp{hW۫Z>hoSh#SJ-k #7YI)em7uNRa!,:|2د*H+dF|n|uܩZ+#h;o6swxaì@e evCm8% -LOV0Yqy"IfUWŜe?lM-:Yn[E.>"THbVP3̽>vmIy~NUs[WHF׿)n;fE_wOi7:d;Q4QO@E 'Y[~rM;^IsH~_6hiSO]\Zh*  ;Kpg_V^Z>Ky~K߰IV Vd#,4+q#$"EX0:PqZ{o0aYʕ(dוc0+!i+XQ-h#_^b~`#.hY~EvA?o+9z{AGYMt?'>9(^|}&E F >Dq[Gi-1t ]F׿4gNwESFkceߦh>Zu|H]me?':ѭR/YzKЙ iJL+Ň9}bg%H':! ڕ\Cz6赭B)E rr1Agsn* 8&>m#*R)( # _Z>s͂2j{uyYK- Aj^F;#v"H;&%E KCiF^+1az#)\@ibܼVO vV2tɶa6\)7 bxHU̾knXM<]Kxaa۠Zh7 P=OXtSVBEO}5lK1dɮ=?,gh_ț%rM|Zo 8e]SI7aT֧o+LBO5tޡSy8Б\W-{6u-)iVoeǛM(pT_W~sնxi{Io -s0CT]-t+Rbp⽤Z[ =Zt0Wo'w#S ǒN$|S}GJ7GFG<xCŇ˿jq"Ifd=鞇cc=7pIA}I*r{ttNkd_7m *^˿4UUq `z蠉fE $=nMmO uѢ ;o&ѭʭL`¶(6Z:>K| |鯛t+z\$ K':6~xͲet}鮝`VS8/(xH IKUbi˰n"w/;}-ckOo5q&!3ۄ HZGr(Oi{W0Ӭp3 2v^*[€#E'q3M%<ع;v`]͇V&eK-@Ixhk[T+v^6"?S% -赓lY+t:"X11x})>˜W7THbPp#C?N"=BDf&EfgcύQ$աm඿kƖ0hjk^)RxmE⚼^*i'&ò>-Wꢘ N7` 0/2mu]mO£ ZB# AG :;QMC=k|̉'s*zm{`Pİ֡;5u;\tT_M!p: .:cjYJm(6qE킖D9ݚfytO6.kf ܫʕWxG~z]5m,'/nbO?vS'܉TέK^ ?\x&E}4G(n9mNkC]l6U!`c$cOK Țfdz Cta'@kl P/0߹-Mۇ nbt ̄&lMwzGbTo^/t|X|vhm}&Hy&;>RYz)cbZ œN7ϗo# HW!?_xȞ(n¿+gO|U[۵ TڃY_kW5\g&AAxZZXZek5VF^na%<&┄8NaFA4WXy?/`c$W+bIEJQ`F+h+/e9bĊΜٷ&Ί')ݼ'C?׆8{%X]g#Ηv]zd7<.\  ؛a=]7B3 nNXW#>\fkkgHS`W!D)wP d%g7"m̮)"F}q[A]jC_9TxjuWMe<&W-O:vpO+0Qn$\= tnlQ,ٜzQ=Hx?oZfwA|fEߦ"E3Oocl;V{ȻkۗiMi*k#WQmr͋-p 0h7#/VQBQOMP:^fC*}͞1{T)euGa/RHek#/uMRw7 r!Gy7A\,0w︷{]-nR$ +9gt7+S:S$X>hQM.nA4Sݮ[RE=g~OG*[2F:ܯ5W۰|+VF@^F=9/ cw=Ztqy8*"@4#3ah?7&$hAޥ}}v[߰ uR[^D%(҄8!Ԗ!"ׄq躿s̉quȸYX8d|\V!((sA겎A LBEͻ\7ft Q!o7S̹\ecXjc'_ӹ??оs6ٿ|)k%G&4n':qsiŜb^p;0jɒΞ+j^,ihBC鶊𷫅\r?cٹG1ra w4]_g9yS)cKvM CB6M(sI$bi&}{Tt›ΈG/ޣ2w7d\o.m'Wm,NY U}9?+w?3L1sq ݵs />AW;r; P[~dgX{smMۗi +uj b|9ϮQ˗^w719/5Mem(VʣO6+<|U_,|(dݦemm֜Vڒ#?uW'`sR7 ;q1pڙ7 >i#MϘ*pAC)d|!eei-)&2WG 5ٞhvb?AB?ɰ,u3PffBR/r&N /Ö0j!1,3 Bm]Y1W@ԏ|hz펶8$A>^ /'${qbpD" ཨl[eĮ .sgc-V_rMUZ1cucݛ{x?Fͳe6{3 n+ (AC*鳶/K z<.VVvfZߺ/\AU(?ş-0أV+aOr-^XAsqG~3kKƨy/$wGnwˬ-O:C|5UKã謘7EતirygyP[>jĠGHymR[˝yw?TPk&㯹g}q[yZOYR>^ c }YaYiCwEJ8+xm|gG.v )JsC#{˸sS1dqg5\åJrЬ૿ԟ6ۑdNWbŧ}-:[s/vf l['*6s&vV55j"竹5w,ξLpgb絒p>LB?$ɬ2$Vu7!OR[rwBٝqRnNrwR% ĈΤM\.hC+ӆay|dGC!x: PeYWt*Qk'FmPHek2 u&6)A1BpVb J>H}eMV8S9iM6Tmg^S׼aY.hcEg+N7h!GqxJg.A3U[z#۶o*HZ+#o;fƖPy +9@(Ͷ%rt}K1I au%d<;|'lMw2^q1+@ ;Kp;ͿvҶ*d]ieDGq3=䝊_im"9a3}B\B@3_[%vo/nee$=i;J!<N O5=^(2#W%>O:trkr>iOadd".eXKt,)A<(^|%OkSg:g@u}n aO\=֛ܻ8<+qqi#q)d|lú2nfE1/lf/?|z 4Kp~_[Z+#l;n᪮9Iv-(pk8RtVSW I C*7gͰ:Kw]Uv(Ga=1j+UiM}oe#:G9@ Fח/lLei+?.O}C_|=Rٚ}v̾'>sw*S,78gԖq1B6Jq GAG:Z6~P(CZ=B7?\x"zӋ/)p9V9SR}u7loKTS /=t_wQܼUbiLq bn+JWr$RK _h 'jm/1#/4a';KpWh~q[Έ<_S`llP!UfF;C?*9ݐtܙaYBm/{LtgR}Ir3Vg2v3o7G (2On=k&Hw:]% 5x^gv#9=$ҥYs_;k/s EYGiI1\cf_Џ mTE);KpOnO׏IBi/>T_Zmw.&)ݼn3'G':Q~ tCk, B.4a"NI`d\W}ǎ&:XY/U_{WmO:c!ΐ):I >3qV*븧3;^y0jn:Pks g^{Ȼ3ֺŷԋR/zYLt|lg;Kp[՟lԗ#>13X'3 F(L,"E) +͌boaDr>rObqJG՗N/M)/\Qd(N!-6}QwnGs*W:WK!M`qcE^!IZ3=^.0/tZ_Rp8!tI U]sEbbCjgJn{Llm/^RL _FenHJN'/4tx/$Hv&%H+kT?a钭~RsāWgkNV(vIE ʗ:{|-9َ Rr@qestʶ$ਿ&I| PchmEr=gO-0`>|~mw<Տݑ %%H$Hw֕q3kʺ1YI?rюbŜ q 8 Nk%[kdG#9m&s{Tt[z p88xѢCxi{6!~ O1r= nS%Ǻ7?Zim'^{c7{Ƌ*<8Ʊ1kXxXC"[D/-4+dBi/>>t<ܤ ?^ouGqI2_+SAy_kN5GÙҬ{v$}^JL+ʕ۞'rܰ?t y?Fj88Fz5h+HZ~0A{3 #?ՏR`B,ǓvHɡ%ʼnOc\WgMJ&x%l,,];,S$2QOT.wˬ-[KqLPSB;~NSY򻹸]! +y(pBv;~v&V!7GEd[ tLr>)9]t8#I&%HD NX%8EF:`*6".e (ԳS,(CڸFRN.*!G6 v7"z2+C~Yi-}&Ml Z)y,0ڙJ[U 嗞!o p PgџG9Lp{tL[EXm.vfyn]I !Uq,A7,e2 y[^|)&3tGCg^>?,ghO|g}G9-W}EAM,λ~:8mpH~/;e\-4w~6TY`Hs"Ĝ( sxѱp (pBVlw䥖0gg\bNuEQF:5 %՗&m QWt* 2E3yz{;ڬ{6>Qu()gK7mNMv.\xȨiҙ-[&,gj0sE*_olQn]'Nkce۞&lxv69$I 㧌'N3.iN|n|uC쐢H99$_љ:4Kp$dy$v Ic;#ŜBwmg w?crgۻUO۟FCi(6KtOi$܂H =ZxQ%|]wkN3>bx]>o^(_M GV+ϡP-]@;ӕj2mc?Xzbnт6eo5ʧ+:W(p1Bٺ_jv&6ZOMI P>.f6LtqfCthᥤi򈺒AJJ{u%7<->OsjcLÈύN0Wik=ZtqPhR49DHW>՗=ې"y9{WISjm=BŖmC£ 4|$;-e6]f  Aծjkgו; |DM#0LN']7wIi_K|")AZHEw3~xB=#EvAK/cD?a-lveۑ)-"ï瞷Zs^8CמoMo[,Lbs# #>gpnRe闞!o״áz,^5"m"7wd{tgF;+րxq l՝!I'K6F(vA2o~=5xsI ҝMʣJu^Ql23 _z#"nQK-xE\ړay 0KX>&*kK'0ĝ0Au_pJ3". ŝYliO0htRnZ,Lp]wkm޾w$#F6/̅!x]*ׇڷOtA5bD6 VGrG{['N:]řXEڮgfG%듎UOg IDAT>7]J0QCVe+RͿ\~ƊO1;+¡CZ2'Սp"Ln9 ;Fx3q^Q~Wl}!^1sgF0MgL}{)M7ŝp3JLK]H7Ml :t36M8U`g%K!:_Fz;VtCղ~#IUi7T_UEߧs}?Vs&.)A\UM :2nOi+:o\t |z3 >NMi)c<ѾH:_Ռ/=BގrM\V,Oih!}r~EAptv.O̯u66`ҙ-vVMμ]bi^q=n97I2ZWTe$A!TGqJ(pVwHJ> aVס ^ȝ{fhT7~ 8~w2 po^r++nVVuwSZHU_ٙE;r(0U_s 28chqwWU]*HG"6^ĥL{nv&V?NLzYFG +'f|7݂ۗ#L t Vc/tZG;;|T{ȣH{e:9:{Tmm ރ+K OJ-|M%t!+48?3 g^f9Y G>ۣ4xG;ФeVK2>K(9(}I6`9!r6_'_"cLNu_go.)D`T$kSSZlͺoL$h(NS ^dս[ew@iqyGM־{ެ677r׵+O@š ngrYhwoq;/I[je|'/Y\ɕ#gdzpMJ-[r,\]YNr,wͷdϸ8ϻKWԱLsߤB'idR/z 12Vs^K>.Em1BWXnFD"gncsqe%w=evŘ)zڙ l '{@dyipDMMY"HG@(YbHvAkA|5zjx))F&#OLh$tqKѭ6k_uCe}ɮ%C|; Q_S^#eGy'488 ,畝!>gCw4~WW?RyHQWzK=6YvK7\7KS?ѽvkua'`9j%_B4adxu4J6d<48xSxL'\_B0"ISsn s6WNdYzp0g[q[;Coc>"}ݱ[THYҮ9}#dǪIeU|j4\M~Lޘx?1soٗ{q-ŷDI{Ӽo/rc#ZgTܔXچjÚΣJf_1"w$qT)wFnH3Lkr<יqxŏo"_Bba8qnigyb'H czg >\RcO>P_AA/ĵ,!٘·?0$$ o'CarpS枉 G2L15&m%zwgM A颟~{:r|ro&{gj}Wŝ\9uncadTTP!A',6'ydfroΚk13%XWD46Η\2E7;7 L-TznVKղ!}4I0A/ߵqxukB<&<ޓO ;:U]yҸͿ xc#:1Ôp,DS}A2:_hB4_h*g̳WΨμ~8uY73Tr{s3gPOS|p䫯o_מg~0 <9l10pBd*VRUٗ 'v%~u^U-FͲ[Y; #]4i1zy@w`gT]LJ%d!ےVjkRS9H9elx5;K/Y:'f<3UdkYSO$jxv \g!'Iɡ:/y>vKV[bԚ?>5 9| Xx48_y\2"Cu^޿X+J9*uCࡷ֘zcet嘻R`jsժiq!2͜tvNo_+.=>$OM>Ý{λ/ԻM7 0bSe31愿,235UKҙS"^6TNxwأ[>0)/U8樰 :SٗsZ]u\uɡ!"J@޼<;P:ko3w \>?<_Kƪפ_-O_|{f q+Y᤯(\aA# iL?18Pn|]o;nZyfꟘaNghg9a]NDic ސy],.5 bW )Ž0dʉµ>#[R4M jWvw\xXji`"ZnͺUje|]矵ꚮuĬ>M?O $ ';4zYpɉkݴJYw8r=Ս_*YT]XOv{s>=ᙡwDf$[c[$f{Y\绮.|id>m}jC${Ő~UmUŖlv%VHC$ךȲ^vK;S@w?!QW*׽B;癘/,eÉrtּ9X|׻g-ZU,fu?wh߱Ls3ٖ&+޲Y ^,Yܺ>LjX•Tdkᥒ| ą>l B:c~·;uio Noi_SZ3_{^5w`K FӤvEr5kmZHH #R)WOc)$-RlsfPM)$,#vƎƑYN$Dl+\rқ>s-]_:+"M3{ZsʚO Id['fd;ƛP$ɦ m#\Ce!j^C7l4g+ͶFBW\7 mmڑni:+^ ^)Ŷ~ʞNf8g;MoZWLU3\z䐐^|K%_}}}gUEv<L,t6v Z%_r/'H{(ّM/SD5:847?|*JF{F.W*U8#}CD3:X&{#N˴檕RԚs'>5 klg[ ';1X|$Qhaf%ffyb'o _ P(-ܖn=&1G"Diw@xwېfXK]=gV{S=*=g`а>)vvؙ}%&=OzJ6V&UꏷuoU佔j 9zF5fhm:ޤB3Q`,W IhwL.0K0%\ȵtFTriDX'>o@FVjots=gq-ۃO ^:k$tF-k zEmh5l2w>5%P:~n+|WR{Ӷ۳Wǎ Aɡ׎ZG̊["C{gT"9O+xyK/Y6j|?ƌwއK+bTCQß_-dԪ.HF-]{IY&$M&g}OΙCBz7AȲ^vK;S@wWLL8OQJ3|Z۔ZY޾w*Els?ѝ+%1]t" _`bK&-* ǿoJӏ7䵚kDw&5UP I.~s>jo[&ZjZxTbX*#oO;x@Efƿctܛ{j^ku}ƾ3ZVLj\χ]*Kknf&j Jv, n?<;f2V'/ΝH.=1+^l`쐷o<@w{C b B  1%fV8eeVT,s/~Ot8O6SSTtE:"4Us Ycb[ycѽEo2k_jk\^\r! Q'7 m?gyt9goCe/ZlHOܑnynC E:FHMl0$Z*tvN4l7,HK fT/CCa3XN§" ocK-g Τ"[+8p:<<`TbvOvV] :. %MD&6QnX)TN`3 VU ]zsկT?,5ri&=aJS^ Doc(yG:12{d҄UQҍ'+hg9au^8%_xbtt#6,A}Cc(ufK M4iƨA]6w&U.`9AJ-?Lj+ Uwsӣ%gg s>F$)MR۞9 IDATQ1/,ӓo.rbThk'TUhp暘aj%Vecn[%)@Bnk%'~>{:>0TNJ3X^\r5 tP%T]qG 6ga|btZjaSdžǾZ mS8jGY/׎tV۲X󽆮,O.a'uZ8T7.Hǀ(mFN t,.9FG(_s51!!M%$u .cnns-Is ]YRlc8ƌKԭ,!wd%Go@oƩ~o)M?oN4B3NP{4_ N/7b@e'8IѽXk\v2;8)_ssug1Z+A>9iPٖ|՝ߓ XURO{#'392Kk1UetF|b44G s-Z>Rev\p@b[E|szQ %4it}4HBs!2:ʈ}#d<}Ad+Lu೫ܓ{*bs3$gqIVeP{eo3LګFtE1BS_v{O_&L?v7FH29c~.~[VOMn.줎'8jmH7̔iX{'$1B%g1BICB=å kf' o8ށBP!s;;Kkf^dfL8fTVlpNVjo fwK0k'4 _[^MDetFzKN\qߠ!7+×$RXCBz5g+M뮌 T,Q'k+#AMʕ'8 jjeVfZ{CLkl-5QCylQamO^̄HYRD->@JQlW'DD1(]#WzudXU*k6۲nlM7ݦHu\ePy$qv>4y$Kπ76St l`gYv!$#Ex'<!$Arr:/XF?^?UkSbUk?)G̦tg@g=P R+M晘`Lf5E,_TOvMϑ HcQ.! TGUmnAh(ysTXʾsmnJRmqYԓ mXV+:| LL btiЃy{ 'C>4I2,x*sC[W~Sj-Μ˫VW" gzGT@egevIBc*>{?p4-NC F{ )¾ߜ?? +z8U˨ĂZtru Q+2Х·Nė"EctjQCɣ{KOU醑g߼Z`m<]YNh*V68T7嫑PŘtqG 6󩹩Q=)#P{CB|3E:*zc=sKs=cE x'TJlpS zSqQ b!0q±|1 BH An`jrvA{LJ S+zD./gB ]Y\'8ҿ> :J+$V$LE6U?eʉµ%'2nI; Jz#PuI?`e??~kV,K/w|R[SK0PQTmSސg3?]a*zUwS_?U+qh$tqS?=O>;7Ys?T,ڳFtKL/;~t}dX1[iJ>B箌밾Owm>P|5itx!lV۲|jz4ݓФYvRʨ N4MUH.4T bz79_]ErAΩ!= ӈR pBD|P$ɺr 6>찐ct B%$uG3 2U"3?`1$huTHj=;lLӦ/w|Hhe}|䋊dզv N N 7sv:!=~Őg]-"D)N Y*sf~c)l^zT `ZM|iz;gTӹ [zeO־Sy'J;54WFo4XzU]+f&`}a*i(v&&ZY"J?b F;$щۏ<3/ʞ2ZHu8I@TN=,-^/#itx.! 9~CKmg߸Z`m_^\黧s#Q񮟗/.XSM l#l} K͒3b[oP~rށ%kצ+pq҉!!}%TD-t*70A_̵v*GR+10Q"C$zwUoK7qvetgSeF4E2arAM,F_vN9HW+:eu;쐐"3Kim~[g~0:Q/G4S}5xMi9mCRTى,K 6!9ƾ$TSٖ本95f5ӚVF)0J5Oo9\dw \894"l N=wI)gQKM<}=+ÉK,h~L;b8w`˖9SC|DO7QnJ)x77*@T_%xRfc}޿Xs+.Z{+w \ФU3!z0߅\Kך!28"Vu^}NdYzϿTj'C$syDXǶzemLb] /U丮@m?R儕7j·f&?ushl/FySgjZQ{m~`Tgqa Av 1YlɅCbg #;=6qäg TjKyNcl@$I{?mF2I؟r/6RؐC%g0:ufuRUZbe vVQ: ]/4U'/dۮ{KB112?XYB5=q3x-ڎy2#PP[E3S g0G:WȰvJoGXVM D*(*ϧWJ? P$R$Yj?('yd88sQoc=EH PwU'o8Q`mMXRAVwBkJS5^Esm}`SdφCIVwf_MG3[DCY*҅z;D<ޗ{ךV0MmO7B/Q NQIâN~]39^/Us<sOΩ[E֖9~:^P>Wt6OGп Ȏ;s._ͷw+I˝qPERӍkeZ+X* WtVB;\zJ612H+5fǫmTSٖD%/꫄OOF%ӃH~覙uwy}Zp9狗4#S~h~,KW76h(1|wPɹ)' >(7uY\kۚ '&&U[NKc)n,vcY>}¥jl,!ZvK;k6V]oX(̵?lˎaU> H3P~˅ѕطp]딘aNݜ:B47 PqeVU> y@݂g5Yƕ;Ŷֵ-^?ē@ ;}T398|"sQ j2zIR̕O~Vje}B]!X%_MwZE3o~ET$X&u YM$4iYoި#۝WK,릙U IKy;^=f'@ŔmYXje9J`97@p#\y8 % PYF6)TsbWMmZ+LJ'ĵu&@5T 5c]B0JU#ʼ9 F njs4I2,'||񷵵G3VZc-5l8(5G=φj>w j_Jr+Gxj\j`rpiVdfaFTM@u2-*H #p ';<&@݄g XCȵw͹Pt_+]yL;]pRHʓ HS0i5Fǽfū9?$W 5 PJ|KWb/Y[]gT?!Z{T+U32Fo9@ #^WHxN ;[jD0Ru5dVKa+_^+Ԇ-U?Yoc|g=48jt|.K>I||6g|KH;}଱oju%|xdamSk۔b[f$@Ih2z9_y[g{w5r}ZAד/Rvԕu$~e!0jV/T>C$6Rlxw4F>]hpְiRYz*ܓy6wL)MuU9)K7H0" 'ѤsQ(A/2 @݅g \+gw=2z.ҍyjN5aHlߌ5u%]4s4 \ZM@${ 0"ubp 1xE4@Pcjsժ#@ ݏ,'p94iBф:W I=* Dr*Ca-w 'v!HrЎro8e RsӍLVVc"J;r-?Ũ&88B`cƣ }7Ѥ[ܠ(*vE(LTʃ'w7o޽PwIe-@)]?%*3zX.Z\ȱtyXc|tY3?d{EHO)Šӆ4I ; (hپ2+Sfe}y2:/HF7S I hjr" _dfE4iSe AV휲Ѧy&G0 0-1GaKJ,IJD4iFUGd-YwRKlbũV:$XBqLsc IDATeӥE7FQlHb (ˎzpl#e|#WB,QD6ÉW0i:%[C%OL/R}n+%[31?覯Z`f+K MF}wa@u h#ڍkgReN[O)xz`>1>ϛ_idBdP9YPqv\gqN[6 Uiu olLZǕinANqHW&/ܘedM3"IYJ^('"a=hnTlKWIOt^iW9_*ܑ qԦ4f3|]!#w39|!}|̖oJ\(mT @䚘i kOs>5hŒ:n4I2 '~\wpOc Q:O_f[% GrO־ʊz*эP14 N;K^(^‡dWտިV@M\=881Ü 7Bz̰~TO_DkOׅarlϪ;i^* 2;/;3GQlQ'Jw%98B /JN}"ǯ kf'|tU9 cbP68O oHO9U];"WbSKl?{˽(¶;c#h^j#,|^ B#:@՘N,Y;˕ؙ-˥4iF:#73A22,'S[Ǝofe qZ{X? ]F Ums- cOd{6(43+HG_ahMbԂt|ݛo)YJDMQeTr/zjȷ&A,sSui͜a8-0~\)7>BR?rfUAJM " _la4b0 Ft[& MHIw]\ MN68X1u}F ~}UW˵tkJ{}A&P%|M\-xꧦ~{.^:?jOom @M()oWLbC@jq%VgcU^.Y@ynZyfJm|Fl#|ǤBdb#aB[T 77c9r!Hrg*5v \c-E(`1|ojѤYqEP1_;G7"ǤJc[cZt%B˶thT iiR R_G&d \mp)EF2@gJAFM4iF "ߐ`b |eRzT _L~e]qqƝ\W6y q,ǗkDwwjSOrwD.i;P9 XvK7˕fB9b3*?'N蝕sΙ;w * |tѫTkO־mg9!~5Vl̔kV%Y.Y6wrSo 6A`4S&OU(5k 7$ t*"V8㟯z?A%@gT+St\}ϷBjPOʕP p+VpQ '*[uG7n>Z+E4oܛtGSw-ǢrPl6Ѯ;G~Mq]a޹s͛7[߾uerrr|jjjl?A{F}O("3/I[.k^-2>/^k\j@E=y'~gkW_} p%pA{[m&"a&9=|GbGgK*vCMLL8w\7"AUkNe{^ɳt0\>oP(&Tg[lD6PC溥]5s=3t-~:2=IA%2L}؛eYGxag9JDoߌ%5]fͫ{zwh{aԊu^2hZ _\ Xd׆4N(mC<};^^/]5R!؞nihyK.Z5$4i9jNVEskV~sPv"{Ÿܯ5SS%2"+{O48"Nf{UWR`aJ!pU>6'fab_?Ykeaa8ɦ4Mi5;_u`p=TvhʕӾZh~aAA *݌N>tϿ7rS$ΎWJTR498a3Ց\RqfXh{ʑ HN~zwm͟?Ămf8ڸq&g~f&Ǟ<$f%VǯL@Jj''ֵ0 T5+6v\knO7ݜz&#/oܹSK'MZwlt?;+uwPM76i %)]B !YBI覛NLqnK3n5H=d;Gܻ=U)P=C]^^} Mt S_Ujπi!ͣLt}|.$YGPثvrW7J*=?S_.p1>?a2>SEڧqK[RjGXDco]dA/8^nnNxvN:p/_(v?re p&22QEJ Qj aF-?ÎMCWI+Z" ֚YCyۯh*c>>eEEDFduJB'JC*cƌ0bĈ-zC޾߾nw{3ψ +pbN^sQ$ zrv5 Ύ׷K.Gtzk׮#"";|_Lյ;AByKl!fRa[xbXn8gUߚ8qՐ!CI$Rz{^l+|GKӚsp:C(~!חʱc:9-[dm۶;p y>%Ic` 8] N($Nkl!!׷aÆ1,Ў#H:^3 vɢcjw-עg^ߑ#G8X7oC32}T\=ML]a-M$~5Ckymm,N;EWl(56PLNmj{-\nf-6_сa=_6ViM;;m"'|$(O M^E` a L+nNu6ᒳ{P/&SmHu~ܚΡh\P KSX*\dWl?eM[Q{˺q"0j,5SNEqyzv6B0jE}T&G$;yY}fy#8#Y{\Bcl}UlOLzx(o^ap" ."䱔nCSY[{Jb!2[mh(/8 NrNioOz`mٯފ^|mOcB E\vxE v}J]pEYdVFpZB'|~䟶.\zzwb}3˙(k1 a'*#z"yh;ٺ:ޛ&hRW<&A`Nw'**rE-ٺRdVuwWEa'+6OyI-J/cNM=~D"YW'FpY(60$ vV!9c8.P/ȐEڐEd~Rd GpFyaM -Z*,b_ͺXh1z[¥/ he&AX73ҋ+iC98_0x1_ٺoeWJ A9sxEa'M uN$"@^][[J/2a֌$FaNOQ9JLmOB5IFUl֬N)uJ8Y` BLyγ6.1A$ vF!9e|j&ʚ0sp 󃑕, U޼q{o~M4ԩӱ=zioFgU?{Vs M_jg vҵkÁAu#7`ޮYD[Hf~%NSq[aٳg:x{k=$'#[޽ݻ6|/M!Csm[MIIZȈ?vc:͜1cidTTfnRsG Kg>yR'շl>z=9jԦkd Kq y@IzlOBIqgWPVPT*֭[7n5=}Z {߻W?jڬٕ ozذa^jdvee7o꧟Ə(f֬^V5?~رcׇ)p,W\ivʕf .t|oԷo=4-0#C$ּ.HM;'bem-nI那Ud ј++;p|AV9f-Bqծ^lĉ"#",.. @VR兾?o##"NMx欙3WZ٨7Gnx\YWcaYѣ JTT_M3 bdANwf`*b~. hF!9ؕ&Z*\!ʋF\޽{w/mB¹ݻ@T^Vղeӣk|tOzM2jusԊ~ʕSYq>(e˖1 +U! <2ӧ/] ֏XX YOVspJЗ7J?ڪ52[k!VѾGΌߚ7 spk+(0~ܺFqqҒY3g.]ݻw @Fj߬h…5j2?FAo߫hDVeefFM7n]\llK["#051~dO8Wcжf]Ut SE>rs5aNpU۰aچ2f0~:Q\\0tcFڔ@W./:%%ʌBFUݽsAvN/?o242Nk5dOy`՝6%$-lKNpK:V;~ק^U^^(2 ׏mܨ η?~S\ll;p=NLlwk@6UM&IH8yf`2LTŽskڲ&?w~sv3:Gp6PBvWٲEK~md1dd蔔t|N+_n\=dgGJp]%# us pe/]jѺU W\nllspk&-g_L+4ڴWJ,uspFzx=\Fzzmݹ};p,^/z9/^٧zk5-s͛7ecpe9r+#8O]9r4p[/_ŚQx=\ŋZkLU `~o.\0uϙ5kE! {0!2ZշO۶ml*VfRXV@l!^)U7ܹs;G  ?wUw?_:㛯=vl>)SV"LFhTdܙCr,AP)Gg ۺRUtJ:\ h;| ճ灲R_d?޷tsھ}ېY~z㏓-cd\`8pu pWyEԘ-~W*jSB )s0 kp >鴲}kc6o 5--5]…ǎ{|d׮ peEEzN'Eza ǯʌB6=Ȩ1V#3 L-4ƶܞ}z!ޖt>_WJk$u^6nL;w̟7oӧ/{YU)+-Ea={˖')u9c/hו_&XExoLe˖MzuG[;v5k&'=בÇ[v<2W^2 w98]`oO_ز_bAk2\4L#8ET*||Ll7nzsW'¼0}ڴU!N]L;98-AN?WS?XΟpEW1U슝oN̿ |z+n# r-[\ȟ~gOVCBYiK@&ܹs p67.;HGQmX3(d5* 5spFz 0&9sy$K_,HD/ $IZg\F7y/->obEEE%%%~EEEEEE%%~J2֭[qZF·x.X0gذaҴ uRs|||`0zDKJJJrrrJK}?8iOiӶ홠|=,xqzJ UF/=͝;}{*_ty:&mOBuB %;ߚ'Fp\z{!XlhٲqqnUZ1za'`YNKKkxС)uxbK$tF,=޸qqƯu}w >"R'$$ިqQQQRLn׬Z]ٳn݊Ku+ϟo͇:t3W|r'},O۴i{**5oܸ/]1|._ԢyW*]f!yT>'e"U{o&>X:WToϛ7ϙ7^^ӷΝ;˽;F7ݜ1%9kV~kڵ23ϒŋg7nA{d-ˏCPppֽ:Of4ݠAGl!鴲'O&:_V\T֬Y3Y) /GZBBBBBBZj:BNNvDJJJݻ>|+c6 g(Npg6j#8Od;&9gkq(VP$2 3kC4RNugϞVKIIcת]+degG[aL~w Jϙ=g5V~a-<ȨqرN|Srrr|siڬٕM7|o>ҥaO)nT*oxÆ1M\sF,FAiӦQ4=n{㚟Κ? tΊD|lq98S/Pe?W^)eY` 0e c=z3$44o߿{Ν;;oo2 'Mz燇Eߐ:|ݺq|Ϗ3bSn;]t#xzQeb~Ԩ/_tێԭ6_?=U g?zO>Y"5<>c6 PwP/Yy#8g],^4j4z\yfM'rÆ cyaÇ~~IUDx2\?st;d\Y ,0DYgv (/]Cn];ޚ~b[H6>ر}`G߿#]^,jiUٳmկc߅-[GgΞmӶcGlVZ?w|su/d$)vO?oĎϟpN'T r "U): ouT3z:ѣG;s} _?Nuh:2,$$4o=CBC<ӧ1ο(իM>NtZϜ=ۦFqOTTgΞmӰaS*RSo!7~BY\\l]#8O۵ޑsQ)ږmWf'xaOTдm Ʀ"&k!wLZٳgO8ё 83$"(@OGpkn)?$0/OTfg>zk Ŧ_#'wvn 5spF1&w4+c~͛iJ9҅c,iIIeV>/,^e嗢QFmmR Qtr$2*|<32;[3S@f$*B:Yz慢ŶULO[) A|Me_D۷}$Tn\B侧U_*mvd=\Ŗ^>$Z:Uk_)[]-4Gc~? 'ؔf0np4[]^mv 7n|]fͮػM R|Uy}|Y lC۩Sc\8RbbIXlQWUghٯ[m~t؝{껴}ܞs1hDߞRc_R]*6^vC1+mw0/9ODŽW/6$$$#҂ .!7sK5WVZ]-9d2&&&涽-/+h^0 ,AP;kv؝{ꛛزo=[~+=v,KD۷s5f27bgabd\M9vͶf_t(oٺ9H?R{j/7K?x0omis|}*zJ+ʦu`Iz9g/Zt:N5-'h^^y߹!9Uxp gyFk\f߮[> dh}<<]jd[H].&nO˖+@ޯwipD>'Պ uMGv^_+|!::, /vkU|!_ޯw~pDk!Y[ \ B 4rV"Q.R8cWz+bM9uquyJA&ê_E)26LrF) 51󆟸( (pZWey:&ݽ4$'¼ZPZ|.EbA*i]Eю_ (pz B0xHdĵ g?oOcН o][S;.SD}Z`,V.L&D0 g[b"WPy^^60x\ 7spYB2Y51}4I@fOWg /~Ughv:G߮]4n)Xl tRZ텞 +H$zdp-)p~vx+%Ŀ=Լ~H+{NAo4hvO9o]1z:MPyb؀,3Ǖ(_ wL >VO>.)x^eq੶gSL34K*Eo8w9?\[ sΥ"c|ԑXtL#pK 3BY@/ǝ"S m?:)ŕ IDAT'7& Ɲ:%*_+LRÄtLJDŽiP SiJ-cͬEs9kۣ n+pV*Z,_}Zfz^=f !2*Q|QT SjaJ-Vg-V(24-$ ? r24{cU4HJҲ?be s+yZ&8 Dc {r :"ҤXBԘZ#+|Q0a˫ dI@{+3v]Hl=c#+=ϥ2!(ZGA\SJ\폑?^ gC^iYndѭ-$ˑp$Ač|CcW xc5u/p$ts'BneL6n%? ^֯{С^A4\QF֗ iA$N[lϾeB}H-'L&~sW_MKw2BA(8F;ft_U<3L@iy:ۺIA/8WzM0a ` 뻒&IdJ;8Z]Z/KHqA{Ɏп_r޽=Ȩ]+xؽ_7"]DIH *.%=~v`xEN8$>={"`+狄i rA'M^yr>{ݶRo0pE@ Ul=|ޭ[JJlKތ&~K䢖UP)ghߺQ_t (vOߌ5̹ 2ә???gf1| p`Mqסl34ZL|UCet^;M Jde'3.]d*~-Q1gXuL3 n2z(`G2ז\-8TV1f|C_-xL=^jOdYWp_r!qV`?^ H 2:w|}+N==@ΚEt6G{vj_ "T|iGA^Bs_F P[]člw2[l=hz B?v"M97(<j*|~pD>.,-+D#hϞ>L=Qm(J4$#^"}ujCwd,!zVnz1ZIQVƕ HF!9[v7K/no>@&l,S$m}:|ҧej qƪMCDWqY&rO'> q:6XRRI!ss}kt*;#$ܩ#'=v6f"=[߾Ou^.fϚh0 7-oWCVԟnT2O|/۾5d{(`>=O}\- ZU@=LLnUe)k$o<(pzI~Ύǧ,..Vj՞yyy)b=”RU lAL~qc//( >ztt# -RVa V e K5 Ӥ!PBHiU Etت쌅+\VԊFlZߦ94~L 4&W| ߓ34Qqg=7*>(ڰaGO" kk;tH<pv%]!s{VˉY7RT~E:3+}T1EB?=#'+*p u*Kap|;nʼn':q}ǯ]/F<[a[l˾UqϾQ2=f !p\2'nU>Fj՞>ǟVMDq]SX$*j;jzM=] **pطo_o.ڭ[JjZZäNǐmv^34sqH+{eb:DHOUbcl [\Wh⎝;^jdsUseZٔ_;)Z-1Rj':X͹j^w8S*ii6R#;Rd0Ne:X|d4E0 m6 Y$D܇8 ?_c+1~Ok,1AX>τbd5>8X P{d}|_L5`)jz,=^By'X,. N )]u|vIhY: N׆Bx __Rd4_L(EfQ'0|3BNg50t| QjE۝ѷe,[BX,6aVVV,G\"T|IDF:LrGHbN#%(ps!p\aNԶQH-d]5¥ihY:8w"h Y Np4Wf0%(pNbB5J*I}j>dy񵊶;ocb !n#X.ugZZZC\= Np$N+3"w[ Ν; Y`vŸ- kwK|ӯ,+41RZk+oBrU>!WqdeeFrMEEEnS$YۿS#pkyܢ)&~K M̼\=#%'D#n#{Ө^\9rիW*r=FAZn#L&ၧOVd .'^uEΎcH-4WgBrNB~<'GU|y`2csʕfFAlvQ>t t0(;va6N!Ek4Y@fgP[vAC>4k&Tv:"ŭ|pȑ.\9w\$)Qb86J2- \ʌJw'ϋ E.2 ;LHɉ-g;!&v ( %ég,ָ|N:Ձ=ag5ܹɵ) ,TT*ٻM.]|w(w;MQ7&$m?j&>N~Ni2w`:֍ZΟ?oJY,.9N+** yf#{#lڸq46w 9&GK@&MnE\HUb3>0WN|8π߲exK.,*, Dg!;I,n~w]u/^Њq 8[8i`6t QcI=Ӥ~[!o^yQ1A!9WvWU&7n'pӧoҥ\ț'bccSj;_ YQ.cڵ9؁LfZo>\ېÿm/|rW|>Qzb$AXJWXo\`ɞ[+$lڿl;_  4}[N,gZe|{`:ş}hUJKKn:Q{ *&{y\CInj'Ҩ^\ϷQຸX]Yۺuлw4 g4ztqXrpNNvgY.pk ="fNbOe;G?o۷8uMONsbbIsʕfcnjـ_V}\UCdf͚uƕ!c2E ϟUt9,W&]p 8NǥmW?@tX;%%"Sb~|z\薄&(Tի&.\`#յk|A.]h?ooDDDdcޟ:uLjMl=p1gpyY?Ό,Ac…裏mZ 0'%%G ̟˖-#\l8G8jX?;*,W {/b%[-&1|>ILbbKGM,+*b^AŊ²}g~/evy9rg繻>̝̗wM1]^5{,Q,O j=w~IV#xt 0fz}Ç M<.&&Cc!!9;X6v/\ڷw={t?iotֿ)-[6OԱõqc` GuwN2$JH7G3ŋ]%?,&::cԩ}ĉ[hgB_d*cEbySOTmߒ 4iXu!ߴT.>pᄒbӔ)S6r=vS^}uCѣ wp|ys.?p8...ۧ32wi}ΞaJ&L;2 cǎW=<=|8իn1%z ""88z8QbKc=tʎ^«=כBbqǬYVzzzś8od8vA~^^`~^^`c=bnkf c/ƍSޞ?Ay#~Ze?8o֭mY,rquU=Rd=z0LӉ,KzNt:R{ IDATt ,,( }v{lHb}gnT.4Ki%DJ6nIտa3Nzk|MTԭ۷nEYԣG=:uDD9sOӽ ⯥{6K1Ah$:NT]]TTT䛟PVVɥg\ARW12mD[D*cEay^X5&m&mKL7lx՞1Yvzt۶7T*gd,٢Ep&؂H$~[s,PEEx%g@$^n}g_~HpOXnnM{rC̭ Cƴf[x-̛7Ǯ]k E6R}=9a„ 3fihz=<7 WO.4'vP̖lDjcK):&&Q?mÆ߇Bm6J$hx|+f M MWY3˻{'|"7Ef),A/Q?ǵ11,K.K? ->}א]xoݠ'ZBCӧO_H4OڋHzy„~w-KsW|Bâ)aCOYh׌7~ݻGJe25aDbvʕfDIQ̜s޳gH$"" A2b_z]/Ux4gs.MsKC\9K"`V4/jo͚r$+1q'N.A4SK/Ǿ[ G~~NXÏ(6#" 8kAKLz&}W.=cMfVV^/@kz왖v\{&niѲm;vxrl߾N "ϡ TA.NqmL|4Gv6mn|ܼE5̙,ɓ6Mѳgb_x|qN`bttL:Ͽয়y=Ν֓&OބBgdӦ;bSڵyDrYw=?zwCu*EL;.Hԟ ˫tСNK E'm8qw^vݻw|pShXéӦ2eF"DUf6loM6M޸aëY<)66_cҤICGmn=zi "ɡ EpWcGs52- >HeeKSc"H$Ri_a@@@~@@@_\+c<1&ݍv7wV#)(((((𯮪t:ѳz!˲dS!IP(/hK [(iĈ= yvc:R9tC4i~~~@aa+**d2wabX{X"Ѹ*~>}i 3{{2$$$)VbD"<;Ϊ\f....hZn}!]ZO.].R4y}}cHu՝fe:z?)]hrg;[Il_ɾkyL\ejvT߱Wi`\f.*$ti-,I-" A o0.+|$t1@h&=n&=F$,Qpt{pwp2Ae+:={Xe V[UJ=*5^SLE | Qp*S3. ;p̊>$~\Rov_^(Sx_0 @pN7uV1~~Y{UFn1&]*uTU=isGIw"M83+j&A`:@cҚY\|8ȹ = =.9#ҍUMm2}u7EH?=2vQp铋/_ ź_j'2@xN Mq<_?vG[(k_of /*OJ-ld>"yWi ]+ `ku(JЙݭזC'̬plU|\ejVcdttvԡ]z3+UyOEC8-AKlEg\(NVg[{vooC88DfW,W.({pLUT"kk*~RӅ볪6L ˛VpLMn:_3,o*H3p<Sd 4]R}b]x'rgN)Ģݰ:-,Ϩå\*w~25Gf8A+L,f8İ,%z"Xi긃1YVׂuBDו}'qm\9!c9ʅXM}km.M)`L-?Vy&bfYi% vd rq@ۇUa22OFd 1u#߳? e7OD*#OtS !#[ʛY?7X~6zX3 m俌o)*Dz2HN~Ya?z8\n`[Uom}8`c&F\s(f׭.<G~֚-w4[׌o9k+nOm95U)՘:pvW;jK^+Ԧ8ecw\\ŴЗP{ྡྷԗSJȭ659 kL~/1`_ѱ<9QhxeZ'WǦ3㰛̊_?UK9UkqTߡ۟;ws6 8`#ʹ"}wD?:6g<#_tgE']òVenT;`>a4~-1Z{c)8D8 (W!ʑ!g}gK%ζ`NMb]iZ Ķ; =X|@mdlR`֙YC%ӊt=}&\.C Z3+NJ.{آ1O/ ;XM;Pز_>THcE^\kWNCL/p.pzlC}wn(Jt'-b}ӎ+PRkߑKxpg {w)g _]p}X.eÚKU1]kǃ1hs '}7g'!/ MfX8YT#^T.Ҙ]R.ʩ6`X! c+<>S\W|X8YK3\:] S:WW0Kz"b^mKi&?NFf Kkq1Xr4/c1mޏy\ʛU3*LEHWre28DXcA:kjQ*S޻ O7Fqڕ1:y 3΢/\#Dn*}vWl/"s( 3Kq'p E\@U!T#-:V1s=!XrvA8&Vty94I dT-J-Ӛ=-="Ifi~RpÚQhW'K ^n%喲?ں n"ӳ!)Ԉ,u,rb +nV͞s|9R\1t! ]/3hn14Iƌ5OMgfEyځw?_m (1("*%tI{ON^+H`}3wpJ",P+N(tf7HӚ~36>" B@d\.F:KHP8[E6|ub+D^g HpE[W~"Ҋt=20rKSq@ZH!8SnL!Ak.wO0,A1,KH?xsCsqPk%\x{w t},>622KФak݈ 43$DlΡͤ伇> ʺiJC+.Gc\\Wcb$#Ix1ÚI#А(. ^y84D~"2;.x?_Mҡ =#%RѤn`Q(n5pWtWnᮂ]|1uKͥn.ɕ{T3ݶgt=8I\GkbĖ#Q}>Çc6N8i0+.vߓbX/}<^ 2jSP=E'_\p`K{iF>\SgfE#S5} EZHVV[s!\ W蕠P!E1uV!JS(>y$ ],@S烚&n1Y8 >C{N#`M#M@b]?b../{ҸilO?,@S~ GKKqY@ GqlKn'6u]ҘHǷnŔs9\W=x.(6G]+SK7o1.B2eo\7yDl蠘]GqY^b>o`V֥;k&ʮc[ȶG 2zeoU{w\)G.zڴeL ksD"uK g7ME{{(mc2:T!wl)qf_mq^οml |[m7<Ҍ@Y̓,K[z.;ۿ*"D/Z xO=$Aں>E5qP8Sqv}n;5f|)>*w|o' !MG7 e&AĊW-1%y$aFY5{ ZGP/1]zL1r\Jɶ6S0}[׸V.ldtwV|_c|$#|GAh vQ$OI{ͭW s_09!Rm}qҕs=SlMfՌI@U֤Vؓ%U ?_M] N$k¿@cenKc;Z0Hy /wEqtܷJ%o?Ӥvu_[; (U]Pm`&nzXo5&V=X|ra]2^ީ~}PF`݄bw /w >ѐ"I:ֿ&8jL )H]z!6ZH]оJ}Ʋn eT.+?⋺"׻3"Aw^!=ژWIҽ{{y=rtcYq ZsD =嘖.{G pC8̊J4foqsR )L7,d5C&Dy<9Y]]XcZdo}"l[x4/eu9&Upxo?)]8lO/)X|Eym(d*fnJ W*~yEayu=O].h$$๔zz2}jSPq.ӥ>R;O j}K[W|nΈtdK#8~9Pڝקt}9]=h|)>Kl5SD!k,zX3jsvS>fk;MD+NMpZ?8Xr9d굙U2haFj@_W qYGǼe/Wqrl+m5_25&V͵HW62 fD:yzW'Cce[J]Sx#u@wU WQ8frc!]r}.*$z]}&|ע}EG?4tmVOj9+ʅ/R$VZ~.\Ua2=9lS]W'Gqwp鳧mi)q}Zø#%s|/?&`gbNZY5Ҥ>CqD[.,TpSp6Y C! gOڿRG}?Sn[9 @Pc<)xHםK/>}xoF^ 'SnIrC5(w˰,,C5΂sLa&1=HK%\׀@|d|R,=AN ˒M1?WK;l˿~xd|y95\S\$@P1Mj!7(pS1MY[rC5!I/}<^=ƒNe`_N)c+AfWWriLCB$}hR =B%ԭ]|?ooGt>ǯ˪ςK c$2 `^;Y 2P=}F)BBR;Oa;WjS-I.P$6; .ɪاiڞ\ψ0?_S{'86]A<0}탃>^bkLd扲RJUYtQa>YQa.њ'X .'M# 2dYQSmd\VT2,܂>]eW1N \+OSҤnÿ40.+2f/S5{Եgz=J˂.K܄ ~VgV|dٯ\׹ݻ#C`ϰV3 ˋYe}xu=^ofV,z@qgFzIE'^R|Q&A7*-W*?SYf@ݝ*Nk";`PeNꏫL^y/q톇J951OzA&{{'-1GD6 [tl_Rdqޯ4g9D0̬űݮ0Aޡ %sn+_qn.u=İ w_60N8'%ve+2\o`P+%tIm 1\w{OcOh;WckfYzG0LklSe`!w(pCdzz>kI*Sj.İ)[L1Mf>8YL=ib jkن+c;E{38>6LC}3m=7kkōM/(^oKk2f`R}Բ-!,\&Ḛf;B.C~Hym2 tMjS@ͥ1=T*uԘYft ź0N^+'8'حsE2Ts>Iu$;mT-my]I2 z%],#AK62,}ж<ʼnOVyVkb\RaƁ12eWJ!r~ά#: (KGjdVͰ5.u]42TG'4 1vR/pI#W1ЇOF,O{Rc<~]Ϸ(P=is˒B{]NLE~U-BgvG MR;<6?]F!'v,72,?tSޣ`񑁁r>YJ; CٻXc)ј4>8@=63Tsd'64m!>^kSo7d/T~jWܬ=8DrhP(f8eYDC%{=6ޢ˗^kyd3fr}|4v] K\' ®t4f߃9͐yj##{^`'fC%'ǫqA$Ȩ}<_۟aY!)^o#(Ͼ.>2yA֌Wxs|@[wUFTcKfJ=&>/ϯT~bHx9wX3lxr>{ˁ.$9$N$9)eE+ʅ벪̬>?u<0UiFi}Vԕ7UQ[sMh%]D95cZaFjw ޕ= 3 6bb ^Zgrfȡ'TPGi]wK $w^;޸bc⽵˚ڕע1Oʹѐ%h4x ҟH|$؉,Pޛ8+~6~Vzܮ5.Z[̣h#9!X|ؚc<],C5cP6賦e|J=)\ynV.l[<Wm UrMAeZOո )e g«"k|xA%tPbBGt{tKU+f=WqǦSO*:,hnnsZz]xQsMp'>͊zNۺ@g̑eZ5 >#{ ןXi`\\W-XwqoWH4 !zO=~Yj2} '^Q?͌tZ*\CTE'j{YҾB(T`'(pZA\z]3{.Dޢ'&.HrdO=\O#)8Gϛ&u=iq ɑH7~5;IydaV rpfek˵f6l+Hk:-t{ SK7U w[x w)_zrxK>p= ;(лAs],9$N(>"*w],l@Ia> \*Xy?޶ L~|] ՜cufVԐcФaTt׼c%[=qRjfK)xf_.‹{gZ~jfݗ&uy<"TlɩjkW85E*yi?.PmDeħHc Ij|H'o (|آQr=¼2=H]k۶&; k&v AP{֌XQ57P۳!gD׼%_/ n|Jm,iܙ }|@7h((p6'!Eq))9;ʯG mMyjkwxoB|=\%E)mw0(:\m|J&D~|.(N S|$tǎ9R3 g95hڪO!Mw 5$X ڬnVجtdΎv^19\ILZGx40.[3rMA)_ |J=BUû, Usy*C&jgb ל*?W]>r]:=ςˏTP['^9@J?f/뻤jj⯗۲,$hAƠ сԞ~4ERC\jm3D.ԆE{U)1]w L-Vdfo[=85܈ ̍v^$NqZ3gDRd/>)ޠtfV =[ihXc >y"ɷaN; \-w|^q-d۶yYah5?}5Oƍe:hfPJ6.5W;XJƧ}')#arç?҆MzMJ1>Hc#K/vr4z2>.<7yY+~-8X,i)nh[}wV|7y|̪. wܽ3!=];S/[g~O!s|F!ȓ+u7YAcŬ ~|Hي﹒0g{[.Nܜw/_m p}/(fF:n``A19<'`OP|UFV)actd|9mc.k?ݪz嫞׆&Isk!r 5AFyGaZQGYm+$L qNOĥjlĞΏGx}~}/(r8v]ltt'vL1ӎ+=V붾&C u=Sr5qGsnU{ [fU3gvW{F(*)]w+@#O4m)q%|0"ru(p2CqiL<4L vR dϿ[Ps7~-gt=z*LE̺ӊjLWKT:ƭHt.ŭ\كŇ:z 7"hvPJ&.5O;+wO[SŌw[{.L;[aqpd͉v^>9\ILZk(zk~FiIڻ|DOrfdbXޑ'[@/CPh{KL mלJWe.!Jӑ\m|jf}a6O k~(pk>)6I2qA┹)xW5k_F*mI7T%N"zAj9.mSvUab8R#,-/hā޸Kur(eӃ=w&x)_u,1ڢ._ʧjވr@䡡>Qܬ=",ikfYzʱҍ.ԌzT]-!u!ݯ4hu|{=K SEzN;j>jTtר0.k#\B]Ouk>R3c}3N]bcOy "H IDATkk[m zd[#rڻ(Sfkz'PCEt)ɵ T,HD"X!- -Bzl/3y&Mv7̙ss&'gx*8k!hľ 8 ߯{j3_oߔL{ JnFRvS,0:0Ƨuڼ;ؘ@ck9*z62>GNJw(G qJ]9x%Q͸-(nV yKdWն(d< ROHl,v:mw8=02/:N;'tzC侔Aϔ0d5X\?*3T[?Zx N1)7/* SemzϷidة, F;+DSYKT۞دD&f92N3zk APv&vZtGi0O&ᒺaMD{=_)QW p=yjM\1/#isi{Eb}^ s{3 k)Pଥ]U=0;! Mw\vfGeifs̲s<;*]'\p7\p[Fs˛gTTabTXu]sTHq&s9(pRJ耳>MEavk-S\q͹>&I6̰sE EKh(Sm3YACxsG5I2ﶓ:& ]yB_BŜHn*g}C'*SnɤeRl,ꮙ*KP2-6؂_8Z# :N“f\SI|NrnUXZ7t_ݽth .ff6o*lOMYST<8koTX8Й?7_p)]C?%ZtVxמKYf|}Sjg{8BROckXY9T|5C V$AK(yB_vowѩGhQpcC]e>Cgafk'AX? p2MwsƁ)ho.뢜ceW$lkUj[V{>BYɤ?J7P " u'fݩ&ܩ$4t_X o[~SB@W ৓".;pCGZ˭EM?^=دzex =>23{*t!Ĝ†{.03%ꃐCE x\m`Vj7yƁfHE4" .:n ma,AP},vpSc}0Uӎ鱣4^t,)ݬ$}v]aE1u^;ǟKVw+Z}tldH_O9|E7լ=Uo:B '\x]Jrs_cnƊD+/}F,,j[tʌvz=K7dIC1SBS; O8˚٬>VabTQ>[_kjsZ̨3tsɮtG„'  \~Iȸ9LJ'qj ])t~xOi}<Û`/~ȭpmI^VbBGqN~ΔȘ^ql-vڱ5}]զCh#bqg|I;G?G!y:[x$흂GNlrifFbSgW-RU=T3_lC]a UXh#a:+!_%*ݟ,}C% 9Mt 4peWs枯\ꊶ4i!<2|EP)whg{mҍ;0dg"#GR$i#cĻ$\Rk^yW9 f&VIwBXscjkb=` 41J)*TeS)k\`!M*V?Zk۷!]gK~msG(ȈGݟ үMKKx y-_mې(MpѱCy!Ƣ0d/0q[%PMDR$iDyau-+,ڙXM. ZŻ' Na~~]amW䷽_nml9 )hRL}Gq=E$i;)E3' OóG~]wz3NrsvKCtaS߆-2CfǦeR/}?+ޑ'I&8 l?xGgb7/ ħ3*p8}̍ K˿ˁ |4Vɼ% >g֯0ŝ迧JM7K@gg&V}],p8}K䚌isU.2m'V^(ƐY*W~I1U[7SՇe_q&ذNKX^pfw#&ͷ'Dj*<1ߨIԥnԏϭF~{$&K6J9ޖ Æ(L,E̡ WCӇA3OWJԥ֥1M%>z3TLr6e&Ltbi]Z;TeknaG*wXw_7åN}v+*gZeRfƖ_r?'bo}[w%mv(p6G_9QMڬmHyvQr6A0;U֖ 2]޾]6ߐwϙO~2쬅 y PG"Nq8OΦY>g쟖Kݕ0RwޏC~£q]R.lL5Ι1M%۶>8O#7SwT,dvj-wRpR/)^ν/PGqΜn^vϿ&ҫ7 tQ&AMACkL-y>*hSAcGƊw hsa#kC~-{]9yb~DҰa`$.EZ@7{g+ag#sߞ,0/}zkwK:+xƗwg$VS+[jd}hcb7 41N\-GkIؘaGW. '׆Jw<ЏNԥ/0qu!?Ob.8i;?~x&}|x&RCqI$EO7kq B%j! eO׻ S$£ltG|݅G">~OY{J>v/*8LȦ)]AE01wVYekoz (wz'ʾ}RGiӢ宺J~}[i+~_b73$6 9Z[LZ.usn K+w/^'IKm&Ns3r&m7k,̙ŝ  N Nz|یJKBmФivux}XbN)1RN+f[g*>sFF8]㷳}2GkQH$\J'S(?Nn+bg [֎b\JoRDp1=٭ K,}LZkw߯crj3I8]c٩GJ:˥HgBu _&VVH|J(Kty=OP Գ^Q5Qmۉs3jpõƊdki}OoQ#cDz?Kpb=~\mtbчz̏mяH/}ҫ4%𯏈T _Ҿ򯜉]\0|1V3]2u[ƖAM6n*Q't1Fqscd2K[;\J5XpWHNKXU'8b_9Q xvW$K--7߹[iiWMyp鯎Dhjdt*s8/;.ahm?Q08JtNx*3h>/dW9ޛ]'7voҍەc+hҔ-:'IK23Ĵ:[6>#Q:5N(p@?nUEz[HmP%]󦷒~MLtL݄' k"޻'%hx]:-3{5QW=Ub|%F{ЌaQ_5XD.3NUƩc!ka )[?2L׼;lŊ'Dqs=]5vxN{2<9oGy~/ -vF&pݘL]bswl&fMlJ˹]ծ!moܯGބʸ~@ѱXE ;KПмbBԶp '7=r#ߺ<>2O0Bsm睉6>][^z]4|򏯪߫Vnwx= G(Lտj[ԦL݄L]3U@8Ifab.m1wd+_4IWP͜(]x .D!$خK6hlZZK͝ +*␆T0'%ot_=^?]*k Gq"DgxVxBbSWCTX9VJ )dG˭ߎi*ٮUz{xQ~Ez{}D!x2nNM0|ܡY$` :kL;Lx+q@jDƣ4qmj,6d~SIl`Etaq<7Sm{RL.#]Ӷ﫭o%~zSq,4pt3r#yBb\PpO5l}L.ʹ…G( F(b.ϭGeW?a z507[S$ʸ9=7[.n}*-߇WԡtVVR/oh^w搒e|S2-\5I1Ij]cGqW P1m81-fPyl">5!Cxb=8~C}G; +xnMo! 2eRwFv;'Qڑ1]q#4IߗĴEz[HCFȡFȻ(jK%utSsilX A_EΨ$Ute?O[Ra*w,- Qn^dgbm'ӫ /V-l&AC>9术\Lq P3"\QA;TMġIz٧Kn*<_lXP$03K+a'K7꣺ R 芏*{O 9-&vlGqdU~zX7rV Y8Goe 6DʐlDL.Ұ.;vVxSSE?4tVV+G?2-Sz$ؿ.T}!esnbQrX ;8%H`_m-qڶSa^8VcgnWZ[5xb9vxҍrKgZY隌iľ$M Oo{Bq `cDkf)ixo™&&-vWy/ Mmh¾ZPF$# IDATi$kSO,nAUbU.8Et񦁁~T]vߒu+jpsܜl{Nwfwk.WK_jo utS6.]Ĉʸ|}iT`&8m4im{ k}B#5z⽣#4+UO.")RWZKK5 p00OEXvE=@g s&\R EɊY#]yflUScXއau߄'] Q!~J@WԶ]-7m;KwW8vEn-I1,Akq/$Su2s;O\WK4ԿGնϮkr&]s-~F ϺWdđ_LЕ4 u0 N7Ϸ]謌3Rp|;R˟{UѤeflU]kQ=9SM dQO/\ptMiEEN 1dC}lGcPHew' L_>Q3BuQQ*wݖkeOqs`],}ÅBDtAD9ersp +CpyXr0|+ڑ)3""+Fb"gb#8k/Yڰ1Ǜ[eb5߃'xqw'x[$ilg/Wo)OZd?J7g_u5>_ܕm +ޅU^zlL~k!kPBܛ'oB<^xoJа탃 pkΕRs_?UJ5F@C7YuuS\ް&ڨGiFjv C>S󱴋r.uZŻMqr0kNc;Nh13ICڴagYzM͌ S|n'ce_,1'-<%ݓ.%ZtOfb,3wT|(p=Y. s BeL_Ĭ8H. KĿ\v%dO &QWn;JOj+g`(1ڃ\LlFJ9ogrAR޵ c:u˜hMS+?'\xSƶ~hϝ9jnʥ #s&aEUR$iϾc-D,,W+C<]1>0 䈦et’qkegNԮ/bNᮔr]j}ւv>%!x!?UJO#"mS"b#%ǘ1߃Bt֧28F$ toi_uE[*=i|z[hE'Mu7NLqGUU31p5EXFNJwxrGĈw PT[XƣInj\#G3:}^P oG]ՖKj )Hq߽<Ȑe[l-f>~ϙ&A(n+0AzOlӋ~<^b| ӞoI-&7[M>ȐT!}FĈw߱2nvMSii|S?XVۢ>yǙ$~@֠?r )m7O=R6 }^Y9/BBa| TٺSwT,>_+3su"i5$hiNy094Ikr}ɮX:W`cDĮꡚIAm],1'Ut}oI6( 3PF%[Zib๣r{BxWe_ 9.m8aZtMƄAﵓ};%hGijҖa*?XNznҍu&'5L5%ۻn/NsV?6 _;62oM Nh4FV{O~ذN~jks2sj[VVSUB;*F_ŭ,5ņK8'Eģΰ0뛚W^Uuũ2y|IRa؋~pf"m ;) _h| Nh4DtWW]'}S}^Ѥ&ꬌdm^V>p@r6Ku⳺74i$[`JdyKRO1h #cŻ(tW#G k&@h:Wb5ߪi 9R*4|`2=׎i*ٶX'ߴYOu ;;:xxsW.QnnS׶8i/Y0Y0Ry喤Գ=Џ1,.mOMD{0 h֙ϼBe4L R{g+?vթC-Jɽ]벵 u?6 TbaFNxI~jzO](pB+0ro^- ޽[\mx#kzm.18xtưn5IWtة-/_x2ָ/T}#[?a:{.ٮx5Fvڴh(5K@2:PHhȿ+dc6PFCcad?9S!F=To|A~p%WjZpӤ$A &ך,e?ys;Kо753)nAX&Ike .V-j>/[ڗZ܌r8Fjx7`'4*kylJO.!ڟG2/営#~\m#Ely*pTfbevئ2 i߳0[ڗ\Wü_Hm/_jk/T ʈn$ǔ(сVHhK46Smv00yk{U澔y+8S$7 ȍ-6}tu-nJn~G)3da'4:mxz'&I{U[qn, {xkVOr>NTwoCzm-,# x5srٍrsţIˋ w/%.x8b[n̻czl!8-)nALo%'Kck6ҭ]wEqsPЕqa6 &x#F`IJ˿^jzJm'_rfeqWe5; .4қN7Ya wsQbU"krD3Ji EtyW@$rNp1dvpB" ãI88c@.9+Nͼ0&Hѡ^{ԹւKW,e+if9S$XXfPme\Zl}^ַ/׵\}p3DŽ& ;8Q>˾k>4q\ Ū5lOoZ2?qM[sdGZ.UocuiO&+?80pR $Mvp /U-dX>'fI \pbsKvm!5vXލ'JΓtԽ΢ά];JM9_,ܶm 9DWs˗a828cC)Ki-z*J{ڠT9R#5_皓;[k֞M0 [ 83M2&F$H{\-\G"S[01]CdTZ&Qz9iv[;+S g玔9K7m tGYVYZTEQA @]*_rI-AOwXُδ=bΊ}ߑ;_d H#ڳr^ .  9K?f\&"m%?0ʏ,Nm?/I=ToJm]l3[?*Ғ`eX63TxjI%{{9'E{P;F&p?dh_AÄ'wUN_DvAXdhYfwOWD _U;0bʑuƯNo%nu/= W\SϺPl: )um靈O|v]Vm_XE9gp  N(2C~{dr00RSw|*ʏ;(Jxhp`ΠnUXZ?Pڤ Tp&r;g*?9UhYcK\0&3f{Ym.W_jֵ7gQb8&и PK&FUlm8HHMgl?Q`]kce=)܌azBG_UH9LhWz 5غ$KuP,J"0 cl,ydW־TkxT7+52JҎx&7[̬9g0\kevumKʣovS|B|׷<]с?Ed:ȗt; O=Z{N'لW,gX~$ؿmi<;*>P.'cc/Zajzf?xYx~%! R߇<-3l,ɨ$ܮ*5+͌$VƧ4B4џww>_b_\%K?0I>!^iQbAf >-[kxOF%v_2FMCAΧէGhnsQ2uNzmI4+1*VSƣ4W rk2v_REuQi  󪭬?J7{chݾ<6cc<ȪĨ>T/w2ڴ!QڗZI$["\/|z]g귴FZ /煮3Tp O-PF%rޅ%yF=To49 /W W)QE3HWSԦ +Cpdh}tY~\>%(yK(V ШlMxX#S$\ֱ]SQdN*8}p3AB&ץeS_\ֵNއm!V@˥#+Uy[߇D~;tp/ϭ KXcy 'o3}r2s_Hn/_Z+>ED (!#XbJ֥s/K֬eF{@}O%+N O8]+}r<ÀK̥o}>|Kj hѲ7ܯMo/kQjdn/8׿k"X)0&s'οPxK~\]ʣI-~QUBw眫\fgY+磂}f ^;N.4hw\8=:GQ~t5if;˯KI2c}Y~S4@ J7j,kH1ݾook_~D7 هcWJFZ׶G,漢>wO{QHAD?[{Wr_9Wq,ط͎AGF?<NmᷖOfou+%ﶧOw@ WiH\ _P/B4 _-r Z> /MediaBox [-0.0000 -0.0000 479.9999 479.9999] /TrimBox [0.0000 0.0000 479.9999 479.9999] /CropBox [-0.0000 -0.0000 479.9999 479.9999] /Resources << /ProcSet [/PDF] >> >> endobj 6 0 obj << /Private 7 0 R>> endobj 7 0 obj << /AIPrivateData1 8 0 R /CreatorVersion 11 /ContainerVersion 9 /RoundtripVersion 11 /NumBlock 1 >> endobj 8 0 obj << /Filter [/ASCIIHexDecode /FlateDecode ] /Length 16445 >> stream 78daed5cdb6e1dc7957d17a07fe879309064e03375bfe84da24c8c012631620793793218ea44e6cc 2129909463e7ebb3d6dad597c38b2d67de065662a7b54e5575d5ae7ddfbbf3d9bf7df5f5e7afdfdd fc75ff79dcb9972f3efbece4767f7e7f73fb4ae0f4e5e1f0f1eefe96c8f49b6f7effdbc9fb9dd39f e98b1f3edcdcdeefdf4d7fbbbdb99a4e6e6ef787b77f7afd5f58e29bcbfbc3fed5ffbcdf9d5f2e2b 5ede5cbf3dbfdfbf9a7e7f733dfde1e6fbc98729b85731bc72050fbebdc0c837371fafdf5d5ebf7f 73f3c32b37b92935fdc345dede5c7cbcda5fdf7f757b73b1bfbb3bb939dcdcdebd9a4e7e3cbf9e7e 7ffe1ebf9c4fffbd3f1c6efe3ebd399c5ffcef76ced71f3f7c385ceedffd697f77f3f116d35f4d1f b0ccddfe7ed229bf3decbfdf1fc2b7afbfcc93df85492ffcf70763ee7ffc70f3fef6fcc3773f8e71 6ef24f8dd3cebebcc29630ae609c7f7abd0d657ffac51777dfe15001ef6b4ffd7cf7dd394986359a 36c5355e7f196d1f7fbec33e40253e0ffc9bfdd587036e82340ec94d9b7fc68895d4fbef2ff77f7f 85ebbadeebb7fcedebdbfbaf2fff8125c7d54c03ffc3c7abb3f31ff7bc1237a0d3cbc3fef4e6f6ea fc9e9b1758bffde6bb8f577fbd3ebf3cbc020334fda353bdd9bfbf24839c63e594d3f49ffb1fa637 3fdeefeff0a34fbef8ea5c0f6ffda9feb84ffe83e9eefff0e7d7e9bf4eff75faafd37f9dfeff633a 86e5904e523e7d7b7ae2c22ffd37a6ff6b131f4dafd5c1e971fef4ad4bf31331fddd03194f40939e 8ba3b771f443d473d42245cf795e70c64b5d714e5fdf1ad7773d18acc55ddbee0fffee9cce41597f adc75b770bba592edbaff6cb98cef76e26a4e3bd9cbecda78f0e92b9844837ce6a6f0d63a1b499fc 76dd859e4ee771367d4398fc5acf6d19aab71c2fbfde8536bffeb84cb7edd7cd3d9c6ca6f9f940f3 dbd78bd260ef368b6cf0bc5d301d9ddd1ba94e34ac8f0d6ff8c01b59dfe8f7713bf3f47119c783c7 b1cab2dd747c2fc675cb69fc17e3fccf5c9fe39ef8c75ef35a12f7e0d4c586d8601b74fa36bc7982 2fba515eebe954ee972eb5a5fcb2354de7a42f9e5ceaedfc144e4de21e7019def260a1509f9a3cd8 e688f6fee905dceb21e36e7025b1a8b76be3333dfb4c81f51d33876f24e28dfd3e4f8f1b02d5f186 a325d7cb32a2d9e2981e4eb7423036ffe678f36372393acec9e6e216d2e4cd14777c90f5adc1eee4 f54cbad79ff6b607e4cc9bb37fcadb1ebe8ad3e376d276ed956c0fb0e5650bcf3f7adfe946d73ccb 0d7cfbbff45e7be9a0fcf6bd7151cb3ff55e7ba94d5fc174fcbe2777b5d9c166f3cfef60d16e6d9d 6eff6ba4cb0fe14fddc7d8fcf3371efd030b1837cf6d3dfb9b9fa0fdeb23b6debcc4d4c5d3ef59ad 7a35e97e207d0fcede1ec87cfcf943cd22b31865bfd870bf394c7af358ebfb07aaf27433fcf56a71 7c7a64bc963136fd6463a4d24fbff97884491cbd86bef11ad2b14dc5af69d5ed5b233d5ca395e263 81a249f1d89e2e13fb1895671b37167862827fc6714ab367b550bd6cfd89f51079aba4d7c98b7781 8965eb2f6d272d5c51361e58dfd8b88d2ca58d27d7b6d6677154f266443c768df2437fa68647763e 6f9cd2f4d829ad0ffc462df3bc533c14f5bfea537fd2f4fe5644faec8beb77cc308dc7939b2b26b9 ee96ecd357b737879bf7fceb97d717878feff673baf053b2853f33e5b9e4e1cf4c7b2e97f833d39e 4d2dfeccbc0799461069a58948f4f5fefee387972f1e91e13f2eaf2fef2fcf0f97ffd84fefb1dcfe 87fdc53ccc16fda9110fb6fbedf7e7b777cf9ce4496afed4da0f28f80bb6f1cc589165d041694e11 46b9cf972f263ffec3e4f5e77e8a7e0a650a713afbebcb17bfd1a0c9ff763abb7ef9e2e3cb17bfc3 3f7efacb9f5ebe70d31ff12f25d627b7cbb564fc4f2ba54e7f01bbfa927690e538f9ec773de0c72b 8265d77a20187645bf9686a9b501c93b78480148ddb590c038b9ed920f793ae1c4bacbb9e1d7dc77 c9e5aa61905a3eb85dc82d4d5c3c7a4e04d2bd6f9a58cb2e350776aa580dbb9bce08d65d7431098c ad043d7828413d646d030fbd349b8821b68dda77cd55feda7721b63ec15c61d91885b4988b909e9b d743ceb173627065575be61bfd2e8750b90d81ae1304e16a676d01480e5948e0d989f4d0b15ae958 cd0e856383eca406c080c86b22e2f147f4ec3579212166a37075d818f7dfda2e14df8db60d0fa446 c3dce69de8e673c5db5bdeb95e485b087de47e5adcb99a8cdaa9bbb11aafdd755d0a16eb22025d05 20787b7046286c072be4b4ab1ea41b1393d736028ec98be0fa5ec31c8685a43d644f8a817f620353 9ed8562b38cd98aa45b04deb3bdfb3d3fa3e463ce08cbd356c35831f6acc46b1b62ba17631496ba9 1bfd3bf8c4916132ae3583dade832dc99f40626281c1476c35b931a6da6abee0746069ae960b2e25 f84a964bba1110d6102f8ead38638f759e985ce2fde69d77389dd66f42c8219d13ddae54316701f1 6bb289004b9841ec47fbc76e7d8d3a72abb91918b00e04d877c8667478858b908b46c4ef6a8964aa 4041c54b3bde9e83bd82a0730696de8b86550f5a11e9114247a478dc85ef89f232a801159f20cbfc 1557667bf3d0c83d1ae840659d14b74526c7dd55085df009dcd89cc6809c652651adde26f658f0e0 c1b4b0e940c0123177d1165e0577d870c65ea70b9bd8a3eb22604e1031ae1f234b76dc4c8a55f7db 424d7a006f383b78dfc54091c144780251b2032ec85a1fae4dd243ea122beca1431e2f065395c013 259c48b541f05e1e4872dc3c178f3eeb163a05ffc42e0e4c4b52678ccf431be0be0af5582f5480e0 ed0e5d5412114aa86b7a70d29f5dfaa4701bbc8bd2ba4df4096fe77de130510fa09cb33b6d5423e0 81e8676982a6aae47fc85aa5f63b33158729942c103953c1566a9214c466095b1edc5ba2c6a4825b e636c0e790748264728800755d813a9374e74ea5cd31b1707198f7e8ab4d8cd436cd7469e81c86dd 66e86a6da65223555e65a842a22ba67f606222f8004287fb2dc6ffbe5060a381b142a0f450ba2129 4a498208b10721303c6335a85c5e107f0569a3d45a68a43f9080bf496d16c7136170842c69620211 5c376d0cc59fb58d04cbd2698c2a858ef4c4b06c962251a161d994413d4eacb47dc9d96abc3b6a2a 487a87fc6318de159a213e40307d849a0d9a1821f8cde81fa11b836ec753d7e1d2236d592c660222 048d4bd54c8986f54cd884bd116429be489ba51cb16c4a10b464da921e3e10a81d97bc2682a83611 4a3e6bff200bb663b61560a450f021e56c0f501e490a39cbbe70a2296d1cbc3ab3d45804ef34dfa0 e53ec85ea4c9212f9595d7474ec5c9539e06b6f1b7219e3119f37432c0958120b781b9c26651d86b 1331a1d3fc30a6a99180e44367169c1c5b03afa3d17501579c0d3053b5ca7e91c242e8cf00893255 9ac8ed35ead5e297d56a33c35a786b674390337906462d14f0b040c866e7a540d65aa15f01250059 7693196ee907309ed93efc04edaf5700844c982043a10e93f7902646abdffdf9d33d3b6c2ec9cd82 7aaaf07d48566895249b05158460d84f380aac9a9b813475723319c86649e478106a6c6e354148a6 4e75abd60ab03c0e4220d23fa0c684d89b84c01e805da340c885b11d40e8e7a0a939509bf84ee972 5abdd13ef900ffa6657b5f0cc1ee15608fb4dcdc58e3b902eea651fe3b953eef32e0ca93f4359034 f445a814aa602e1dcea16d00843c7581d830d82580e95bf572e960cf821028e72004d6776c03ee45 abc3f383ba9a88941ebb1010250bc9219aa2a925d9447a7e4d2e1a7cb596a2b641bb48eb4b666df2 4268d29c1c56ea6e121d9e25d44a95bece6db874b45bf05e87aea7830522c3f28515215fc987b479 7ddc640b62c9643c21408b8398a9a431cb547af7e69579ddaaa8354c3ce8023248cf9315381e0617 bf784d4c6ee88a004949d1d451e1fbce0c84024972d57aa73002814f9ea53740b83268cf3742f3f7 92c64dcaf91f81838384c27e50a0f240a44b69c2e419726236118b9926d21c4827e5402475f3c3a1 ffeb98e8ebf16d136c330b146f929ec89e7c0589086566a0b7ab2618e806d340606f5548514c0175 cd1486eeb6c30f32ad0e01cfc31ccb87a1b9f13ed8c505d92620c1d417347f2f76a8cc18249b4ec3 150ead0e9fa4d027014385cceba056e73dca9427724ae69509e14993293ad850b88e4d175a823773 0c77b8ce88dd4e353f441393cc194d8fa4846f6c0a461604db6e2d6e274208b894cc01d76f2310e0 36d63d0839deea98786196aba4680251140c328ae97dd050e10310b043b4f004f6c3482d0a67ad8f 79661a1289ccf3ca0f6778056ab7c445a02db3821d5e5c61dcd4e1393366b9300700a190938f1d13 153e19a326f3ba937c035876935d4c84116bb342f2e68e326a6bd58482f62e3af99f8e7ccb6130be 45ae6c51140cbd525d1cc16619ee4488ab8f9dc466d09405d1b279dd8ac1a162a11bcd338776b78b a37a66a0441047c5313d77cb9c82a3e7e30c099405ee2ac39f960ec1a8a87d45da1ce9531a04f23a 67664ea0c9804facc513c31cd915ad5d4c6f7225382b2d5b0cecc801b0a110eb2c0006364f0c687a 1ac6dfa779165c5360d0f68e46c7de0f756698b13dcc4ba1f50b81c18b14e482309ccf66a30c848d 0af4574119d912282778bd74a223f7c444fa0a8076d856746e3bc8d362a5cd3202ea7859d2c5af18 d829f2b8810e388e8b3b7429c81a83cbed1831b67956a90a6d2aa378a950b02a2c4811067f02ba9e 3189c21e86cdb893d9f81bb92d9c6ff2efa22e0942ed2701c5cb16373aeb8f5c869327dc08f344a8 7a4baa66560bedf195814e01104033939179176a0520bd334c018910e1a481e4c55034e600f81608 9f8508e0775f25764c1524b3acf05f8a3c039fa9e728136c1594af408e1b46ba7a455170d0ea30f9 f0bbe1647b097aa77c080433c975a02b97334d0ad5eaf0e342651412a584fcb1da26d88bd9e916ed 5a1ed3e4977b6de089c21b852a8565f665786d0de1153078d00e8117f80f9e0c01d01bdc49e98a95 ac05cd1ca3f95fbd0e3901c694bf9cb4caeb06e01908913a9d5ccb597d180204c22932b82ecc3384 36fb299dd90c825073b83c3aa8d24516a3752199ee6b60ea09bfd96a102766ff02747b966bc661b8 76206d686f2ede38114abb75b30e4c8031c808895ab96b174d8153159670ade45fcf389140651e80 e6912242a03b73796812184808ab98ce0bab9d3da94a9a0502b981970464933a9a5e66c178600456 b2f1dc93c3cfc42a3330943a0312530281009b170014de4b18f21b02d83d3083cafc5253e08e0d55 6617a0bfc8687a1785a698cb25d7820e35227184789805c27b5e79e5ae7157b8fa9442b375a3bdab 512f30a985f7c3f23a0e82e07043911603cb60a781a91c000e6b6916f34edee88a5839e8ac706798 9524e6e94fd1e427a6bdc0f88d469dfe61839d827681289a4257b695bc136912b9496678698aa828 a19827bcab525a4264f6ccc741eb9c82e9608cf65243851922ca93c3e13bb3ac34039186d5fc7a61 140a62a1588a14d6a61526a522a5d2c2263ad0a1f29a3012d6acc8c98661e7b602251a4a095c1f98 4e26a268641685c42c1441176c58a14746049e8a21f0ba9d21319a6be603b38c3435a02895f79981 4e5934863799de8a27519409631c496dc9145de46a30bcbe8df0ddcbf764822dd391f41ad64bb05c 9d6bb2f02002332b4ca1415d9ba3e4e5263465d1aa5c3fbc283359ce89212573160233735c7c56f9 8ab53ced9bec3f9d5306695419dc3cb60015c7c51d937c4199f230bf11a4760241454ea4a34d53c5 684da406e2c45a1c13871dc6b683d409c8584a1ab122d41cd3d5016e786054c963e6e60683514d12 b13c3a6f9cea4adbc04bc9486456889a0d835e52233a3646af9634649847a957c1e1c4de9810d740 15c175ada30a6194c902cd1432af49a7921a0bee7715d260544ccf916587870566cd52184e9924d2 876e143506e8c98d31e1e74d87c0ce9b4fe7a97a69a9e9382b1ab31b2f368cc72402f36013c1d56d 0ed42c050bf9cdae0fe54d3f89fa853aa4d2903101ccac7c603a44f7eb94cbe966629a1fa6209251 b91a3d44128dce6995e129a47fd2c4c2ca4ca033119bd95f1d93bc410f278e84a58846cbd6e91230 e111983043e01220f04d861e7e0fd406d6a7af44153b07913d8cd594f9a61b00ed01245b66816310 6382633b3d47c8945c63a6cf0542e542cec5c6b0f6698acc3e1471148d8a96a20c8e4c21f6ef02f3 1b3099412517f0802fd4c7b4b4bc2f22499e239cb64c933807ca326e7427ea88c91c9500b945c7a4 b43285ec7422a50c1575e3a54100f615663fd7e9054c053739c320b0d33a05567e7ae42b9c3ce13f 980bc21739da53ba37d4a057a64199ffa5068f8c04b02e5c5f2740de184d95a3f5661cc3e0785647 89691dc49a7e980b82d54b22e023f100e44fc7021111985d27248664c205da0f566182b9da30280a 539554b37450299ba55ade9d4abb33af4010ee5bb6cc487166b85c632e49fabf1413fc3482788291 84a4cf462a9f3d418f634fcd1d796aac51f2c6e0a30cbf8d9e1ab552eb549d9966d73ce0156414c9 ca23b379e0ce7e041e08328f5d3620cb25305aa045624e920f0b82ad8bf55540994128b5c282c8a8 e3d492f23360a46186aa3d6c4144be509d9c2203ecd68370c38edeed825c3c75deb367402675e116 c2958bc23b0b73240e8b6e95aecf0cc28bc33dd18a2778e7899b5990c8500a7be6ab173014ab711c b6a06f96fa5ad79f89b06e6346b85a6238ccf2f90c72b54445269b3acf8d7243c62d04b2f28230a8 a4488a2c0b58cd0b3c6c41ba0e226962d18dca14860ab1325d2be678eb06e16a2c68323a5ac0b323 90ce9fb33a144ed173dd803a45a11df52b082b0fa92be65d9963bf228398a2c902429dc1b8d96a0b 48abdfa27806e7cc72591001075da51cc905b9b0db8715492b78b053c0aab5cddcc2a487371f022e ce11c28826d9ed2f207d4f38f3b6da0c2625ee681ae9d8530741a0bad44751e2b9ae884ecaa02bf8 153cdb8233679e3dc5c3876740307c6446b6d01fdf307cf432bd9b65239d89ce9cc7ccf02bb261f8 15dc30fc0ace0cbfae3f33edba8d2dc337d5a11e288715ec2c6dc857806b6eb67901f95ec7ec3e9d a7198c52f7544130b10a2537c890029d6206a9bed86f70d882d472cc21afebcf9a70ddc68cf0142c 823b4ddc6851a6f2a8d9562dcad02dd5beaebf22f3364493198407137d1f34f17316ee2970bea0c3 165c14576322d76d1880fe4a56f3c08c5c3cc52787674032156456650af17681405d6dc10daf2244 64faa31f2f9bb125bfe5eac8a09b09cc95fd1664cb7e0bb86118f8ec6c2ac8cf80db2b5ec0e58ab3 1ffefd72c5898e543cbee2f5681bebb982dbf78634a2a927c1995c674fd1f0391004878d654c6eca 24d62c29de807828a67ea3ea33f53970bb2b761c8587745bc0ed74b8f299f5e267c0b1a5b3a7f6f9 1cf837cbed47a754c17aa81564a9852583c316a48661a7042bbf39e40d27b03ede8f8cec85154a4a eac707df80ecb96a7eceb7c35fe82ba8f7d2cf6b7905d92604dfb4594637a8bf6841b84277560b98 c1cd911f9ff73950d567e672fbf18d6fc0f52258d82a6a3b9941ee9cf559c5f033c8de892cdb87e0 b02a59b92291eea39fdb300c9c8f733802e7532feb2f9459b63123ea2d812f1424b01baa221682eb b1015580ceca53c38aa57a840c36b8b092b481b3c1550d9d2150aacf8090f454e70e19162795977b 0aa433ccfae4610b82dfa0299dd54494565af80df15550a3d196df5493eaf5c88563d61561ccc61a b2ec88517ec3cf0bb23def02e2825a34e5b98285098e6a496d5b7f6695751b3372f11447fdf22c30 eb8d99d113a24346dcdeba322bc33198dfc09a5eb7564a4767849a2ff681549adfc8b44cb3049f7a 30a170013ae620adaf44fe0e96613750502134500489b856975e0fef03263a96c246229dd5367a55 517d6dd11a46d4b0135584e63618cdb13994881ff507b516c28d54f45d252b6c06649a82086e83a5 bc38ec397b0d52ae73c50c9ea3790aa98e5c1f41f6e980d670e7998b2092a0998854d5f28120a6ef 724f52aa4bfd2d33392f4f24a9dacc469240ef0c371c98a50eaa953421a1cf2d3fc5fad4445be61e cf4657110344d2adb188e3e19843ebe1eed8ebc03405d55bf6709f3926bbee676a38e6cd78299e89 69d3b581f78b10ad76d3792d7b5e1c131dd60028a2b19e4f36a8ea8ae5fa6c68a259831608da8363 c980fc53fc68c0c1fe5566175379a6c512030b184caeafc0994861012bb2ae1846174c60509fc92d 11fc90ea5cc62f6c68889036cfea01f32ddd3b438a4ad3815e526b634cf0735f48e4bbb85a8a142c 04a0052a4537925538f5ec7b12ff90e7739f272a294a77a9a9d781eb3b32ad63865019a4883d8839 99f10ec67804190c199847e907bb556180b92628b5b982d378047ac75d3d5391293586834cffc7d1 745003930c4a5c0fde26c8d62a65c6a8e3b5147b7c883832211110dbf2696d168aa0d64ed8a54a26 1c994fe6c1582e26d81df3cc8c909880808cb285218fec1925a5b2c165b0a842556f135d1929c7c8 3e411c823d80d532b72cb3b094d099f4bb18d93915a5194367f5b538f620d36056363f5a76ae3234 64ec55c2489992335959e044a8c2a8de0dc868b17a46afd6490329efb68791c61053295355d994d1 acbe0d1d508464b5c56871c6766ca7ad6569ca885d69c3441f7654f7e82531ce6e9145f8ae429e93 49646f0d7d045600b3d259ec9818fe387bf4d4be47b0a9fd4df9a56029b420c15439499937fac5e3 e0d45481addf345071eebf601d3398ef1f5991b6ae3a7a28ec26686eb4c6335ba5f8809db332416c 66e4a5b0b6d7ebe87c514aad17bbfaa5e19169c94a3f774c84b6318599d5a450460313952a148cb7 d67b662388946eedb4d6c014a81b71bfd1face68e23c599460f14c50b32d9a2d3344b235da837aec ce20d2d2603c364b56674acf27b54f42b79075895425c981f4564d559610e3dc2aee5bb49832f9e1 24b268d725d714bad11ce75c27c2dc3e955e5316a74b1bc43e9abd982ba589a6a4637837f7412680 2d4bea79ef99d55322ec16f1467f36f396582c18adeacb6677424f66020a2b179dde41b3b03b3368 d01b038b9049da2cb3834a8d2459c3224ba3d66f92d9091855e6181329a1da3f2b23aefaf98b8710 9d9980ac028d906c0a39c9beb0e1b4835032faa12ddf4fa8bb27aab3cd59cf2946980bd1d469f7c8 a93879cad3184103c5933d4724b803a9e4814090135d3f82896a96487322261be38631f56cce2279 631f169cbda285f95656a5f1371396406f2b8bc371a0622da566fd6111925a7b0205aa5ae2dd8d7e 3482ea6e50dddb8d6883dd438c21407fb8dd43c937ebbb09764d596ac1e4bd731136da44769d386f 321bc6ed409f74e65d08863652d08f68f2cb3d3b46b65ee567f663b1f6c278038eb2e76718544291 6cce68bca656b788da49fb98e8ac439da761072a4bf8992245242908649c1095b5941a33d351f893 daf799fc62890960f576141518cd1631911aad2a08e6b0c6029f47d23939559358c1add69acf2a16 05427be3a705c14a587d54a2e4be33e4903d0492939bbfb7814a73e6d9c532420576243b2fb0b0a7 d63a99e832c0b3733aaf1afd5d1022b11b5f8fe8131a3980855c03a475659458db50271357e8e6fd 85517e64d22af951f0e9a3a58f11821961b6f5ab679596ad911a6c9bed8d51133fd449496a1bda6b 5c0a6e9f150f53f9ecb888d446a4de8a90bf86be570bd1b8cd5ac4d76cf853c673415889559e8513 e9ab3a153dbc4d844a366ad037a16d8cccb249e5d30833754b3550e52f3bfb34627c53d1b333cd14 b31fdf84381687a3bc3667bd659dddd22359596ad9d0df33b6c8cb6d26d3859d7dadcdbad9b2a84d 44f4d7ffc75b3b9aa8061f167fa2cab9d9ba8d7a528ad35b2bbf4df4e5c18db3ca2456616b75ed73 7f769213a7f26032cf8ee21e644ed91c4475d8d9eed10d89ac99d3a45852062b38d6b165a03a5551 d74dc19db161fa568d4865b197488c6c21a1f576f61111357753698b1f81f8b07cd256f99100990a 6e4a30055fbb5d71653b96be85632fb64c3a9b6686812dfc848c171aab12019e5e76da2085fba7fc ce134770db79537aa3fa4a57845d26faa26c9948753baab8f3fa645a97b27d11a13d18326fb569f3 63e285d9a29eba514c6d6c3a63572d934698bdb340b26a30bc2c6afaf1d95b92672a33de4de9028c ea923397dcda436b224b70bc3e21d3c5f18e9aa2316b4690ac31fbd9a8d6d4e249c618056d044c41 463ea8718e6e082bb7e3b3bda42f94f46da10ff34780899fe8d015edecc8511726abe2f46aa33205 d436a2063f95199fcd509a206dd6de933d9904dab2b3794d0eb80a572c2657f9b92cbce56613e92e 65f3dc9b633d43ed15b454fc10c52bfd51acfb8abb822be666351228ad028345edb20bec3ae1dcc4 de4ad90eaa5cae8f20b39a8911e361ffb58ccfae00aaa24b3031b8547155491f55e0baf55958de8a 071f2111355555f5a548a8ad14ca7a98a8a79a968acdd496b45365446c56a96297476182210c158d 4b0f6c3bcdac959308917dd82a1d17faf5dd9a471acfa89a73b6b241547bab17e8ba86319da0a634 7ee8a2cc3ebfc0c9d6a6e6b2f599d2043b7d2f47873a073b263ce6a4868eee74f0e136aa052c8f8f 18d58ec6f5e93b973a2a4c81e57c03f18aa8ec5857831eb37e7964bdcd37d8d23fd101642b2d2eae b107da10358cf1a34a95121f3a15274f791a73c6139b4f79b8bacc1e8d8c67f6c10cb157cd805da2 fc3222caf83643aa63c64288f53ac934e8b0ac8850cac6179bfa1a530d8afc3a345892a6c86d6889 bd21149654862391929f2d38aed48629c83eb33c66ce723968bbc7d7b06c39ee721599c5c9f6f55d 8b431755fac594eec42ea4237d4e9fb117d37e9ead5e674fd164f5ecf0dfb337e37bec2faedfe943 ebcf3fe7c7da5f9dbfdf7f737b7e79e0e7d9efefcebfdf4fe7d7d737f7e7f7fb0ff8697a7fbbbfbb bfb9dd4ffc4a9d08272d139ef930fc7e7f7b75798d257ef673f3e7473efc86fdf991e303fae7076c 3fc47f6ad4679f7df1c7d3972f5ebef827b4322ac2> endstream endobj 5 0 obj << /Filter [/ASCII85Decode /FlateDecode ] /Length 34 >> stream GhOt'1B7FZ"#S^CKb'rAF*Y:rk'%+Q~> endstream endobj 2 0 obj << /Type /Pages /Kids [4 0 R] /Count 1 >> endobj 1 0 obj << /Type /Catalog /Pages 2 0 R /OCProperties<< /D<< /Order[]/ON[]/OFF[]/RBGroups[]>>/OCGs[]>> >> endobj 3 0 obj << /CreationDate /ModDate /Producer /Creator /Title >> endobj xref 0 9 0000000000 65535 f 0000017206 00000 n 0000017142 00000 n 0000017325 00000 n 0000000010 00000 n 0000017012 00000 n 0000000291 00000 n 0000000331 00000 n 0000000467 00000 n trailer << /Size 9 /Root 1 0 R /Info 3 0 R /ID [<5587793bbad24a52ab1ae826d4d40cba><5587793bbad24a52ab1ae826d4d40cba>] >> startxref 17783 %%EOFjgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-transparent.eps000066400000000000000000010321521402514743400240020ustar00rootroot00000000000000ū%!PS-Adobe-3.0 EPSF-3.0 %%BoundingBox: 0 0 480 480 %%LanguageLevel: 2 %%Creator: CorelDRAW %%Title: jg.eps %%CreationDate: Mon Nov 12 20:31:40 2018 %%DocumentProcessColors: Cyan Magenta Yellow Black %%DocumentSuppliedResources: (atend) %%EndComments %%BeginProlog /AutoFlatness false def /AutoSteps 0 def /CMYKMarks true def /UseLevel 2 def %Build: CorelDRAW Version 14.0.0.567 %Color profile: Generic offset separations profile /CorelIsEPS true def %%BeginResource: procset wCorel14Dict 14.0 0 /wCorel14Dict 300 dict def wCorel14Dict begin % Copyright (c)1992-2007 Corel Corporation % All rights reserved. v14 r0.0 /bd{bind def}bind def/ld{load def}bd/xd{exch def}bd/_ null def/rp{{pop}repeat} bd/@cp/closepath ld/@gs/gsave ld/@gr/grestore ld/@np/newpath ld/Tl/translate ld /$sv 0 def/@sv{/$sv save def}bd/@rs{$sv restore}bd/spg/showpage ld/showpage{} bd currentscreen/@dsp xd/$dsp/@dsp def/$dsa xd/$dsf xd/$sdf false def/$SDF false def/$Scra 0 def/SetScr/setscreen ld/@ss{2 index 0 eq{$dsf 3 1 roll 4 -1 roll pop}if exch $Scra add exch load SetScr}bd/SepMode_5 where{pop}{/SepMode_5 0 def}ifelse/CorelIsSeps where{pop}{/CorelIsSeps false def}ifelse /CorelIsInRIPSeps where{pop}{/CorelIsInRIPSeps false def}ifelse/CorelIsEPS where{pop}{/CorelIsEPS false def}ifelse/CurrentInkName_5 where{pop} {/CurrentInkName_5(Composite)def}ifelse/$ink_5 where{pop}{/$ink_5 -1 def} ifelse/fill_color 6 array def/num_fill_inks 1 def/$o 0 def/$fil 0 def /outl_color 6 array def/num_outl_inks 1 def/$O 0 def/$PF false def/$bkg false def/$op false def matrix currentmatrix/$ctm xd/$ptm matrix def/$ttm matrix def /$stm matrix def/$ffpnt true def/CorelDrawReencodeVect[16#0/grave 16#5/breve 16#6/dotaccent 16#8/ring 16#A/hungarumlaut 16#B/ogonek 16#C/caron 16#D/dotlessi 16#27/quotesingle 16#60/grave 16#7C/bar 16#80/Euro 16#82/quotesinglbase/florin/quotedblbase/ellipsis/dagger/daggerdbl 16#88/circumflex/perthousand/Scaron/guilsinglleft/OE 16#91/quoteleft/quoteright/quotedblleft/quotedblright/bullet/endash/emdash 16#98/tilde/trademark/scaron/guilsinglright/oe 16#9F/Ydieresis 16#A1/exclamdown/cent/sterling/currency/yen/brokenbar/section 16#a8/dieresis/copyright/ordfeminine/guillemotleft/logicalnot/minus/registered/macron 16#b0/degree/plusminus/twosuperior/threesuperior/acute/mu/paragraph/periodcentered 16#b8/cedilla/onesuperior/ordmasculine/guillemotright/onequarter/onehalf/threequarters/questiondown 16#c0/Agrave/Aacute/Acircumflex/Atilde/Adieresis/Aring/AE/Ccedilla 16#c8/Egrave/Eacute/Ecircumflex/Edieresis/Igrave/Iacute/Icircumflex/Idieresis 16#d0/Eth/Ntilde/Ograve/Oacute/Ocircumflex/Otilde/Odieresis/multiply 16#d8/Oslash/Ugrave/Uacute/Ucircumflex/Udieresis/Yacute/Thorn/germandbls 16#e0/agrave/aacute/acircumflex/atilde/adieresis/aring/ae/ccedilla 16#e8/egrave/eacute/ecircumflex/edieresis/igrave/iacute/icircumflex/idieresis 16#f0/eth/ntilde/ograve/oacute/ocircumflex/otilde/odieresis/divide 16#f8/oslash/ugrave/uacute/ucircumflex/udieresis/yacute/thorn/ydieresis]def /get_ps_level/languagelevel where{pop systemdict/languagelevel get exec}{1 }ifelse def/level2 get_ps_level 2 ge def/level3 get_ps_level 3 ge def /is_distilling{/product where{pop systemdict/setdistillerparams known product (Adobe PostScript Parser)ne and}{false}ifelse}bd/is_rip_separation{ is_distilling{false}{level2{currentpagedevice/Separations 2 copy known{get}{ pop pop false}ifelse}{false}ifelse}ifelse}bd/is_current_sep_color{ is_separation{gsave false setoverprint 1 1 1 1 5 -1 roll findcmykcustomcolor 1 setcustomcolor currentgray 0 eq grestore}{pop false}ifelse}bd /get_sep_color_index{dup length 1 sub 0 1 3 -1 roll{dup 3 -1 roll dup 3 -1 roll get is_current_sep_color{exit}{exch pop}ifelse}for pop -1}bd/is_separation{ /LumSepsDict where{pop false}{/AldusSepsDict where{pop false}{ is_rip_separation{true}{1 0 0 0 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne 0 1 0 0 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne 0 0 1 0 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne 0 0 0 1 gsave setcmykcolor currentcmykcolor grestore add add add 0 ne and and and not}ifelse}ifelse}ifelse}bind def/is_composite{is_separation not is_distilling or}bd/is_sim_devicen{level2 level3 not and{is_distilling is_rip_separation or}{false}ifelse}bd/@PL{/LV where{pop LV 2 ge level2 not and {@np/Courier findfont 12 scalefont setfont 72 144 m (The PostScript level set in the Corel application is higher than)show 72 132 m (the PostScript level of this device. Change the PS Level in the Corel)show 72 120 m(application to Level 1 by selecting the PostScript tab in the print)show 72 108 m(dialog, and selecting Level 1 from the Compatibility drop down list.) show flush spg quit}if}if}bd/@BeginSysCorelDict{systemdict/Corel30Dict known {systemdict/Corel30Dict get exec}if systemdict/CorelLexDict known{1 systemdict /CorelLexDict get exec}if}bd/@EndSysCorelDict{systemdict/Corel30Dict known {end}if/EndCorelLexDict where{pop EndCorelLexDict}if}bd AutoFlatness{/@ifl{dup currentflat exch sub 10 gt{ ([Error: PathTooComplex; OffendingCommand: AnyPaintingOperator]\n)print flush @np exit}{currentflat 2 add setflat}ifelse}bd/@fill/fill ld/fill{currentflat{ {@fill}stopped{@ifl}{exit}ifelse}bind loop setflat}bd/@eofill/eofill ld/eofill {currentflat{{@eofill}stopped{@ifl}{exit}ifelse}bind loop setflat}bd/@clip /clip ld/clip{currentflat{{@clip}stopped{@ifl}{exit}ifelse}bind loop setflat} bd/@eoclip/eoclip ld/eoclip{currentflat{{@eoclip}stopped{@ifl}{exit}ifelse} bind loop setflat}bd/@stroke/stroke ld/stroke{currentflat{{@stroke}stopped {@ifl}{exit}ifelse}bind loop setflat}bd}if level2{/@ssa{true setstrokeadjust} bd}{/@ssa{}bd}ifelse/d/setdash ld/j/setlinejoin ld/J/setlinecap ld/M /setmiterlimit ld/w/setlinewidth ld/O{/$o xd}bd/R{/$O xd}bd/W/eoclip ld/c /curveto ld/C/c ld/l/lineto ld/L/l ld/rl/rlineto ld/m/moveto ld/n/newpath ld/N /newpath ld/P{11 rp}bd/u{}bd/U{}bd/A{pop}bd/q/@gs ld/Q/@gr ld/&{}bd/@j{@sv @np} bd/@J{@rs}bd/g{1 exch sub 0 0 0 1 null 1 set_fill_color/$fil 0 def}bd/G{1 sub neg 0 0 0 1 null 1 set_outline_color}bd/set_fill_color{/fill_color exch def /num_fill_inks fill_color length 6 idiv def/bFillDeviceN num_fill_inks 1 gt def /$fil 0 def}bd/set_outline_color{/outl_color exch def/num_outl_inks outl_color length 6 idiv def/bOutlDeviceN num_outl_inks 1 gt def}bd /get_devicen_color_names{dup length 6 idiv dup 5 mul exch getinterval}bd /create_colorarray_from_devicen_params{/ink_names_temp exch def /tint_params_temp exch def/ink_values_temp exch def[ink_values_temp aload pop tint_params_temp aload pop ink_names_temp aload pop]}bd /get_devicen_color_specs{dup length 6 idiv dup 4 mul getinterval}bd /get_devicen_color{dup length 6 idiv 0 exch getinterval}bd/mult_devicen_color{ /colorarray exch def/mult_vals exch def 0 1 mult_vals length 1 sub{colorarray exch dup mult_vals exch get exch dup colorarray exch get 3 -1 roll mul put}for colorarray}bd/combine_devicen_colors{/colorarray2 exch def/colorarray1 exch def /num_inks1 colorarray1 length 6 idiv def/num_inks2 colorarray2 length 6 idiv def/num3 0 def/colorarray3[num_inks1 num_inks2 add 6 mul{0}repeat]def 0 1 num_inks1 1 sub{colorarray1 exch get colorarray3 num3 3 -1 roll put/num3 num3 1 add def}for 0 1 num_inks2 1 sub{colorarray2 exch get colorarray3 num3 3 -1 roll put/num3 num3 1 add def}for colorarray1 num_inks1 dup 4 mul getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks1 4 mul add def colorarray2 num_inks2 dup 4 mul getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks2 4 mul add def colorarray1 num_inks1 dup 5 mul exch getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks1 add def colorarray2 num_inks2 dup 5 mul exch getinterval colorarray3 num3 3 -1 roll putinterval/num3 num3 num_inks2 add def colorarray3}bd/get_devicen_color_spec{ /colorant_index exch def/colorarray exch def/ncolorants colorarray length 6 idiv def[colorarray colorant_index get colorarray ncolorants colorant_index 4 mul add 4 getinterval aload pop colorarray ncolorants 5 mul colorant_index add get]}bd/set_devicen_color{level3{/colorarray exch def/numcolorants colorarray length 6 idiv def colorarray get_devicen_color_specs/tint_params exch def[ /DeviceN colorarray get_devicen_color_names/DeviceCMYK{tint_params CorelTintTransformFunction}]setcolorspace colorarray get_devicen_color aload pop setcolor}{/DeviceCMYK setcolorspace devicen_to_cmyk aload pop pop @tc_5 setprocesscolor_5}ifelse}bd/sf{/bmp_fill_fg_color xd}bd/i{dup 0 ne{setflat} {pop}ifelse}bd/v{4 -2 roll 2 copy 6 -2 roll c}bd/V/v ld/y{2 copy c}bd/Y/y ld /@w{matrix rotate/$ptm xd matrix scale $ptm dup concatmatrix/$ptm xd 1 eq{$ptm exch dup concatmatrix/$ptm xd}if 1 w}bd/@g{1 eq dup/$sdf xd{/$scp xd/$sca xd /$scf xd}if}bd/@G{1 eq dup/$SDF xd{/$SCP xd/$SCA xd/$SCF xd}if}bd/@D{2 index 0 eq{$dsf 3 1 roll 4 -1 roll pop}if 3 copy exch $Scra add exch load SetScr/$dsp xd/$dsa xd/$dsf xd}bd/$ngx{$SDF{$SCF SepMode_5 0 eq{$SCA}{$dsa}ifelse $SCP @ss }if}bd/@MN{2 copy le{pop}{exch pop}ifelse}bd/@MX{2 copy ge{pop}{exch pop} ifelse}bd/InRange{3 -1 roll @MN @MX}bd/@sqr{dup 0 rl dup 0 exch rl neg 0 rl @cp }bd/currentscale{1 0 dtransform matrix defaultmatrix idtransform dup mul exch dup mul add sqrt 0 1 dtransform matrix defaultmatrix idtransform dup mul exch dup mul add sqrt}bd/@unscale{}bd/wDstChck{2 1 roll dup 3 -1 roll eq{1 add}if} bd/@dot{dup mul exch dup mul add 1 exch sub}bd/@lin{exch pop abs 1 exch sub}bd /cmyk2rgb{3{dup 5 -1 roll add 1 exch sub dup 0 lt{pop 0}if exch}repeat pop}bd /rgb2cmyk{3{1 exch sub 3 1 roll}repeat 3 copy @MN @MN 3{dup 5 -1 roll sub neg exch}repeat}bd/rgb2g{2 index .299 mul 2 index .587 mul add 1 index .114 mul add 4 1 roll pop pop pop}bd/devicen_to_cmyk{/convertcolor exch def convertcolor get_devicen_color aload pop convertcolor get_devicen_color_specs CorelTintTransformFunction}bd/WaldoColor_5 where{pop}{/SetRgb/setrgbcolor ld /GetRgb/currentrgbcolor ld/SetGry/setgray ld/GetGry/currentgray ld/SetRgb2 systemdict/setrgbcolor get def/GetRgb2 systemdict/currentrgbcolor get def /SetHsb systemdict/sethsbcolor get def/GetHsb systemdict/currenthsbcolor get def/rgb2hsb{SetRgb2 GetHsb}bd/hsb2rgb{3 -1 roll dup floor sub 3 1 roll SetHsb GetRgb2}bd/setcmykcolor where{pop/LumSepsDict where{pop/SetCmyk_5{LumSepsDict /setcmykcolor get exec}def}{/AldusSepsDict where{pop/SetCmyk_5{AldusSepsDict /setcmykcolor get exec}def}{/SetCmyk_5/setcmykcolor ld}ifelse}ifelse}{ /SetCmyk_5{cmyk2rgb SetRgb}bd}ifelse/currentcmykcolor where{pop/GetCmyk /currentcmykcolor ld}{/GetCmyk{GetRgb rgb2cmyk}bd}ifelse/setoverprint where {pop}{/setoverprint{/$op xd}bd}ifelse/currentoverprint where{pop}{ /currentoverprint{$op}bd}ifelse/@tc_5{5 -1 roll dup 1 ge{pop}{4{dup 6 -1 roll mul exch}repeat pop}ifelse}bd/@trp{exch pop 5 1 roll @tc_5}bd /setprocesscolor_5{SepMode_5 0 eq{SetCmyk_5}{SepsColor not{4 1 roll pop pop pop 1 exch sub SetGry}{SetCmyk_5}ifelse}ifelse}bd/findcmykcustomcolor where{pop}{ /findcmykcustomcolor{5 array astore}bd}ifelse/Corelsetcustomcolor_exists false def/setcustomcolor where{pop/Corelsetcustomcolor_exists true def}if CorelIsSeps true eq CorelIsInRIPSeps false eq and{/Corelsetcustomcolor_exists false def}if Corelsetcustomcolor_exists false eq{/setcustomcolor{exch aload pop SepMode_5 0 eq{pop @tc_5 setprocesscolor_5}{CurrentInkName_5 eq{4 index}{0}ifelse 6 1 roll 5 rp 1 sub neg SetGry}ifelse}bd}if/@scc_5{dup type/booleantype eq{dup currentoverprint ne{setoverprint}{pop}ifelse}{1 eq setoverprint}ifelse dup _ eq {pop setprocesscolor_5 pop}{dup(CorelRegistrationColor)eq{5 rp 1 exch sub setregcolor}{findcmykcustomcolor exch setcustomcolor}ifelse}ifelse SepMode_5 0 eq{true}{GetGry 1 eq currentoverprint and not}ifelse}bd/colorimage where{pop /ColorImage{colorimage}def}{/ColorImage{/ncolors xd/$multi xd $multi true eq{ ncolors 3 eq{/daqB xd/daqG xd/daqR xd pop pop exch pop abs{daqR pop daqG pop daqB pop}repeat}{/daqK xd/daqY xd/daqM xd/daqC xd pop pop exch pop abs{daqC pop daqM pop daqY pop daqK pop}repeat}ifelse}{/dataaq xd{dataaq ncolors dup 3 eq{ /$dat xd 0 1 $dat length 3 div 1 sub{dup 3 mul $dat 1 index get 255 div $dat 2 index 1 add get 255 div $dat 3 index 2 add get 255 div rgb2g 255 mul cvi exch pop $dat 3 1 roll put}for $dat 0 $dat length 3 idiv getinterval pop}{4 eq{ /$dat xd 0 1 $dat length 4 div 1 sub{dup 4 mul $dat 1 index get 255 div $dat 2 index 1 add get 255 div $dat 3 index 2 add get 255 div $dat 4 index 3 add get 255 div cmyk2rgb rgb2g 255 mul cvi exch pop $dat 3 1 roll put}for $dat 0 $dat length ncolors idiv getinterval}if}ifelse}image}ifelse}bd}ifelse/setcmykcolor{ 1 5 1 roll null 6 array astore currentoverprint set_current_color/$ffpnt xd}bd /currentcmykcolor{GetCmyk}bd/setrgbcolor{rgb2cmyk setcmykcolor}bd /currentrgbcolor{currentcmykcolor cmyk2rgb}bd/sethsbcolor{hsb2rgb setrgbcolor} bd/currenthsbcolor{currentrgbcolor rgb2hsb}bd/setgray{dup dup setrgbcolor}bd /currentgray{currentrgbcolor rgb2g}bd/InsideDCS false def/IMAGE/image ld/image {InsideDCS{IMAGE}{/EPSDict where{pop SepMode_5 0 eq{IMAGE}{dup type/dicttype eq {dup/ImageType get 1 ne{IMAGE}{dup dup/BitsPerComponent get 8 eq exch /BitsPerComponent get 1 eq or currentcolorspace 0 get/DeviceGray eq and{ CurrentInkName_5(Black)eq{IMAGE}{dup/DataSource get/TCC xd/Height get abs{TCC pop}repeat}ifelse}{IMAGE}ifelse}ifelse}{2 index 1 ne{CurrentInkName_5(Black)eq {IMAGE}{/TCC xd pop pop exch pop abs{TCC pop}repeat}ifelse}{IMAGE}ifelse} ifelse}ifelse}{IMAGE}ifelse}ifelse}bd}ifelse/WaldoColor_5 true def /WaldoColor_13 where{pop}{/separate_color{SepMode_5 0 ne{[exch/colorarray_sep exch def/ink_num -1 def colorarray_sep length 6 idiv 1 gt{colorarray_sep get_devicen_color_names dup length 1 sub 0 1 3 -1 roll{exch dup 3 -1 roll dup 3 1 roll get CurrentInkName_5 eq{/ink_num exch def}{pop}ifelse}for pop ink_num -1 ne{colorarray_sep ink_num get_devicen_color_spec aload pop pop SepsColor not{ pop pop pop pop 1 0 0 0 5 -1 roll}if null}{0 0 0 0 0 null}ifelse}{ colorarray_sep 5 get $ink_5 4 eq{CurrentInkName_5 eq{colorarray_sep aload pop pop SepsColor not{pop pop pop pop 0 0 0 1}if null}{0 0 0 0 0 null}ifelse}{ colorarray_sep 0 get colorarray_sep $ink_5 1 add get 3 -1 roll null eq{0 0 0 4 -1 roll SepsColor{4 $ink_5 1 add roll}if null}{pop pop 0 0 0 0 0 null}ifelse }ifelse}ifelse]}if}bd/separate_cmyk_color{$ink_5 -1 ne{[exch aload pop 3 $ink5 sub index/colorarray_sep exch def/ink_num -1 def colorarray_sep get_devicen_color_names dup length 1 sub 0 1 3 -1 roll{exch dup 3 -1 roll dup 3 1 roll get CurrentInkName_5 eq{/ink_num exch def}{pop}ifelse}for pop ink_num -1 ne{[colorarray_sep ink_num get_devicen_color_spec aload pop]}{[0 0 0 0 0 null] }ifelse}if}bd/set_current_color{dup type/booleantype eq{dup currentoverprint ne {setoverprint}{pop}ifelse}{1 eq setoverprint}ifelse/cur_color exch def /nNumColors cur_color length 6 idiv def nNumColors 1 eq{cur_color 5 get (CorelRegistrationColor)eq{cur_color aload pop 5 rp 1 exch sub setregcolor}{ SepMode_5 0 eq{cur_color aload pop dup null eq{pop @tc_5 setprocesscolor_5}{ findcmykcustomcolor exch setcustomcolor}ifelse}{cur_color separate_color aload pop pop @tc_5 setprocesscolor_5}ifelse}ifelse}{SepMode_5 0 eq{is_distilling is_rip_separation or{cur_color set_devicen_color}{cur_color devicen_to_cmyk setprocesscolor_5}ifelse}{cur_color separate_color aload pop pop @tc_5 setprocesscolor_5}ifelse}ifelse SepMode_5 0 eq{true}{GetGry 1 eq currentoverprint and not}ifelse}bd}ifelse/WaldoColor_13 true def/$fm 0 def /wfill{1 $fm eq{fill}{eofill}ifelse}bd/@Pf{@sv SepMode_5 0 eq $Psc 0 ne or $ink_5 3 eq or{0 J 0 j[]0 d fill_color $o set_current_color pop $ctm setmatrix 72 1000 div dup matrix scale dup concat dup Bburx exch Bbury exch itransform ceiling cvi/Bbury xd ceiling cvi/Bburx xd Bbllx exch Bblly exch itransform floor cvi/Bblly xd floor cvi/Bbllx xd $Prm aload pop $Psn load exec}{1 SetGry wfill}ifelse @rs @np}bd/F{matrix currentmatrix $sdf{$scf $sca $scp @ss}if $fil 1 eq{CorelPtrnDoFill}{$fil 2 eq{@ff}{$fil 3 eq{@Pf}{level3{fill_color $o set_current_color{wfill}{@np}ifelse}{/overprint_flag $o def is_distilling is_rip_separation or{0 1 num_fill_inks 1 sub{dup 0 gt{/overprint_flag true def }if fill_color exch get_devicen_color_spec overprint_flag set_current_color{ @gs wfill @gr}{@np exit}ifelse}for}{fill_color overprint_flag set_current_color {@gs wfill @gr}{@np}ifelse}ifelse}ifelse}ifelse}ifelse}ifelse $sdf{$dsf $dsa $dsp @ss}if setmatrix}bd/f{@cp F}bd/S{matrix currentmatrix $ctm setmatrix $SDF {$SCF $SCA $SCP @ss}if level3{outl_color $O set_current_color{matrix currentmatrix $ptm concat stroke setmatrix}{@np}ifelse}{/overprint_flag $O def is_distilling is_rip_separation or{0 1 num_outl_inks 1 sub{dup 0 gt{ /overprint_flag true def}if outl_color exch get_devicen_color_spec overprint_flag set_current_color{matrix currentmatrix $ptm concat @gs stroke @gr setmatrix}{@np exit}ifelse}for}{outl_color overprint_flag set_current_color {matrix currentmatrix $ptm concat @gs stroke @gr setmatrix}{@np}ifelse}ifelse }ifelse $SDF{$dsf $dsa $dsp @ss}if setmatrix}bd/s{@cp S}bd/B{@gs F @gr S}bd/b{ @cp B}bd/_E{5 array astore exch cvlit xd}bd/@cc{currentfile $dat readhexstring pop}bd/@sm{/$ctm $ctm currentmatrix def}bd/@E{/Bbury xd/Bburx xd/Bblly xd /Bbllx xd}bd/@c{@cp}bd/@P{/$fil 3 def/$Psn xd/$Psc xd array astore/$Prm xd}bd /tcc{@cc}def/@B{@gs S @gr F}bd/@b{@cp @B}bd/@sep{CurrentInkName_5(Composite)eq {/$ink_5 -1 def}{CurrentInkName_5(Cyan)eq{/$ink_5 0 def}{CurrentInkName_5 (Magenta)eq{/$ink_5 1 def}{CurrentInkName_5(Yellow)eq{/$ink_5 2 def}{ CurrentInkName_5(Black)eq{/$ink_5 3 def}{/$ink_5 4 def}ifelse}ifelse}ifelse} ifelse}ifelse}bd/@whi{@gs -72000 dup m -72000 72000 l 72000 dup l 72000 -72000 l @cp 1 SetGry fill @gr}bd/@neg{[{1 exch sub}/exec cvx currenttransfer/exec cvx]cvx settransfer @whi}bd/deflevel 0 def/@sax{/deflevel deflevel 1 add def} bd/@eax{/deflevel deflevel dup 0 gt{1 sub}if def deflevel 0 gt{/eax load}{eax} ifelse}bd/eax{{exec}forall}bd/@rax{deflevel 0 eq{@rs @sv}if}bd systemdict /pdfmark known not{/pdfmark/cleartomark ld}if/wclip{1 $fm eq{clip}{eoclip} ifelse}bd level2{/setregcolor{/neg_flag exch def[/Separation/All/DeviceCMYK{ dup dup dup}]setcolorspace 1.0 neg_flag sub setcolor}bd}{/setregcolor{1 exch sub dup dup dup setcmykcolor}bd}ifelse/CorelTintTransformFunction{ /colorantSpecArray exch def/nColorants colorantSpecArray length 4 idiv def /inColor nColorants 1 add 1 roll nColorants array astore def/outColor 4 array def 0 1 3{/nOutInk exch def 1 0 1 nColorants 1 sub{dup inColor exch get exch 4 mul nOutInk add colorantSpecArray exch get mul 1 exch sub mul}for 1 exch sub outColor nOutInk 3 -1 roll put}for outColor aload pop}bind def % Copyright (c)1992-2007 Corel Corporation % All rights reserved. v14 r0.0 /@ii{concat 3 index 3 index m 3 index 1 index l 2 copy l 1 index 3 index l 3 index 3 index l clip pop pop pop pop}bd/@i{@sm @gs @ii 6 index 1 ne{/$frg true def pop pop}{1 eq{bmp_fill_fg_color $O set_current_color/$frg xd}{/$frg false def}ifelse 1 eq{@gs $ctm setmatrix F @gr}if}ifelse @np/$ury xd/$urx xd/$lly xd /$llx xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul 8 div ceiling cvi string def $bkg $frg or{$SDF{$SCF $SCA $SCP @ss}if $llx $lly Tl $urx $llx sub $ury $lly sub scale $bkg{fill_color set_current_color pop}if $wid $hei abs $bts 1 eq {$bkg}{$bts}ifelse[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]/tcc load $bts 1 eq{imagemask}{image}ifelse $SDF{$dsf $dsa $dsp @ss}if}{$hei abs{tcc pop} repeat}ifelse @gr $ctm setmatrix}bd/@I{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd /$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd $ngx $llx $lly Tl $urx $llx sub $ury $lly sub scale $wid $hei abs $bts[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse ]/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def $msimage false eq $ncl 1 eq or{/@cc load false $ncl ColorImage}{$wid $bts mul 8 div ceiling cvi $ncl 3 eq{dup dup/$dat1 exch string def/$dat2 exch string def/$dat3 exch string def/@cc1 load/@cc2 load/@cc3 load}{dup dup dup/$dat1 exch string def/$dat2 exch string def/$dat3 exch string def/$dat4 exch string def/@cc1 load/@cc2 load /@cc3 load/@cc4 load}ifelse true $ncl ColorImage}ifelse $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd/@cc1{currentfile $dat1 readhexstring pop}bd/@cc2{ currentfile $dat2 readhexstring pop}bd/@cc3{currentfile $dat3 readhexstring pop }bd/@cc4{currentfile $dat4 readhexstring pop}bd/$msimage false def/COMP 0 def /MaskedImage false def/bImgDeviceN false def/nNumInksDeviceN 0 def /sNamesDeviceN[]def/tint_params[]def level2{/@I_2{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def $ngx $ncl 1 eq{/DeviceGray}{$ncl 3 eq{/DeviceRGB} {/DeviceCMYK}ifelse}ifelse setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale 8 dict begin/ImageType 1 def/Width $wid def/Height $hei abs def /BitsPerComponent $bts def/Decode $ncl 1 eq{[0 1]}{$ncl 3 eq{[0 1 0 1 0 1]}{[0 1 0 1 0 1 0 1]}ifelse}ifelse def/ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt {$hei}{0}ifelse]def/DataSource currentfile/ASCII85Decode filter COMP 1 eq {/DCTDecode filter}{COMP 2 eq{/RunLengthDecode filter}{COMP 3 eq{/LZWDecode filter}if}ifelse}ifelse def currentdict end image $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd}{/@I_2{}bd}ifelse level2{/@I_2D{@sm @gs @ii @np/$ury xd /$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd $ngx/scanline $wid $bts mul $ncl mul 8 div ceiling cvi string def/readscanline{currentfile scanline readhexstring pop}bind def level3{[/DeviceN sNamesDeviceN/DeviceCMYK{ tint_params CorelTintTransformFunction}]setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale 8 dict begin/ImageType 1 def/Width $wid def/Height $hei abs def/BitsPerComponent $bts def/Decode[nNumInksDeviceN{0 1}repeat]def /ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0}ifelse]def/DataSource{ readscanline}def currentdict end image}{/scanline_height $lly $ury sub 1 sub $hei div def/plate_scanline $wid string def/cmyk_scanline $wid 4 mul string def is_distilling is_rip_separation or{/bSimDeviceN true def}{/bSimDeviceN false def}ifelse/scanline_img_dict 8 dict begin/ImageType 1 def/Width $wid def /Height 1 def/BitsPerComponent $bts def/Decode bSimDeviceN{[0 1]}{[0 1 0 1 0 1 0 1]}ifelse def/ImageMatrix[$wid 0 0 1 neg 0 1]def/DataSource bSimDeviceN{ plate_scanline}{cmyk_scanline}ifelse def currentdict end def 0 1 $hei 1 sub{ @gs/nScanIndex exch def readscanline pop/$t_lly $ury $lly scanline_height nScanIndex mul sub sub ceiling cvi def/$t_ury $t_lly scanline_height sub ceiling cvi def bSimDeviceN{0 1 $ncl 1 sub{@gs/nInkIndex exch def 0 1 plate_scanline length 1 sub{dup $ncl mul nInkIndex add scanline exch get plate_scanline 3 1 roll put}for[0 1 $ncl 1 sub{nInkIndex eq{1.0}{0.0}ifelse }for]/sepTintTransformParams exch def[/Separation sNamesDeviceN nInkIndex get /DeviceCMYK{sepTintTransformParams aload pop tint_params CorelTintTransformFunction @tc_5}]setcolorspace $llx $t_lly Tl $urx $llx sub $t_ury $t_lly sub scale nInkIndex 0 eq currentoverprint not and{false setoverprint}{true setoverprint}ifelse scanline_img_dict image @gr}for}{0 1 $wid 1 sub{dup $ncl mul scanline exch $ncl getinterval 0 1 $ncl 1 sub{2 copy get 255 div 3 1 roll pop}for pop tint_params CorelTintTransformFunction 5 -1 roll cmyk_scanline exch 0 1 3{3 1 roll 2 copy 5 -1 roll dup 8 exch sub index 255 mul cvi 3 1 roll exch 4 mul add exch put}for 6 rp}for/DeviceCMYK setcolorspace $llx $t_lly Tl $urx $llx sub $t_ury $t_lly sub scale scanline_img_dict image}ifelse @gr}for}ifelse $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd}{/@I_2D{}bd}ifelse/@I_3{@sm @gs @ii @np/$ury xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/$dat $wid $bts mul $ncl mul 8 div ceiling cvi string def $ngx bImgDeviceN{[/DeviceN sNamesDeviceN/DeviceCMYK{ tint_params CorelTintTransformFunction}]}{$ncl 1 eq{/DeviceGray}{$ncl 3 eq {/DeviceRGB}{/DeviceCMYK}ifelse}ifelse}ifelse setcolorspace $llx $lly Tl $urx $llx sub $ury $lly sub scale/ImageDataDict 8 dict def ImageDataDict begin /ImageType 1 def/Width $wid def/Height $hei abs def/BitsPerComponent $bts def /Decode[$ncl{0 1}repeat]def/ImageMatrix[$wid 0 0 $hei neg 0 $hei 0 gt{$hei}{0} ifelse]def/DataSource currentfile/ASCII85Decode filter COMP 1 eq{/DCTDecode filter}{COMP 2 eq{/RunLengthDecode filter}{COMP 3 eq{/LZWDecode filter}if} ifelse}ifelse def end/MaskedImageDict 7 dict def MaskedImageDict begin /ImageType 3 def/InterleaveType 3 def/MaskDict ImageMaskDict def/DataDict ImageDataDict def end MaskedImageDict image $SDF{$dsf $dsa $dsp @ss}if @gr $ctm setmatrix}bd/@SetMask{/$mbts xd/$mhei xd/$mwid xd/ImageMaskDict 8 dict def ImageMaskDict begin/ImageType 1 def/Width $mwid def/Height $mhei abs def /BitsPerComponent $mbts def/DataSource maskstream def/ImageMatrix[$mwid 0 0 $mhei neg 0 $mhei 0 gt{$mhei}{0}ifelse]def/Decode[1 0]def end}bd/@daq{dup type /arraytype eq{{}forall}if}bd/@BMP{/@cc xd UseLevel 3 eq MaskedImage true eq and {7 -2 roll pop pop @I_3}{12 index 1 gt UseLevel 2 eq UseLevel 3 eq or and{7 -2 roll pop pop bImgDeviceN{@I_2D}{@I_2}ifelse}{11 index 1 eq{12 -1 roll pop @i}{ 7 -2 roll pop pop @I}ifelse}ifelse}ifelse}bd/disable_raster_output{/@BMP load /old_raster_func exch bind def/@BMP{8 rp/$ury xd/$urx xd/$lly xd/$llx xd/$ncl xd/$bts xd/$hei xd/$wid xd/scanline $wid $bts mul $ncl mul 8 div ceiling cvi string def 0 1 $hei 1 sub{currentfile scanline readhexstring pop pop pop}for }def}bd/enable_raster_output{/old_raster_func where{pop/old_raster_func load /@BMP exch bd}if}bd end %%EndResource %%EndProlog %%BeginSetup wCorel14Dict begin @BeginSysCorelDict @ssa 1.00 setflat /$fst 128 def %%EndSetup %%Page: 1 1 %%ViewingOrientation: 0 1 1 0 %LogicalPage: 1 %%BeginPageSetup @sv @sm @sv %%EndPageSetup @rax %Note: Object 0.00000 0.00000 479.99962 479.99962 @E /$fm 0 def 0.00000 479.99962 m 479.99962 479.99962 L 479.99962 0.00000 L 0.00000 0.00000 L 0.00000 479.99962 L @c N @rax %Note: Object 138.39987 151.22324 217.76117 211.53288 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 164.08035 151.92652 m 166.89231 152.68025 168.57780 155.60022 167.82463 158.41247 C 167.55817 159.40573 167.02072 160.25839 166.31461 160.91178 C 176.48050 178.48658 L 177.30340 178.38624 178.16202 178.50019 178.96847 178.86246 C 179.80724 179.23890 180.48331 179.83559 180.95811 180.55389 C 206.78542 171.52271 L 206.70945 170.87924 206.75254 170.21254 206.92913 169.55178 C 207.00822 169.25698 207.11112 168.97408 207.23528 168.70564 C 188.26186 158.48192 L 187.18101 160.15748 185.09669 161.03254 183.07474 160.49055 C 180.57090 159.81987 179.06995 157.22022 179.74148 154.71723 C 180.41187 152.21339 183.01153 150.71244 185.51537 151.38312 C 187.75757 151.98378 189.19502 154.13301 188.98866 156.37351 C 208.62794 166.88494 L 209.91061 165.80551 211.68227 165.34403 213.41395 165.80750 C 216.22649 166.56180 217.91140 169.48176 217.15824 172.29373 C 216.40450 175.10542 213.48454 176.78976 210.67342 176.03745 C 210.66236 176.03405 L 201.61729 188.87584 L 202.78261 190.03039 203.31865 191.76350 202.86652 193.45238 C 202.80047 193.69956 202.71487 193.93569 202.61310 194.16246 C 211.14028 202.51502 L 212.09329 202.09096 213.18917 201.97616 214.26803 202.26557 C 216.77131 202.93625 218.27197 205.53591 217.60157 208.03975 c 216.93090 210.54274 214.33124 212.04369 211.82740 211.37301 C 209.32413 210.70233 207.82346 208.10239 208.49414 205.59912 c 208.66224 204.97124 208.95222 204.40630 209.33150 203.92271 C 201.19550 195.95565 L 200.06844 196.82476 198.56438 197.18079 197.09235 196.78564 c 194.68942 196.14217 193.21030 193.71997 193.68907 191.31307 C 180.97058 185.76198 L 179.72079 187.65071 177.23424 188.41946 175.10627 187.46334 c 172.74132 186.40176 171.67521 183.59575 172.73679 181.23165 c 173.09679 180.42973 173.65663 179.77748 174.33269 179.30665 C 164.36013 162.06576 L 163.41307 162.37984 162.36907 162.43115 161.33924 162.15562 C 160.22268 161.85628 159.28441 161.21594 158.60523 160.37631 C 147.40923 169.58154 L 148.39455 171.76082 147.58328 174.38343 145.46920 175.60403 C 143.21792 176.90372 140.32885 176.12957 139.02917 173.87830 c 137.72948 171.62702 138.50362 168.73795 140.75490 167.43827 C 142.46164 166.45323 144.53490 166.66016 146.00409 167.79827 C 157.56917 158.29257 L 157.36224 157.45521 157.35742 156.55748 157.59496 155.67080 C 158.34926 152.85940 161.26866 151.17279 164.08035 151.92652 C @c 208.63446 174.96907 m 208.17553 174.57987 207.78917 174.11698 207.48699 173.60391 C 181.72375 182.68441 L 181.75861 183.01805 181.75776 183.35764 181.72035 183.69609 C 181.77846 183.62948 L 194.65455 189.26306 L 195.79323 187.86813 197.68904 187.18441 199.53298 187.67820 C 199.65883 187.71392 L 208.63446 174.96907 L @c F @rax %Note: Object 87.87118 161.42117 166.14510 231.41934 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 89.41465 198.74835 m 91.47345 196.69011 94.84498 196.69039 96.90406 198.74863 C 97.63087 199.47600 98.10057 200.36778 98.31345 201.30605 C 118.61631 201.28961 L 118.94117 200.52680 119.46898 199.84025 120.18586 199.32293 C 120.93137 198.78463 121.78630 198.49776 122.64548 198.44561 C 127.73820 171.56296 L 127.14293 171.30671 126.58706 170.93622 126.10318 170.45291 C 125.88746 170.23691 125.69386 170.00646 125.52321 169.76494 C 107.18249 181.08425 L 108.09326 182.85817 107.80894 185.10094 106.32869 186.58063 C 104.49581 188.41380 101.49420 188.41380 99.66189 186.58091 C 97.82901 184.74831 97.82872 181.74643 99.66161 179.91354 C 101.30287 178.27200 103.88268 178.10164 105.72009 179.40076 C 124.64306 167.64831 L 124.34939 165.99798 124.83553 164.23257 126.10290 162.96463 C 128.16255 160.90611 131.53351 160.90696 133.59231 162.96520 C 135.65055 165.02372 135.64913 168.39468 133.59175 170.45291 C 133.58324 170.46113 L 140.18230 184.71515 L 141.76488 184.28315 143.53370 184.68539 144.77017 185.92157 C 144.95131 186.10243 145.11288 186.29461 145.25858 186.49616 C 156.75562 183.28762 L 156.86476 182.25014 157.31348 181.24384 158.10350 180.45411 C 159.93581 178.62180 162.93770 178.62151 164.77058 180.45439 c 166.60318 182.28671 166.60318 185.28831 164.77058 187.12148 C 162.93798 188.95408 159.93581 188.95380 158.10350 187.12148 c 157.64372 186.66198 157.29931 186.12794 157.07027 185.55789 C 146.10246 188.62044 L 146.29181 190.03096 145.84791 191.51150 144.76989 192.58866 c 143.01099 194.34813 140.17408 194.41786 138.32872 192.79984 C 127.16220 201.03874 L 128.17304 203.06523 127.59562 205.60337 125.70350 206.96825 c 123.60161 208.48535 120.63855 208.00573 119.12202 205.90441 c 118.60753 205.19150 118.32265 204.38050 118.25291 203.55987 C 98.33556 203.57603 L 98.13373 204.55285 97.65638 205.48261 96.90265 206.23691 C 96.08513 207.05414 95.06154 207.54652 93.99487 207.71490 C 96.36888 222.01342 L 98.74885 222.25011 100.61461 224.26384 100.61461 226.70504 C 100.61433 229.30441 98.49940 231.41934 95.90003 231.41934 c 93.30038 231.41934 91.18545 229.30441 91.18573 226.70476 C 91.18573 224.73411 92.40151 223.04211 94.12186 222.33883 C 91.67244 207.57033 L 90.84359 207.33080 90.06406 206.88605 89.41465 206.23720 C 87.35726 204.17811 87.35613 200.80658 89.41465 198.74835 C @c 131.64718 171.68457 m 131.08082 171.88753 130.48668 171.99071 129.89140 171.99581 C 124.87351 198.84784 L 125.17994 198.98447 125.47361 199.15483 125.74800 199.35638 C 125.71909 199.27304 L 137.03613 190.93861 L 136.39776 189.25512 136.75323 187.27115 138.10309 185.92157 C 138.19691 185.83030 L 131.64718 171.68457 L @c F @rax %Note: Object 79.80605 212.25458 124.79669 299.89587 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 92.63083 286.82164 m 91.87767 284.00967 93.56372 281.08998 96.37569 280.33597 C 97.36894 280.07008 98.37609 280.10920 99.29537 280.39408 C 109.43235 262.80283 L 108.93402 262.14038 108.60350 261.33987 108.51392 260.46028 C 108.42066 259.54554 108.59953 258.66198 108.98391 257.89153 C 88.24932 240.03978 L 87.72973 240.42728 87.13106 240.72350 86.47030 240.90094 C 86.17550 240.97975 85.87899 241.03219 85.58476 241.05883 C 86.21717 262.60214 L 88.20879 262.70050 90.00879 264.06822 90.55020 266.09017 C 91.22145 268.59373 89.72050 271.19339 87.21723 271.86350 C 84.71339 272.53474 82.11373 271.03408 81.44277 268.53024 C 80.84183 266.28803 81.98419 263.96872 84.02797 263.02706 C 83.31165 240.76318 L 81.73531 240.19228 80.44980 238.88835 79.98520 237.15694 C 79.23231 234.34413 80.91865 231.42501 83.73033 230.67128 C 86.54230 229.91811 89.46085 231.60472 90.21487 234.41556 C 90.21770 234.42690 L 105.86126 235.83912 L 106.27880 234.25257 107.51159 232.92170 109.20019 232.46901 C 109.44737 232.40268 109.69455 232.35874 109.94202 232.33323 C 112.91159 220.77241 L 112.06800 220.15928 111.42085 219.26750 111.13200 218.18835 C 110.46132 215.68507 111.96198 213.08570 114.46583 212.41446 c 116.96882 211.74378 119.56847 213.24444 120.23972 215.74828 C 120.91039 218.25128 119.40917 220.85121 116.90589 221.52217 c 116.27802 221.69027 115.64362 221.72173 115.03531 221.63499 C 112.20350 232.66460 L 113.51991 233.20602 114.58035 234.33080 114.97408 235.80283 c 115.61811 238.20576 114.26003 240.69770 111.93619 241.48658 C 113.48816 255.27657 L 115.74850 255.41461 117.65792 257.18372 117.89376 259.50472 c 118.15654 262.08340 116.25987 264.40980 113.68176 264.67228 c 112.80728 264.76157 111.96255 264.60283 111.21676 264.25276 C 101.27197 281.50980 L 102.01720 282.17310 102.58384 283.05128 102.85994 284.08110 C 103.15899 285.19767 103.07367 286.33039 102.68617 287.33839 C 116.25591 292.43197 L 117.65083 290.48882 120.32759 289.87994 122.44167 291.10054 C 124.69294 292.40050 125.46709 295.28957 124.16740 297.54085 c 122.86743 299.79213 119.97836 300.56598 117.72709 299.26630 C 116.02063 298.28098 115.16343 296.38176 115.41430 294.54038 C 101.39953 289.27757 L 100.77789 289.87569 100.00290 290.32838 99.11622 290.56620 C 96.30425 291.31852 93.38400 289.63361 92.63083 286.82164 C @c 90.30926 236.71559 m 90.20183 237.30746 89.99405 237.87354 89.70066 238.39172 C 110.44630 256.16324 L 110.71786 255.96624 111.01238 255.79701 111.32419 255.66038 C 111.23745 255.64337 L 109.67811 241.67565 L 107.90079 241.38652 106.36072 240.08683 105.86665 238.24290 C 105.83433 238.11619 L 90.30926 236.71559 L @c F @rax %Note: Object 133.64957 202.31915 400.19357 265.86142 @E /$fm 1 def 0 O 0 @g [ 1.00 0.84 0.73 0.73 0.89 null ] set_fill_color 233.89965 250.84460 m 233.89965 245.83890 L 224.99972 245.83890 l 220.22674 245.83890 217.83940 243.35943 217.83940 238.40050 c 217.83940 214.62406 L 212.76454 214.62406 L 212.76454 239.23502 l 212.76454 246.97446 216.54198 250.84460 224.09688 250.84460 c 233.89965 250.84460 L @c 261.91531 250.91405 m 269.47049 250.91405 273.24765 247.04447 273.24765 239.30504 c 273.24765 226.23420 l 273.24765 218.49420 269.47049 214.62406 261.91531 214.62406 c 249.12312 214.62406 l 242.68195 214.62406 239.46038 217.82239 239.46038 224.21849 c 239.46038 227.06816 l 239.46038 232.44463 242.51896 235.13329 248.63698 235.13329 c 265.39143 235.13329 L 265.39143 231.03071 L 249.95735 231.03071 l 246.20315 231.03071 244.32633 229.43225 244.32633 226.23420 c 244.32633 224.56573 l 244.32633 220.85830 246.22753 219.00416 250.02709 219.00416 c 261.08135 219.00416 l 265.80784 219.00416 268.17194 221.46123 268.17194 226.37395 c 268.17194 238.81748 l 268.17194 243.59159 265.87786 245.97808 261.28970 245.97808 c 240.57213 245.97808 L 240.57213 250.91405 L 261.91531 250.91405 l @c 304.46277 250.91405 m 312.01710 250.91405 315.79512 247.04447 315.79512 239.30504 c 315.79512 226.23420 l 315.79512 218.49420 312.01710 214.62406 304.46277 214.62406 c 289.02954 214.62406 L 289.02954 219.62976 L 303.48879 219.62976 l 308.30995 219.62976 310.71940 222.10923 310.71940 227.06816 c 310.71940 238.46995 l 310.71940 243.38268 308.30995 245.83890 303.48879 245.83890 c 293.40935 245.83890 l 288.63496 245.83890 286.24791 243.38268 286.24791 238.46995 c 286.24791 202.31915 L 281.17304 202.31915 L 281.17304 239.30504 l 281.17304 247.04447 284.95077 250.91405 292.50539 250.91405 c 304.46277 250.91405 l @c 329.14290 265.86142 m 329.14290 250.91405 L 347.35691 250.91405 l 354.91209 250.91405 358.68869 247.04447 358.68869 239.30504 c 358.68869 214.62406 L 353.61354 214.62406 L 353.61354 238.46995 l 353.61354 243.38268 351.20353 245.83890 346.38378 245.83890 c 329.14290 245.83890 L 329.14290 214.62406 L 324.06803 214.62406 L 324.06803 265.86142 L 329.14290 265.86142 L @c 400.19357 265.37499 m 400.19357 260.16066 L 385.10759 260.16066 L 385.10759 214.62406 L 379.82353 214.62406 L 379.82353 260.16066 L 364.52920 260.16066 L 364.52920 265.37499 L 400.19357 265.37499 L @c 159.30283 265.37499 m 159.30283 226.16476 l 159.30283 218.47124 155.52567 214.62406 147.97049 214.62406 c 133.64957 214.62406 L 133.64957 219.83811 L 146.99792 219.83811 l 151.67849 219.83811 154.01877 222.22545 154.01877 226.99899 c 154.01877 265.37499 L 159.30283 265.37499 L @c 203.30957 265.37499 m 203.30957 260.16066 L 179.60230 260.16066 l 174.92088 260.16066 172.58145 257.77361 172.58145 253.00006 c 172.58145 226.99899 l 172.58145 222.22545 174.92088 219.83811 179.60230 219.83811 c 193.22872 219.83811 l 197.44668 219.83811 199.55537 221.94709 199.55537 226.16476 c 199.55537 238.81748 L 185.16472 238.81748 L 185.16472 243.47565 L 204.49106 243.47565 L 204.49106 224.91383 l 204.49106 218.05398 201.08523 214.62406 194.27131 214.62406 c 178.62973 214.62406 l 171.07512 214.62406 167.29710 218.47124 167.29710 226.16476 c 167.29710 253.83402 l 167.29710 261.52781 171.07512 265.37499 178.62973 265.37499 c 203.30957 265.37499 L @c F @rax %Note: Object 116.83162 268.46674 196.19320 328.77638 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 170.51244 328.07310 m 167.70076 327.31937 166.01528 324.39940 166.76816 321.58715 C 167.03461 320.59389 167.57206 319.74123 168.27846 319.08784 C 158.11257 301.51304 L 157.28967 301.61339 156.43106 301.49943 155.62460 301.13717 C 154.78583 300.76072 154.10976 300.16403 153.63496 299.44573 C 127.80765 308.47691 L 127.88334 309.12038 127.84054 309.78709 127.66394 310.44784 C 127.58457 310.74265 127.48195 311.02554 127.35780 311.29398 C 146.33121 321.51770 L 147.41178 319.84214 149.49638 318.96709 151.51833 319.50907 C 154.02217 320.17975 155.52283 322.77940 154.85159 325.28239 C 154.18091 327.78624 151.58154 329.28718 149.07770 328.61650 C 146.83550 328.01584 145.39805 325.86661 145.60441 323.62611 C 125.96513 313.11468 L 124.68246 314.19411 122.91052 314.65559 121.17883 314.19213 C 118.36658 313.43783 116.68167 310.51786 117.43455 307.70589 C 118.18828 304.89420 121.10854 303.20986 123.91965 303.96217 C 123.93071 303.96557 L 132.97550 291.12378 L 131.81046 289.96923 131.27414 288.23613 131.72655 286.54724 C 131.79260 286.30006 131.87820 286.06394 131.97997 285.83717 C 123.45279 277.48460 L 122.49978 277.90866 121.40391 278.02346 120.32504 277.73405 C 117.82176 277.06337 116.32082 274.46372 116.99150 271.95987 c 117.66217 269.45688 120.26183 267.95594 122.76567 268.62661 C 125.26866 269.29729 126.76961 271.89723 126.09893 274.40050 c 125.93083 275.02838 125.64057 275.59332 125.26157 276.07691 C 133.39757 284.04397 L 134.52463 283.17487 136.02869 282.81883 137.50072 283.21398 c 139.90365 283.85745 141.38277 286.27965 140.90400 288.68655 C 153.62249 294.23764 L 154.87200 292.34891 157.35883 291.58016 159.48680 292.53628 c 161.85146 293.59786 162.91786 296.40387 161.85628 298.76797 c 161.49600 299.56989 160.93616 300.22214 160.26038 300.69298 C 170.23294 317.93386 L 171.18000 317.61978 172.22372 317.56847 173.25383 317.84400 C 174.37039 318.14334 175.30866 318.78369 175.98784 319.62331 C 187.18384 310.41808 L 186.19852 308.23880 187.00951 305.61619 189.12387 304.39559 C 191.37515 303.09591 194.26394 303.87005 195.56391 306.12132 c 196.86359 308.37260 196.08945 311.26167 193.83817 312.56135 C 192.13143 313.54639 190.05817 313.33946 188.58898 312.20135 C 177.02391 321.70706 L 177.23083 322.54441 177.23537 323.44214 176.99811 324.32882 C 176.24381 327.14022 173.32441 328.82683 170.51244 328.07310 C @c 125.95861 305.03055 m 126.41754 305.41975 126.80391 305.88265 127.10608 306.39572 C 152.86932 297.31521 L 152.83446 296.98157 152.83502 296.64198 152.87272 296.30353 C 152.81461 296.37014 L 139.93852 290.73657 L 138.79984 292.13150 136.90375 292.81521 135.06009 292.32142 C 134.93424 292.28570 L 125.95861 305.03055 L @c F @rax %Note: Object 168.44825 255.61984 246.72246 318.57874 @E /$fm 0 def 0 O 0 @g [ 1.00 0.84 0.11 0.01 0.00 null ] set_fill_color 245.17899 281.25128 m 243.12019 283.30951 239.74866 283.30951 237.68986 281.25071 C 236.96306 280.52334 236.49364 279.63241 236.27991 278.69357 C 215.97732 278.71058 L 215.65276 279.47367 215.12466 280.15937 214.40721 280.67698 C 213.66142 281.21499 212.80791 281.50129 211.94816 281.55402 C 206.85600 308.43666 L 207.45014 308.69320 208.00658 309.06312 208.49017 309.54643 C 208.70561 309.76271 208.89893 309.99288 209.06957 310.23496 C 227.41058 298.91509 L 226.49981 297.14117 226.78469 294.89839 228.26438 293.41871 C 230.09698 291.58611 233.09943 291.58611 234.93146 293.41928 C 236.76463 295.25187 236.76463 298.25348 234.93203 300.08608 C 233.29020 301.72762 230.71039 301.89827 228.87326 300.59915 C 209.95002 312.35102 L 210.24425 314.00164 209.75754 315.76762 208.49017 317.03499 C 206.43137 319.09380 203.05956 319.09294 201.00076 317.03499 C 198.94337 314.97562 198.94422 311.60523 201.00161 309.54643 C 201.01011 309.53792 L 194.41049 295.28476 L 192.82791 295.71676 191.05994 295.31452 189.82346 294.07805 C 189.64290 293.89748 189.48047 293.70472 189.33477 293.50375 C 177.83745 296.71257 L 177.72888 297.74920 177.27959 298.75606 176.49043 299.54551 C 174.65698 301.37811 171.65537 301.37811 169.82277 299.54551 c 167.99017 297.71291 167.99017 294.71102 169.82277 292.87843 C 171.65537 291.04526 174.65698 291.04611 176.48957 292.87843 c 176.94992 293.33877 177.29433 293.87225 177.52309 294.44202 C 188.49146 291.37975 L 188.30154 289.96980 188.74517 288.48869 189.82290 287.41124 c 191.58208 285.65178 194.41899 285.58120 196.26406 287.19950 C 207.43087 278.96117 L 206.42003 276.93468 206.99773 274.39710 208.88957 273.03165 c 210.99118 271.51483 213.95480 271.99417 215.47162 274.09578 c 215.98583 274.80813 216.27014 275.61969 216.33987 276.44003 C 236.25751 276.42302 L 236.45934 275.44649 236.93669 274.51672 237.69043 273.76299 C 237.96624 273.48718 238.26472 273.24907 238.58135 273.04724 C 234.78973 264.97587 L 232.43471 265.39398 230.09244 263.96220 229.43027 261.61200 C 228.72557 259.11014 230.18740 256.50085 232.68926 255.79587 c 235.19112 255.09090 237.80069 256.55301 238.50567 259.05487 C 239.04028 260.95209 238.32935 262.90913 236.86356 264.05348 C 240.72066 272.26715 L 242.30466 272.05427 243.96888 272.55288 245.17899 273.76299 C 247.23638 275.82151 247.23723 279.19332 245.17899 281.25128 C @c 202.94561 308.31534 m 203.51197 308.11209 204.10668 308.00806 204.70224 308.00324 C 209.72069 281.15178 L 209.41398 281.01543 209.12060 280.84592 208.84620 280.64409 C 208.87455 280.72658 L 197.55723 289.06186 L 198.19502 290.74535 197.83956 292.72876 196.49027 294.07805 C 196.39616 294.16904 L 202.94561 308.31534 L @c F %%PageTrailer @rs @rs %%Trailer @EndSysCorelDict end %%DocumentSuppliedResources: procset wCorel14Dict 14.0 0 %%EOF II*#image descriptionmmK'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'K'*u-T |V7@gcD,Sz%pQ?f džk"(@jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-transparent.jpg000066400000000000000000004326641402514743400240060ustar00rootroot00000000000000JFIF,,ExifMM*;JiP p >RESTU bRESTU ^http://ns.adobe.com/xap/1.0/ RESTU C     C   " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?S(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((9/Z3yT0@viK?QOnբ5-B#QL0s1v%$Ir~xK^-ݷ6 ^XLIZ}?Kz6{6*=R\tiAmu[n75FT ~MVq>`+lo"=P/uu} {X J fԼ_p6e54?X;~'բE!o"R[?QJ jUZQvgXlM,e(JaEVHQEQEy/?]$_k7@OFl@{;4cy=Ɋі#.XGvN$|OAlÏ,&5㯌&ھ2YOcMVq55 z%?&xN5-ùGIg` R埀2xrzc%C>W_h/=ZMi$MnȽ<䎙O5!u_d|_f4{'~K*(D(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((o7gMu؅Uo]Ӽ7ͨjiP2EM|uV, }NM>Zp[5f#1҃qM]K>{90NRQFM>Uվo]/.din$ie1'ܓPQEF$?m݅Q@?bOM\:8smbH>Eo kIOzmjLzu̇3d_q`$㻊Ie Oe9[w ( (0hmӈmbys( k&S6'~~ג񄰒oэ~c%\S^W}"*{Z$s?7{/ (N? xN47S,scPFA5ELp{Nr5RjxVf dQ*}>+cnK)$C-hO|#huU&`eQy-vQEpQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQMwXљ*bpaxO~uWv>-?I",F:A^erjm_Dy?E_#|LԾVk'l1{"ܚa.qN݇Ԯ᱖> ~žĚ͂O2?ٯ-8-H!BqUP:AKŔ0Y֊]YgⱳsQ'-m?8:~:setg:p[SwF8PsW붩z|:WsI`A;;9IuZwee Z)R%V28j:RoUn]M0#DAbx?^x(3CfuQ3x;D}ko&MB ўZcřu7F\kmx6TJU8m$O |5:'mג[܁5TQ_1uq刬Vx  %h÷ޡEWQ@O ^вK3tW*v O&hG"^?g>^0Mb4ə1܃_p~c僞z_?e2ᡘSޞ ϖ袊(__%[9nntl c/C* )Z_+O&2B,b)b[,pzgs_*rH֬ v(==_kwϳx{} Gh;?1#;xoխ}op}W,//kZZKv̈́71]΂H#Aj~XN5%uQEQEQEQEQET2]<ѣV` T杅tŢ) ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((]$etIE|Z^3Lz)oWݏ~:xj 47+ <.xOL;{h"`*;3SE迦~Ö&]@+=(((((L𼲺)fv8 RM~i~__KdcYfOCǫ~;"O mu?h(y8o~x6t驺aldFYϲ$+~ˣ,~sO^>]W51oc?f/ٲ冫xhѭMy&AQӒƽ]` %4q+Jt2?.z&>I'ܚۯ6>&u0`ZmgQӥQ-^h^GU}d/;Uq.;r=kR_5Wϟ7'ϰ`{<-58?C6z>M@QG꧃pG A?Yo!& L7 ;}T>'&]gjwz'n\?&݁=0 _G#̲l&G %7ף}~mq;ٜ[u_XmQ=.i MVLLa^_WtkGKtpت81eAEW9QEQHNq*h <2Ƶ);-b[1?< ?gI㏈RzI c¨џ/QYkK|58.ڼnxzk"U^}U1afF.in˶帖yI$yK3O]~0RhHsW{+f+QZ<"+aQTgoNitN("*GV<:omdYa3F GQ㸾%|>|EF:$Jȣr{bllïKvN:9aqn`[_=ymQ_ ~QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEɦ'WXE,@I)WLi7 D'䍰Lg;!qmMzng]_sҖO.woet'n[hד~R2Q[3PAoZ/3Xim$ l TWX ռ5qy>.Z43 2`ǩ4V8˱}˸6˛QλN} cG<+xgIlfA^Gx PI5 #Ԓk>~:=mo&y1lG$_p24]5Z5Ip6>Qj1YYxl,/5_t`<汘ږob/SON&7}0rq?'? <3MlT$~uTW晎wݫJU?axw/ɕ/+Q^ EPEPEPEPEPEPY~(%=kZZi_=IrEjW_^=2|nVSd<4q;^⽌/g;nd%xk q^4 Gj]3phK@UsxѽS b߲]Eψ|!V̓،$7ϯC&!-3jmxRDAq^>EG?^rޥ/K˴Qy{o[Isu & *&ՋR]?F,M5VkTQEdnQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEwZ_)b6A_Ejo |i]Ug{)?? .{") }}lqEW'AEP6,^/6إ\95[▽^}[@%ɕ'H&AsG Z߷w [ Mo6Y:ƞcq~~͒9dؕI]ُߑ8ʼO.;/vڗܴ^D> sRi\eZe{{%K+c/8RUJ9 eߦh!i8[ 'oكmCX֞ !Ղ;N$‚Oֿmʫ<<Ji=rΰθ-vm/z^Z~_H|-E7ZIros}z6U ZVp󌣗S#CkءXYb1^ڞ'&`b[7kv_ݨ|\SQ#OD_Y[#\$xϟOh 2B/4A/Z8-H BqUP:ARRo֝h-p]%~~~_!xS_O_r۹V }g׵|+LjW' ??/+'xgXQ<=Oss_|GOoZĈnz?C;_c OS䨭S}W0u)>jM/lJỳeиtu=#*zO_o*:c6e.hԁ J_oo?^'$N}@͸s^}}OUcp9ũI:j(L(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((?kρWH|Kۙ22ƹ{3VSpH_E]m\E⺧y]G5 ]{סX`pA)+O?3jV?I{D9>>{_=߰m.XZ=WJ?3_.A9ϒ]S=pfm:~=UϜ+_^`*}t\kI 1%c03x^_[G:͒s(=zG߈&o60럼ёfS~_Q»Ij3Oم(' g]Z.n4imq{#~?ĺlj1?{W޾4MQ`\0Ha~:l.|%If畳̏>E_WOr*>/_~GV (V 4ݟ o~>&˱PRNIuR=Uz8՟P vQ9gnyf>㊰ԩJ\z_}C8[2%5Em [‘F#E  >6Dwm_],#lѿw8}u_ ?!񝧄e/p.:TfG R^^q&hhMGͮ7dgbRIӟw='Fd |S?wo8Q좾4>wBJ-^Mio29 7uEZ\fuVcޟ&yIOցwY5ҁ1/~iz&a֗Ο+ʿZu;M6a'OVu[&qұWoUS_.?YGL4r)XzZϊ+vȮd!Uvé %e=0y-Fq?zO3|O~_wxghz`.B?{_eGTе5+):I d}<0^*I$RBu}SBBbPr=q^ԥs_½o`sZfowfY_fr%?Wh z3/mbve>ǣ}AR<+f}Or RI/y5( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (xþ^ -4;Ā3߀5pIB Fu*BI$Ex?<\șǚ2W?w k֚n6&6=]f m1H`{0A?c:sLYsn8VN<*cV 3jU_>sQɱ7~_]Lf? }[7ț>YA^\dcAJ=O>7s?uz[$A~>ύۿwi}S̀E~SdeFGPXd_gvѮEayn*7J^Z4~H <5|NuݯIfh~ ?~8ח%^ _*|{H)57f;$DM.J>CgԨ *k~~ <-0E&ʷԕwfCIm>g<n6_b'Eǻml5[-j;> YR{iB *[ľ5Kl4 <1]{wjе{*lY{005ˉ.">V޵cp9 |?Y| rE&k q?yPT6} o|U:q}UDC>dW8{1]Ο2W/⼧1^Y>_?gV<} Mkf!]lG'&MkKN^60ίn߼UQ@ķsDV>_8*ҡɻF]t/kC:DŽ{}H5͜8A 7Yo /; k_ 4lɧj2*:x}_o\Ev雕8+J46ޯG{8ͥX(B(KMG"++ PE|3|[hX̌ZL}V~í}+Ez,~'/pӳyYWi{\Nx]IklmrQ0ᇸ&+I⏅m ˶ ˝2~Jrí}?h?w/i?"H\Zc7pX=ןn?U`'g?ه f=WLܢ|Iy^[1Zot+kPf q2=wXԳ0U$+?Kյ ^]\i&rFr+H}s]{X'١Q9ʸ ǮO]}^a|DpbUvvO"_mnM)ѢyOD>-ܲԭ+#ER[:logC z~&tͨrNۏzjoڗx_ h4?BxE^M_pUKK~i6u?x;cKTQ]4ѿW /tn%4.QчBVgĦ?eo6ovJVdcG׾:{2'xDmܬ7I)~g_RAn$E`rd?ry~*5(Bݻ5Gg5s\bI~=GοN:|S״c溷#+ Pw,u.smYJ%A;GpHֲ3_?]-"ouY dAJ{+rW{+Ƥ1:|c"),չs쟄_^ lHV]նr`p{ v9h pe˒}#~èZCum*Oo2 #3u# k3YV&>_1;7i%?GQE'؅Q@Q@^jV[uo 2V xώd/1&׭ϛ6ϼg+@׶]lf'.l=G~3/fRS^k{Wbx{̛A%q?gڼ)zl2Kq>ӞXm45ԡ3k/ڷxsswkmA`Aw$>̱cJ.xVO[FUsO]Q_Q@wÞ7 F]PYWe"3K?ez345tK5mtm&TaS{5Ri~ g[փ׌VKHǾhW~ԓoůwbC-2  Eb1迵$cqUEkɳ/uNVy%#'+ yc_e~MsQ_Ɣ8+%GZRUj;Nl(#oǍ|sJs>HvInٟ]m Wljgk{ϴ@mݵAR0?w5-Fѥ<mȹ{S_;)Khc0?QV.fkjϧh3_㝃E-m̾\پ9FrUՏץ=.馿h(+'~$-nY@{85zC-#MUؖ৤Jvz׫e)gYr\\KO6_|x~ۮw!rxQ -R-Va%~χ/G^ce?V )o E,qTQtW|$k_4%ffڭ~߁uh:%~} #\ bF# 5ˊ[o>+üO> __(:?Y ( ( (?޾pu?o m/uC58^ %v_{s"2Ee̾onnaTI*$q>d643!cIkSş2x]; aSG++þ|]|z.w]gk HG{'Q>hm?2x/eQoW%W15ŷ6^x /zV-{(|@ׂn|۔N85NYF:hkc5K)~׿P+z>6Yi^t5٘?3_{h!(ϰcZli:{b.>?@cU10ww+<`b{V误ͷ\+^|wCBաFcqOP{08 ڢ9)٭QJpNjt~U|Pu|,%%mxܑ}#}1|t<| 3Hz2?(.x1GiluhIlq@ {Sh[+IC+ H#phNXUҢ]$Cyk֔X5LvPorּ6Z@8ܤyqs9rxVHu+-OE y"Yb_[jpiNm.OqVMW,BM߻}єVgKJRѯԬfI2ob;ȭ"@ھW?lxͶvx_NlZ)p?0XfWws}oew>ſ 42Zn|kqz{Gը^ N CPIW-u+inem$]ݏrO$׭8w ʸ:?l8Pܯ)>,γʏSpOu]=UO7G-}Z2=tQ0+oa6e]E8kn32}_߂<\7%/sPHPtɍ 4#wW-#/F&^5fIOOOw?xD-He=ԎA c>˧j5 ԭ?{2Ok;._"k v?#$Di_1k,.X*s}|q>jד[iMee>y /qs3%,RM{F.Q/a?zKG'澞)/wK!Ct r"Nvd~[9薿{_UCܷo6g/[-*/ J|*_'zeW؊Vz?jX*QVRK|A~8FUE+Dr>V𮆊ʜJjq;Zq Si3+XnVM-%h&!/EFG+?,R|^'\t8~]ɕ"n="RRXO˺?źf]¢ddg8XR^[D/mOo?CL6\P0^3SuqI~/u<[8ZnO^d`/Q[RMh,x_ s.~!x+{j97J.#ǩ)+G?uZXƱCc r*\)('OtW3XxAԒj? Y*`]8I5PKy#G+ <'uax{O]<`h8׋xnMP&=#,.!R̷goT>^|]YI7 Lyo-~d^"lHk76}H;+#b964a!snP=󟋿b_;Ҟ0@ʗ^ 4K-ndV1?kdWq>H팃WVmk⯅t?MnH\֓,]'+/ߌ|s{4-ԾCVoױyG9\3^80|JK]8B4~=y^ܽqWsSMLPj7Z}sm O!v «_^> Rѳ//Gѣ=דiѼ Reʓ=~hZ((+ƺ7!"knBֶȫ$ѝH*p[? LR2*pA+? ? je> @?< ~o|a4.f':E}kmmKn}2{S%38 QG_t1ONXLھ }S_8,z( ?z ( ( (>@>\Ki64ZJ;?sZ^ -f;y$Reu#x wq/&|  |H}@e_pQF8>%nŗ%Or"rJ]FoWg8s7TpM?4dpK:"^G^zh?%\9iuf8_癳>+B:E~͟7G"&RZ^KQEQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@q_>1SM M Adv8Bgׂ%I`o20Hg5j>).MZ[B<͖c}Cò+Z~[O/3'*U^}>?ohd#, 6?TUo pxRyLG*ΊS|ҿ~Pkzl?eM=O+zSηl>U{Rj:i$_5. kZ_fKzWO|}rcE-1xd oo{O|@iV/,ň{gZ ų}UѰdWbO.M0>V?z~ ~;9܏QSN4or?~g5ꑫ4 3{d9M`U&&#<g>xCJic(7;wY-gׇJpTW-?}&](Zy^g:/|ؓ.ڎ+cSsk{wcő׾Q03iuc&eObϧ|& xYv1i9[8Q'Վ2OrIn_ͥ|+!mSDe~);/Eo?3\T20JOOQE~~Jw_ ~1ki|6ξr>,7ګXi~b#Ѱw? \?PZr| \r@0x^>9~ &'s3\ U^//:5eq^Jp[F;}~(u{7d&bk@vIgsH?>riZBO'/4/ z(''ݻᰴ0t,<b%`+ ( ( ( ( {h1Mꮡ IE Vz.1kK kf=L0 ErwlQU(PQETW6BO4r(eaAhBi=}KF8-;0@# 8\}Gq|1|UkKXYu[{UW: )c}E|_љ>&8w'gV3Fjkѻ(U_YSbZ?~Li^Ts2,T(9c~|<=;Mˤ2?oGqOgV*x}oM<=WFҜySk۞EWG!EPcm u;8.:ֽ'n#מ#^ҙ-# zO}E{yfqʤ=|q`s%ѯ^LG["O.p:c]48^R\bzc'ZtW~'WcO|t>ǚ͸b2[[-oM/#* QSәz?[翩=Er$~|P "?,7]zbEu]Js7N[~J:ZRR٭S<?> ]eY@$g_ ]La¿:M~c&W4.N=]TW1C)#~2X]xf1RhZ!c #Ր1u_N%ޜV vjxQEEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP߷yK+/i^? 'RO$} ~C{o-I4I#C+ _h/#|R{g%F@;k>4,2\[ݟkt{XW8Vzptߪ}>Jo׊<7pdSZq=W_x@hz-|_e.1,_*RSV~hqN. 5Ưc?#7 ԡ0Zǎ7h%>-_/[4R-R9ee@rTWƃYxE4{Vhd^2>J2VgLG*.r]9O~(((((*i.MR; T2M< ^IUF.MF*)FrCjúU֥]GeclIϟ+6^|XtO轾EjuV2XZ8i{/Aɮk}g/M { OOv<{M~r_JI_]-OW&ݴoЋkWNPe y~ G?;@xDhIH: +gѾxjDEC,&~]|nuIӋuw۱'dVJ{*N~¾ ^m+TԴpğ#r 7ƛOmMjږsF¾NNOn߿z?K=գ2;>RAexaIaeet9V~A׵?nWӵb{flH1ԯC~k>0$_eMӝXiioUmRg5)P]d}5=~ZN4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE![Rm}:'WF`}5jiҒi˟_ ~|At9C=o:f1;OrM}|_ޗԧw\lY"zgeOl ߮衮 >ib#n k:1gAٔgzjNM:UKg4;c)TWu-W^]>LpO,'L .cЃ C9Ǩ]]~3VT+4Q F#K>YA"l/'+V-cY ?[c%iG=e{`,?G6O+бM|YBƱ4O=e{`|,_3x%|J51r`_1(Rk/׊Y?_&%9eas)09mIUIM[V#"̳z1S{ + (>|m7IJϢܛt-XnAc}UҼg9ϒZnU kj|PW6YUµ_%ΦkKH(Š(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((_oV_dе6G^'#ـkR -|=4W <YΠٍ}&A<'KIzw~W>C2e%/CX_5" xgPfX˟}}ҾȋKR7"7Ѻ}"H?fߋkgյ̡?Yd88,Qh{}ό,2ʫz7qno=^(؂(R@P2I_)\WjfVm2k}>, =[; q-ßDWND~HzW%+%}Cq! 8FW(Տ&R=/I HCW5_C¾I9gX`?x9g9/U3Ͱԝ((((((((((((((((((((+ǞO/[М) ߁+zNT֦u)Ƭ%Nj?F~D]։嬭 8GS*}M~˯OY#@bo 8sz? jjuge~CeY Ҋ7uT˳?̋%N[tkСEzٳ?ˤx ˩ܡP\=MzxU%7Vb>T0r^͞y5}wV(هU#Ъ?Uf+<  G[YDZcԳI$S[U3Ok'[Sk'/y-[z^HLQZ+>LQZ(1F)h9jsqNcSFZG-⿅g5;ḏe"!~ZZKӿ.&?0.E>~V] M_H ͸!\t#d9ƬHl[NTjJ՜[O*)v RkKW~Z>u9Ek9kGo k,$у #a\̳l6WIδ结X沧B-Gz~[3|6>?/Sc}v`Cpkը18NMT`8zxj_ KQEsEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_9|l^h^a%&#a"k7 %1X֙(c|P{nj'''_SC똵x_E۷~MU[. yKK߶^֟5kx1[DR2koܧ"+ r^ E~<.=z%Oa{Xg96~K\iR5iӧ#͇=~8LaP6VZEpF *W-ْQVAERQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEu3Hm"=(H@ߢ|_j~Qд_Z]tkK;cr 1 q_5Vr\f3w(TWjz4AEWӟ;#^MyÆbXn#V=N~ik568Af'ܓ_~|5TMkeǶjja)I;h_ú%]|*~&uQE~:~QEQEEM6 چ]KlT'c_O7Mmqo24rF*F#",S.CO#6˩f:Jih>m5C`IP^,KHo x6Q=+t#5<,';$+6S [8TO=ܿbe?-s3d}GoZ7p4<3,6*g|U&TRS@ 9Ţ("u `2 SIXZ1ªߘz&Lv((((((((((4J~˯sI1 ,~$vqE5w+_9@}E|? ,j(gL^Ww(>!h(((o_tu cQҬbu$^ (g^+M܋%%?@}E|Yu-$eRׯ@i.o*j>|QTWÿG Qc*+|??k(?Vr>|Qw+_9@Xw(Z?,}E|;G P>⢾?Vr!_!ohӗğ6vdw34(QEQEQEQEQEQERg|W\)C%aȭiҩUڜ[~J5kREy2^7s}c7x)ety? ~5cjZy^gFĒ3_L~#>Դȇn-%F&G(HhzW~ f|SḋҴ_^d|F?6=7zW;QTbUz^+>ͩeGxŤ%3ك+_vE꺞/20X \ z\K_bםzS`_מEW)QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEWֿ/|o "RtthowyO\EݴP+?e3O w?r_?Ds+k!4};FuSaZic';؜( QQE((((((((4|cyHѭZɾMjd4Q_w+_9G>|QTWÿG Qc*+|??k(?Vr>|Qw+_9@Xw(Z?,}E|;G P>⢾?Vr|??k( qQ_w+_9G>|Q(ZTW~σj_j$Xc||6Dh1ċֽfQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@:Mޙۥݍm 2H\wiJA.mTQ3Ï\tڂ_'?[3qӕ*+;~:o|Mg.^}_ZU` O L_.m:!Ha;[AjQ|=w| oxCkLDvcdWxX#x?P1F GPki7Ŀm]JЛ y 1e*O5/dtIbntO%l4uk~kUǪQEQEQEQEQEQEQEQEQEN~%a}G9[W˕%t[UuME>];BSQOEQHNOJ:wjWpXi4W25ff<$__w o/|7J)3Qp'ǣy:S/ۦOox7Qh|Nb^,H 0ŽF~]lv:|iwYmW~'(,(((((((()ɹƙڿB&eKj %Q@((((((((((((((((((((((((((((((((((((((((R{Q^^)bzMl[)W0ʱOIg $xV?궫ʵ:Wȼ~ NZJPg+glC(͏Š((((o? 2P Qt4 xma Tp=I|!_pY?x1Y'uۣ(oAJXABۼ_E*G49 $__y>|+4sQWU U D)Sj-[pl{/L+3,((((((((??|?߰ jrK~H-ʒ:w-:TA_.! oο'SBc袊b O SAMfL?Hغ{a)3J;fJി$~8KMPzOrvGF?;ɤx>-~.)%61Fbi6_ wdAEYܪ>#KM:Oӭ!a1q"*N¹"ڤ/4,>k}M }oWYXfF?#nοB(a\+|_gȴÕ  Z\+|_gȴÕ  ZV-CW6h+|_gȵEs[m"V-CW6k9[ ^:?E[m" rou[^ @ '/xƓ_hjGqyhci"]Cl RTgw=Š(AEPEPEPEPEP_VG ސ|ldOm&O_lW eX#kzc5extU\0Y*~WV>Sp53*ѥ?7 RG"1VG*GPE2]+{=OOl4 &#%ˍc+ cHހ 9"HR7dl֕)שT'd}Me'HyA{-| |e >.մkd BreD;[=W|-^DTET}]Ɩ>.2-kvf6s#8mdOc?rou^%@xXk~Õ  Z?j-~L.~Õ  Z?j-~@\+|_gȴÕ  Z_o3Z<'jڌk2$!#!Lqۈר'$^E(((((((((((((((((((((((((((((((((((((((((+m9|Kulj/f^HaG.\#y~NZ"7=}/ OӪ5tV|OK肊(ΏB(() d(k?hڎ3zEk{'{~ѿijρKcb9XoW;d>=3ܻR4d_\3R E}^oqog%uGƣ]Mj7OIb1^ZA/h$}{ |Wοc|<6{k~-_ |[xևJe?yMwf( G'*r 4DVF@T_79-9fq+G|:ry{L*ׅ[G#OђuR%QLAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_7Iwb3 ?MߔO5>P^>W_.KQwKpDWu~n~ɑyߴG` [Jk􎾃}5&U1 (??R ( (J^uWiId?'UrQdNq9%} ewsmk $V dO .|_QN~%aIE;BSQONu___)1QE18?ſosW}_'eo[Ԙ_MҨkпg/ e:E)iAKT@QEQEQEQEQEQEQEQEQEQEQEy7/ڧb&ϊ4˹|:dYaج)qyqҢ)-rr_'T?Om l Ui?)_-4i?)_-4\,ATW-׮~_ǟߴ ƥzVvahXr "(+ IZ|^IR=DXJlVhcʓɠ +~пU5?&x ES\i\v?:+~пU5?&x ES\hXsBT??/Msc#x ES\h~пU5?&莊w?/MsBT?.?:+ Cۗ➰Ok+]7=V9Gozj+Ztiݷ ,YJX _v~ϟPdv`ZkorRSN9 S HQE((((/oI" 3DP2Y|Y wMƝᶺBt#V 0\{TW׏೟ufm)/.Se-'ę헲Zi֐| M+E;?5O씟}Gp_<Q4?h_*EGE;}GkM Gm➴O^QWANi_{xt9AL[#EE~5|=|L&?G&^쌖7 2'䂾@/L7'!cGKLgz .> d`FA u1Q@Q@/M?>&+/.濠o)eԲW?H'd?M ~6W|,>'xPޟu?g*q<@(Xx ES\h~пU5?&c#x ES\h~пU5?&莊w?/MsBT?.?:+~пU5?&x ES\hXsBTӿv*+a.?+XO;r= }ck 7wXAޡ u W}=mUGGp%E`#Ǿm{ݎ܌yβ}FA;ަ (((((e/(e/(?(#Oђu~H?t^?2ZnD)((((((((((((((((((((((((((((((((((((((((((((+o\xKg^ߒk\i^ ]7}7 }_ώˑґ㟱^gֿF+0`ܴosֿC+'|?_B(?L (1B1a}΄is)gtaqUU1rbt0TeIFݿ:4>V.`Lnr+'{.nu i1{08_u߉"X׮kpdEzkgo^3%It (yYoS~0|76I+<0Rejnޛ輿7c?E__V֥~X>?ߓN)L֗ω:$ʾ/L#)l߄.E~W߰ &4nk*sJ?7gyCNOK ($(((((((((??|?߰ jrK~H-ʒ:w-:TA_.! oο'SBc袊b pm;/2GN91пg/ e:/K_t*G: ZARQEQEQEQEQEQEQEQEQEQ_!Jjf`nxeH(GU_O;HM~7Qs )6#ۡraLrxSNwk:֫eW=Y$4q++9,%ITQRAo-1 Ibz!^wY.a-?G)ַC¦O !E{1ǿ$){ C7^jbIScd koiZE WVLƒ9f=~ED~Z5_)4|n5+Rz 6xtd[IWW5òi_&s;/g? lW5;/g? lW5;/g? lW5;/g? lW5nL GM.F¯2-SUg(ӑ'WF*riR |_ռ31K{\8U2E'^ 㺂9eE!ʰ<+]o$?aq -Vi'Yd'c%R%( (C | hl^d^F豢 ܚW5> k %f4b>ga~޾9uM.l=m&K_ʿ3~`+k> /> Rnƛi%@Mb^cubY!IVv4Ge{M0<>bISop!D?/z?~=&O ހ<> S=%zZ4ڦ[ֳ۽.> }Q@hğQZ[qu3k#y:bcQ"qݧ94kMAڔ skynےXdr5W#߈o h:<#Zށ貀HQ4?ahB(টd?땗[_7Lgrz~jYH( 'sx/Zlj-<uJɴ6GCHg^ C7^{M0<>bISop!D?/z?~=&O ހ<>bISok~<[!gK?skϼ]ƿU|9?km ErQE =G)٫ɯx'Y̱QwM@~aNkkFص}YKi)6}zB/ƚ;bԼT/9#[?kOTQԮtJw3zAQZj>' x5-N\*(2K42Ig_Rx1Ncf~'旽R_|?֬2S8v_Ց1&ݮ-tYG^'_`,H`*)W9g+{ZtK)5C WվAEW{aEP_֥s=>1_?ų%@?H-}%>?1&U4#` 3u׌>[@dfVů`3oq 澚'*{LֳGI]| (d(((((((((K~H- s'/ *J,R},w-:TAM )+-}pm;/2ƏB|5MҨkпg/ e:E)iAKT@QEQE@޿~Tgi z.i 6v-ܐ"HHR3_wCi?J;o_΍ :xT!U Ӷ޿1?wCi?G-OQp;o_΍ :xT!U Ӷ޿1?wCi?G-OQp;CZ,x^VdkZޣ" Bnq8OA_QE./KM+OdHs1M?Ck{3qk~uKsWo%3]H]ً1?RM&4S~fjׁ.'AIXSޤ޿a'UN=;;$gƭ?㐣jݸo mӡ_5,sGCYeooknF **I$u{m5S[ǮZ,Jt e\3=QOo ' i6>% RʾI?ЬYJ83r0 4_ipcЌGi$ U բ(((o )3GJg4i6L=vq+?)m/$Ů3' ei#+卣 &$vO嫣F쎥YN Tk' 4|!emt& fǠ2; M 3׈o%-/\k]KMtta ͢v?¿ \-93'X> %VgwX!?^iQ >Ϋ ( i&A3Yu?5M?>&+/.楔dA^OSDLd9pyС&aET~2K:ofo< ͒-.7I$Yh_~ƭ~:΂oh3(<9 u[TaESQEQEQEWQ2ߊ?E_HWQ2ߊ?E@MQPYq@xXk$:|T -~U"XQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQT^E}0i[I4Kre%'dxC{˫kZ}|sv쨣>W+O.t tZrTpɮsGSfK-,g:n8s ~ې2\Mwz+9?T͹xej%o߱|(|֋ 3[lP9A޾gqh6>-΁;n1=Hg00Y^-}e\GC ?rjɣ?7iׂH9TL#X{oa/Y }Cs¯Ez*’xNR pHE' WO_t"2@ y Oep>'p-X|4/9m?Ge~?1'jqvvז+wg5tѾ u.Kij&'x?u}~zW؜UleWZ0x,>_B8|4ybwQErE&qQu e"A՝Ҿm-k3FQYfR#StYq!׃@5r/4ĺE:&?6j#TiwH-@2H}.I2(TMAjߗe||QGUv/NxM.Dz~!v b2`jzvm!/,$`^  ~^ש~͟'ucGJWOlo?+G!1u}k":jWn>Ӆba7*2v7[guQ_ҁEPEPEPEPEPEPEPEPEP9[W˕O_Am_.TYӿ!m]W5 Yt[UuME>ESWh|[G5G?v_eI/^;~kc.Qמס~_˦Tt?1R((7 {#^.|?m/#]ƥ'Ÿؖ[L~wϚq=kqſ9RQKE~Î-?XpU W8NJqoE_'%Q`KE~Î-OF~յavKai-ו]8qE0H3's!OJЮv(?\t Wj,(b-\v#7L]v_aѤA_?E/CgNJH.DKl>kXv vi 'gQEQ!EPEPEPEPEPEPEPEPEPEPEP_?[O yk%( ΟBO fkz$UC;7_:&4 4QEAG_S}C(t.r,_ImZ+ (_.NOಿwxT_uO'Ñ_,~^? ךJj՚蛌@>/,+gWL>忇4=1)g}ES$((((+/+Wboyf0l('9'_fPe*#@ҋ#PET~7<:B*+2$ Dg.?휕kTaESLgrz~kɐ|LV_]o_K)~xCe~xCЁtQEQ!EPEPEPEPU5}.[otȖ{;^ nYO*2x+>"Nqb&)X5럵~c@ẆבԖ}OL~m8IW=NRG4 SD)(((+(oJ"+(oJ"禊(,8 S,d_?D>*ׅ[,(b ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( oKr_fR6#ߚCxkkwDT0Q.Fq!B?"׷(<ʂ|π(?B( & HN 9~"ǗӢE+ 5-qn3_K")gbP2IedWYabks|.WyJ׵UWcs/S;ۚzMIxFϻysWnr-{2u=f;.dctV&>=|g PWKϬ⿝1#ol?7,?2ohωZ|jI/ #|au?d7j?a?[zwxO#GP4!iVK֧6|e}3~f=Dj};zG,Z ~Ѯoq= -tvM;W /MZR/+y}k׏O'_? m-|cf|&xҌ=EgĹfS?eJmgW>|Imr\m >b0+/|1Ny;.apO{#Hic<~]z{WϷyge٫?\(O Wt7z6h}'=AG ֵQ%(4Dpz4 |OO빮|$0u _OkcVvi4(Qz{Zmo Gqs2Q~x{18L0x;gWק~%\#a'`㺳vӯ^o<5迵a,t\0}?4_4+Qmgyy\wn |;x$Va%GZ8/4 {pSn'׺K[nQE~~~QEQEQEQEQEQEQEQEQE?S>|_QN~%aIE;BSQONu___)1QE18?ſosW}_'eo[Ԙ_2Uyz%oGHc- )j ( ( ( ( ( >(5gnKWO\&,Mj`֛NZmAgOB?뎕]W~Q)_~"XQEx7&_sJt-+o_Ε&R lȟW?\'_C*~QEDQ@Q@Q@Q@|d~ VT6-EW˹&Frkn.#yb%.T Oa_OA~ҿGgwfl lD@Źviړ9ߎG?h˯]Rs}X!8AӞbO5QRPQ^CHD:q{$#b;R]k>dn|{Q|#Š4D/`g→|itÐ|OECI\r̺+*O Ð|OECI\r̺+*O Ð|OECI\r̺+*O Ð|OECI\rϷ?e_ ?* &0Y_럀_)J-^EHi.^L$׵|FO, iyET~#4 __hH3+0W5dQ@[* ?͚&Vs`i}~lԲ_>87OX:Wx}kq)$otQL((((*%~*%^(,p ȏK_.Z6̟"!ejK (টd?땗[_7Lgrz~jYH+O$2&+O$2& * ( ( ( ( (?N:=x{']wƝGJv'7IV'7IV%QLAEPEPEP_7E~(ׄ_Q}!_7E~(ׄ_Q=4QEAgGOc% S,dTaESQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW%W"Ck!Bgd[Ym=W7+L fl v:{7tܶ*VExS/u^ Hߠ;YTQ_ѱa$%Nr'9}¶ S~$4{}ãúhpHM]=S:SU)5k?VO4Y Lr`G3]5|]|7֬dfhu"碇r kp0cj^qr8RAEW{A^#aW_zҦ09;v?{ufxC4?ԩu݁<&*uZa;[ ZO/35ay$~## y扃"V^k/QK.l˞VD?H#[8xg<6\wDL|5OԩQGk~LüSlF ]Rh5EWEPEPEPEPEPEPEPEPEP9[W˕Tw-Xۃ~Ν! oο'W˧Bj/IИ(GN9>c-jLhz/K_t*UÉD}G: ZARQEQEQEQEQE|Pk薮M|Y`?o6}ڂԟ!>0+BSqҿ+]D)oۿLW[wɟWҿ*L??X蹫N[?'U5$ :* ( ( ( (<+dǒ9\ 2+_U{%/Into"o+Nn~vߵ Kİ| ѮT^vۻ) spGc;S=뺵zG8ϭ>mt&Lm#[ZDE*UQEQEQEQEM?`%,?2gK@yET~#4 __hH3+0W5dQ@[* ?͚&Vs`i}~lԲ_>87OX:Wx}kq)$otQL((((*%~o |U,p?~n?("?/Ykڿ2?#RM,(b>_ɐ|LV_]o_@SO2Ke ?"Ol?"O3N($(((((nlo:4?P_7wu(zڒϤz o&Zzz o&ZhQE1Q@Q@Q@|-^DU|-^DTED>*ׅ[G#OђuR%QLAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP_>~  2K]Ār|bP }R)Uц#Ѓ] [bfge:k&~CQ^@~NS-Լ+#Iu:8J ^/vזY/w^aEêIԭuz];±AGvdXe ^仲~'3><r}wg-z9 'B^QZemk  8aU@vTKmyv 9v /^Š(# (?5jO )QlA3{⯴sĞkKOuv=|+$>I;yqG>5[;Vx7kZ|1aޗ>^/q2Usk?D(h ( ( ( ( ( ( ( ( (8~2i :].?r,1)r~_o2%' Ƕc5uoUuRPVYI5RKVzUkwˁ_Ԕ+-'h$i]$#s(cWH-QLA__hlkT~Lwҿਞ54!85c}cG5hxs&A"  n?*ǣIG?eu0H4k":R2*z w{]+qBƾ (((((+񪮅?mů?b/)co.)aTz*YH+ s&3+[ h5IMڭi87OX:Wx}kq)$otQL((((?(?_՟lxe )}>I-wUD?ثGlkLzLh*J?^>|IvɫFԬ$~G ɡ~wx6X8Oݕd(Lgrz~kɐ|LV_]o_K)~xCe~xCЁtQEQ!EPEPEPEPEPwc|]Q҇b.ӨC׎Ԗ}#__4 __4 D)(((+(oJ"+(oJ"禊(,8 S,d_?D>*ׅ[,(b ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ()7izE0@>I~H]5Ӿ7I~~#<_wikR"~d[Iyw KY]QWԓ_pM.Z>/_? >' ]{~#|8u+n;.b_.7@ 0S4mOUEP3S^UjIk?bThQ$WinQ\'QEQHx?߇bk2;"˧Erv޿><3\x[Zfi\s= #q^P|q.:dHp8i';}55epy}m43sZ9kr%n7;c|gw?Jߴ>+ɤkMt!ᾜjܧ<$;E*0dt8*G 8˱jy%Ѧ86R?i⼏+ÿeOS|VlZg4Hz>=kf3a+~iQEqQEQE||Q*||_+-*R^-b%Fwf%87з_UO-JUE~*>7з_UO-EUB߁_wT>7з_U TWZDU4@?;?JU`Kj[xM ^|F9QEϊ;{x_9P3;ʠWu_^(IbJyL^co%_O9c_6x_>(-z5vQN0+q(Hi s OX y<GM|g?g{fѭ|YMSPE[ñT%Wo?,+hI*6r;rNeE~*>7з_UO-EUB߁_wT>7з_U TW>w ~}%QB߁_wT\,~Q_Rh~8IU'OH#Uvom-;È FG}X#sOkֺ&;<̳r(#>9_WkgZ)4 i. D|q:'Ǽu?[KzNp? 0s;WM[4>ѫ7ۧ`pYeQ'3 Oɩ\Eo:116ۨf#zzӾ:߃ۧhq)s>QV@QE߷+ү?`9:TH+"_sW~O_.jHuEUQEQEQEQEx> 4Xc2I\CGhk#KdDY#u* __J{ ekxi?Jõ&4y~ iK?uoZi>,)., |<jsգ@9j3ʒC+R:i ٓ o])xLQkrԑVN@J1Kk Rg?Z$x}vkdGOɩV>z_('?S%<y~rg*''@BQ_=g*''G<y~r%.'4HZI~%\,nf>$G.i< ZOmš,mB@oroƩ/-fE#λb8IKg_^a\bR KD [?1Hq5iaenݎ4QZQTHQEٯo-o?GK(U_ӏO#~1ֿF `+'JhQ_+~ݟm>qxF-|o>g1_#5?$V>oGG;Z~&D\,~Q_MOjH?|E;Z~&D\,~Q_BN]ٮI&Q,i*U>>|K[]GS iS\,~~?e/ ܥ׎';ðI >?:qWϋ|Kzچ\X ;*(QmOT F{2Mss!IYI>SGTPYԚm}m'BiI IJ$Vͼ]$wDjC?e?d_o =펗DGݹ2? $z@0)j (>_ɐ|LV_]o_@SO2Ke ?"Ol?"O3N($(((((nlo:4?P_7wu(zڒϤz o&Zzz o&ZhQE1Q@Q@Q@|-^DU|-^DTED>*ׅ[G#OђuR%QLAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPr+%l6 _UPu+RG_TFߢ?4oR/Ԣ,?~7ܕ4)"ڕOóթ~HC)I?-$6~QEQEQE]f6RLhuWB]X^;% tPBc5] +ДM_..3iN.޶<*k9nchn 7*"WGM;0(f. 9^Њ3ڿl.f~l"?}_|,E__Br{g)5 ( (+,c]WOtgVпk[)p} >(4`J)p} >\CF{ʀ^ ME5 >sPM}% M iZ(l;[i?&ikV[E\='HAU?|ϿsB_t|W #kBX[8R ?!#B vM RE1Q@Q@R')n*5GkE|ڊ*M00-C4mѱWHC_|S:>~r- GƒqNݍ<,e_݁MG/%7?Wͣi6=%n)wxYŞ0xLJiTٳQHg9E8?&1(4`J)p} >\CFѴJ+W@igśo iO%Rq☏`[Y` g 3gl~}Nǧ鳯ϥ[7.;M )If]~x 5mB?) _C*%0} ~<W?\Ԑ3($((((+)'T߀'BMdunY9d'o:X6G.lo I+)A_Hk@ xaaq."`n[7`b,22>;$񦒙)7%56*VgYo w5C\CFуh(4`J)vCS[Y\^ΐ,9ڱƅAE{oثyMᶸ`|bQ6VͱOIg#6w6%{@vTcnH "g#O8wed|+g*[_?unJ'#>|4rOvv?ij7rI FV^ƑiIee4Aԓ'I$[QE1Q@[* ?͚&Vs`i}~lԲ_>87OX:Wx}kq)$ot\O|#KAct6;i0BQKh4%>C@ E.QKrD0UFbN9Q^UNxß1nf,cjkĺͮgPh}Dq=ۏyqTIvb>=x|ÐWQgY*NK oTUT_\; =Z8}ҔWMQ_EPEPEP?{W>5]<>¹}Gy':4lUV+< w|=m%뜵ݾ`2'ǃȹEhݚ{^X%-\^M^o_5ZZNa%ÑL+n}ݧ l^r#MYtici]+X}N:s{845ܿ_sm<浨/O'5G(_O<+ko"( MmQ  ?B4y_[…iD¿&+8OP ?xWEZ5 VG- ZT + a%Q(((((*9`td4`( Tnv}2'JP ?x[EwtP  /O'5]…iD¿&( MmWwEp~< ?B4y_[@'(_O<+ko"+Ƀ[p09=&HJ(g@!8#QEQEQEQQ\eo$FI*I'}KVC6Z=gcp&dEe`AƸP ?xWEs!r_LXBi MڇᯊnRđZ\1%on y_ֽ7c=+wy 9]Od0̿ MmV< <~4 .iMHFpX O_ZيT57Y#`YNA^a^EPEPEPEPEPEPEPMCIՠ0BzTӧ)0Ar_~< /O'5]x.[_vm+Cڥ; 3[PEPEPEPEPEPEP}___wf`I\f)IMW MmWwEr:?xwRQҼ7PK)c8UA8(((((ZƋMNlm->q[[XXyWh/O'5G(_O<+ko"( x*Y IYM@g=}k(((((((((((((((((((((((((((((((((((((((((((((((*^q/3eROu?w-?_[dGڱǵfL~gt3FмAn6gp WwO~P,-(SqMem?ckT4I6wwv=>(O߂(((((j6Mw1ZZ¥w=K?÷/p[Mg*dWn;az#f8<'_nuAk+ ͼ tj:Ec/ܞ@}FGnX+oSqZ}0YZ3kzۚ4QEpQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEOX4 *Rm"i2M~s|vu4ȶӕ.>r~]6GskeEGF xC*~U^M>Iuggu(I9[{/DQEaퟳ#|)N[ Ler]A?"R=;5 8.YE9P]H kB?cMoZ\yt,𮟐rE~_U(XJ~<8 ;:+~z[UgQEQhQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEٯo-o?GK(U_ӏO#~1ֿF `+'JhQE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (kW?001swdNq9;%>{iY? ]3yzE#!Ws 珇翹i..%bK+gcԒy&5WP{y34O5tr:}տ:bZw*;FᑊJ+>tY/.u+OEsd)V<烜}_1HȲ#u!;gH?XuNZV UX$gE~G9VKFn~|HҖ Q^ 񔝮n^Q\/߇'g=#V_s)kR_i`?%gEp~RuCKGq__ K}:G|{ts-ƏԿAwCi4wCi4}G>j[V7s%m4¢(1|;F?ڛ㗆WgGa^3m$vwbg8zexN"]9%&]vuajb#V2qM}O>}~0h[O [E8 I$OEBa԰tBm}3_^XD/삻5߄#Qi,݀ݏpѺAhF"V73<>"kГhfo4xcN״<脱{>A|xkxBǧ]Gq?²c'kZt0ژe^U3%?28ƬZ?0+=(((((((((((((((((((F^IX$ t2 #?Fuz/cty'H(u'p( @9?p_~?a,BĊ~i6kY(?4 ѦW_JVSvs}n|i3VzvG #~}Yx Z_E"%b9s՘$s_:0I7xթ>JЂi>QE~<~QEQEQEQEQEQEQEQEQEQEQEQEQEQEQE~?n?|94>6k q%[R~SH߁?c_r IҚvQE2B(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Y a8q-^U5]6 gK̶?Hta5U\]Ԣެ~EQ]/_|;AOe1@:fR\MSU 8JtjJEiEٮQVfI [t{zf\ʰ Y Jy+/x#EVe66˰|~,Iks4axJ]/m]<;uxU->Hß[ctUh(̳Xد^ A'l~DoCQu̳?eϤ?"CFk*ˤ'U~S^76C_6'\KdXϝ_x76C_?o~*:=y8<7j}?xqQEɊ+W?KbW~Kfe{b 5)ƭ'xS?!a*ʅx.0+cGf.j,-sI;_<5-c㫑?Ksj#dTmU~{.K\ؾem\={yc(8ɭ3N^e0fWO9u> hxegl6>\y~r{)/ i^-4],eڑSz'OZ8X \>o_ALKF6r]y|n?c-}.A})n[(-`,wɯ(Z3^NOk~{4ïW?zګp|ӑ{d hfY֛VMQKMR +#`(((((((((((((((?c Rx(t{7.ϻv|Ǧ0kqſ9_R18\r8?*LhZP`l<7mv5E1Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@?AZƝ*;$M?vhV";sRO\~O8Mt +.G+~Vj+}Bຂ;a+px5G2{.z}k9a3D%i]?Ui /\񾦚~wm!!G=M׀L Lٰ-Ej}mX}WVhr~z~a9ш\Z;~'  kG{yGd)p['(ξ"W<_aB=xIqt)j!ocO5߈cPЯ F}\ ;:"0Gc_kg{oݤRX&@zWcik9yRKKX~oLyW!<+.+*N= όv+NcLϥxhw*u(mfM$lc'+߶E} ǒ J{ @CS\+<5zczw_S 5 )mWQMT56WF :;?W ( ( ( ( ( ( ( ( ( ( ( ( ( ( (E{ϬkE坻*/Vc* T#:!FFVE|WW hV:eǮ!W閮6ip$m6Ɋk0|־— fu!ҏz>dԪ{5)KGOŧՇ?ֵݭݕGv=}qsX|Uc.} M"3Ep@{Uߍ֚Me+eb_7?/le|S#?ӯbsN45[5Rrv[^jK㦯YY~ŧG~π_5Oߘe-\}ǞGo{Ծ2q%m\ `zQ碃 oMii(c}II䓓_[T2Z+?/v|/'q4t۾|/}/Gcڦ~d'kV+JS?{#N*VKD@*K ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?;?lm㖧0]6^(6}FƽkxEfW$7Oγof=mW>lSYޛJ}*bM+0fv@՗#~ua>S2k6~QE??e SjZxE62.wp{ Tuxr=7~:7iʖIW#teοX+@>إ&9GF=/V˭GWc!>k|FЭK׳_; υCj^fڻ~zǷLd<"hQjw֏puu=򯀾8/m俉>hseQOm8į|/ST/n^~00Y/`$^tG\Gpo t{w꼯nJ#"cr< \{qL҆,kǯ|Zs_5Ǘ1KuBF~cJ0,U[>m{8j8^+[}#EKoi:oͳe_I_>.=N-CG.4oQx5WLi V&=ʸT@9 vp9 pFH7zu7^xUXZu?+~O%Q_~QEQEQEQEQEQEQEQEQEQEQE#0E,$Ea۩#$h4v&=_`v~.i_ _wnJtV_k rש׈xGh~v/EW^;7Νszzu_j }${ڃܐ k?:4Q eD,kjUR[O\JTkXzXBRIWo4 ;=ZGcڠ("U$Iɭ ( IɹIݳ1"d(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((]VIFn4Ҿm-ɨ4QEQEQEQE|nhڿܫ/᜷c#u㏁#_|%؉v7:ƿD>8h|"n{,޿g9cbFb`9,VYW .|PêOV?_h / i:x}i7JQpJqS(-⻂H'&E(ȡA/ڣGū3ven-6R9_,B˴bN00+:?l+>=%VOuQxU6Reݺ7ulG.M{j_;#Ũy |#=;Z1^Γw]2\dPNj- V_4N?g<u0 ^> ~ӟoҵ-$E#U$8#{֕iiڅtU⤊mk5 tG]a 1i]&ӡv(8(F`K^]oῄn^Đ\ܡ!FAdAtW˖o9186< ͥEx[|1Ri#_ű~&kNԭ5{(,nawG<zU0Ǧ3gn?ȳEW!QEQHN(h qK@Q@m 4H 3¨I5?kͷӧojK"y )qaW~70e.{-:󿈿<dW#G}pOQGڿ*ԕ\ gD -.١Ni8oza5tC YnG7gێ1_Xar/{_h5'$/z[oGEWgQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEVG|Wx#׺ޱr}C(8w&_ۧ(|o.,b[˔S|kc#/gɰZ+{}ۿCZhSxoBDp>_Y$ vxS]L4,ı> ੪xx(w\8ƫ}||c)tMrPg+-zۏ`_w4ߍ:T"@VA<\Tr ܮǍ>xI-3á*Hkβ*JJٮO=ĸnT[˺~QPX^éX۸ YcqѕAO_4ӳ?M]QHaEPEPs—IJȯhTcw-gQJl͏ڧ@㧉QWlWR%_1[~ ˉGs~G. C̚g__df-OfvU_νb`C6沿YYP?8䳪Wk~\;SԾkGQ^)A_1~ڿ.|UZxJc1^F,, $1=/+B1Y]F~d+5Җ^MOs>W- 7~S^wc2}J Khk(ArM{=qBqzEX 0?@b1l+JEv-arf5JXZR[Cī?/H<7MbI2O8{ׁaOjQMVJ/odt=2FJh YG۫1C56<6O%_>#rNcgϏN?3Z!~+#Zﴩsv~_G۱u7zm-~jA֫ mJ⺁):^W_Ή \7Ҙۑ';^vT\-zjoq~h|t5#? lP]^~<3d S9xf ^^~ѧbHe]LJ3o2}h?vEJ+Z~:xI5?Ky%'#Wt"F(bx!g[Y)xeCGH>WCK^>!m -E`pqy wV_z_gˌ.߃z%Kuk @{O @2|GVcg'WX,Blʳ %r]d-S֑#7^޾*Z<}φV֍|!,NF#Gb kʿW&׈=3Ga&̣Ag؜5˷_\pq2}hp_N:2]1-9,4]6LNZqj1\U s,ʧ3ZECyW~3K $4uOu}Ggn@m&۵yFQ_vcuZh;;)<=/{|QExg"voGգ_.ߦGr~X/i&L[#@aJ<4&S.z'ݤRo^.:hN ۏ5xZjFFXԞ ~0toQ@"xM C{ 42"ϿN0{K׋5 U˻n>Y0G&>}ҳIzϩO啸g2gw]W]إII#` ЃOcOۺjZΡg:lfbE~Y*帙a]^S>[jQEyQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE\E,Le8d>x5?_! n.ZXYj;$!'>(۝8[Xpu1yC5g%'f{EUQIdt7+<Əp$hqoPUqºu_q5W&&3iwwO9a3R;Q\`QEQEW?OsXUص#DRʾޯn 4QW2Xj!X$ʕ5_fJZ~6>30Ykm/<[Y]*4?f={^Ԟ0|h*/G({0Aýc_[F;|w9>Ƨ{,;]\H]ݹgv9$M~ fʄ2oY[/}3"~(7#_Z}l ZTf_s8tm_ zE|hR/ݎ]~oS_*)ҒK+ +O ( (9?׌1ld~_e x;<1kueqg$m%dg ,3}YgmI"`{yX~eٮ#/ Fo5u}f.4ԄǝiY];h}|\U|#uΏnX\%YGckjxqI/T,E ZZxCʤE-f>G@Դk}o%Rםb){FI筎8Z#% +s<KA"1Jc'{ïN5"tGJs7N] (oĻin\/a2&Y@>F=k{ú΍ϡdW pҿ*!iMw^ik eAe[5}_·ywz]4m8 =ح~ivGu7r-s F5y$KOo*k͝WV taՊvg& (Š(#>(xO_J7t-A\բ=J{ۃ׎"[ў~aa/^ks3G|96,R ߋv|mŲ-FCjTr@u\u[~%k}>&/bqop_l^Y#[J^]*EGBk Grۿ_L( x,g[u?NNi閺ueu ќ ? _4?%$tŠ(0((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((mXY^Aͥ4R*WR0A"QM6КRVg??bog|* M~v; \~$|%7­Sz#!q8#0E>J%|꺔5bWlI-b;yTi=3k}kQa"i@@yFr'ؑ7oo-pA4#K38ԓڽK~"[J[Dk`O~Gjأ$!ƺ4\c ɕLSml7W?s8NJ8E(ndKyӺ_7 uܵb{}o6<i+)$*oq0tESU}?G.Ukkd-yƟj+f-"Alqo?/= '\V S^i?__NJ4o2 jV$A7<APwx<]4sL%Un=5Uu2W iãpx5>1Ѵq1S]1nG?*|'Ğ jK| HGy>~ CpxISyrk_S jRY V:?_>>\.Rz5cc~>??x4~oex||fI`nv_;GvT+.gIׄ})?XxC5;u4MO#=2~2|,E{tl~Vv WEyo j%R=r4}q1嘎Js|' 9{J+y/?V||sɾOWr\#a1U~DhzLJQ^)PA#2mTxLU:XсsuN_8u~>~>~+]<e4QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEV~7\ncc0$[=VQRD1\do؞|#]2?'?{ܞ+勋kShg}?P rpA]k+ 25k?#RUj'϶:yWգj8{~^X'e/|k|7i~5I54aQOA7_exO:/txM QRv؀k⯄rs4'VЁ5;D%Tvo~!o.Jk 1?xa+7j-_;q^eo(^Oi/3v퉠x9~e){l$ԗ⼚ EW韘FƿYگz*;bP%_{_A56_M%/0Yٿ*?ۣ@a"=GNB"3)vWIkV>u7h~p+nkM ]RNkgWǟW~ߑs&g]P/`{[8>ֿ+|sUnoi!=p8i'ݿ}ofT7U+z\g=2h.# W~ƿ kwK^"ϔ?0+c"~>GWnY^_y}kى/i>޷-XU؞>(~^/ugH =ZO>ˌzi|M+D2)de1t _:pP/)Ttӿ_#,U\T8)SkG&ײM[5ǟ'59RIu[q-%eۤ}~tWW{:^c6iTUhVV~/|Yz΋1+IĶcaB+ُ͟d>)雦+j-dXswj;>q\w_#K3R.QE|EPEPEPEP_:? bcNfYJ^|^?YxSA5)6PH{(Ԟw5u?ok?ur!p>S޾ۅbES_~uƹn ԟ*(+7'G7<=3@Sm"0q՜O?boz7Ԡqvع{/WԵ[V)2mp>K k/U=>ࢊ+((( wzWWmu;V +"pGZ~ '6-׆}ю& .a8_vǕʰ9yqt_y|ɬma$ӛ{l.]CM䵾33_x֟ft{=V,`}̟U>Wݳ0/, WZu>{ˏL%NǠ +VW\-dsz}Llrћ[^: qu;Hٷ}cl7]}~B\iEXImqrQч zj_~Ymb18ǠrwlW๯{V봿=tݡQ}g~ܟ4]#V|Sguu`>{=_wywZUFb3JѹSdVZkNp&vC9=Ms5^ 6&\-{zyvsRU18&{4crx[Y=lxB(f@xlg?n1vJԓ^\P$]igOoǫW>bp[Ɂ-uױ{]m~b|wZK~֋CYY8V~_a־a˪{\4'1y/cG5~$:}6>vJ0ᇸ$W|S𩡱Cx}p>ttKLNWu|qwM1p_Sr%涒Gן 4x[ޟwh0Whd{g{{oMuw=<+\xZB딿zO|ᯊii-29C䁳ӽy߈|Kk։f0H$}~U_r\"E^ K=o}]1+y-FӼtH?G>$o j ңskFanx'_># }I|۷_ysĎWafvwŻ,7!r,&< 2i^ưǼv:lnqM&|~[EgmPā`*KYUktk+Vx\wV=kJ5-m FQR~Xf37l]>sc+lSc^Q *N }++V+J|j`jT7tkP+v3Z/`(XQծ_ ~}~l+(4(--bX-b$TU@OEm%$QEQEQEQEQEQEQEr2[?=E2i ?_%?ׇxFyI4q> W8_i K&]0/#;Jƾ%8LU*jTܒjYtgqM`tSv]j=(吢(zKKESl;_DʑƥrUFI'O~e(f7iAa\P&z~L((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((ߏ߳whݵ-}hmRNtsҿеoY2xx}CهH~(4ivrxG1XzM2xgXUNݐ{'xkO9\KOVz{جP,uq7W^WQn12.le MndzчɯYohWXBxd<:VOpkߴ,n+eff'W7W.nޥ>φlhV%}ݗQEEPEP_Ott->m&*~Y:Hch1z.&[9k[ 描_<_z^wjPܫ Q',~$!+1WQ_ok3h8wXk>oFY >Ycdn>WUqZ(|۹Q]|}*~tLliI~Z׷o (t((((((w_|'oEd6Mė@~,~^.Yin|56Wɴsȿ}&[cs;J=]_y9e:p.j_ϢϬ>+>V&cZLذfV'k/ߴ~*m_رTaM<{ -3K׵-gdϰw?AN{Z0-X5ÏG~U>'kZY~QÐU2wc?&[QEƟT_u|";3" 8Ӛe~xwᇅ|)t=3BK6OD d 5=3\uJ?ftD 8n1UZ. KoĬg Qj;z _R #q> B^KS {⋸DS >K~,AK]sF3l# y"$r.j߃mǐ߳׉O떭bK(\}aX)=Hab5 tv%~?s\CSE]?|jaj޲}vAEW}QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW_[x-^]'9XzgZiZq6^\!X'@{kJN%Vz5c3얖yxyIk~E_`MZ[o<NKfLp }X׎- 8S~8ڰ[ a]?0&|׊0ZnY){[e_D}IxuXNI%? x7ZnGXF;$O5E)NPiB hQE%Q@#(e AKE|CQ~Xo9FL*Kܴ={`~:[SW-%e&9bOAWxwُIo.yZWQx`p߰q|P襅ǻe._#^ !nVgdz=/ټ;iՎ# N9oSVNskVRa~Dpk/oټ= eІ#n0+;ԋe\;S%Is*Y|7W1]YLH!pzGWl *m7j12I6?j>`1Xսq٦-(jGU66\H $T O`| ~. My(K }Y^3߁9a%GF9d 1Gzp~b~T]58*Pm'k~>|UKUY5MT6=X?b/nǚص8i1%{Ï|IXM|cἾyS i[8 la^_c39[ ;}|TZWd}G~c}:6[1ۃi}'w">i^uo鍕Q!^rگkQ-yqLy$.~?M֊o^!_"-+ -5JK(}/"]7rVx5~Kons$9?$П bxɿD-C.n#r?_W|/ #ipQۇԮ%z =|ע׍qIޖ<%~GJUIsV7[՜wτ]aoq[Hˉn_  h:Vyd'~F,55JTbIYQYQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@߂š=V&%?eaQ2[>OmA!& L7 ;}T>'&eǚΏXc0r);_[qlJOo5[3xp]j~e{=}⯊?G-`xNױZ63&B l vTܩӧ KWkV mGE/ +co)״ivOܕFԏ_+}h)-:Ny7t~::kR >Id>[pm*u["%> }[^;Ws-^}A\D?Qp댞W,\/6cO_1PM|y|>;t+ևfgf>q[^ocŞ3~~X!K_i0o1F:'ד^E'_Qկ7)ya0X|%G + ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( FR@4%ߋ*u)-0#uXgbZK2X݅RTe*Ge`'_)Wax=־O?sUN/gu(O+ Dڻ/n. Wfѫ_!oÚu=#I{BAP9'2I< ߄J %7\L<٘铁t(ྫzmiӸ -~׹M=|ڵhX ( o<u'ȓPwqrBM_RW~֞ǟ/MFk&E"E"@? ʊr:СPSkz~q% JW#(<({73ៈ1߈lN|oo+~G㞕6VoAj"B: / ڄ N;5c3 pM4S] WFQ妮 +Ϯ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( m&;i$2du#x an| }rXWv=+fx|iLsLSTp{5M=G^N?.f{ycOUt>~"x?,?hyigVl{"98]3p#WQE|^#WQ֯')> a_QEsaEPH@`A- ^u/[ Yq&ǒa v|wEܖ֓FpFQyTu= M֔.(.aY2/aiX{Dwsۀڮouk媷XZ;[Y.d8H`_T|wj-m!"X4i0^V Tqu=8?_)]?Nc_1F*Ow{Sx\U_SڵV~z Z(Š((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-transparent.pdf000066400000000000000000000205771402514743400237730ustar00rootroot00000000000000%PDF-1.5 % 4 0 obj << /Type /Page /Parent 2 0 R /Contents 10 0 R /MediaBox [-0.0000 -0.0000 479.9999 479.9999] /TrimBox [0.0000 0.0000 479.9999 479.9999] /CropBox [-0.0000 -0.0000 479.9999 479.9999] /Resources << /ProcSet [/PDF] /ExtGState << /GS12 12 0 R >> /Properties << /Pr11 11 0 R >> >> >> endobj 10 0 obj << /Filter [/FlateDecode ] /Length 6441 >> stream xڍɮ5GVwT!xK5Xڑy.#{`k_~/_rE/?gܯҳ<o~os?zJ_OyWZ~_K `y uWs 鯑R2_!dZ.WWKR~,ď*]k`(d쾲^7K:o̯^d!]ҍlckؔS4>H?>=[6RjI ck;v4vs}U_i8әM|)lDF^Rz{ͬ;[2E0cI5̉I~9N!TJl~ݓϵRa'^m!Y8I{NL׵vvKSHmMyflyhwifCRDoDHNqy lqv$d30ƴpl1P(R_ͳzk`;iCln,h: B.73u0y֜y ۬ҲyLj/fH,HM#6.S%R:)i$C9r 8!Eg*]CH;X߽&˲1F`k:+O? GM h6S G'h6Ab+#.}myQ  &ڰg%Z,@Cxѻ reI}anrfVR!fa}B7}]k3 iGDGJxhڇ!P j"^3Cy*FĵňY5#'n#:n1G8r\ժØ"N*9t9bixh!9t2d ;v c>7ŮL:6 s8Yٷ:[c0m# vdB v%{3t4 DP#ڐ wϯqACfvtګ1ز;vaЖ#Lr"d6H-fS/C2T"%AؚشN&dK=V2S=8Է,fbʡ7hԢXqc8bv3qmfV[]`U]9DOUKXFɐQ$ci ;)dDnv)Ll0:~Y`!2/W F{بe NƶD$'MXe,V{Lc`5_8U[]ajXRۨu]t2 M_1 zB G1DqGtU_:ٶx{e/ m L]IL`!{a8(,w"E ^U(2!mPcgv$k.YqRotnY(ee@TNh=&Wg0Dy/l)&7).<3ЙJЖ:1mOy^-\L lQV}i.clmp-Bm#b6ɹ"vxLW+d6f@A䧈JeWLc + &a:)\(p.eʧQON%y1C0$ذl֔gI- R>".$8I,Z$!L~1…@YF3W>YJWZY1ow-/KxH*@hL/Ac?(\7rgr"qjFoAv.{_]5n_|مzrX{9#G, ^Sf1\pD)FS%'xI%ɕ$%~HrͶ~+|F$<$ >$ ^2\A"`s-8'HYҐs4@x{xiwq!2q޼G~FeL.PNDI[.iMe._{UZ放qH~{RE])d[n)W7ϻz>}w '~{k{v" ^uluzcU)ES~<|۩xtqM߫}H?A>e^(Cv=@V#'V{[,nf8 nH 7?|vHp(λJX^۹翷|/B\ߔ#Q;/]Nʩh9{>h?tm^}Ԝ~{ ."eP$6Jw$.OAr)erQyCLO几s7 Zw ٘2.$Xo^(!v7 6d զ={  +VI0HUO&0M]A}@ҚwSj LiNڗ:^GuUwTGY ] d&7q:uF TY j9yV4HU4 tYm+ڼ+C zs6K,[HWoɈ(-YӾB$(/d9b,Uxng<7R2)*6;jGJ3b0~t`kH$Ӄl$x;,V*rz0 !kGZͫ<(W)T&N`pѴlu)JTl;X7]4X~=|*<'6r.j Nr'H`? )n&%8rb 89zB;{dxqOE HBAtؑZRLP܉) 6QДIQr9qg4mf9H $)7:r)1fwE8鲋<$t"6$Lr UwHGGdi5 Υ0VT^HwÆ''os]n'EJyxlSNP?n-`\rDX1]q /l*c@խ ]FuN6$5ǥPd8aG\YGmUb 85&G7:ѷ5nzŠeDpdRң[Gjʛ)H/7[]tfP(ѭY5BsBi[QӚ!K -g9٠Tvw+-7WN>kt1t0u r`Eq=PNkfNo[ӇV?Pj/5 .5Aƭ/nwIuUF=.rSxFi|lNΆt6d%Ynf:8ɺpH (N¬hS C/o/)Όlq:MQ.XNP@ &y RZB|"Mg`fVL;#历4xI:RX %CC);ar *޹iK.sPZb|{mS"M`&L]ddGɚAfQKޯS1b-: @2< x0gO%b1|9C8 3""s)lTȅ{#Сŕa٥ D$q=3rT2h-LSMdUj',EӦτ Զi\6} >O>JEuGnNhypD,*}56d[,*]lL6};J1 ͐汗Ȥ R+ Tu>j\P(sO~ Mts{9 ӡ{ind~9oFhH@(zt{ \K]^sH81KyE6l*MBK\l~>jv4mw ۱Î> G8h9*iUGw/!q*mvq^%W-&-ƧxVQIaX-&i[􌐈-7S]9R@,S4/J]CvbD[g.)hAm4v@̯qisu؈] t ib' JYl?>=p\-S`Q`/(uNC49pwt5,i9+ΏpH8͗=Roc%gKlSpsNݛob~\1rZM-@:9FqoslͶ\9ѩ:[?N*y칄έyH}]DH x_PQ?sH {џ^q|t{df|0IGZڙx(r惺qc?bendstream endobj 11 0 obj << /Type/OCG/Name >> endobj 2 0 obj << /Type /Pages /Kids [4 0 R] /Count 1 >> endobj 6 0 obj << /Kids [7 0 R] >> endobj 7 0 obj << /Limits [ ] /Names [ 5 0 R] >> endobj 5 0 obj << /D [4 0 R /XYZ -4 484 0] >> endobj 1 0 obj << /Type /Catalog /Pages 2 0 R /Names << /Dests 6 0 R>> /Outlines 8 0 R /OCProperties<< /D<< /Order[11 0 R ]/ON[11 0 R ]/OFF[]/RBGroups[]>>/OCGs[11 0 R ]>> >> endobj 8 0 obj << /Count 1 /First 9 0 R /Last 9 0 R >> endobj 9 0 obj << /Title /Dest /Parent 8 0 R >> endobj 3 0 obj << /CreationDate /ModDate /Producer /Author /Creator /Title >> endobj 12 0 obj << /Type /ExtGState /op false/OP false/OPM 0 >> endobj xref 0 13 0000000000 65535 f 0000007225 00000 n 0000006925 00000 n 0000007587 00000 n 0000000017 00000 n 0000007174 00000 n 0000006989 00000 n 0000007029 00000 n 0000007408 00000 n 0000007470 00000 n 0000000324 00000 n 0000006849 00000 n 0000008081 00000 n trailer << /Size 13 /Root 1 0 R /Info 3 0 R /ID [<4b38d7a21af4f09fc8851273514d091d><4b38d7a21af4f09fc8851273514d091d>] >> startxref 8152 %%EOFjgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-transparent.png000066400000000000000000004706601402514743400240100ustar00rootroot00000000000000PNG  IHDR pHYs.#.#x?v IDATx|ud;6T@@( TQzwybذ,( V,F ޳˖>vvv;l{GZ0r t(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(td/.ݹ-K [g>|N>+?F=kE."vV}a]L ^i"G(t7+\ Ph>o9'tp[{zng(t'"ݐW1іѮ_? {W^ՓeCmQZ:6ެOk(lВhOP:W|j.fv;`e ц5 H?g«ΦZiaF~|3h-t{*6}qieǮhOlGHuCB]ݗG-lG%?)]ZG[Jzkȉ[Iqf%dE.^N_{e~RbTxˆ|,t򲼮])^S~r{ڭ$։9|V%&>+bzs;,pFBOTvX<ֵp9rNQ?۶]4G~"{ys>tؿ.QZ#CSH,ՊsT|=E^^RdK\$=e ݶc'^#bX;Jo{ӎȉ_N7뼣~AJ8GF qF~\-KXa>.~i}͌G%7M% ַU-G{顩;[矝{~mebxNIYgƽr)ru)U S6e1|qkP;mמF@w~ &f| ;/vl/n-^~RmiYm[Y ~>wG {lڝP>]#[52m_3{miu=VͶG!Uյq[_ TwoZoGLs]lk$8z3@Aڵ2}!S:3SӮj' XtN;->!S^M/r'W ๐)tQr!<7/x{aQ~  Bǝ[ M\wiӳ@! ݨRS.С" }¡+c=52Y3͙;o/.5;_pugTvρ BDωHy[=[kkkkϏ?m=܌Ǟ{kv.|pn뱹{_p1<-^3 @ߧ))fk]xCBhg|rWK21bSat1cǾf̘i# I#^ns̙m:K=q+>d7m8tGs7~]zv}m;ee hk۬Hq1:+_98S׊mG_bVmǓ}/{ʓ{,.-Qז~;Eot ?^ۛKa[Dpk'wz  4a6 }kk=  vIO^ ?k9mCmRM=8oK̆c4Z;MkH/ްa|F#[ZڭNO\=5Ŵ K'FY?~.޽:'O_PRQ1=-G+t['O~7ma٥=27ͽ'zKzz @#}_{t=B{jtH6Sw_8ܙ׶m}I T1z=~?b'+t"h햗6٬e3*\f20 y`oۤa6 ;j_ؑux}/QC5I2#3 O+rA#-v(t49|B!g؇w޺ukq(tK6w_}@EP(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P@j t(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@Zs-MlښVkrʓE ,aaV3 MZ [ZrC;EjZ˜l]^F{;~x(t~k[o7ȉ]DwP.Q kɭJ~ɏӞo7 m B/j< ",aa,jpTmyF~LKGXXƻ3#n#[b8bNaYZ(t:٣r6-{-a-XCъnsJ䙵 Wɫx,>yꊥFܥd+r3KkZnib׊\Y\,t{z?/m;|{Bn{/-櫯6^FQOZ0#)o۰3Oz(x8d7ljgmx#ms?27U44:x3m_rS}Lҏ3Λ}kn7 AB3=]Z5n{Y3W}i[x?W槈3z_My }C`)Y_3"ʵжظ.iJj[s-˶W}9-7xgmu^wŃWrn[xZhe kuֆV0{ڻe.,)ӿW؞gܻ+W1H_"v.PrVHFOlkiFێwV+sv-ӽl߳~~[єyIŎ1rx^_co>Gmz>R{o_TUyb[?Wߎzy0՟v uO߼όI[袰-8Rku!"d\4_ !Ukw6;Ya(3e_=F5xҾ]4x& }asv!qB_+N,QQ~g'8dG$9kC?_mivۡDB7r;uG "\k=G-*,,0:B6َ3zI韾7[L7syI;Ѿ;笳Fn,_.x%PR.kӆeز[oTaa&LX!*: q1ڰt6l4p-}O4iĽoH-?;?|h^vl^gz *A# ^cSsu^){gzsVA)ǚVFt&..AF!Q%m;;iq|D !QI%9wH*]þ(t!1RWڑ6ZqtzZy,#d ʼ${\0r9}<<҅.l]۬ȹmv֟8#~r μ0{yhq^5U<{ra{":8(b;̣zQg}sXW}E3i ݕE%;{9?@cпW|f τ|Ł=oSyipujmvx'_#E[!_wlwYq;cƎ}{yyy> F#.vqǖnݺ3wLW:>54'̜1c~]]]׿ÝO=Եsr~xx{t?qՖ7?jeffy`.yinӼLA}O_dd3hV[HQFr{c5.~~g3*tYhE.zz' ]*{yc{F%{p9nʱ:}@ B=b׊\豸4TR닲pW=]flob!/R=zkʉᶎ:VKGXE?NL3](]MMB2;%mdjX;5{ "G B=gqQZ[M=n ~!tlk ?л{?O!_fnʕgfâzôI:?U?%R}NmRПqEʟ׏Nw(>zXnH tR/E{!8=?xKB1[u~`hz̗/`=0ž*1*c6-`ii[gw;[ ߱q]wߣSp[RzS56n~Q)ޤ7}[;Go~Dxxv({}kP;Q{;*t=~ܴ.϶E==}^sݤI%.sK%b:sO@Ǩ_(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(B@ P(t@: P(BiZ%  H@@: t$@  H@@: t$@  H@@: t$@  H@@: t$@  H@@: t$@  H@@: t$@  H@@: t$@  H@@: t$@  H@@: t$@  H@@:  ShuuvA/]5:|UkӸ]E];䮳fINtLڂjUkf}T9@y1UО}aiUfVhW<◺ bX\G\\U^<˜W<3C W5uv}p.]QSO\"q"D[g1(Da[*؞ rfymܻ+_^~s_,wEskҷg{ގi9 ;mtAIvk;7LW .xvwm~u{X{9-{mPґbォ}B< 8q6g]vٚsxz\ȆPC+hlO7; f\5gwճ\M=ffGaSz&E_Me;kqiWG~eq/ vkNE7ڛmj9H+󓓢kmNjpt>,;v~imov!xLCo\-K 3zQ#~sxi+w?{yYZ SRۖIv͘aO9%¶Z_k>pUG%?!6 ׹ ^XMp6ψt#틵po6{:=stn3|S}Ǥ<β6uZGGf~ʾr=H ;O5z0QIX!̳eΨ.7|Z@cҟ)P3_z0oٌ[}zQ[@r_觺i~XP{_CSopԞ8KS=k r,:h-<LӎH\$bX\]l~gh~wҦY#Ѓ>)mj:&uM[۫?Nz5W>dk@]  0Y1W[""qt8hH׻=3!^ /ji\)%RY"3Gco鸬C2cb-kk{N܉N (,iN}޾/r<#Lڬ O;zߗxwI'#/>$Ne яtxҲMMOX\\\JԝPqd*4c O--f]K@p"}#,*z^am$?O9NQxҾ<ýT;h~ kk s@~8 sÖٛ]4p ]nmB΍ם=0*^;kzZZ{n} _0Ԉ-@!>@)u/?)bq{g&޻,}Wa~/=‹?,{]Woژѣ}ݛva]FMZs@bMsvւ~@`!lG]{N%kGU^гv\nݖ/ 9瀚TquMKG^`xl&,~u[@!7}XWٞ~qt^ɎU1@em@KGO^\EVk%*"ϳ6m<__ݮ~.'| 3= } ݇lYJwo뮽6WCP~^nɎYͣ5~mc^{/u(}HϞ۵ĺڤ@GКMn~0Ț1x׺ sR"0T8=B=[:F,;9%zw%>Cʛ m;I{f]r(j^*8xSؕ`?;!CێN˟~×wtTdd;PW@G<[^<)*5t7&XTRӣ4)?㼼"sfix~|#gg.4}Iۅg 9>fѿWqZvò|8&sˉ#vYQEs @ 6#m75|A7qf[ 0tc`sΌMZi okmO#`n;vSc k6hZ<nLC5r;M^GFE65D?G V=juOqMqp0BCSoxlStP`6{zp[B#!>H+*qB-=؆!\we^֞<%OKUGM]_]&Tg뇜4t觾jg]鋶bc-G-&&%qe =T^6/Z;E<İ7_cKuFGF:lͭAyϪ|Ϟn83v9ϻ͹ހ믏5-WT$c9 kf~#σuS;V<3sgD?Ñ}:{_˝8/jتڥKW}[%՟sflkuF}Z`,_lɓ;> M4m%˖.e7zvx@"{v }+eD{?5,{'=iOzI#p%ПKG _~/}ߧ?[8Gr3̓h:q5|W;u~0 Ab״ܬ,=++!bW 4˻>s6M t]_~O2b7n}/]О}anG4{f|D/Սtȏ@3#]|,̵YS4\.x|E1/"/o5#~@z@/q$5;5|\ve1{UM])t6 sjaZyؔ#0= +Ϯx8[e+)&s;7[5h㓢-5WqK[>O{#BH/ܣ`iiNu\Dxַs*ppLòcVfMH|N?}vAĥ]]+_/uO,rޛgg P**s @n7rLndzjOqx稭1P^VEʟ72~y[jf<Ϭ]h5G_R\}>zbu8C׋g&_aGsfןcmOk5Q|fz&lͿmVAdDxX5-b.ȦxA֠1\ͯV4fŷyͻ\>x sW8稣Gſ,GƯkʮc:߃;ͽL!WOz梬c7z {ZpiYCN6D[ZL_فud6T_pH‹L0y^l#S*ʧeY*D[;¢Ěi9q>7s;mjt7k0_4]b¬.+>hҗanOjG_|Z O~I ]M_}E^vk؝=_M]X[k$fj3?7K OW[~^q^ /Y~zoƒuS~Pgd5 V=7ۛ9)o .y'w)bX@̓p5%b%@ V2=u˻?|O^N]sÖY<[d.ۺubuY?8=U=)kh43Lwğ̓7z>6p?|.*.֦Ό(32anSB0{EwE\68]hNzw=?\C_qF_t՝D]qclz#zDEӦyڎY.XUK}:.k̘q}S}=̓sBL^7!ˁC`.4I?A zo׏{s38='}OPCçPګl\+#?/)ݶ I3j%Efm`Ggo=tM؈&oy@N:|#,gwddZw\ѣp}E:Lgv3'G}:Lc/ȗ>qY'6ȴ=ng?:ZܢբѽiR|6n1ޞ@DzUז1[riMǿcScͲE@ڴ9=oa1m̂(KX7`Em_܊_&2ǡDl7.,KlľrSŰ>5n_,!vngp|@aKMAbGOzݵG'>,sW~}wk7ȏU _8#B3pIy..-я;%+nڱ?-)pUFчzs56kbg_dƲR9q Q+ tz 7:oY^in~@k:ѼqʧaOjt;sz,.)Y8G;{nm@{| j#n_`;QWT sv%#ҧզ':k{[(h֎y/8B+"z^Qi1A;ĥfpU^}_UrgU:Nl!hQQ؞nd9|sL#1ԛ>-]֨@SUՆ}?~) t s3\.|gS@G;zo}{j} ]n}iG$-Z8k@W8Э;~tg-:Dyng̹MMq1_r9|߿ܝO\4F$3vQ7DyzX3Ϟ~&j%Szj()+j9g9d ɼPW6&EhRw+kӬ/kquߔ;)_ zG~MZ]޴(|#_% ~s[wm=B`¢HKx[[{Z_rh!g\5gwճj _:EfSGF8ڭqܤ)9yih烈ʍ[#NӎYqy9 B4sNJ-bw}me{~XP<İX&\7IZjlDxf.,)3!C2c]vhWbcv%}:~7 =fb]eUx|sQc5[jYmaF~|\Dx;} XÃ河 ^A8OڝK~U-mx؞Ȍ.~+IDxx{۬|PAa^\gW}{V,uW%v66⵲{yhH]-󓲦OZcG}񕦙ziiSDs}fV?QXiڭpl|fC+.0я1\mhSY @]~Wڑhd-*[>{PQp%̶G,)ohp4 t菜[qmOOz&[y*=,@;3;gy[g:W?3FKg/~ڻw?"gƦwGPKYKwj|~^ғŵڰ;G{lgw?> s"%?(͑#mh\]o|˗HhcþL'8+gfĮ|kNNʦJܵ-=M{\0@]z5b\{Yeq/4"T;ȴn"a|A1#~ּ1q9_,s#%f{,ĶUf~q$YE0YT9[iӌm{vmІ󓣊]hV߮1߹wHJF5JjU,/t 9:ΑdOO~押dw(i&$RwκVk-5E5"'kLھmRΟ$G׈_Hl2#\v~{ Gr[rx^~n?k9Ȳ4 G=1:wqD"ϵ@aŵmyKJԛn<&!5egmCE'jmxoA>UMwGXnq6 g~g{/B_~y[.;t8=lqq'X0KĜvZ<#/>/L>oGP`FObYa-)-?>%OKElu)@!laˍi9qL#sJsDek*YctZ|vϝqCVN++%r{þ#C  .bSlfWa޻K?\}2>+ž][?Y fptY7vyo|@jjM틮wߣ&xq1.:3 rt[{ޜzDbq:_AvmNT1n?'f=[5H_.OVw5W_qc⵽`mFr=jqYR-"3~~Q/҇E$> Z xP}þL-q'=7{=˭f/C:̝sRl6ke}?=$DY[;-s`;i3k]۸Pwe^B/Yf/8.#%[k܃KE/pu6G\{ ts՗8ABBfDF-lûۻwOmf4uVn;f:gone%/0@!ap֢]f澡}~T6#Rg"-c6_{tLXe݇y*^w=eTݙNS)).Uuk\5h$ :KYn']3ę9g~ޯ׼syΗe=^RòmQ YkQ ]&".8m+`8c"D,e}7v  $'%fdg'[";:MuOu2S⮾M!^7!qCnO?صGn~rs>=޴SX\k׮5gra>!;.?˭f٦뮈vRo@ϑ#k4n#Tgܥۥ$wu$t'ycx]bd*ΜM[î/JMIH;W ^Xj#WR Μ̚* {2yĭ1 Ar@9HNQՔ,w l:jmmAV+НȸB,Pg,3;;!..>(`Yk0sM9HN/b!rcp%ϝ]6PLQ3OS}xQCֶ)\MY(ɼWE . r] 1#Y}/Ϩ5:58:\Fpڗ,[6ձ-q[!RLJ5]C=*zufQ7trH. }B7NW|TgG7 H]'c9([~H ]@~SB{Ӫl8! e<{Q{sXmş=}"#Wg# -]/ͨTt"#)ڶQcj#{_x!{{޼KloXz9å]xnR{}_Aܬ3Y3#_wVhH.`|HbZ_zl o/&`9O?Ր̤'&:ܹNGfF,:#;o'ꠊ? jd?1v1>_XdΉ JNnHqF$y/[dW~$tѵ,O6buSs277Yl#F<8=HH dռޓխ#$s[E)Ό-7OA{K$u"u}vZzeVmM6oCRQUPm[ꍻK,O=ԓO>iy l}4͏oȈ0K|}k}_䚗qFH `ϝ^>i3V*?xY۞{G-̑8 Kx8TbcrYmCޱeḟ71#maP}Y`jc, \NxǞ1}K̍9Q{ABq2jJܕWn.N:G~y)7Hsz] mp; eC/e,%ȇz_xAgZZu4esIZὣ7άmXϑ ʪ@cwL(eBp˖IReJM MT0FBw[E%u-v][ MU/nhy\=zԨѻmo„[ BNNvB||BqHN`dz}itI]SJ2ڸ{]N#Gў=Q1%Z5Nvc2涏Ͻ ns8G2Rϩu[%F ]c8 ]Bw/^av--k̭mz[?WCIcO[yl$t:^ r8.*ZSߔ?!Kc #Oڳ_jڥ|_P ]SnK["NX\^6,^={kRq.{/~*f[Gm{Vgn}Pųˀ:%IRU57Rs˭kܞ?+)&O[ 1=s;ABH֭=˴ċ<-oeQꦯ /EjkkI怺Н@$7dkEE1uobӴWF{e{('>0(r…Ku~U1 I~3Jϔ7u6nkзxߴ0Mg,^tH[CMlzeTl̍ \޾$t</k'BBQʌyp3 }̚2'&'p80_٣eZГ=2AH8 ]*ܾ}Lx23fh4rYIr8p/^-{jܛ[^^|qX" vnjSBW Mr؊ FFb6bq%Q 9T$ښ)Eql[D+,r1ᇞݻ;cbcsr ]A"}4E߯7R*R]_]Y{xz674zxRߩogf͞z7r1):6qҤ-RzE. 2.cj^{{Ϟ9ӹ<đ~}||SSS3gUbc×~^Jta[loO_9C ?qkM~"""/ ,np4nn攒ETs.o <?H{X#R'KkNwG[͓WbTzy05r=%wC/^ra-Xj\vBG$t=zYI;ͬj}}6G`O$t+3l$t4CwCVfTV56=G0O @H uTpK5x;]x]Eо'Aޡֿs$t"y|}VSpA^7P'LLO޻b64K垉#;z1hH2h9qΙUc2;vp ښ԰3ͪUst \%Ѻ)L!$dֵ}E+Ijk>AF!$tڝY7m>2)]׊eW{g S?91G[jXlc+T3 ݅,{?+~͚mxini5kihm_Y^5~YbkIǚ6d'm"_TԊPy*u5}Cw$|}#Xެ5ym5#)YǷǏ>-^\۵uTxt $tc 9֋S lg`M1NF:%o߹I[֐>=1p]ݒ{:h/Hp7EuHk^g$Z][uY5Hp;89vm>:BųG}p3bP1Nk;7:h еy,Onзx?7g>&eܑ~TIءCb}ÇJXCmbU1-1.d )~,m29شt s04y<{Q:|S@1{\qyںy Y3/Rr=Sڤ]7rv⮂S⮺2+CuolZ1lzꝟ 6 }H )ށvFExyNYmk3yMKHoc'6Ϯ%xHĢ=KD%`Ku\AŌ1Sol~ϠeKD,hX }ȅގIyW<7.5{l&>\iLwtxK x7.7f1El-Ck&Iqܶ6z Bŭr(j׸1#d>__)9r؁qWO6u4vn+ucH˶*Fݐ-?wò-ܔa*Y3߇ Y3*h1!K9Ж B-9oraɣJ_ Rip^&>8H=i N ].e25t՜9WYZ]IS|:mJg/⍌bòTz;@)Us"yTK pidW~;9Ju,S9oM=sgbδ~>&vMfqb1eq%pƟV75T5zW9X`ӖLhdG^x/_2GJnm}ȝuJЯMҀu ֽ^$o?(ayW1[3c̐#;qn83bm?<@ $t3VIJnaMoM]ѰgRu//oo13S53Dh 9#ݚ_[ϫ;'2  m|]{xolz˰lZeNks$79?DЧeD^Ʀ[;O&D_/횈{,N3#-klWi ˓C1 }65jk?s7p}9%>5;/El^a>ʰܸ իm0>zqHTH"9{/KohԷ؝ mv׌sѫ3 ˞&)%!g@AB 7Y{7rufQ?:tUfYy>ĸ=W[\VjC^ʤ~6_>ras[>a:K$Zgg%|_nR%V,xH6 K0mbuMU`v<9aKC~uׄ/zʰ ‰ōqW_wK&dHthW5 X3{;XBBM~#=ڍ/:xyjB{c:&zJŖ\EfD,G#"wbqɝ8)ol)x*0n&99;1com>t'vmV^~.F6_~ZgMO52n+)#0 㼿 xi[Z4w$tck*ofbִډv>HM S==Ocɧ.{¸mѣ|#)ڶrTƧ=5=30iO|33~Ώ5)q}]X'>#^moa2KxwyЫMn^K0s[0S9@iHPө^2soڵ#"gzKǸW?VpmĔr C1lIoX5ք~%Vmz|\^a]}nK"ސ$s ҽ9+t$$t(_ns0%316<؊ɝ63z{Oxa!_$t(N6O$mM-"ָ黵3\;ОСX Rv-it~~P8 ]@*$tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*@B@H  $tT P:*"w txq@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@@(P t@*M-Awlޛ]7tݘd]F׸5 t^YCshتҶەQ;F8Mox[uwӷ358G[ZrWR+<(k3jju~$M'G;qwmi5l٧^8E,/pAϠΉr@ny/KohԷx9҇(|ə/U\1َXxxx,Ju"&(:v* Z'E?m87"@=(K7^!w xXlK=:o:v9'O6v;{(n~,mJK^y}W&Uawf]>  t'nk"粠 o;=?>a~H ׅ\;Ms[R ע@ 1yڬ:]okۍH 1ZZ n/1p\MG5(#oH:rS%?T.\tx}OF іߕ!up> t.ܯ)l6YIUr언>E\ {-s列)7^X.ox#x}I>pU/,{ǣ+PpǏ=ovyCǮINO|]_=n>!۶ wt |kݵӵnzu>󗽓%=_NwkHJ?qatvZ)1~|W $e~UWE[l鐈I)zo=~zhj%9_&/jgS1uUɡ!^rG'5[oзxǮ1y3cPpH?nMfnEcspk%yfMK{m*{YzC˸mPϡ^)qzGz7\U]YVޠ1^2L|~i7!q7Z3ّ>kt1?wWrHMT: ߚoNݰֶѸ7xG|!g\R:?;glNR꙳[cnOUKt S<8q-΍uz#+p tjj `=^qT}xCU|[/KZ+OޞУ{)W0kZ%aX,NkKF,#hzXCRfVV֌Dp t@ŶNEy[eU8_*.nk!qL`SOXmڮ[an7W/Lъ/_qnu / /Me Yhjv׉)lpVϳV>\[u r`/ t@JWeػCS;+1N)ɚf[ڴ]I_/1OĝA++U66ziPPY IDATq~;lhnFLHA[K0?[n;k WF5o8q0G T[2lK~ Oֶ-*֘2j{Ͽp t@e,1_jnbMRFa2Kmiޙp\1MW[<3'xEzů/J HML$Mtuuf_|['˗Exݭq;z,g@T$F~;T z z/]2$|Kgmy|GtO{Sゔ?L_=̢z}q ~N/qk@H6gEɿU|L߮]c,#nWͭSߕqvgԎ6F\m^`xn]C=_ t@e1Q9(sySK7fdocw5ܺO]Nan}ZESjOx4G^oo\J賒K^96ӮG&ĭqZ\q˴ċ=kmy~߳'FsL#ʋ)挷ٞ^3kG~X{ t@xl(>Vɧٵٲő|^4;)Ҵȡ,57~,Fg=hߐgmO.o׊?-+&WL3ޜc'NHvK/x\<QǯԸ5Qᦛ]UM6̭bM~.PN):|2! #^sybĖO2nյwM7g˸-KSȨoH;vIEcڝj!Q[Z0'xۚS_=!U,90)fa=UAņNx"6^~WrWRu>3nEQ{+{1QM4 `Ka>~V?2msd˘I1~|{ⱗ[yn]G{SW<5kd{蓦ūV #exJ/❜oM1!/xe؃Ό :ĭ⊹i1*n%u_W7?Wc >T0+)ڴ5^I8W?dHA[%^IofeJQJN⛼.+ô];.#m1%R ,@8(59Z}ŜbZֶE_=eijޢUnqƞRmkq.#: Q>3(챿\TDz߲ ~~⑾=w)T]ߔ;\˫^K[=? 2- uݝu^/_>+#gcGD(:c⤄X>ƭ>ţk3;Y3*xv`裦M{nXSގo1#~@;[5>EsߚVxu;bc.'绢: |aZw7#;2\'_͹#皎0o;ȩiԿ~Y֦30۔)׿ǑXq[~wVΐ. b0h-ĉ {Pʸ2Q %%ofUK۳Z]OWύEAxЧŕvi"?b.'0"g 2!Y=/%ýLśSRw[{V1׈M-Aqk2s,mqo:uGBK=G[H3Ĩl%#륡?U}Gz8k֖5WRG#q+Ep[^˸]7GrD9RX~~pkP O[$:`ƾ#晽%Ԓ!  ZjڞUKvkϖ7]b˱2z`>Ƃ<+Nw{ZWۻ׷lknm. 􄐰wR߻NT<@>QF765xYxxxz,qwQغ9R#F?zK\__#`~瞃ůKїqan,,yGo>){ ?pVl=[ciR'm޸ТAŏ`bG2.UJY@QMGԋ:+P>K]8:ɩ~žAKm|]{-MWK,Lwtp41i.FwtBMȍ|+u7.#]#hقvg]c׿wf{'Ǝﳯ>&4m~[' S(MKHrgA0U,}b=rg&ދ(CIcOmvfPcg;'zi-էc7[3Pn5\"]Ϟ[LMlڞm9?2Aͼdlϗ{/MQ 1ߩ㻛wb̈m.{ف:3n76 x/ș <30G<ʸ(ЁOֺ7%iM/zOsR%f77vqzEz8~K\oWu$Dc˞yh ӥM&3빣esSl|S7+MUSK`ЊJkp3%y:`Mg|\q`uo4CΨōW"VgkjvZfv!qL=301)3*OfIfUSlaO>/)d%zmexء P0.̹o+ns[uۣꦖUMLߺet$W~̱F<ڑ`{Hӎ^sAg7I<"E_@od mNUovݵ,Ζ7]"U_?5]zig@ ~!xO KV5GΖ:.g_;Vў=4)RG8I]IwnݖV3Ж^ٔ"0/L5-nҸ5Eq7+)6GS>!ωWӽ 3|Ύ ؏ڦ_jn_9֖c` Z"glQW}׾7 m--gBuolԷxIW0SR++Ӧ%yp:ڝ72KJa֗5C,^,^]["nO$,vZ(UfX Sx>e3c8}@GqݶO3ܶoiъq93[[w.Nӟ3%g:8λgknwlw?MR@z9 t L :څ1; vS˭̨$U\2Wwސuk"פ:έ{%{?+y+hÇ'jufaǸ홁aMQt(.Ψ-E?Úu__W\EKӛt-Y;k}:!:5p\q*Ѿ!veM]VG9cicWCC xeFE`9c<.J{-V+WL@zp. N/_6U669kwӂKM"u7p t ݶGÂTo)We\ߎa[S+]s?s֯[7?,g, [.ӥ@jIY9 lQ.b5̞Z8~Dh7D:bk}9umݷ\(g娡q>,O6nDp?߳ګ_;8W?hEFesKƚ5E":ڕcFE͑qk2s[<-m+>%o>5%paFF|;'B1wg([ܴKt' y+zwΈ ȇRhE[;[}q{{-|kZDG<4:1T15RS323QPYH}Uºs;Bv+)E榙`ۆl?xpr)segg%$%e\@<[J:ڭJj 9VIgyuHgÆfNN8о>]5~!O:ڭ:}1H`ϳrW)<8dk>2&O<̙ֈ\ǟ{nk_y3kz3@)}R!;{u;=vL.)s[ [aVuѺ홵ء >,e\@~P,c,3d'cPϡ^)ypP'9< t 77wfKK$ykJͭ;zi_z,e|g5oub0ImolvEJ@GT\|J~>ϫlnc.ǚ+amq/-ec06hR8o):iIɹIs9>^e̷#SV{:Ҕej 7O)̪mc CX?~&MRVZ":n[Oq'QSCݧϬmݣ>=$kY}c{&Lظ,߽2*پsr2رVZd2ǡÆMqW_s͹RQCUVVfN6mɓGwǎo̘W^q_IUUV,j6/0c_v=CK33SҥK;̝;7_^ƍY{vƬ<]K,g ^HŘG~}fy\]/?s{嗝y_iJ+*S6KJك3U۾7GΕk'.k:%uܡc% -zX]{Y6mԁk J|9wtnnTԑ"̞;g=Ru`EuRyN;w_Kz}nݪeq]_?U@G@:jwV/f5E!KwjL6_ӓ_>ɓ75N`]~W;udEaQdϽlNZMɃ=oCԩ&N쳡瓆lѐI鬭))ZY?|-0c6d |:atċNw¢ZҶ:f.)0gމ8i<~ҋWf6}Di>0qX>gϝ3W"3d6_kz1M8,5"$ibLm^WwGK. Wwɼir{/nXMFAvҵ 7tCu^w?y63fL{oD.[^6sY{'zdY}]OS{- ~t^=_-?}+J+ my9YǬޛ'-=c.ۺm1:7O)J^h5˻AShqs:k!9_q v#Gݺ_0~u4eO>E*4/:sqey%' !7kij7wtwW6<3fKӢ4Ѿ[nAC+F9n8#tMYt=Ѿﱀ͇NEA4Uc}xߜ1 >H&W'*fbtPC,t ҽGghtb+< :;hM}ٿXT%>_XѤ%C^TulA@'+3^Ҧ.nRq%KٙkXh!]s' ;i.kI-2tlϘYP4G?f孃{߶Ww22*B0v|ro*ڡcqNtN.)?ԓO5?"[h|:]}Vojdq`=6/<%vzܫ5mMke]w}5t@tb- --8' j&'sEJy QyNay?^r=_/?6Z׽zϝwq'x{:Љxy{mfΉYā6L VVtݸ[x$W^w9#j>9KJO"5[^V+K[Fcv}tWrʲAޤ[Owj\}>띅]?q陉mUTx'Q nSWP}YEeݺʚWΝ':찇ǃ&ntCߐo F嘢UwG9xGVolР9Sq>oĈg;c֬>zziG UߵQ5Ov?(~9ėNtWD6M7MZzZ %P5W\5˗ ]@s%,aӎWz;sVn]VCe/t̜9_߾f#ܴ]={VDߦ?"t ͕N~'KpEyy䲭:_3oEO^m Bװ:^Vu=h=zMg-vŽDnYKStL@YuϳsOmzz݃{oڪ|ռռ/4dM÷0kSqߜy3?p@SMsҥK:>#BєhQZVZbM8?t QS:,o'lj֬}O9<@ oQЍH̯Y\p:P3GP[cY^_;۵םU>sƌ~QMNztw. M鬧8묳~~M8,@ҡC% QW*vl~Y}_#pCVc?;U@s%C55yܼCSO}sۢ%j.ܽ;NؑFNe-]|E_tu9Ccƌ8zCߧ4鳡 6>ǖg`9쳯Lf9S O@sKvnQK?_vio.)1^|Y :v^VuG묻_󯟥ca=W_yedNJnjt^k}.ߜ~ǝǿ {ϙ=w>jkyƾ{a6c>|C;N3tь|҉a}ǘ;gNh{wk2W\u=W)H@ҟ{~ˎ]O QK''z/ }6tLjYwΜٽI|->~rޒqmm-s[5?o޿~ Zj] 8dlOo?yʈAyφ<sr5ל{-d z]uw?أ/d= %+;<n;vZ?ˮuםǟtBӚm{O>u@:Wƍev~-t͍6iA5ԔQ{_^{c=gCSQScSvt\piFFݗZk(GyVkv΅xVJVܒ/O ]Ks.PO>x<G뮿N:/ ]KSMy5ל{ 'si]RnͶvzZZj_=.hE`џ;G_kp),+!UL)zs#6tzB\|ɧ!N?t !4?'=iy駏QE;^m{oUW$s>jN:kRG@ٛxXq6mUOYV6)/*^_V44R:֪vgϙֿz1禮طz=Z}ҰIOCPf퐗Ӈ1D-@\ˮO~uu}>rMI݃ح`tO7 ]7Mk}7ٸkT(f+zfw=3D-@rΫᙟ~wDu}*j+f٩#j̷\/zrл՛!jK{.8qj];wq뷻!kdS5:]iG@ 4[=65[\\i~}y}{Hgm̕;qkKsǦ:HwKJ ]@*4y/G3@G^]so-ݒywg}u7{|ߴ!nJ8!ӤW(%ckIF}T*KT[5Nk=m-2qzeϻqxSU@] 4Yey U|[0ǔhKd̲e%֯.箯s@2Y#o:rKeMu%d]7qY>q㬲2#3=tbEl8z9vy/0ީ t4YC(]T7~n<>cG)ٝ歬IN]Vc>vC_T\ѹsˬE =zf΋{nD}|C*:MZSRKX !9+췒˝SF_I;gjčnQc獮m߻6=[z71i ?whjD*j(jvՅՅ<=>Oxm+,Qu!nixY;8igMwF]r?oE@YGz'u{mfk:fy?o}_]N1#Ow.z>\|%.Sm&9S&tiv+K5fn/hۖTt옛zۇE5Zn-S**̜}mYZ|ʠ){a̕Df(~ܯ56I~[?_vrmc9v@ys_-@:4clO5OyClR[h-S>2dq-nz{~̓z퐮ډ^_pǝ_.;}Ow[hVnQ[%' !7ki:ٙ-0xD~V~ ne'VUЏޣǨTM<]ђ?yɃZ\WO~z۳S Glcܶcד-iʻWML7jlƏ-=qP-2jʌ/l}&z_SW8lҿl5hZthj[/=U~7=U˳U)?>ܷmGWtN}{xuS闊p@s${;vzUWTVf%;fYcO3 uӹU%I ]@c'/s&WozοMǬh>o]u2-ZY9 UkNayݞJ:;Ȩ=k&,97Umgjݟ=U㑘- 'Kq&?v]9睅VPZٶm̂T'Zb+Uc2pTEޝSNO~=ZB/p㍓^seefVw@@~t <5ge*se'jQYm7xYfFFk;Z~o/mSt92+8O2xzD/|tk=}˞S]Έܱ/O/L h++tiewK;_69s@҆עmmdݾSS# _tiնatdgwO˟_Tmu}nh~vMu<ŗ]Vg._|>Fw7Gj}mt2cWddd՜]Slݳ~{DTPOO+gg>h9+z2$<о;1}ϛa3>=T'mק:nF.9~hז=OzguO5nҳ}#m;3/{n}z)Ǿ/e}jf5ىusn\h/lX5;s#zπKK*:l SJd :~!}6oYT, WGhtnwOk}Ũdmع5ۢeJ+^ٿnY7Mts_]EO5W%2٘o FF[]ꨋd,y]^s{8]m^3aɹɞJtQif|Y{]Dt %_DT}d=MPۻqN8~vwT9vv >5w]}̽eJIͶgzZ|]1-|K^Qc;^sOly??pU_mY7崓7lk2Nh"7mzr.˖. U@ POQxw{Jf^GEhT;g+ZGllMƆ]`uk÷,rTA}d';Q]{6iaɐcf~V^}MXP}fUTVfƂ[z{v9ӣ*C֩dk=#9eJiYEܺD@S!Czɞ]=9{7OZv*2r6y4N}Z^A̘2uYmLY_n7{!Vᭃ{w\.}[8ʻUo;aw3RQgC qwaM}QYKw㮹jNzcmCpuQ6fĚܬ<%J jqN97Wo4迷t tHzz3;~;=5BmǛg3OtA uծ^63k̜KN$XQm>߸}'y+lO]owwl?7 IDAThH:ݻuSQ{5r6c]׉/7Le _|zۀv-~`RYcSvЋ+^~꠴M*7 rZUo/=&?~ʭWuܙo/>ڪi3}ziܤV0.Plo3{EYP7 q{E@9mZ3jѣf~z4 u;<ܶcדE.-:]㢻xdN=D{ zzU׀_sDj5m•]E8[Y65N/б/).Xq l̏kV'ܽuY)zoS;.ޞ}w.ݏ=f&z"C>燪G+3;1uIAiE}^?.?x^q6]q'K~H /N+ܫs7xM$Y"5^ أ뱋Щf,9.[qO'nrph2WЩe'y/}2OEoӇ1cI@F";3|U}c`s(*kA2}b'VdhQZ^[?6k|5/RbZTfgˆ^{5!I6xyUo+=:w^mzeϋB=[Ov3ZpCEշ,Jf,d H[U}HyTVwhy,kvl9/|< [IkӘC6moRP}鉃:o,DM|tKK׮y=y|{%)E'j]GRM@&ҭ:]m7fq'gdYLhD߸[ o\vy ;+/{N[&1lu>mT=y':4as:o=}~XZVU,_t[=6̹܏Y/zxeyed鐛41ں%s8XrZg@iС>^\ѝOIIЍfrfyt)>C{ϚuL>ɌPrnRVɆ=∞{1)>ep^HƴHxu%C3U}rZ~ܯ_Wy}äeg1 W|WVt~d7{EY盛?2uɌ.=}NN:qv1~5T;͞qB@2[іzւ)Gʚ_w{z+_-*Yf:~A*kaU}~~k?0ޭz^;$O&lrTAL PREK oҊʔN(Xen-?>clЁ&{nhIޚrf}o.^uaIdkI~oj =2 _Tޭ*\1߆Ϡj_tSJ+*+zg-{\cݍ՘V5v5ax3NƆ Ё:Zh ŗJg67K\{>3ZQV/{i{VoorzmMt630o}khNtny oţ>/pYR\133d!cfNZ]啭OEk3c#>t~j':{cmQ\͏P[~T;1uҒ!Czm'bFAY؝~E;LJln\:*աkc\E啭[ggzΫ[U8tqMv| ID|c*w~T}ouPVz73#=ʔeeR9p0tU*kUv]xUlϣ/xykn-'bL@Xn LNjVy9sۿWZg?{pƿ#'+C|j'AnVFIt绬2#SUN<>=Kg}Yco[:(ZFǿr@vt@tdf40z'K6fzYPַۜXrx1Ͷzl啕٫;w< W In ko=S>@Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :Ā1 @ :@NeeeYs 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0xW?&w %kj@-*5܂kB݊=Y?/l;;yΝ3i(9;w8`s8`s8`s8`s8`s8`s8`s8`s8`s8`s8`s8`s* "KߨTemݽ& f+78e1xL8d!06E7M] ]Xvv zٝb+70 `:Jx)H1M]=0WUذiԊ?Δ5s,~SW)9n6]v`J( #>y&{2w*TsQ }Ιr.}++s839X7y1ws.hMˢWeTO¼TL_TΖ 9X^gsllDH#]WE)9_V6ǦM9X"8rچ6L)>Xyua5ЈCqiэ%b}v#%m\ъW#Qh~*uzN(V<`sH_+:?0s0O8y;{zSaqGԴ7bN>DlQ&<.GKtG_LtVH&Jj<.W-;o͓4w;tvJ}MxL5.S0%`2Jⱪyt֮FuQ,>A^`Ǒ\R{n]$;PEsަySwJ#g`s`ܛ^e ikΏΓK.1e퓼8@X:FV@pu6s`s`L\%6@rl^SO{#*/6>[sl\㎓1E}3Keǽn^}(޴ȒqQȇ!Ax:Q9|vNE9^}i3`sNBG˃+iO'ۢަʍM#N6xcJѪ9j\o^QǙRLTlx?T(. 6?˛L^xtgϳdu+;1BIrJݥ⎚)}ȇ׃BGIxyLT I)!Lsy.mD) 9#I,]I;X|TlQaߦϫ#Mc ܅OD,6)]X6+T(\SǦ|i,mEgYzNB^^@:sK#_ϥ>re*g`PX)h%캨0k0mefH[(Wk?А|48r(cSlFf])GGvسhu OIA9آ>d.'+*+CJRI{c= uDю ` (TC |o۸~B;i0ޱKe2{ 8zxtac{^:P%H2|R{&]~!7͸0 99P5XS5=fLkZ2mƱ~v,=ϴ]t-K+=ْ*?"[WQPh/=}ӯL\ (L#Igi˞J˦Ӛp/C1l>jtm]m5Jm+X"."~NTb5yR/sd̞`-`48"R+~O=vioPn9`+yb~2yݘ^HcM=+71(q}t1תB% @s+iǼT܁vLkQж\61rmcܗx[1[R16~6$>}:czm}D׍PXglP]nM`wNHPNG׻gvw?MrV_)b 19rFۼ%^ R'z-8"dgs)JPkkaS{+I+֥l/U5ty78_ck EYgSKߠm>irGZ|m5HoF.JrI{]sa]6<4S4M縑Hmjd7w[^ 9qge(hK<{%463q6m8Bھ9=9[tE<yUnvt!$Vdj}ǍK,7P@ 򾩧ރciwc5_ F>&EUs/9vk_ƐIMп9澧mΥd{ٝ6:Bq`E w⼬Ҧ!b>?^]&+ 4|5܌Pis3Wk5t>渕,C7&:c6fUzcɧYSݸtPXRP\Tآ>TGus Ql{\(;1"ȟ|h6Og}K#QTv|YcF4Pafq~7]ּ>?S]$wq$/msH[evn` V&>"(oKlX ܅OΖשhk"Gyoڸ~J#sUs{\c8;7wFqek/*;ürKm A{U`w,u~4LE{3b'k>m(;X|TlQocc=аwws>ֵh[r'{y\/-Zz7G?DK-Ętt3~{>6PkgumȟkGw ɭcl?16&[bI{# 9O-qdOgCcHg~My?bƚfVtÞs1cy x;zxZ9w.f~06ra']]8oF|"АsK+;Glq1WtϕJL/&g!/W1["Asd6Y 9>ЄJ%O m[ҵ)%{!#NȫPq*u߉~U,VJS#onlqʤ|}-C\=>:&OyIly{<؍am*;4#KE4qBGE%>ϖ׭+E X&JF n9zCq`j9 ^B;DjҦm >͒#Csěc,g#KmxU[U%4 r}OƉ"S 9O ]iw?| MZds*VFNs4p.˖-k)N'cu`֤+cc8'yA8W9rwtE, >cSV*5=TyCqCZciUvl|qGF܅h_ӎ->{yO}{1tUgV#9v}vL#'ÿ.m'S;iކW׶\ \VslgϱSaפi[ʎ~Y0z|ҿA9[_r'gѧ76dZzn2?L(_f6pZfbWqegoF_Od۵#];LDޓ_IFkgIUnQrכ\ez?g#5Zy7)43uq^jQ %5.!@mNIA;q93Vi[t`.dBIMi"$abP+ >)mׁׅ][oMϳdukn{Mw ;PW/ktFJI]sJr_86wcħwk۶iON\m}O(B#1ȟ-%vvɖs9k)<GeH@A||"j)K~&F4V6¼<30_(PRҏ ͳZx5.EU1Zx r%}3i8`kyMWy̟~~.瑻ѥwemHGS_$rnYn&|(Vz-yRbo5Ǟ khu~};3b^t!&[UV(fKw5@5HL16vNE]7j: *wy_mFfIt6O'/R3m7Cu9.6S[H!fL} |&+f D{\ch uigJu =khLN3%v^!?"I>{vo7ZyBq`%"DhvLqok3)n+55ĭtix)]Դ6tfh4b>oEJ߁'Rgwncsͱi\_&ZjwU#7ͱ#_m3TSasIcK(J+'0_fK4Lӧ0/E;Kۺ-hi6ˊs4om\?3S|[[Ӥ-H=Br1uEO{^fʝt7*oO7&{<} F9v\-KͻbE;yUK‘=Jo,=5f+Tve}6Ǧs*tVd5YbL9\\oyITIu:mOi#Zנ>,m~?V/ FVUԶmclV`7ZFWSΥo٬ [8=n6OMC5Ǧwu#G[qk{r8`ϳeuiWjXT^͜ʉgҷxX'ڹQ[M_E=.zB x\ŧ-]<Gelbg7~!mTljR=-ӥJ3Ŗ{ֳ>Xc8_:?L,ɔJ[YzP#Qʎ/N}MSYr m֦ސzS6s#ZL^?v@|++5 ;kw``ʕjk{ZrI[&b HH(Pg}4B}~3҅V<­Դ9{0uRVʎۯ)TΖAlBI^GOk>K?C i#w9qa#I{v{fCKۺ/)hmTTa] a4<Ɇ =!Hb:JnL MX-'O-i~OS0#A6_E)L=LwzU|PtOΓv]:qnȹĻ~QӻkZy2>q>ΛI^佷1vg@ "":Drneƅ9~>_=ϟN#_i_.GQs￿cQ 9#[;rrRq&R}ϱsoG2/;NU$eo+xux h+'"́7SKZsmW?;nbP9kz<:a ={c38%/>HCyY0Jd{$/5y^'M+=?ڐ=ͱV߅}|6TiK}͙Bmu 7fJ߻s)YH•' ^UV} we*G]?{@Eo kC6ϺFxoir<6Og}}C'qQ1?ewOE&H򼺾5.N1[7ښљ[[-P󃬊_fbRI^ W9z +6\-Դ-{#ճ1&OQ iU湾w+:x^c'm^x-k{9 ͙i3t>n]?&]5=fYO^}RmK> //_oq}Ʌ _1D>"GϝO(,>U۵le }SWzEi/%*vMA# 9OӅ!~;JPvupML|?nˬ,>[.7i.|ke-m붐 9}mUN9U8y=DoB{\"__MJƏ, k J30m<͖߇B?y cT8l(X/JA=eE'˯ʇMO󦰑.\mJ|aI9@),ً ~cd96UkuAcٺ>sO^ ʀuf\K)iPq[:gPTa]c=w;>òf_\Ra"{JӚPqi2f ipL0%W#GNcȹO ֮1ː뙛ӎ b-lV&9n,oJ[dzLAq"> ۝ۦUHZ6t6FZ8t 8SO۱.wtqo'ck-};U8lBqFĺ[i\ElSI7njс J?hq\_ W9hιTܑ:ZY2Sbk mB6qʎE>ˏ׆`se1AIu*kؼg8w9J^' 6sY=.JѧSws[ц9F:8K5c"nvҕpT!'Sшu@q_' tğӯisf\`O8N;S+g4Cq9T{۹ͧY_מV,sSsouwniK6quvد.v燹b;wZ1$XŊ9,y=%-/|@w(q'SgE{wjEjvLeU Ed{}wKiϓ2GiSN\Q|q+˯ne^n§ׇkrs YaW# &+ie.f]XqLmǒ't ͏E x"娶$0T|ɒ}_q*u_Uf;w$&L 0En?ߞ^^<>}ǐo kC;'s0lX۱)!X: L SDKj5_o -̔rÙU8Vty+ׯ:^4ULx"X'+ gH*rà8(㗎Z;^{.)btD^ly:mW#˙,:'>\R1["},鸢^eJ*Juv_f^{NМC s ]ɋ|=՝g"Ty<>@3'0s 5/ϳuO9'ќCPeek*L=|$pc%6Sө>HxMnGkYPΖo $w70/%\-͒it8@]W%V*;-.6vBP01;s3%v|ns-Gt|hf6D5&iGw[GGR}U8rd-4P(yr/UX\"(@g+/cu>|bbq'mdB6<9"M8mƲ_SX8#ĞF^I FN3V(s3@<G5o cKftzJIc9y9hs'-\ ˜8E xLQ!rcҜhǔ8B!#~,-;Fny?iR]-lefO6vL9'O>0uN,mK#΀Sшc>vnPmkwϤm-;6lfK*NxBBv7BsCq` a6jyqp_g* "Kߨmo><ymm0selS8&l؉AjG!S9|>WܫS!KZ+:Il{~BMll_ۨlMIi|FNۻUCGaP=4g;/L lQx-W晩f(t@ߞ=$iKohà8hJ}MM);VWSSE ٟ=bʎF,K(̉ѵv~Y0f:.6ӌCq@.S5sbŃy8`d1zM>`tq&-s:.91mGq_ )Bc<XVN@sNvUvbbq'7-[5w,C+֝4i Z2(T|GzrlĨ-ҙ1#a6/hCqW/ɓLY㞛bPYzYqٟ}z8og*'S }!+9ոZ5 !G4ԼU W9Ln-Iz"7n_/۹W ֯1y1m^S]Y4r294sY^΂cm$`z`)ZwϽi4J?ʑ\>cse*grE-\ 2u.@syYN-y%y"}J²c2M3B^. ))E Cc=V>@_J˧?=(bFj;J7,!p^\ҶԒ[J+Z-)zᬚʣ Е\U~+D;=("c3%_Nsk5R-:WܫWiגΔNxkv!+# BslU'n~P]8*G^J'W;wn|=뻲c%bbkUl/^LXѼ/X|b.Ori5](E>/+?1V<}}{✘}1sNi_u޴2,[^a1r]Jx9a3CqfOssclx7x4*Q= ꪼq,&<#/&̉ΓK4ǖu[F.@s0{>7m)]kW?;[t-;^kGW^֧+ck9g#`s0{B^roinF:}U8rd-le˂њcׇ 09垵4nъE.ӧO__vl=#k8'7 ts9H|ҥwt>lAqazeZ2g=-<3uϼ s̃nؽ{Lw6saw7myL>Δ5$7p>Nǚs^ c#7 ~Qݽ|fhh 7`ualqz\`MZ=!H"]vl˂Qzz!w~)zT\q%vnZ&sb/3·qʹl!yWnJ[V5 E L 9XxmA>h۱8 b UQ*mIw/eB˧[!M'\.Ƨy 0k™m_u1؃,[\(_HsBs.eZ(wvesK=Tklǝ;y%JwhtDD^`9.lX)} RJA9PEZ}fqN  ?2^lfo>18'dJԥ0˿ {v`)EJqEh3} ؇V_slY9:u\l0/%%y5lw `5\s0Ē.44؝D>KbC#F_9i)Tj*κfM'Y4bPհ|=4L%~{S6XkDq`=T|մ?@#0GT:.!!?v>`:|m/ӊm'bPEʑ*NB^а$mG2g;`޺ukgrY GA\\"88`*W1[F.4k꼂Fqb >=Ė\QBĘi|<8mYWHEӈEK/i}ڒX(IL(hW30|[>dmgV`BqI۝sKt(hГ)iĒ шeyLY/s +/)c*/`sHp|ƅuM+cJAf3?Grm9ad-}'e~ve:/`sHRE9o7 i#mǾhe[_k&*N#ѣ#pΝ_b\r'P_\mN?vp9F= 9X$Ks>&@rvΝl/G t"ܦ+u[t@9T:To("YbqpxUQʊO e~}=$\sbv4b*G`Vԛ75td.`^PEpEJ_-Iڎ\r9~=YZ8GanrrrtPO>0 `,}%k-mS=ZҚ)CCC sHY/P۱~v.TѹR[YT$\8d wK~#c#VMX sj9Xync*i_dctb`1B!FPEʗΡ"UQꊎeN qw #nxV`PP%Uov!5^3&?0_((\Ҿ$K=铃=w=6j>s`P9}~3+mǪ^%»t<{Cܻ{7ڤz3u-J5<[c5/(7o~O2ew*9FFFDNmݺqo2dAG/.lBY˖-o:`DDDK9e@q`GϝO(nh!qojs̙ ΥP*[7o&?]MW׮]m7韂WEcWX@``MO6srLxv5]231`lx\965X~^14ik3c=tȡ#f;??\9򢢣%n5d{U氝!u=f̮;ve;'kR*_}"l9Xg/הV(j ތ^JRs>oI[c9yg''sqǏv޵k y-[|ܹV5JMIږԫ_O`)jGyڎۑCqq޾<@wm|Wۅem'sx _%22Z={>9~uo؇)-%v̐bً6Wya:kwիgϜ9km#+*5lWR<ʕJ>S]듧N@|++ow`L^{6|v`wf^ӦM_okZzW|}5tM޽O=KN/ 9X&8zxtamǾjg-]yBe%.5cƺ 6zEL_ m \ԭK)ii^ld*0Um/Pj;flG\O)y9Ex4m!wwgΘ؎k+moާNq7۹X3/4RPU`bYpMYSC\El$H ݅@I ѸQ1/L&mܰa-şOJOOJc;k6r{v`z(*мs^TۉFk]hד9zvl>f:aa/|}}s\vEK0txcǎqE|IR! Zy Hisqug%ֶfl6''ٳgrsiŕiG.{xxdqmm |bcia}%AqV֝vkeOKM:&m5o;a) |VN4'Haau~zڙI~mHHQU75hI2aJr11}⦅_tP(K_ Yz1Ȳoo%KoᲩ-[6O9c:gZBqVƝΦmvKs|{ ](Tj`'O5>)Img4 HHJL,KHecLeM]r vɢ?}Ͽ|R:^b"70O(dKUc,k$ IDAT#\_߮= Ӧm66\P_듒cE >ZyvvԬUՋ_֦q}'4iѓ5{aF?d*/k'4on>}}{4a*/0?(HU.c9]]"UEڎ1ّ] .'&:Z~]!)9Gӽ^qeJK)K=xH*ɄC>9uϞLeMz}jy+VX1Os=|ؘɜ8-Usgq.Wqlkdڎ#{E Acm}mYsM?ѳ05ZŹ>s󫩯 #n47oܼ<'cɆB{\gXe?,O?Og;0O(hsDz{V??'q 6j9XmŹ˹^~*uc]Νm.6/޵k>g/e?SNݸiƩݳ{hŹ>n޺ʔףKHZjw:9^w3`q5p :oMvpO>n Ts^a<]zs bʹ7olm\}f;::AJ}'&&$0;wtK,s8)?D|sIx8S۲tH{oM-*]$'E27k:w˘ goP܌tOcP\m۵j#߷o.s޽Źu-||8rM/nf}9>ct8үUF'y9d[-'M>YɅ _RVFgwΝjV}hu-ΟU\\]s5 9qbݑ88S-i0f؝e/~{?U!9@ X 9\FN}NҊdsLs'l9Co ЌeѵvӌPsdGF|!m^4z |39u@qf➴cN9I,=!mD Od ˚dKfi\-~wQdz+)^tAzRS{4A@HP;)^7{}sIf<󸙝b(a#ATߥYVs]zhΙs4J^ AڝTfZbr,,E8GԔ Q_|qTH:G8GRTThraRеU_tӅ.N;9tL\BIN>FFm u9cr]5RQW}K߃w)Q]T99Zk t-UOٳg:qEE vލw={p^/JYQ!.,)+@k"lm۶3eu.r#A=k.0G`.G&>%g=m54 ۷o5{ ֨hmJFIUvKUZޫ׺朗pt2V}h:BC323pk{饨lP,)Hg16/_ɫl]۱s:x-]2 `n}qsT*$9f4kjՍ߬)̶˥IjG]vhZvGTJ9ShhZz;^1:tĈvXr~ o3S;Kݗ-jLlw TjNrqi/w2s4xÛptP DW%cVNfUtk,aQn;v$R,>4.vubBB}(Y*x9v}{ƆV:8\ٖ1بecͩiW{=o_:҇ch:vpqϝ:u:T*Z!!!r hT퉥#gV(ҹu4jE5zӢʚBM[Jƈ[W z_ZkK@>:S8@M4J'mUm۾}٢/`:9QnfMmXsy`wWK&u}p=c{ݻ 4x-#îo/>_8o^R$Li7WTߗR wWY/O?mE%%npyb5ߟV\uRަΝl viJjIURջ^}v.iڴU_z,WZZZ-{#V:^G,&uWz`Ο?A @㕓c#:@~sJs5&4w< mVf19ۻ̩Ѐ?8({@Ԑ?T7Rsإ"\1.)jyR1Ş/Ӟ\8}||ϜΝ|!VEEJ"av(:㛻oxEv(e)$^zX;95~:p5dׯ_o.wh4NrД]veڲ7s%'Ce_˗*XS&.*EyVo ČgWuvJP0wyc{qܖ!n]VIN 'e?5y[/}E쭏񛪢B/s.?B ;oἵ6f.erܯqmڶ$wKښzlOز7sص =ӻg q>d =MpWM #[aС9~VOסCs?xРi w7K=zǶo-{i M6!Ce/Mѕ+ͺ٥V="îZ7RJBM9-0[ N/S_7Uǎe~~?sn̠ g''gGhH`f֬YrP_f͜cwW)ֵ){{F8~PRmHĴY>(hCm=Vg_7y;) *urE݃|}KJC*iEE?@;>ɏaÆ`nlG(-)q7=nՖ45WQnLJJk~΁J778u1C:Mqźm^|9=-ͤ$njݦe3v-752N[5ӤANa}02q M2囼FA_v.CSvb>[?s5=z9z1.6 O2nN"1{HWkO}i}' 7kNuss+eC~?P(u^z5G%N:eӟջ[ԨͿ ‹/l^΁Z$}[7v :o[-yݒdzFtt5-%Xrw/Uͯ[rފgnٲeLfVvTԗ2WObN4<--ÿ)ǖCĒqqכj⚹EEIi+:wt1f?Rw u1=gC]=O7V_S[UI+kBk׮ʕ_}}^%7tYMu۶Q.V\>|P_7R:"22UWZbVKki4כ.]j߃b^nu~;#Gzfedfڪ'k=bvfyϿ+觱+++u{wzWi*kj{OJ΁l獲aGM^М}YKg\&DG';ZC])u]q;kk%'%EIWC">tƦCǎΟ;sĝm۵= 1S&O^w~,K/(eOc{Ҹ7}swE(wÿKzɣ7f6kAY6m?ڥt=..Vq;*qv7S(wFTTTZm}232jRAov=@SE8~w1Os-r~:[ٍI/ y'߳ĥ}ȜG̖c4FbmsʱJ>obҾ˗.iZ>CGX|'nsdpr>sorjm^2qb1|c;>eqfzK=yDw[՘]rX]vޑѐo >}uϞ4es2j.l9zTVEWj}qA¹i]\o$Ex~=CL%1J'{hN9ӹkg>Uh4j{hļ_>p|NE4r51 KMJN q.;5g2(=33ױc_}kxWoe{4{nh{@8쀯2O󟯿kkgvZh}zITMԩֈIL]1Թ󙓧NK27۱s!CsC,.<58uu"1TRrrs̟ܽ`yhzbBBԇ/V> wMX\\U.(*,)p}۶.GS~=rXO{C8ݻeI-e]gן;u&~'_^S(q1.Æ?!wO0z̘-7m+w /;>>.]۶4~w{ fR2/ݸqsH^a_+e2-oI]YRizs?~['..tx}_g!re D~Nk &&6Y~_|Y&Q M_^*GGF6!w/`{ƖQ rUfы~.ƺTÇ!??ߧuqq)[-H6m/wRvZ}ߵkW[9rXZ|V^^"WB{{{tLL|=wZT)g_q6r\nNں=‹/׿vcz o,UUU˿T*7/7,<3yHǤ6kf˾HpvI-埩ݐA4V)sR)*M =ЩOxa_+u^:y},6Z21OK(|`9욱eBTrb;iaNeUtjɴUh}osT*}!go&ב׌W;y<9 q9|hbX9Pa.|S7}+:[P9V\aUI-M/y{ӢJ-z0vkõLjȾ, 7k4cR`9얱eUr:_(|ؚųJǍu$UO nϬ!ww3qGE8T^W,+V `WKQG+` 9F砞;sZyzhнݗUO9t2{9?0>{39;;`+1_&'jk;fo=M![vI*\16%%"]Z@Su9Oӆp@8]*}i9'2++Esb<=Ԋb[fkK5:SߛT6uq|YټW[4D~\{p&Lw57W4t^JQ&e_!ʵzemP;f'9s(PwU~GC)+:AWqT(<'Jv{ʔ?Fsg hhʪt/VX=}u2&l%IU+fg?>9=pV^FZziU%^ٮ=XpVݶ`L9Zbi15IeeT pUer1;FcoClrszsr4!RmQ̏QhbG8G…Rt~Ȑ?-VHwPc0#ovR4X0RTV̋q'kd-0Rt`^ץi+OC:k>c滃%[/ےRc=eb$d58Lj>1%*ܚ:[Jƨk*8+^Z-Wʒ9ѷ\IhLhV z=+{SliBaQӚ:y'qڝcB t%UWjtz~o'eAh9zMڃm<ב׬zʜ}T} D IDATէ^cgl.d)Oi7W{oؖ}C]O.9.*~.ܜn1"WU : jV*4>m/?듀졜s/5 MhL^8ʫ=o.8wN{ϥ'rXر]O͖ȍpر7eͽ JBmIEEFXiQ\)ݗ/?eBw6~'2h{ p.n=u`OܪY{?rEb_:{wU~ܗėsNM\|x\Ih/.=7YRI/m~A[~ ם~X͛'E-ƂpOrʵwxbXe#ޚ;|t~JKo Z[2m\Mv~ Kz<~N-C:s`瀝k飾z5_RZ#\whU9/o囫S6Ǖ-R}?WjLw=6>P4[Z+ q>$U=ƀpعz~ùWW{ykWkǨ~yK\_6;\uj]5>.NlW}ɒ`[/{QZ`9+sΉ [SwcOfUvM cؘܚV']\< pK K-lm)f-;J3'fX6 N<&ktZ<8uO okRCSjM-NdVt^ќhGdقR‹/E-HCTh4:ZZ݃mД8xR]ٮ'w?dWbVވ' yRԂu^8T\cT 9x~\|[ UgE8VԟK 1)a8ߞX:rdvzBt9OӦǺ%-{8آ.@CG8#ᾕ 㦥ݗeYhe1"96^//u"Sz%ڈwU5g\Z$StRqՕ,ۻC&ZKh*kzER#Fv.)UGƅ=E3wn= ØX-[J݇9E8ut\iy~<ՉÖք{nج땦m-aZ+n<̤9 })^x)}]O-Oa9{30t^Ӵm뫾$8ʲ*5N-7<7R(OwyGFGCK3j-Huִ #'Nhd{Ϩ>o;*J,_r>tL9VPv :(eom5r.֢i7f[kߏRjbK- [~h̶U4nsJ Gs_1ީ}7?肎^ ;ӍqbKE)atSHY:q;ӥ?aU)nJgwdl+ǽcCY2^M+u>+n$~@T\)~^5ywBFz:SsMS#:+黝-cځRoެe\ɳ+xPPX2Gd4vW^.z@p~gOꫩN[KpH{2s4m 7\v-v jGs]_V9?c4TsB ̝4Mܶ4xNRF6uEr\g-Esb~<,ѹ3{ȎjE7cV._r_uh9˴Bl{bܷÿK ٞE[5SP+O G}Œ1-!`xϻ/>j/CT 瀙 5z+y/lAtpI՗ĤpἴJ(z,qE؜y1&y tQf}98~ėhbCXSWtB!v[3b>!.v\yO>j8Q`4k>hL瀙չ:1)ThR8_}ߪ+3lq{sYk显NZ[Lnws;1ٿ'9kw1O/ͶE?=7z㘢tnϻ}rnrP瀙R$ۥ#%cKqT|[þ;BFJ9tWf&>QK0cs&%P/u/{Bu-[c!>1Xp`4&s:^GnE07W_&1Q G^)ҹ*KէqnZx~צ4us e! uwLK+ U},V>l5^]Ģ(qU%:7ra &}?K{$W0JQ!w&&{x͞C6^{#;jU)ԵoOCUi;喜+9{xs֌oCykM]qP27uqlزed9`S»+?Y[N f'RwuNo71󄆵{u[V.+<,PHK"W'~mo ߍ -4`~O?}0/}eKoǫ5 3)Zx_x5unJz8*PԔ?)Lkf'fA74XCRa Es=n+ElՇ8w3[zL|+z纎M(DwZ|VrpȐ1.KǖS]}xsy?jՍX^ 4%sMZVU NN_\ϻ{ZP;ZAU cϣ '?O-zNA.,kjR˖+ߵ?<])L+yBx=ǚP+4 G}H|U3"瀅2gEZzXFyOkk{2)%>, )F0DX*]K4:w]Q{ ޜz˭=O̖rQ!"hb9jA|[&^?}(fJ5OxLw9}H awmOɆ%'6w GOR"V8:)׉=I>^ױ6>k&V꼌ōNgL ,}Rуoj.&;3(eۨ_*y:K~Χsәx pQfjl1ˢ8&}ۨQ#[qެ.0'wN^ru{ݫ#cXeC{4\sJN& G^%źo{W\0;{[B{o5VlvE'q |.*’ 51dgw$m=;?Oyo'ec9uU__l⃆;2N*Ry9v_ wLsƶ=8l^%9 tL ݶ@gC#彍/Z7;ڷz/#]I*lU->Դ7G3*zU< K=2טªco[پJtnco#= 5锔U>+*$dYbz}Zxnqlpİ>e`ðqe[\ËI곷z0 :ggC5& 2ܷaDD)ק1'&ͽ7x}0߸72aRqUd'^.G7 2m`WUƜ^K/S}_̕r<B8d&† *n'C=-0wŢ^]}g 9,+9@-Kq^R;>,Z}+/?`j8_|pS»;ƽy<9}'aR ZĬ?>[_ &^?=im/c\T3zԟvh`Uʟ0\6ꦴP1)K^z6볷R48q>Ux.ߜTv$G?L7gkW}`x-:1i~1!㯙}tY+o]O˹V;@]@$.}Cܗ&&ۢ~N.V=/pߥ"M#UGj"{dRx^Gȫ& g? G?u!MwP=cg+.=dyDl.*Ej:J=ץG@S&c3΁la:osz1b291֑IaFg}9sґ@eˁ 'fk:NL&7԰je|?@CQgP#c:O<>qG:1QŕoSLzYjr* hyF88uՂOy0=)fC{Voi + mfPzS\ve~UZsMKƉ:yѝ>cI DF[[s}zoӌ#&۹{#;vSt,gU~~Z3ޛw*P-fn \ܕ`}mdh^uT27]^xp?|dl-xLlRXJ-цR `.9Ј| iB^MI碽 GX2?g RqmJd~vx~ݒ9Z޿շ/n$ vSeQ_B/{_jّZIgG}$m΁FU(+bѬ1 hl{?)[7CKȽ+ǽcC .ᾸMM1x۟%ۿvXPxۨQKE3gZ&ŗC8 MP okb¸->W81?&%nu1:tf?/Tz./d^.GM9QYVVsd CIRԩog-6ܷfXI*tXXe Mآ?3q1a\FaߥvRΎyX#;L9nzk՟_(|qG:iw|(R(S["MfͽmQssT8T͉zɄ;26;FL$N+֏~sA.uXLpx`;J $pu{8t-;1"0\r׌ی#-W%]:#emLj+R%zC4A'dk-Uy~7@G8̱I=SKa+Sl9εMrE(3":w^|ˌr]_vtrxAǬek'*[}1sQkTTIU sRU{yjkSNk;YKZr>oYkkRokKc$>بꝥл`.9`|S~M=&tHGev̷O?5 F-x,ec?%g:eʤ"{:)uYʚ"p~8*ykrptRX7s)9$~q{r2|bne;Ec:{p߾qa1)m]0;G K!TPCm=V__xh+os}޼=1n[ۯN΁ IDAT`xFWu3 tQfY+pX}@~i^|[ϟpp۪>XT%7jbM 7p+b3vꋚj8Ŭ2a|FszxNtvE9i><]A]ǽ_zČ2mp1j:8ut-J]ӐX]Lp,13LhӿcAmKMDorsTOC#l[Ӿ븇zX>(pͯ3fEήrۺ_tZn<2d-R 9? q9l>N|[5&sVT7]/gkœBό>JNW߽4TkZ]|I+k;0_~v8y5|K|}TvswL{R @ sIqDuJ&O!2J9*z5mᾷ7KF<1Xh/u#+n8W@cB8_*BUR՛cT1,KL7¸K#۶U_u/ejl3ݼ߶ AbUҵMlmy)3½Ԋ @sueFd_޸&U5ÂIU\ zQ3mW']zv'-B#.Llkח6/ {9^D8_"2͙&OuyW%A2_Ϋlcޓ伷ZbI ]T9by[?hyuwlؠ"\~ 9?^粭[}흾~OKՓ.Mh?{G{/I().b}"-Ӊﯔ49Sﺎ[20pK'pvpxm]3gGە"[ۢ/k ݿ!{U:7DќOXWH07Nn;o ]u'hHȁp(U׵)NgWt1}޾e_|t^xk{./?>l@0+]V<-*E%p}.2I_CcpyUI^'u!ѩ]ק9Yѽkժ!37KeO *uؘn%ltoSJ+kθ+=`IIF"1gNO 7C{@8P+GC=n~S^i鬋JQ.go(5pSھ)e [rp!fDGxxs%fǗV۠eZJz@ӼTIFE 0}9G>o\ &{9) fG tlMw<2d- `oS^Pōp[1r[BFڢvMJW5J8ɩݺ8x薴]uxv'+9T`V}u>kPz5kbU5Syu&LxYtcˢxQw ``@L6Q[ӷI]{s\رnkhyu͞^7mjzm tݳ{LK"#ܶۢx>ًQ[0o飾}pԲ;m=g ER>Ϊ9*Jm5="@̡ܷTT,6C$um^k1 18&pP_y-#jV*4^-e͗z$e=[ qS= @ tk?倔lg1{sԖkt,UZt|Ne)jqN:j!&|Xm/cO=D}G8 11n[&w/u9S|J6 \մrRw9p_kz{gs#z$w392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 392# 39:xBAqApQoG\pѩjGKQǽj֫ץ" k[R7pE\5aIHBg\#Y~usNr9=w$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b9$f@b=r\3@RsO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO< <sO<\R IDAT <sO< <sO< <sn`eCW7=>iduCkڦ\^+.\>p^zО/kN}^F<X6Jye7VuNmSk^g~Epc忻i7V4XePG M W *)RR|AůUP@MyE?|gY}zG]~QrO޽~wZeW}Tz-}pe23]G<H%ЙK}fq:-\OI0tÇSڙ>ͻ5ʟilQ`uӖg=]ySȾ?p'o?hi[^|9@"׼Zuϭ_wƂ 7/hwe}]]mmKG=<Ӻ]zO.Z{Vw~QA~m_ܶϿu#$kIyf-%e?pRstӟU)^;S_YGz997g-/u8Q 3gHk[FgUeϒE^~#J}S|9@[]}MKfqcg,~<9o] ,kH}?ДWMN}sUn|Au\"U7m~O0q^g9@YQ:ê-R^\AN?,{%+L}Μe{N~qՔ){HQ]dI]g"-\^aa~^K곬9alsMU=K}/sNVZvͫUL}jR|ʋ<ӝ=_%7өxwpΚ7_X<2y{-U[>WpXoE {ϭR]V4N>G{mܧgUtN<XisV]v{5'rϳyw>{{&gVR-T7-`}"AMSO_ sW}^u{w%Oa5};|bآTgHGf>ig2IVSsϕ˿w/jmQV{ -Kwy_fWuAOl>679֜=6cC=z!=_L}&L<,k3'=n~e7;}ky8`?hw9)oG|vTڬ#?sɬgiE59 hC>t79֢ږjrZsEV'.z|U~G~^<[+?퉊۟_Z?=[G*}諞Ȓ?;qvvKuͭS竸d11;x%W2IY\Rsn.tػPsǎ?~ڣ>]Ӽɗ>8ntNܺ=ۖQnƔ]ϛNiί96Yz7s>^ -\a[~핍cF~cw9 :ؠMK^XuŻi﵆.x+Oߡ߭yq/^dxVWކf X<~GMwkgw#qgOڱy+;3?jڢ-0iv> *:xlPV6蹕Wn|{U\x꘾wxˆ.\}}?ue8~`[zVv?i\^^q-oT^v!xy:;GPǞ"|mːu-Zr=.Vciςg`݈@lm_Z}/U%W}m?_ Ŏ85zo{wrڻpw wuyςUmsoLòik[gkZӳ'3CܻG~]{| y=Fnw^li]1b¨@n58{rmˠ^oTYт, [7 'k7sgo58shlA^^knV4n?ytu-CXݯς.{:OŽ:#z=M6JOztX{T[v# v>{>Q {|ъ[ոuG\/{G}iQAmv7w)#@>ϯ2Vvw.]_߯(#exhSmy1[N(vXf~6wmj-m.kEd_g3<%Wx޳+񵪳;aGnS^ng՗\S lr'fcG[R69 HuЕ/7Oojv MK{ѳ+zw:omukhIgOunͭ6W7m}{pv.i瞶{E/il,YD}Gu3Ɩ]<Q@kj+}^iWu{A%3v, #Hr^?;˞݉#}Hϗ\){Ȣ?Yl?gzÇE>卻9mќ\ YϮvqm鷏']b֧kgbak72'Q>ex%q>1v7y[#3=qmsۿ㓥Ew<]g䓶swA^^k{džƝw[r/{!#K{vŹR7KNdx]sɏU9m~Ķњ6t#z=ՙgMykNqh^G?9"`eWMo7\o]^ycˮ6}**ke5'Oyq5#6ғv*z.eהTzl_{?qYn7~e7GuuݐXP7ۏ-kMck߶lږ>3v,#?3^XZW3՜e{>@49tSV6m7ᑥ3W5fOOT*{dߟ:YDn.|n [{{ xwr"? @9t3Sߨt+no;ޮ>5{tĵv+Ǖ_|Ħ Z~ovw< #>h^OtA *:z׷Y}FMUM[l· [.߫ҋv=хkś yNں7)նu7h ttM$:kxB[)PP-nKPhq-w}zW723s&^=9$ilIP<"hwG&,e.gk鲈Zg;8:@dpv%_kam> l#[UC;i<-0t0@Y?-VNb4[d>]<zrkta$ި-cEWϙ^^FE*)c{dj+O hH&̿yt]IS4A=& `7siэyE{J-]LMw~2~ ۹3\]7ʲ-$kq{Klg%6h[}dmnoq٨eE_߶u ]!%Ecۧ==ķ{o'udb"~R4]]=g4b;@u9y,c#p΁i4}z,sE(6o9֙V^Nד&2l.v&6mw'_PjeWߴgWrf";nu\D;֖҇e;@u9:L=hWlsX3l֊ArAYzXr`Y2wa;p֙jڝ8_t(@Y[Ҫӳf}W^tF.{>&%0H_]z/.v6s/PTT[v`>b; T}0e]7B:SMv!U <)h޼.*{MG7ٕ|I*嚷;:LwBCpyAbi\Ӵ߶sE˳Uns7sD3uQV4U_}g&kZ'qF7o.oX5ֽl}o9C1^fvceJ!rsĎ Jnc"_yl\ǬՒA聒幧b᯸_0}+PzbyWYuOMPxj[_@59 VPb~83YjIV#$FQiks9Ǎ ]K3J\!Lu4}fPw>pFUb^憞ioyfiy/ ]SܘR_"TU tԝ/k-TqguGՕ*|f6p\t:MP]גy` {*^SϬ=ngڟz,s/o k^IӵMU}&璵]=yqE /8-]Ҡw?Q BIEƀ'[krz:s '>WA3rY(s5LjP<pPZ.*=7zg4_q#d;Gw.J636DMt2c}Y˿iw3,=۽2}$*ch]2@T8k*ZwQr#{xz\8V҇בA׷2g\y=]ת<Й,N oΧAP?wtGOEfG9phHyڕy>EӉ[~+~]*HQ)qlۛw9gw7>.u=&O'/S?Xrӭ[lg)4vݫFے-x}6oBbѫ{Nsqt[^=E oM{zOx1y vfiMNf;y Qu vSQUN߷?N5N ^nu6'<ћ,"71?gkoȊ_emI|\spZ?)[9E%p=ӷCCg8-;yiMqys5]ۦَt$,ΠZI Sj; ο,h- .]E#h"a]"MmYr77rݧm A<k,~IwdZ+  '#Z(2~í[pet|`m[T-AIoks?m&_[jnyI}Ak41E\ז> Ơ5}.h㺀ss9&+OZ|;ry]Wc u2l=1]>rgQqTlaj}9>mk#@rO)0T^VwsbLYg"\=u3j)ITsFm{y=ZL_h6of#劯۸~VOZ<o sĖL+JeZ8vYyQd0?# >kPzQ;y"^mRU=60YVҎDk!{{-Cvn(~`'{oQ>D&}hLon;&gM^kg9cz*&.el\AԢ}{j􎘢Q ZF<ů\sovzIxLW}w3yseNs4Q.u&`v|Ooj-N\ɵ" ?lÛ 3ٲڙeAM;O 7w!3Ə&h z&s5T/.\4KgjL[}{SkO ? P(TM=DwLZg蓥5{PXW'^n0vvZ8.b>2{ƙ̕kL4̿=m+Ow%wiҎ ^t:ߩu9Mێ;g--~ yBGFɷ% gq[7d憍Q2}I:oE+/i|=Um}oHg./Kh!˱e.zxoZ,>ŸxkI\BicO22%jN_l)(P[tјm1EclN'˟Mt/LJSst*E%1:om3.R-G6Y6O.= Gə\pt?/Ogry§ )16[u"_p-w#b0[=D1-2 _7>0t܅MQ>Wvot@c?ˣ7m]?QO[4ZJ֣}? \k,RZGZ;3#xV``[C,6MjM j?_|*cyB@;[͟Sj^]%n;}@YpK9&܇:XЃ\t>ۯH&5x/M-v&T̲hMn~{MWƊ/k3RERiw(}N%jzИz:sMJ`P0>Ac>CN'i؟r,siG^cl j6ooImeB= ]FEmm̛Ӡ&k:q^y2`_tM7v]NL (+_;vsk߿ӆDž h쑑INܒbs ?7aj=9p) #;Ctʃv^IӵT5v?/4P6n_|+&3]h.gQkA-^isi't3A>}zHN+-s>4_'5=/N5 Ơn}H뭼Sw:OWOej=bp?ﻛyLL{g_#v1yO{?kzAa䓮6zgM^ m2~jR_|N*[$Lv$ݶ= vř!]86<.X%*qE/]tǯߦA Ycv-|'=:~,ߟSUF뵏 TIKo7T8*AʄΚ-h3Vt>T?ڲTWF{v}$hlxN,~=OovD5'k;3\O׵A;._ssu}Gi_-RGL.L|c˗Z)f9hܗrxV`UPaf.?1 !ɌG:U<${"ԺЃ*ԃJ+#];ϥA_+J7DC7%1/Mmyﬥ4$fe7囘^؇9@D;ar O nUGES71?Z~7t=EtZ#*2 b፼^|jyQh!yyp*yL~!UQuw1?{X*wk3jYTW)\Yh zEkLOX Zeq%qYvi 푫/g`-_=S ,mnx-cv#2DM粖F4Fd‰4n٧-],s 빟yV]]%tY+PrLtp֦ۓΥͥVM Ojg\fbΏ_uhDM~WarwAt%_pEɄ\ ] **&/?&t0J{UT!!Ψ ܄p$^Ci|e&БyNsvunwDXOn#˼Q[;~CT:d\UU]a̩=蚾OґG}Dtt~={O*s>*Y6L*ክ[zyv9ruǭ]ϊ z-韕Y:]S+(:.i¸hqoʅ)ne/۩ewndZ^ׁUQ_K׶6Z?dLB%Pl^[[ps?[߈yI=<3į[WY@šxeNJ{?]Hx&Qn;Zֻ56СC3kDe$iQw:cFYg\d볙oȝoJs)U۾f;KU1D#~_:0{=2Y3!BZa ]V^N핫{}|>^*z=|gw {>k?W|1ҿ%Ί8jN/Xcj1E$kٻ=yYm)\s еZO p^D׏?lgqtsg/ͫ&zta_G{d*^ Ed6oI(́gA se<8T,P1?Qj ap^lglHklMGԑj=^^\iOg~ڹ}\u)܂IiǞk34&sHong5ݲZ^kTĊ3QVIwF4A?Cis6E[J@iڰUng隡xPux':sZv{`ɑȺ򭕹55cuo}#F5h&"^~eֆ31cYfX eόͽDzJ +(-_09_Z}X_8 UlWjDeШ)\ 3Y9K)^yVx^β@)笵 /4~aL_HN4} <"T DNl2Y,V4^Zq@?n3RWFN{ֹ߬GBn=r9 DzsF([6^őu(h/^&*W_h V♸_{K$\mmmc~(y`zQM+\`Fvx7{"m!s2Qk1q%w@ wvSՉFF [}h##WEQvk )*uWhHtT6$ ?kxns@šx ԘIقFO_GoN;3In4W'S9n)Nl Mmϳdž+TyikL잁%'KWdȗ/S72t-i=4ܯ}]a aud{B^Q,/m>erUE`ղNv~6άP<` v uoW>$T{*oisf;n_;m!Bv_M׶lV;cFxsT/>+Pzoh0uϧj;Ejqot͖k0 #ϟUڼ 'xn?Vv1LJߺxU6Vh(vU4b#?MH9rlu1/+eRg۽ͧ9{Иk\@kFERkFL?H!Hy9?镜oY^KރXP.abj7~gt@ӆnhIt;C:l>퀦6?ߟv>;֛{nY;FfZF2wIKc-L'hx$P>+>_vX{C'Wk;22/m.R\7YD5]hz9}$:/RsY*P{1EO=׮(%O[peC矛 ~0Okg3,94gZT(TY~:I}cT:We0Ą]}G,Z3mZROS7OgQ C:=22%_ov^po z^`a=X42\Uj͗ Eט,2l)tbT,{x7C[Q.82ށgACe nkoӏ>({<oۺ~RsV9թ~%M`MfE7rjn $_ؼx6ņ.e_z\s:+WUF냯F\kgdT|Ǻ%|Ɩx`EUk^<.||oSv_1ە3ZOAɊgunt+T*?\ݟR+t|g܋?[{{EHS|_Y%h,<};/|>N*s[TKz8`i/P:;0]v-.j˵O86\[O2sl˴ry .2WeKSWm@Pw]=X!OWiai=|;N̞6_0ZWgjLLޛt 6kIe{`)?mmw)4_oJ׻Y&y\5_gvvu)~2~JB=@r\Ul_A;9?|//~.|4AXDMwFF&S N7u,:6o‰(Wu69<Ч휭50=H.e_HJOlAjs).t>acVy\J&D7%K_6Z1sС5=lX(@~g^EC>7>6NO(~SϤѓח'Sn݂9S4޲ f4RPK`E瓳yF=uXu:GRVM@e_h<ż̤IT@Ҙղ:l+}71? nSO߷w)b alyѡkg1[,W<v.22Ls5YR<}nW.sË9?5f;5g#vl^WOXd?/wez wn\Vh(f\-hLYOy- ~ֺHb:gypYڼ 'x`[(`En\sŚS6q^M,NmW51YJFԑb;@UB;G>>L=kWkpӥS[{{EҠ棎︟oT{9L[z9{Tmiy32 pw IDATV<=WdPxP|~vᱝ.j;x^} f,9::nƋY䓪2r9\#\Uyנ'dә\u6%<)kvvG*˛::],|$PyԺs۩2`^|1 q/Kb>W6HnG*_!T)c; @uEErnudi9}bkLA-UL{>Misǩ.."^=24(`*U3̢cOdlћ," R lH-'q ?vpFGk$ijYJgӠxntVy'SlV<6>.0rrd(`<ϲ1hәk!k80gP`dӃ}m[e}\!ͮt7XztS/b^&dS5=Jq$la5_8bݺGbM#w.~slYpYB]Ϧsl)X!7{H3m[ ,I@?9ߖxu_53tmk[GOH[znj8#},c'E~ W`O=ħR&юn}/ f?yPj{2&wL> Ru=$T2}.{@]w;r YPfT$:ܯMמk5oRdљ݊ fR+<$F}ėj)l_[:[ծxfmҙ9NC=\=<(9y` U66q9˼.h.SNeꍲ~٪J8]Oذ$[McHmK4P,Zq[s"/B*R:esG]E3d6w̱mѹ Z.tP0"t(DMo@>Cx8/oo?t}'KtԱѹps*Ҡ)njmLx6yE:>bKbԣ|.vG9U.nRX/s xG6?tFkR_Hv,Ց?/cU6NTd@]o*ֹߨ; ۹fZ5(PlgK=㈅b1yÎ{Y qBoM|D;J#hTl!Vh3j{:kodZ}"7p̶PUMo&!!1Kc`;ϥTm;o1y<.r+}?9'Ci|%M۶2k<;Pr,s` 953|KbtWܛ~RbUXdj͞kR@n`@Jb3uNv`ǪQ<o(BnQҤS\K׶.mnyƃtκ2Vij4];z :3;ήN )@Oߗ2؟z**WQ6u^KGwT?gݎ%L}338ucp6cOdn]Y\j^gS“}{ WFG7sDKYx=w> i=ĩYvG98-<dž?󕵹^|Yٳ)#Xx#oWs4Y,|m'-\u`;@Ix/^:*6waP-فW}̱2k+s k=Jɱj.:_ pFԑ jguk;'&P@~gO5H=(TސPپw粝ej-H.H`;Gu0 Dzg{Mf;AR3~u7'X;ho֤ y2ǗsfGlfU8bÃ1 %|,UYg?ߴHڡy p9}{ٕ|,rM{{c;#B95ijćyFýgČ )e+,[0y|Ch)_ߊ,Uܐ1y4t[gQݴrv|oREB02A ,sph;]ip,7Fu,cآ͒Z}ïk?9{eR[W`[û2VD@ɉi!z3GO1S{JN{L&f'?f ݄앑 !pz*lgɶD}\o\m/lP<`xu3LLnB1(H.H`; N 1\ J=hmKϐRήskNdl6ؘ# HW* jz,1+E=|bksN]{?j=@v>Ui2p6861x:΂'2! s389MK]L=bmǗ]s=@t/[߸g2=El *s WlRR+ijÑ 3FoO7YD]prt!PUG\1fgm. _|bP<`͝\z/"?YJ[lgpD.)^u7'dhL^ͽ1! >Wc5&ے]פe7eLEկ!A<|f_[!yNszQ9xS}[Hy'K״ 5#`K*U|똾\./Zvd[h4jirrRRR@JJ_jjoaARՊKNdXlwQHHgl,ʈ-IQYbm.;;r:uT@&:{L+Vd;TKm//ts8:,MTwKbLެ`3ˮ#בf3CU@=lؑ={6We0B6&< |.+#zўݻ3}_7wlYDE=wǏsN:Vv&`G۵ ]#~g4ݮihMJ}-/h`N@ٳZo63֡x )?`Zr  '{WQԵBn׮6Joo~R<:ϴ {N v3e1&=r UN޽{s*Ic1yLݦο|K 9 l9qyWYJ;|.Y) ^*Z¶]Ndl6wYvm*GaK;{%eT`[yq_.X @YvF-IVuf:?3 KwwJҍR&R JHtHIwK-;3~Eb=;;q,Y3s`>@&N?"=^~+y#&Nҏ1sRivjf˹(aWǥ]J)^:6~sکĺ`7nVjJ,CsX n+ޯKsI-RƠĺ (wY. Gb%dޡܿew)!=%]_~գ[zRf]pRt*{]Sjm -Ԧu=׮^": }!a1D%8Z g(uF[c^ȃ|0jgIE*k]\m>CDd͍+5?b}Dg`~G-[~+i +YӍ,(3]U'(Tw3g#2αzL|T)ί4kvDg >]8ր}˃mCZ9sPX\3k)0Pp?HۥEg %K6d"9arf1cA">P(3 }V!Jn6ؾ]aC:l7^!:XLmuA4*,xBͭ[ts GǴs*_M9w#}(xOfMBd~wPu@P<sZ+LdFݎ;·EEgyo9U+_':`V\)AA!)mo͐s/9%?WSr _ekx sʘs1ā3 &Ftx_=f&XjU_sn);;{\k0z/'L:7M?Hֵٶ.9x Qe7vs{o۷e5ǼY Ѣ0k׮VkpX ~ܜ cR3N/3gG{MPereQ<eh.h];·?N,UCmj0h^L_M&u^H \W-{MOg4n.u.ryx1ofy$Ģx b|RkIEssԂuQ !ʹ='ӳ-7t8+>>>ۼRv@AE9;skI?<h}RKۗq" ] 9 RBB/Q9((.Ð}vvg+`r/j;]s{or};/r2,풆y@Vd`ȃ9(/fB;%K澼flרt^v-_ Aa~9ma٭E3ss_^[gdx;;V͵&sPHi4eemG.]Wr_dc:^/h9 C˾ewI,n{οessF>e]V˕\dF~o@ CnȶeZw+LOj;C`wTNZK:; h4h4G++~~Jo_zRY#FcpttLsppHvxyyGTj@CmXXX`hhhx5NtFQ-:RF{{{׮S4 *fggoN\ș_ׯq̙sX [[ߋg__(7JtF`](Br܊zݐQJ{j͞aKsTG*BBٳgOsgֹtRk׮UNOKs܆  ~|G6k_,y[;a;ݪu=E=-k׮J*LFGEd<<=jժu嫯o۶ʕ\ Y` TjXb5jt\z?nժ^oohѹx K7h繛:Q,K,nGΛ Oj.{uY#Wd9-QxxX/֒ŋ??~?VToA-ȎhT߿w4V?yxǤz˖- 6N:mhl3E9($';]A<"lf}Q+R3NO/b( I_,jix7oި'|mND C.{xď;vG}􍃃c|Qrr9s|#S]Dvw_s{6c'N,TX߿okx"EM:us, sPH|Fswmkn0h#zCs{o+dk9}v ;jǎ(2"_tHx9pР|$D>lϛ6n*: ~?ҐҼe˗ *Ht6 7B=*2~yҤijn`yC}tdCvv^բ/N>9<~ԶMׯ],: ` -]:HZ+W{ͮEgRҥ?ev o8tyEJ'O2egԩ}ǎu=#:(B3 YZv,©S'ta{lL,5HOwѭS'O3ѣG:eit}Z^vm/vEGE6Wﴝnw^t& sPH\zv[hvzјanPuC6z9;Q'h""׫wÇEEg𷩓'Ob[mتU=ӆ vݫZaY 6tٳ~9y{w]pFʕ%:9($^3-lyhP/De}5Ȩ7m-0yȋ \bs'p6mvWTkk3DgzIUTb@9ǎ_|z*KUB;uں-Eʣx s )yֺǞ:[n+ikgv%l&aٷlжyV\ZXhh!Yd\]\8Ѱv:gEɋ֦u=6N ҙ /[o/ȫm[vzZepyr(Br<m7ب3bk ٳr2<#yE/x%SW\Vf a̴_e+V۷Jyrc>9bķs@/> 2wWDg*6&;pgΞSF eP<g,m{D㎈mg">5/իZ)Txzs@~s: ? ZmF'Yj֬yjj=Ŋ{':[~pիW޽͞'ԩS" Nݻxܘq޵KrFK9kԸXj+Ey*:I-y~0$E2 ~fCУ"] E˔-~]~V%*>2jZꗤ1~On0d.Zhȴ?,"<<@<ݢE>|)f^lkwQupĈovϊ פJ*_F}V?ׯ\8{Enj;[aIT&??i4hĿ]â/X]\r6iX{%  Qzm,F3fw]~y>T/}Vp]PKm"9=aÿT_y% z^Âﺹ 6sw~\:{@+(wikDžkGEwo9(79ڪӖ;Giredxs&M%w^3n6lt\F 84,022¿eo޸QQRqzmZetm`ȽNҥ޳uɒʽVA&}i'JCۦu=nެ ]|s{Fu`=+~I׭[A22ryΝƎ3k1rĢx 0f{޹DGBӛ+bsL;7Y9ma|Νe-\8T5l;wj۲\R׮W:udW5;,{i}-Z< yiƮgN'^1'NlPtrc׮ߨ(uhؠǏ ˵ԥa޼\րٳZi,[tEεQF.:@y>l)gM\s-_$dc{.2vv5psiU}RtKvbRAt|p-k~']jT~I]Əy:r̝WR&9wʹllbĈoi 8hi޽ԮU+ɱt,'&XArģx 0o.swĦC!iss5f3u3lWR<11>۶n$cƎ=cqrX:;;{ݵ+լQ˗{վqz+]7yqwk_-n(cg֩[Lmw1t{w{{mƕ*{ͺk{ɱ'.\ht (DmڎKGrs_/;/% ­Z\so(92I- %&${O~ 7/V…C)Ѻu[mعcmr/n(C V静iqÆn[:[] H}V{n sP;'יs&Nk;}񼺏*U<dE]5k:?﫯G17P988ZOw{ݻw/]vc޾$=vW^VyC.k<긬=aGy5f!}niQuҐw*>5/ן咓\=oN֨QsѨ ?eysKш#uss7QV=8qҤi>s-~(C)ڵ!uѹ`?o*lyx@GBӛ<y?Agt?84?ӯKEoxTK #c04nK~j3Vecu򾿚#1涝}nl N$ GMzr䘷wڑyծ]rNIo߾(0t環ҥ6@,`&x=VWI5erf?QɦУ:^5 >7<>Un٩Z`]="&/5jt\yk윒ly.:Z=/;jj\\͜0K1o\\(Ko ]-.:˿ecgvOw[c}u1~Tx-{kFBABVX(֤D_Vٜsc /~sժUaz^L tFkkvYskgG?]K.:Z_څ~B͚r8":HZmoJIKKsLINvCzsR^s# GsD{  4眼((@\W>|y^8r9o^{ @׮6HCt̟݇#GVtKAxy҇DgE}(:͍P 'x &KRڐ+FI-:˿r&nJr.4*,dQG|S )44"R "S~.=483Ǐk,d֪PJ2΀ M,,@~i}?k\r?Q_=:&b}i;<53 2ܙ 1Fk)SS< x0t4(սZ*EפLOwm?[2 J/&d/ '!3u.{mVQ<\*n{j*5օ\0<+6쯾EJmEgɯ^/{Ұinќةim/ieΜc'~!@6򠂧a%ܤX]Ey$or޴W9,&}z_ɽHz* KBZ}2aٳfk@Hj~wPZn ;7:!nOu+\}jc.M??qҩiY/0)(s`.ϟݴI`-(K=<˕];#KԗPbݺgvth렎Rb=k5&錮Y]7tVE U*(mLz_ _xOμ/n5jj/.l;9s"!))Kx9:ZTh2N:k:TykbNTJS&sRic꺳u_%) <=w۰iF@ K VgKpJə_*HӞ{%^eV*o9DcszLmey &f孤~Ґ1ov#\G R)<8mV]ΨNJn7(  {i7|$:*o/9k88P<+n094zDj7z.Ə;U~\]%4}幸O"R Y]'>xrE[n 9;$˹9X&"j;Kn$}kᆵ|ϋȐWSyM~(ꧬ3NWn՟}sYT.`6n-<,,P+TxѣM<=b9X 'cg<?^dd_{}ȹڷ+SdV/}(9HV}v:s V,_>@y.D|s9#хgLuiϊΒSlȥY]s/Q_◛IoUU`8vXcs9jyPQ<+ns(n9f̜o>0 xVbͤAFS{߿|2Wȯ<==DgBBB=c^='agJ3qStSj9Ja?kOΑ{Nj=|GVg<^Lx\MYJrB⹇G\9رo~_:?5 9X '[UWFt*>EȫvŜvp8u*<~VM;7iTu6מ]1o~Pr޽B|R"#=o~^…B=*byy  xVWa)ށER.,\ݟ ik[euM<|x|T. +7oިxr]Tr̛[%eeo]:wC>:]=[^^j5#ŋ<'8JY|95 9XituVfMBu}xGPMou ;>8YV87aLM9Ac̑kZkkܨ-c׏o>edh9J6{/^g9. *Tay;kVc9J*} yT?cZjc^(jpPn ^{#׮Su65*n?(M7ta_T\YhPZK,Z熜9-Q_͝(ր͝3g0>q)bx/\pSYs =zc^/o%J17`mkM+ͥ&Zu45{w)T wj6 t8zQzV͹?f|-v$:rpw Y .A'?7chq-Y\?3lE11林"+s߷;l7ȹFh0h=o=י{Ny~rӓ09`T66yFK#:幸O~4,52zj5qҎw-0WJfW<MvSMcrOItw}rѯ_rΟ[}䑫x.yw<Ǎ?S5&M%crL,oTE>z\`ӦOh9sK,,GvFcݻs <~^$CׄJsx/uWGIlM_j>:/iwKI]?%o!S6{VEz ɽwQ5ro߾F~7 r 3~ۼ#G6fȵSw^w\súH] Y˱]NҪcyR{r7M>1"E>Tbhl31cᄏ@uΝ=[!}fϙKg|on^k.[ܟr76o޴)-ַ|󑯟_fs^&Mbr̭͜9k89}>u)SȽΜ9 s?l N,n7(RTܵƍwfuݱɜ%킿lI͓]rrê+)c_~R(O~2rռyѢe˖/PPry72=-Qƍ]?̧G(-5yO>Rf9+s΋P^I}G\T=Osu&MQb-eP<Æ;)ٶwt kpL<"YorI9gpd=zجsU.l3OY\k?/wlߞel{Ssb*Y2XjczhY4((Dz\A3f?g &M8]6kҴN:oQj=|j } cg}nnj=oŃ;+[X/S˹N-N8ik:LF3fNlLRGJ 7mm7*o= I_LuR3N}\%uCDuիXĉ?QQ͛|c*Wru1fٲ>1$Wrɽ e˖ eɒ-%*ZJZ !lLۭٞ -%GXy+IOM?7qq_TPhxXX?.IM*:GNs]zzۃ-#1!{HCd䝴sp; 2I<֯J]ZDbǯ#G[k…3׮]K.&Oϸ{J888)% [J.:RL&*##C+ѣ"~ak׮U믿*͎J6=wvŊsI'&Gȓ_ell r3VWAT\ZCk=zTuw2R8jE@G?ܹos[oÏ>;KsG':umذ ^^1rt 7*^pK:t+u78KYn6\xwR OTT,@nP<Tz/Svu-MB.,?ky>\V΂O?n֠A䅴]*UZ \tX+Wӧ*9P5AuRS;j?jibN}QVNշxK-۶nCnnn\Z߭%λ /~cMFZt(C6lصS[DgϢxwRfw&i!#Jɏ26W>yFI7w`#GVZ,-6hpDAӧoUW':S6mw%$&ը^;wJy 2dO?/*:W7>oW-CsH-ۥ]׵Ul?0'E8QWt,mҹifEgK@@`XrmZ3AU\=rh77Y`ݜSvCh׶.]FVt&WW,^m6St /4lxb<=bEǵ9?t0YkoٞirE" ~3`Q慍6YX(lr W"!!ޣOkFt&(M۶kw`): [LMKwرc{=*%9Et&䝃c9sƼR|V5ysxlsD6+P`wDE/3HYDg*Ye˗hذqYDrw߱sW[鱴}@+Ξ9CWP]j,@vڷ=!15,,!C?mQ|ڴIvv:yxk׍?p//YP<L6߂Sdwlwߪ޽S[A\ZDg"0P;ICC>mڤKthkոI3fߠAyQlٿ]vc.6ve{}JkҮM&,O/gus С~{I Nc]~8[i<ڭ[7+,Yx͛K L:^t *x? <::wŊ-]:DҮuջ~CiDT*,L}Et@ wew&('+iwk[{ERƶٶGT|[5kּPFիWTJNtW9gx뙙zk׮UpB+/WtR7nTKu_Zkl(>>QF'gqȑ.^q{ӕ+W޻wh7̏p5%K+:S) K?9'`ӲnNZ;[}g+z"j ]riR7Z_I!C 䄭zu 5. 9{wq4Dg 5EgaQ7nV-EgX'`Kn09dw].lNEOip'׍wmVww l2ဒPPQ<+>)ҨllLZuKnσrhN[o7K9os sbFoٷl/E۫vwh#=0c1_݌ՙ-F2 ӌ^\T (ZeӺֽHM66?>[S:ǤWQew8zQߪK!gM2nm9ASImݥXg<Q<+%l|7ZWw3K/'u60z:۪R4^7T3X `vOmit:Z ^X!pꙸkJ3e&~X7K9oRd<9X wRew&e%Y-WctU1д&Ґk5uL,F]Hrf4o_6tnc{q^+OZ|yx=8E@)I3Gz!FImιo* =Pt>8 b hMa)74%PkzY#N&F\}It JxģdC[B܎חQb=iw܋KNՑJ̨?R3Nrjo1'[uN:{M%l+':ǿiwSt%Q<uo'KGQy6]9#]~m JB4re]V/4!:d^c9@i=H,VǧEJ㍡(Z&Tt+^ʨ!:dzDgTw)aDgKQG.S{sQ("i߻W"3B,лΐd~kEtbġ3&x/G1lkбί!g#R J_9#^ xBmEȉ-)UD瀼$KxY'fl[IbJ契>C?Yo(omd,ͭ8}[dRr];JNg6Q)6@~F,̽3*nUljrk[E|szZ2~{T!gM覻)oz6IgtɽE\l.粦wY5U=9)7ʜs$s> `a_xn?ldFĦrzZ]=Zd 9KCt07`a8xʘsDhXzw$n9Gj5=83s05=BXY3/{~":GAbQW\r8]9Y$}ʺ}^3bKj)h5_[wXiO}듕& K E{秢s4j>AܰNrWyƝ%Wקq1љ@Sw!`r=NwyrfQ< Q(ZܪGOy!1 IDATmC/ 1RPmhjGfedV\r(@ҩքc3^RtcJeYyO}کXQJ?K^Lx9YwqZ3.U~WP)=)AgtiK˯ztKzܿlJO v6~gc?=E\lREiw;ryP<KRȃ!-Nj(P{ܼi\eW<,4pJ])Y]#}Bz݄3027WJ/ G[uڼ^ߩcNHo3Ǐ=%vlػ(uӓI&$;;"M)"((+ Ei"HQt*@ !'3Lys^6323O|?׵=YFI>{ P]vW]p-s|ǍWbfQⴺݣU> Yk*_~Hy)(w'kH-=wGE8m)^Ϗߜ8žWPo}#UdU_ٮ[lф)%=vgB}=jnIwjmd۲}{2ؖa1}}p"< *G_j .oE+_K|6yRBx-ZǥVx0 ۤL\n}]ۯuy])YoEg''T%f>[b ]!F{|1I߳.9[4"k9yhLE/5nG (oΏgă-{e{U'bm˚+vD鬃y3*qZ~*[/>l}i%]ܱ-0$fPZ^䎺yx)ѺQx.N/=_<ކ\'ƩcͯI;Z}⵿rGZ(u-p-sT0ͱ6>g)ohZMBԧk!^tAgQ+k,mW+M-1?Zx33G!g剚D@nH3Xco\,|g?on͚'F ucGt%V%oI6|S3qը[/bt{o'6mu\X%9w <G_Ӆ˛Lk)[ޕVu%W, ~>\Ê;jOĤ0_EN6z+xd tv]ͮ:gj$GG.{?H(xu'O H;žwϗu\b[_~][RR)y,pϴoSu]O<1x=xv/N;_w'4-9QW Lsg!"QWWTWyRq#?)#< 0Xʛ'ZK_kzbg]rhZ?]t;D>u"_?$zpxmjE T5rM\,ԑ98w9"3thox1ٔXviK뺖M$TsȘ$uM@USH]ÿ(u p?sOvGh|q tKӅMVFzB.}5ߓ}5j+zʘhrU $~xgZ}(u-B,:D+E멈>L9Ӷg1-ʵq{)zk\r%^~ȒeFE^ vQ>ă(k^ܛ=V%GK*<:&U@EWP$s2t58(Ɨ-CՃ+S+_wal8֭)^۫zx.#z_7nb/u- .Y.yoٿvӀMC'Y˯I;FlXegb4 ќ{Wlgsa[6Zov ђ|%1޻/s{?Lu3֨=4DCKͮo:G}UgwY)bt-o8>nieWZ˛[dJ̎3_kk+@UCxϏ>ԟ9[v8Qҡ0Q ]{UG,p0ZL)ğ9SR-swKr>{j!@-z( v3lV\=p>\5;la7IO aOzrc˔7f,'ݵ/w!_1 jPW|v?Sp2{Joie UW !)5Cn\|GE7g_%{Fx9cu9u~$W T'Uítwĩe-N7O>/'f᧫.xjXUQ5:׾/B}饝C4q%t.b/yreX~[nSnΡLS?]lrwh'fp<_3t}Z]>k>-?_`?Xg&ҮS蟠$گtUc6 wMG?KnՃS5sт'a=)\+ۤLB2ߜ,R޼go\U.dw^=)p_E/Z[w-C^rL-7^6MV] HVik1+_?X}^JzzWG"15k(ܹ <v[}m&KOcՕi̓Lm0w5|qGK[9?|C566$z'n恼{M:z7 ugLgvSuU]8+xSMBԕ6[i|^bˆoK1aje+h~>-ڝX`;c_ެ:=ŝ&_+xjOw(妑uW6"}H]D#cj׊<33Gϟ$5ڻU9gTd}7PEԵTeEi9+.oނEn|WZIav!C.YjBfVLCTS]!LfğL@UoZ'|OXߒlm{6Omӟn~5[58o9XˠmA*zEwҔcYFkDys _%'m>>zlgRp=ͮzϜO(u-U}aONm-eG{ڑSy&Wpf1:5*SMl[(]ĺׯY,m7^h+qB/BZ}(D}Fzn%g(srf;TdG?8ţkQ{F9ml2BٓVr5 &5-p?_5 OU)zk\e)GĽގ=V ( Dn1^jIwobp:XoFJ#w/9W<܌nڝZιJ*u=xiV4QB%-9~\O"<Mb8-u7r\bTrEZ(2cP-  ʛ'ZWɞ{VL9nsqV*Bs;=͘Hht=RQtg|]K:9?~soNM,r7X 4C yJa4ZlZk^*9,pv00h*2=U#6O) M@Gs&x[8h/bx^+P:\s{GJ[{b2X~RP]{{4O1wsc=_E<331ʾnsllұ &TW$`:kZ,M9Ė7jiQ 7On*y;oh}K賞:X/rҎIZR"{[ kj~ lΉi <:]R߃!?oՇ[맒Vȹz_Bc9/Jԏ8~O\_E'j#<U2Enc7e,2 PˋV2qG\SGT]72p]o3J;wgm_q'j.DK*mTc7)k<,+OﻴXqj׈N w*F׭[_Kn껕|v{7 ㊫)&e~3MyFT9_.c]R"\mBCݢ=I@{{Ehqy O2<%kˆuݵvpPOT}Eð~j+=w͙}wv ( }xS+_Ɠ>j5|qxU~Dy.ܟ;ѼeީqM ey?.z56$%[}^)[;DX{~빷bh9@ͰR[utrjÞ> /7},1޳d]i%])/]UcN5 EQ'PuUUE*^ܛλh~kcZ+S=Q#Gx89Wd1;RK9Q8mcRkjԦn9Eo{lg+R_٦{nwb(r&߽-PEnE֩NDvGI;rJlaaw׳~ٳ'Ok& ڍbH]UR^'^smk 411{VK;/[a`=O暚/M1şO֩Ůگ*xsTyW6抇j|]ÞjyF9C WLY50`<7ӣOTfq2S!fjy IDAT*Ro]Vm7oIO ȝlN6gSƏƵ*3Y߳p^g7:yq\8WJ`V)7\kO3XcV׏'gtt~wo ŸP_eȺ+Eޣ[{k5[Sr>^pxҁQ5:%^ R'>}`Mojq9 OMtgxWmiλxN?s&X'Y O 1~ʴ;}XԗӏqtwE{}HL͙{~;ĩ:{1wӶ NMjgTML>)Dv4!o<"y"K.^sQ?Huy!R^*:kF_<# gfvx[ T]|][b\"T_qxTܟ;SDwq5X2JO~ 1s8ah8ɢ.=Voӽ -\[P1TZ!3_,9-1YG{+C|youyfso΢늽DõZ.M9&Nǻb2Z#n͚'V0.1 <eD{ãk[ĩeWd O!TomkT))Ŗ扻h1ⷌU;Seiȱ7G ?3oD|"C6DWYdiU;0_e{>?I|TѺQx~4lS~~O{ߞ,|ѼAwmp墸'fpKڑ^μ81NV!e-fS>gڀ;D]yr-Cz/{_<ܴ~Ek>oC M/9|W $z)u!3ϒ)Ar~mMWّebnE)=5[14JVA> ?oNb;7OH9SѼH2SDITqϰyz#)%))RX|x-tC1 #nPHk߿\d~ţdv~ g«sH[bԵH On0! tC۲:s]uWv{:e;j8}0Xxx3% /+E=5 <-knosJ!k/欹h&N;O?9.i"fk;UtY' <~ye~3swv{ UO(b|5쩲L6ˆK!5V}g}?ʛp+#< U|;~1D0#NWnڊs+K5-qF[rM<4&3E%U]Nu25g"Dʬ.Fk/W%u-@ϊ~*ƕEăA?$ZqxsJTA(ɹq6 u-Fx@'g!3r?h 5-?ᣔ>&T&L&\=3ͺTh]ϗa\SS1uxLa7_qqepBnOH]77ق,J>eF8rf||\"~nNM}p[R٦沆RWybDt1饝ڞ=יS-g̛!zt}rц8_Z璊,597'9+Ekm_euz$Rs t:ܸղ&]hnX]uz2jkc]h)^_)$<#uC*ţE3⡶ʾROW;sB]DuVj[}&Nj=W[d"qf&kBV?X}^*I5HjB;b]7f/n+2K۵[rHWugttG/}DlQ>+}U~ĚaiqH-uRQ}pv.a>:Cwׄ_vs*5UoG*qlR s!qh.M&S|yrglAY'Mo]uVT駫TՃZu[+,qs59j5kwwf;QWw1{WJ]#/ ~W)/)P͉-zGvdy\GnC۲m[}Tٵځ8+jk|8u ykL%Z;QMĵl>㉺=ĵS~)RoyFYR s̀ usDLX<+uW T]ZwyusL\Q_e-6]?@Pn5ݯ3ŖxGs#ʬsk _MG+l5\z3D-uR"<5 UkdlW:) u]zx{WD0}bl\sۜĝw R'&:;}apח |9DjN.Yj6Yr`9*_cjݱ~ /=*F{"(۟QpViq97C|yZev2Cρ;G-_ -ژdcogkdb T'5ExeɼESܖ3s_i欎!3]B=XzͿEһ^1n# yjO[9-uvZQω1n Q>4x-;gv3M*4j4B.!nEOO:3wq薸&2|{CUv=__.p_E0Zuk/{xu {m95GdĽZ(D}F:\ijӀybSCg'fϝ)FkG)]oU2h].:˭{쮎,_5pzCj{sKB7/JX50jD׉"r@Fiͮvf8*e\Cݒ ;?so{rs6ҔH]珎Jz}moٓ|‚W|9=§8;w+%Mn4u\&1U1dZ[J%ϖZ>Ț/FD(]'e",J>_j v4^qM5 2dN>֡ֈʮUf6GZ}/[9/ϴhՒE㤮FD#cx[H%Z_PSnzI~vCyIy>#Hntc!&hAݮt4WehL_OԅƁ/}٫ʣQMS~3},j@z﯒,*u-թqMD;ckxh9oeyg>+Nh%7zb_xG!+uoŮ~˜;Eϻ=P/yo[+#<6s[!W{?8nIߵi[LVFzmJ*u-UEx)5C L3obg)(FNzpY~0sg:3Lk8% (2>8'G ,6t]Q̛B^Y+@Gx-wgõ}̙G!GخMBԧV xgM۞5juŖvS)rۋmߙ)dkݽߜ2.;_<ƙ +wk4/s?:]Oym{ \]Q#n-)Mc%{dPh{565|wUX)zk_*mDk,F0͉T]rwAiW9gUKNoZ'Pus2}~O{${Wv-Bn8`kB^᯼pk#<2ڂD~ks_?oU eBC"f P]vŚ,_mYg<7ٻO䘚Eq=p*[BK%gǭJԏ8kjjZGht\|h|hXU ujyqe27):G wo }c~W{l}bflH2 aFkԍ/Ykj7 [*?w>x">cBsmg>'M۞=Gqm? ;WgffW;Snfͮ:Q^ gwݿN7MwCo{%hzu= t?VիPuSMn0_ kvcT tR=ÿM( \?G)cc/V^ '^O ؒlžw^<ES?~UGt@Ex!qnؚRkƌ9%0g>[d [FS_>V:ZG*4+^h߽n;]QPPˋ~ux{l`I]WEs"@o9,u=&s5UmEg_wʌ~8]X`[ٚu;6nB&Uv=@ikgt&gVm;E쓺w8_`竻w+kaʜ#b{4 U}yLmĽ[ }+ZeЧϵ ~?P-/zD}wv?8R{r߱Jr_K\3G!+WdvjݓXÚ|S?Gޞ[b }`+NN-}*B.]7;R/HuU5Ex<:1?]|d~'u=Z_ܓ]ÞWH[c,I>U`IUþN//+N(t{w psTȚaR&Wl:8XUBޘ$`J.H]p#RB-P؎WJ]Ke [V ε*_6PE <@棔wH-5K:^IԵ7(D}&\W:UH]GeJ-*u F?,1Q:W <@Ln8}au89e~IDATA_I]Ce][jH]ÿUKpsT:~۰!0HZQ;4 Т9*Qw~w%5,u-7˭k룐 53r>6 u´J1crͨ&KOUE+cZ R]WUd{[ /ɩR_lN"O:W!<M(d Mݖ=SGh9!N2Եmۛ^yEPkF׭1duDxJ3|N!3:/\K4c{bOX3(j|ٱǥ~%Y)k܁Uf\7v0rC-ɆطAܚO{b?}z{7J<4#c eC׉ndcoW8TsZ|\o+׽U4 V͛R3dՒ7dvwS+0wHnQ7PИ>e__)$,;ǮE&[@yWzA CjHG "D[3Κ}pfwW{~KسaW TusmCZ\qװ_sNN[wI?|3kT48nI0 V TM@5"N%i199999999999999999999999999999999999g4ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{=y<`O'ؓs9{4XlIENDB`jgrapht-jgrapht-1.5.1/etc/logo/jgrapht-logo-transparent.psd000066400000000000000000021724371402514743400240150ustar00rootroot000000000000008BPS*8BIM++8BIMFCF?W:0:0:08BIMnorm8BIMluni ,,55210/.0-.-.-,--,-.,...-.-0.5799:;<;BF;;AJFLOCAB@B@BBAABCAOOLLGTJJIEC@@A<A>DEGFHHGILTQNSORQT\[\^]\]]^kiXWUWUYYW]hcmha[eh_XXVSUSWTSQRTUVUX`YYWblnqligi|nfdYY^_MMLMG?C>>?>>@ADHHKG=@=>?>@DGMMOFOJJIKPLJGGK??@=@S686E?6757O;<<>;>:?><>>@?@CKDBA@=><?=?>?@?D?6556465665>;\ZTbYUUTVUTWOQOONPONON^QPQPPPORRSPPRSRW]cUTSRRRSRRRRSRRQSRRRsommkihgfigfiigdhij|}zy{y|~|}{||~ztsssvrrr}{|{zsjkjs{|}{y{y|y{}yyy|{xwxxvxvvxvvwwvvwwwwxxxxxyxz||~{x{llmjmnokmnmpptty878888::<:008:87667555545559FC@>@>>?;;>;<;<;<N6677?=...H5577868=@CC?@?BE?IFD@=998887:CKEDA>888989::99:99:@ABQUWtpd``bfmcWTQR[PPNPMLJJMMOOOKQQV\]_]hkcgYSSRWUWUSjia]]]YZ\_[WPPQMQSUQFJFFIEGC?=A=@CBFHNOTLMMNOEBEAAA@DCEDDERPHKE<<HF??=<<=;50/1001/0...//.1.00/202232486*, Exq9aQ p )Wk{mYCZ7aҒH0o c_%FwY a#jA9&h U]DBj"ȁ&(,O1x%=sNcoZO.N>>-p" Q}с mE[9[Ye06Im[7 ~F)pO#,#*#3WBkB wG<ڄ)mg[Kk{yfEbLSo n+B 3S.1&R/6#%Ysf}i˄96 ? q!GI!cq[ -Qg}{kO ;6v_o@a^ M>V; 2,&e(| 6 HOI\qs&u }]'Hkq5&4YJ3GW76&rwa&79 1KO! | ?q{b)_0 ?f aPws+}iccVP }NS -+ ?(~k+*P۷7N ;yƶsB"nKBkyEG V_;B ]N?s"#z b @HH  XB)^ZUm:%57f2;7 fG|Sw1 OozLpuW4"9RyYO.+ [ˁ-.-$ہSW>ke})rhWB@ց{mz@ D({c7Eʴ!)ۂ*9u18=u@U w$z]q GBk`J."J2]   }kkQa]N0)E#y[3D 79  pcOӶ݄+D~o &z)ky#[skykl|zZ\^6-_/%6'=STI#VqW!KG,.#Q [9/'63K{yiކ-P o =%y{"eO&E?)0g-mxomHa__Խ&;aQEXy) -AVUC2 IUGEWggWA< YG6 kwDV<g;%t P w/]~ ;Qg*{# ak /=V9oc/ Ic!NeL WW <a'reKB;.1-@qi.u=5LC3^{5)19~)@" %aζa9}# UWku LDGqvV ;gkI Sq!wmUe NYmB 36 eW B_ fPk^4`sKD=gy+N_mcQ3{"'?6kyE|o}(73JfN,E }ES&)"^6SvS#C[Df c+IsCaq 5@)ɁrCN&ŴԵ=@U5KU.;c.XC45؁,kSNe"#rp)a#tOP *naƷuin2ݹ~t S?? tD($[.ʄ?k Xm 'gjp:YE֐K XRW cs+16WE&U/$v457 aҿ]%J4e0S_c v6{RX$soҊCq %ȿo'=I۷gk$IHC^nsޖO gf ccw{, K{36=QSCw Se"i[a m##1d34 0#?̆?l9 cw$S2,]_εCUsc +O3{v #%Ci yS+G/w{ cCZ8C$:f+q`13? 3cwcE) co B<iG'<?!s QO 0oԺA8gsae _3?3/-K;33?S !aS'uЄ9[X" s s_7McxkS;1w8(R _3Wu)$%=P?S% (Ch%;ME7! sr݆# qk ~eツ- _3 Y²c/KqhGk?Si+Z9EBj{w {jC u& [7~WJ2P;W7 ցI|{wWy X7g l`I76WGehw4e7Om6ȁSWikuwZ*o7@*ZW|/Rix=Vk M C+ak +ցO:*#;M8On!meY }Ug" #<;# U fH!-ށtL7g{hQJL?RU 6o=lY3u?_I?ЁcTBҐg1s\2\|m/6-b $q+-4MĒv(-gÁ C|$ V{Y'-%" Ym$++0Y >U'uq$Pr%-{ i ys NVL6~{zuNང?  cCA $BC4 yQo)Ĝ 9D? #U[!Y   m|Xg 0j  'h]@ _ū;'M!7531/-+)'%#sFba  _ Ł# Qɸd!Aށ9ay<mDuGԁ}j{zuVq;=?ACEGIKMOr*wkE?'1F>ہL F ޭ 6/tsj`@pˁΫE%M-}[0" *cˁP-B(TobL(zik{: dEJ >$D@sT 7+n$6%q]YĒI_ ª{S-AUO?#  [ WkciO!4 q<U@.)M?9={O2=Qeā7r f$LNZ:ҁ%X\O5E&G0kkY.z xn$K {Y gӂ%kaQ*EYleS8 ,\ -PL%3?ow=20H.A3ȁvļ'ā"P<X0 `+ӹf=%o %A[t!o=A@1c23GA+y =b#U;)s26=b#^  LwĠm; )߇2]=b# eG;]„2C=b# ) Lo2 =b# (^_߁2=b#?'Ɂ2#=b#_8Kk2=b#1U2v=b#+,ҁ2v#=b#쾜w5 '߁2=b#+2^=b#P'2Ձ=b#z ́2tB=b#rA$2=b#kxd2=b#c2T=b#\/2H=b#T[CX2g=b#M\ׁ2v=b#Et^2~=b#> 綇o_M;) n=T6Vc]=O/{_ w9=O 'xU>==O J#~Xʁ=Os=O%#R=O- wOi=O6o.=O>=OF 2)4=ON K`xt8=OW6^=O_ܧRhc=OgԨuyJ=OoͪI&=Owū -- _=OFGG܁sU=O1^waa .iкS=OL?Oڊ3{Q=Om&k`+=O=O =OƁ=O.]=OH?=Ozh%pr=Os4Sa=OkP6P=Odm@=O\)]:=OUB=߁:=OM`%:=OF:=O >:=O7l:=O/$[5P:=O%(<<R3:=O- Z$n: $'Fspg_WOG57GOWakqsqkc[QG9sog_WOF1O5}:+[}ȩ{IK}αS!Ģq?O=܁:eg#mw-ޣ[OE :>֍3;ڪFʀ"ON!Z:{e kҤySOV8<::#sO^S$i:*y  aOfu6L:R]s8OnS0:M Ow }o: :DX#OZ:ӹTb6O3<ف:)j{GON$:9~WOq:,gx@O:HVu(O }f: ʴ.:SOZ8I: O.<T-:L[OH"q:5EcOh:߁OՁ:/]n2O }:ƾݮg );K]o ƾخºO{Y:5_00yctU'  ,[=:k7Os)::Q G K=cOlB"c: kiEV[_v=Oda9F:i SS=zO]U):E:= ۦO U}r :D[!j{>:={>ONW:;#=YOF&:ҁ:. /=HЧO$?>":%D9CT%t=aO,7\:+w kmA4=\O40}|:\=O=({_:\!S .=5ϨOE!W:C:\+<_I\=OM"9W&:\B\Yj=&-OU9"s :\p eM=[O] U:\-=HOfyρ:\́K=OnyJ:\w P5=OvVc:\26=LשO~M9 U:\ 0ĞO= Ok3"1:\'`8Ioi= OSDOb':\=2J[^A{=O> ql7:\FURc0=o%O }yd!:\N-Zk =H.O(V6:\Vbs=.6O39 :\^ j{=>O6 7:\frٜ=FOʁ:\kuȜ=IO>:Uk؁u=IOy:nkǁu=IO+_R:nku{=IOnJ:nkuz=IO+ /G]s:nkuz=IOZ 1G_w:nkuz=IO 3KawӁ:nkuz=IOF 5Kc{:nkuz=IOG 7Og}:nkuz=IOg:nkuz=IO؁:nkuz=IO:nkuz=IO:nkuz=IOªgK:nkuz=IO {eO7<:nkuz=IO ־{cK5:nkuz=IO ҼwaK3u3:nk ?Ukuz=IO Һw_G1 :nk/muz=IO} θs]G/i,:nk_uz=IO<=:nkMuz=IO:nkc֧uz=IOd7:nkLݥuz=IO%F:nkuz=IO6:nkCuz=IO) ~:nkvuz=IObW:nkuz=IO[):nkuz=IO% c :nkfuz=IO7 |G:nk4uz=IOH#::nk ߜuz=IO.ʁ:nkuz=IO 83:nk'uz=IOW4kWuN:nkuz=IOuQO03:nk!¢wi][Cuz=IO9Ɔ-9 с:nkۉW%uz=IO Knu_C ^:nkAuz=IOnU:nkHuz=IO,:nkDuz=IO؁:nk uz=IOi:nk uz=IOl3g\:nkUuz=IO%:nk&uz=IOKށ:nkuz=IO!:nkuz=IOp5`b:nkuz=IO:nk;uz=IO:nkuz=IOU(:nk(uz=IOt9Yi:nk1nuz=IOS:nk;Puz=IOm@nk><u=IO}/)Pnk>(u=IOhRp:ank>u=IOOJrok>s=IOy7[}k>l=IOA6lk>d=IO Kw|k>\=IOu k>T=IOY'k> L3}=IO<k>>`o=IOr-D~*{Qk>;$U=IO oa<k>i ;=IOB nGܔwk8*"=IOx:CT-,k/<g=IOk=f:k'M=IOeƁ8k`V(=IOQ ?gkc=IOJ+xJkϢ 5f=IOk6Q`x}kd 8=IOa ́Y2)VkH; =IOĒc('kX =IOqQgkS=IO/Ôi7fiDkZy>=IO,fE}J#;k*2v=IOV<W  SPkr}=IOj0@~X  k*  P=IO [,C>Uk}a=IOO_  ]wk5#[N=IOUX_)eka y+=IO+gf '9K[m Ⲇo_M;)  /ASc|kY/ :=IOU=2`Qk7G3#+ ,3;CTq1=IOG؁kg !l|Ι=IOrXfkmM1c Κ,=IOk k IJӎZΚ=IO^:\kjΛ=IO*h0dk݄Μ7=IOT>kJPΜ=IO~T ́ekuΝ=IO/k ۈΞ=IO.G+k.Ο=IOA?'߁kBΠ=IO(iQ.ҁkYΡ =IOS?J  kMkΣ=IO}5kk5,ΤF=IO ;'ɉGkΥ=IOs_ߍke*ΧB=IOXo0k:ΩU=IO'jxm]•<k"7άK=IOQ@(9)ߚyksԧkή~=IO|W}3)sw/k4Yβg=IOKʘg7 1cҢsC kNᲂS#η麊[+=IOUƮ {aE) %A]x mS9I| 'A]w̮ ~mQ7® qW=!*2x6*&k"PACzeS<H $lm3OBA|y!Uoq[? #J]ncM8#m YȆ2MCTx {+"|ÁCC"n.LDuv1?āQ6Ɓ!o$KEu 9ˁi-%sq/yM?ac /keYYFF6W 9;Y BGE5=0  [ӁysV]+  K^e[66сr#(vnsYjh4ہ OMKIGECA?%2"5J%c( {+)݁/oU.|m2e Mu²i4Tu5`<*9\с=X#%')+-/1357ue@RvEN>9UHK<LFmd}QB-U?`Y=FY:-_7c458Q=Qg0\SYem-OE g+'r*+# ]v& t X }$81DP"/!C;i MԔIJ18w Ctj3R?? _K!oB gq]-3 M _.c  /^*# YU ) +leA<MI/A/ }5E׌A݁}q}f:< {z?$)J=mLفl2h F2a$/(فW,l'[R  wg܁6 (Wȁdgtwq4K( zہGd g /r ;RB: [s}j{SޖQ0G_voW?#,4xF{3eIM#)9'N[g ocWA΄9O) ̆? S kβO !C*by Yg!qI?' 9WunIx" }k>֎I @M_w+s+LOWO72S):26\8nQ]7NS K0,H g_NKsg (x;ΈC4)%GAP.,[k#\DJ'sPb/wڒr wj  Oݾ9xW+m]:7O*#k6n*ø g2Cf2< `y־5_g }Z7,GQAw*?SEP(øG'+s]w ۸VRGaW8[gIW%e1dzm\$xUx<nc2$Fg ބ* uIowgTTJ-gGI wY bA)V KX>25i2;/f@e/:+߆-u%KñP0yeMw&i -po0ee[ X:  ?$`E%qM 4OT#.#}= sC.1/=Tul H}#e={"3&o:} -b{sT(Im?0MYy> W] (q ?̱{A L_C]cOj- )sF%];LA' oA/ 7#{1gI36$/}#NDZ^;oDo*2#I  G_ 6bсbUQ5_)["aC|!_Y:m3$h nB"{&!|=7"q -O_k_O0-q_5 #=O[K5!{D Q uw!k}9a{6#^57GSaWiUSEDe0uK!SU+_5&6 Ewy0kڀ'SsS@81OysPm!pmn+eOOy'D?Cwq7UD[ >  EG40RWIQ9 kc"05 ]ۧ},_j'kG v \w M|@? *)`cd7Vcs/ C9OP=O@ݲ[@ [,}]Ԣ-i^$'qhbz[[?  fH Xs9 RǨ)Ákum72Zv3!]MMFaS^? 7ut2:mgLR}.63Hs]( Y=," scn#g_ CKma]wm@7L>&~y* 5 &]ɸW.f ?R q{ N{(f[].> 'ŁI0Oq56,#,Q ;J&; OsU#bMFVrw_t1oq$(O[-H1I#qs*6##FW%1& B>3j SgWog*aB[w }(9 <GO!"3+ ? 9bsq[Ac#)Y}i"}ʌ 7f)PO^ Icψ?-5ȑm[+6+7YmOE k{Z3B ; /[qs]/# #, ]a-  >qokOZ2Ya5-1Es8%,2#u$KPi3aCW g m{ %?*e:{Q?soXGN $u΁'wR}!&02D  )'/yHD#K@P=i,.vٌ/  3OeyscA43#- []) ""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$((((((((,((****.**********-,,........**********........22000000444444444484444444448884446622222222222222222222227;666668<8442322....*********+//...******+++100.........**.*****((((,((((((*****************,***************((((((((((**30052222222222222222222222222222222222266444444444444444444488888888:;::::::>>?>>>BBBBBBBBBDDDDDDDDDDDDDE@@@@@@@@@@@@@@>>>>>@@BBBBBBBBBBBBBBBFFFFFEDBBBBBBBBBBBBBBBBBBBBBBBBBBDDEDDDDDDDDDDDB@@@@>>><<<<<<=:::::::::++++++****(((((((((((((((((******************((((((($$$$&&&&&&&&********-,,-(((((())+00...*********+++****..0026;66444940000000000000000000000444448662222222229444444444500000023.......*******-,,000000000,,,****,,,,,,1,,,,**********&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&""  ""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$((((((((,((****.**********-,,........**********........22000000444444444484444444448884446622222222222222222222227;666668<8442322....*********+//...******+++100.........**.*****((((,((((((*****************,***************((((((((((**30052222222222222222222222222222222222266444444444444444444488888888:;::::::>>?>>>BBBBBBBBBDDDDDDDDDDDDDE@@@@@@@@@@@@@@>>>>>@@BBBBBBBBBBBBBBBFFFFFEDBBBBBBBBBBBBBBBBBBBBBBBBBBDDEDDDDDDDDDDDB@@@@>>><<<<<<=:::::::::++++++****(((((((((((((((((******************((((((($$$$&&&&&&&&********-,,-(((((())+00...*********+++****..0026;66444940000000000000000000000444448662222222229444444444500000023.......*******-,,000000000,,,****,,,,,,1,,,,**********&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"" 쓷ꓻ瓾쓁铁擁㓁ᓁޓߓޓޓܓܓܓۓړړړؓړؓړדٓ֓ٓ֓ؓՓؓԓٓԓדԓՓԓӓԓғԓГԓϓԓ͓ԓ̓ՓʓԓӓғғГГϓϓϓߓ듖듔듒듑듐퓁듎铁꓎瓁꓌䓁듋ᓁ듊듈ޓ듇ݓ듅ۓ꓅ړ꓃ٓ꓂ؓ듁ד듁ד듁֓듁Փ꓁Փ꓁Փ꓁Փ듁ԓ듁ԓϓԓГԓГՓѓؓԓғؓӓԓؓѓՓٓГדٓϓד͓ٓדړ͓֓ۓ˓ՓܓʓՓۓɓՓۓݓ퓁Փۓߓ퓁ԓٓ퓁ԓٓ쓁ԓٓ퓁ԓٓ퓁ӓٓ퓁ғٓ퓁ϓٓ퓁̓ٓ퓁ɓٓ퓁Ɠٓ쓁ēۓ퓁ۓ퓁ۓ퓁ݓ퓁ۓⓖݓ퓁ܓⓙߓ퓁ޓⓝ퓁ߓⓠ퓁ᓤ퓁ⓦ퓁⓪퓁⓮퓁⓱⓵퓁ᓸ퓁ᓼ퓁⓾퓁퓁퓁퓁퓁퓁퓁퓁쓁꓁듁ד쓪דꓫؓ瓮ٓ䓯ٓ㓱ړēᓲܓƓܓɓޓݓ˓ޓۓΓܓۓГܓۓޓӓۓٓޓՓړٓܓؓړٓܓٓړٓۓٓٓٓړٓՓٓړדғٓړՓΓٓٓӓ˓ٓٓѓǓٓٓϓÓۓ͓ؓۓٓ˓ۓؓɓݓדȓݓԓƓܓٓߓѓޓړᓁϓޓᓁ͓㓁ʓ品ȓ“铁ƓƓ듁œȓ̓瓥ϓ擡ғ擜Փ瓁ٓ瓁ٓȓٓɓٓ㓩̓ٓ㓧͓ٓ㓤ϓٓ㓣ѓړœӓۓǓՓۓȓדړʓ͓ٓٓٓؓΓٓؓГٓדӓٓ֓ՓٓՓ֓ٓՓ֓ٓ֓ٓԓۓԓۓԓۓԓܓԓۓԓۓԓړԓړԓړՓړ֓ړ֓ړ֓퓁ؓؓړړܓ퓁ޓ铁ޓ瓁䓁ⓖᓁ㓘ߓ䓙듢ޓ哛ؓݓ瓜ٓۓٓٓړٓړדړדܓדܓՓܓܓܓۓۓⓁۓᓁۓᓁۓᓁۓۓᓁۓᓁٓՓٓ֓ޓړדݓۓדܓۓٓܓܓٓۓݓۓړݓۓړߓݓٓᓦߓٓⓤᓁٓ㓣㓁ٓ員品ٓ铞瓁ٓ득铁ٓ꓁ړ퓁ړړۓܓܓޓߓⓁߓ㓁ޓ品ܓ蓁ܓ꓁ۓړړړٓٓٓٓٓٓٓړړړܓܓݓޓ쓁铁ᓴ品铁⓳㓁瓁䓱Ⓛ品擯Ⓛ㓁铮ᓁᓁ쓫ᓁߓߓޓߓݓݓܓݓۓۓۓۓړړٓٓٓٓٓٓٓٓٓٓٓٓٓٓٓٓٓٓړړۓۓۓۓܓۓݓܓޓۓߓۓᓁۓ㓁ړ䓁ړ瓁ٓ铁ٓ쓁듁퓁듁蓁擁䓁דⓁדדߓדޓדݓדܓٓۓٓړٓؓד͓ד“֓ՓՓ퓁퓁퓁ړۓܓݓȓޓӓߓ֓דⓁד䓁ٓ擁ٓ蓁ۓ쓁ܓݓߓᓪ㓩ߓޓݓܓۓۓړٓٓٓٓٓٓٓٓٓړۓۓܓݓޓߓ듁듁铁瓁品㓄ޓݓܓܓۓړړٓٓٓٓٓٓٓٓړړۓܓܓݓߓᓁ㓁品瓁꓁퓁퓁꓁蓁品㓁Ⓛߓޓݓܓۓړړٓٓꓛ쓁ٓ蓞蓁ٓ䓡擁ٓⓣ䓁ٓⓁٓޓٓݓړܓޓړړܓۓړܓۓؓܓܓؓړݓ֓ړޓ֓ړ֓ړړړړړړᓁۓⓁۓۓۓܓݓ֓ܓ֓ۓ֓ۓؓٓؓٓړٓۓؓܓؓޓⓛ䓕擔ꓒړٓٓٓٓړړۓۓܓۓړ꓁ړ蓁ړ֓ٓ֓ٓ֓ٓדٓדٓؓٓٓٓړדۓՓړӓړѓړϓړΓړ˓ؓʓؓɓٓ㓁ړ品֓品ғ品ϓ퓦瓁˓蓁ɓꓲœ蓳Ó擴䓵⓶ߓٓݓߓړݓƓޓǓܓɓܓ˓ܓ͓ړÓϓړƓѓړʓӓړΓՓٓѓדٓՓٓؓؓٓٓٓٓړړٓړړ֓ړۓӓړۓГړܓΓܓݓ˓ܓߓɓܓߓǓۓēړ“ٓᓿؓؓד֓蓁品ꓔ品蓖䓁擗㓁䓙Ⓛⓛޓޓܓޓۓܓܓړ“ړēړǓړɓړ˓ؓΓؓѓؓԓؓԓړԓړԓړԓߓړԓɓړԓɓܓՓʓۓ֓˓ۓ֓̓ړ֓Γړؓϓؓ֓ГؓՓғؓӓӓؓӓՓѓԓГԓϓԓΓԓ擁ԓ蓁ԓ쓁ՓՓՓ듁֓꓁֓꓁ד꓁ؓ듁ٓ듁ړ듁ۓ듁ܓ듁ޓ꓁꓁⓵듁䓳듁擱듁铭擏듁퓪哐듁䓑꓁ⓓ꓁ⓓ듁ᓕ듁Γ듁Γ듁Γ듁ϓ듁Г듁ѓ듁ѓ듁ғ듁ӓɓԓ˓Փ̓ՓΓՓΓԓГԓғԓӓԓՓԓדՓٓՓٓՓٓՓٓ֓ٓדٓדٓٓۓٓۓۓۓܓݓݓݓߓߓߓᓁ㓁品쓿瓁铁퓁 ""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$((((((((,((****.**********-,,........**********........22000000444444444484444444448884446622222222222222222222227;666668<8442322....*********+//...******+++100.........**.*****((((,((((((*****************,***************((((((((((**30052222222222222222222222222222222222266444444444444444444488888888:;::::::>>?>>>BBBBBBBBBDDDDDDDDDDDDDE@@@@@@@@@@@@@@>>>>>@@BBBBBBBBBBBBBBBFFFFFEDBBBBBBBBBBBBBBBBBBBBBBBBBBDDEDDDDDDDDDDDB@@@@>>><<<<<<=:::::::::++++++****(((((((((((((((((******************((((((($$$$&&&&&&&&********-,,-(((((())+00...*********+++****..0026;66444940000000000000000000000444448662222222229444444444500000023.......*******-,,000000000,,,****,,,,,,1,,,,**********&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"" ݁݁ݷ݁ݻ݁ݾ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݽ݁ݖ݁ݔ݁ݒݡ݁ݑݦ݁ݐݫ݁ݎݮ݁ݎݰ݁݌ݲ݁݋ݶ݁݊ݷ݈݁ݹ݁݇ݻ݁݅ݽ݁݅ݿ݂݁݃݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁݁ݼ݁݁ݺ݁݁ݹ݁݁ݷ݁݁ݶ݁݁ݳ݁݁ݲ݁݁ݰ݁݁ݮ݁݁ݬ݁݁ݪ݁݁ݪ݁݁ݩ݁݁ݨ݁݁ݩ݁݁ݨ݁݁ݨ݁݁ݧ݁݁ݧ݁݁ݦ݁݁ݥ݁݁ݥ݁݁ݤ݁݅ݤ݈݁ݢ݁݌ݢ݁ݎݡ݁ݒݟ݁ݖݟ݁ݙݝ݁ݝݝ݁ݠݛ݁ݤݙ݁ݦݘ݁ݪݖ݁ݮݔ݁ݱݒ݁ݵݐ݁ݸ݋݁ݼ݁݁ݾ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݤ݁ݧݿ݁ݪݾ݁ݫݼ݁ݮݺ݁ݿݯݹ݁ݽݱݸ݁ݻݲݶ݁ݹݲݵ݁ݶݴݴ݁ݴݴݵ݁ݲݶݶ݁ݰݶݶ݁ݮݸݷ݁ݬݸݷ݁ݪݹݷ݁ݩݹݸ݁ݪݻݸ݁ݫݿݸ݁ݭݸ݁ݰݸ݁ݴݸ݁ݷݹ݁ݹݸ݁ݽݸ݁ݸ݁ݷ݁ݸ݁ݷ݁ݶ݁ݶ݁ݵ݁ݵ݁ݳ݁ݲ݁ݰ݁ݮ݁ݥݪ݁ݡ݁ݜ݁݁݁݁݁݁݁݁݁ݩ݁݁ݧ݁݁ݤ݁݁ݣ݁݁ݠ݁݁ݝ݁݁ݜ݁݁ݙ݁݁ݖ݁݁ݔ݁݁ݑ݁݁ݎ݁݁݌݁݁݊݁݁݊݁݁݊݁݁݊݁݁݊݁݁݊݁݁݊݁݁݋݁݁݌݁݁ݍ݁݁ݎ݁݁ݏ݁݁ݐ݁݁ݐ݁݁ݑ݁݁ݒ݁݁ݒ݁݁ݓ݁݁ݓ݁݉݁ݔ݁ݍ݁ݔ݁ݐ݁ݔ݁ݒ݁ݕ݁ݓ݁ݖ݁ݖ݁ݖ݁ݜ݁ݘ݁ݟ݁ݙ݁ݢ݁ݛ݁ݣ݁ݜ݁ݥ݁ݞ݁ݧ݁ݠ݁ݨ݁ݡ݁ݪ݁ݣ݁ݫ݁ݤ݁ݫ݁ݦ݁ݭ݁ݨ݁݁ݩ݁݁ݫ݁݁ݬ݁݁ݼ݁݁ݿ݁݁݁݁݁݁݁݁݁݁݁݁݁ݯ݁݁ݯ݁݁ݭ݁݁ݭ݁݁ݬ݁݁ݫ݁݁ݪ݁݁ݪ݁݁ݨ݁݁ݦ݁݁ݤ݁݁ݣ݁݁ݡ݁݁ݞ݁݁ݝ݁݁ݜ݁݁ݚ݁ݺ݁ݖ݁ݽ݁݁ݿ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁݁ݿ݁݁ݾ݁݁ݽ݁݁ݼ݁݁ݻ݁݁ݻ݁݁ݹ݁݁ݹ݁݁ݸ݁݁ݶ݁݁ݵ݁݁ݴ݁݁ݳ݁݁ݱ݁݁ݯ݁݁ݮ݁݁ݫ݁݁ݩ݁݁ݧ݁݁ݢ݁݁ݣ݁݁ݣ݁݁ݤ݁݁ݤ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݤ݁݁ݤ݁݁ݤ݁݁ݣ݁݁ݤ݁݁ݥ݁݁ݦ݁݁ݧ݁݁ݨ݁݁ݪ݁݁ݫ݁݁ݬ݁݁ݭ݁݁ݮ݁ݯ݁ݱ݁ݲ݁ݳ݁ݴ݁ݵ݁ݶ݁ݸ݁ݹ݁ݺ݁ݻ݁ݼ݁ݽ݁ݾ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݽ݁ݼ݁ݻ݁ݹ݁ݸ݁ݵ݁ݳ݁ݰ݁ݫ݁ݪ݁ݩ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݾ݁ݾ݁ݾ݁ݽ݁ݽ݁ݽ݁ݾ݁ݿ݄ݿݾݾݽ݁ݼ݁ݼ݁ݻ݁ݹ݁ݸ݁ݶ݁ݵ݁ݳ݁ݯ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݌݁݁ݒ݁݁ݗ݁݁ݛ݁݁ݞ݁݁ݡ݁݁ݣ݁݁ݥ݁݁ݧ݁݁ݨ݁݁ݩ݁݁ݫ݁݁ݫ݁݁ݬ݁݁ݭ݁݁ݮ݁݁ݮ݁݁ݮ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݭ݁݁ݬ݁݁ݫ݁݁ݪ݁݁ݩ݁݁ݧ݁݁ݥ݁݁ݤ݁݁ݡ݁݁ݟ݁݁ݛ݁݁ݕ݁݁ݔ݁݁ݒ݁݁ݐ݁݁ݍ݈݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݛ݁ݢ݁ݪݦ݁ݯ݁ݱ݁ݲ݁ݳ݁ݴ݁ݵ݁ݶ݁ݷ݁ݶ݁ݷ݁ݸ݁ݸݿ݁ݸݼ݁ݸݺ݁ݸݶ݁ݸݳ݁ݸݱ݁ݸݮ݁ݷݿݫ݁ݸݻݫ݁ݷݺݪ݁ݷݹݩ݁ݷݹݫ݁ݷݷݮ݁ݶݷݰ݁ݶݶݲ݁ݵݵݵ݁ݵݳݶ݁ݵݳݸ݁ݶݱݻ݁ݸݰݽ݁ݹݯݿ݁ݻݭ݁ݼݬ݁ݽݩ݁ݿݧ݁ݣ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁݁ݻ݁݌ݸ݁ݏݴ݁ݒݲ݁ݔݮ݁ݖݪ݁ݗݧ݁ݙݣ݁ݛݠ݁ݜݜ݁ݞݚ݁ݞݖ݁ݠݒ݁ݠݏ݁ݢ݋݁ݣ݈݁ݣ݄݁ݤ݂݁ݤ݁݁ݦ݁݁ݧ݁݁ݧ݁݁ݨ݁݁ݧ݁݁ݨ݁݁ݨ݁݁ݩ݁݁ݪ݁݁ݩ݁݁ݫ݁݁ݬ݁݁ݮ݁݁ݯ݁݁ݲ݁݁ݴ݁݁ݵ݁݁ݷ݁݁ݸ݁݁ݻ݁݁ݽ݁݁ݾ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݃݁ݿ݄݁ݽ݆݁ݼ݇݁ݺ݉݁ݷ݊݁ݵ݊݁ݳ݌݁ݱݍ݁ݭݏ݁ݪݐ݁ݧݑ݁ݡݓ݁ݓ݁ݕ݁ݽ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁ݻ݁ݶ݁݁݁ ""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$((((((((,((****.**********-,,........**********........22000000444444444484444444448884446622222222222222222222227;666668<8442322....*********+//...******+++100.........**.*****((((,((((((*****************,***************((((((((((**30052222222222222222222222222222222222266444444444444444444488888888:;::::::>>?>>>BBBBBBBBBDDDDDDDDDDDDDE@@@@@@@@@@@@@@>>>>>@@BBBBBBBBBBBBBBBFFFFFEDBBBBBBBBBBBBBBBBBBBBBBBBBBDDEDDDDDDDDDDDB@@@@>>><<<<<<=:::::::::++++++****(((((((((((((((((******************((((((($$$$&&&&&&&&********-,,-(((((())+00...*********+++****..0026;66444940000000000000000000000444448662222222229444444444500000023.......*******-,,000000000,,,****,,,,,,1,,,,**********&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"" ""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$((((((((,((****.**********-,,........**********........22000000444444444484444444448884446622222222222222222222227;666668<8442322....*********+//...******+++100.........**.*****((((,((((((*****************,***************((((((((((**30052222222222222222222222222222222222266444444444444444444488888888:;::::::>>?>>>BBBBBBBBBDDDDDDDDDDDDDE@@@@@@@@@@@@@@>>>>>@@BBBBBBBBBBBBBBBFFFFFEDBBBBBBBBBBBBBBBBBBBBBBBBBBDDEDDDDDDDDDDDB@@@@>>><<<<<<=:::::::::++++++****(((((((((((((((((******************((((((($$$$&&&&&&&&********-,,-(((((())+00...*********+++****..0026;66444940000000000000000000000444448662222222229444444444500000023.......*******-,,000000000,,,****,,,,,,1,,,,**********&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"" ""$$$$$$$$$$$$$$$$$$$$$$$$$$$$$((((((((,((****.**********-,,........**********........22000000444444444484444444448884446622222222222222222222227;666668<8442322....*********+//...******+++100.........**.*****((((,((((((*****************,***************((((((((((**30052222222222222222222222222222222222266444444444444444444488888888:;::::::>>?>>>BBBBBBBBBDDDDDDDDDDDDDE@@@@@@@@@@@@@@>>>>>@@BBBBBBBBBBBBBBBFFFFFEDBBBBBBBBBBBBBBBBBBBBBBBBBBDDEDDDDDDDDDDDB@@@@>>><<<<<<=:::::::::++++++****(((((((((((((((((******************((((((($$$$&&&&&&&&********-,,-(((((())+00...*********+++****..0026;66444940000000000000000000000444448662222222229444444444500000023.......*******-,,000000000,,,****,,,,,,1,,,,**********&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"" 쓷ꓻ瓾쓁铁擁㓁ᓁޓߓޓޓܓܓܓۓړړړؓړؓړדٓ֓ٓ֓ؓՓؓԓٓԓדԓՓԓӓԓғԓГԓϓԓ͓ԓ̓ՓʓԓӓғғГГϓϓϓߓ듖듔듒듑듐퓁듎铁꓎瓁꓌䓁듋ᓁ듊듈ޓ듇ݓ듅ۓ꓅ړ꓃ٓ꓂ؓ듁ד듁ד듁֓듁Փ꓁Փ꓁Փ꓁Փ듁ԓ듁ԓϓԓГԓГՓѓؓԓғؓӓԓؓѓՓٓГדٓϓד͓ٓדړ͓֓ۓ˓ՓܓʓՓۓɓՓۓݓ퓁Փۓߓ퓁ԓٓ퓁ԓٓ쓁ԓٓ퓁ԓٓ퓁ӓٓ퓁ғٓ퓁ϓٓ퓁̓ٓ퓁ɓٓ퓁Ɠٓ쓁ēۓ퓁ۓ퓁ۓ퓁ݓ퓁ۓⓖݓ퓁ܓⓙߓ퓁ޓⓝ퓁ߓⓠ퓁ᓤ퓁ⓦ퓁⓪퓁⓮퓁⓱⓵퓁ᓸ퓁ᓼ퓁⓾퓁퓁퓁퓁퓁퓁퓁퓁쓁꓁듁ד쓪דꓫؓ瓮ٓ䓯ٓ㓱ړēᓲܓƓܓɓޓݓ˓ޓۓΓܓۓГܓۓޓӓۓٓޓՓړٓܓؓړٓܓٓړٓۓٓٓٓړٓՓٓړדғٓړՓΓٓٓӓ˓ٓٓѓǓٓٓϓÓۓ͓ؓۓٓ˓ۓؓɓݓדȓݓԓƓܓٓߓѓޓړᓁϓޓᓁ͓㓁ʓ品ȓ“铁ƓƓ듁œȓ̓瓥ϓ擡ғ擜Փ瓁ٓ瓁ٓȓٓɓٓ㓩̓ٓ㓧͓ٓ㓤ϓٓ㓣ѓړœӓۓǓՓۓȓדړʓ͓ٓٓٓؓΓٓؓГٓדӓٓ֓ՓٓՓ֓ٓՓ֓ٓ֓ٓԓۓԓۓԓۓԓܓԓۓԓۓԓړԓړԓړՓړ֓ړ֓ړ֓퓁ؓؓړړܓ퓁ޓ铁ޓ瓁䓁ⓖᓁ㓘ߓ䓙듢ޓ哛ؓݓ瓜ٓۓٓٓړٓړדړדܓדܓՓܓܓܓۓۓⓁۓᓁۓᓁۓᓁۓۓᓁۓᓁٓՓٓ֓ޓړדݓۓדܓۓٓܓܓٓۓݓۓړݓۓړߓݓٓᓦߓٓⓤᓁٓ㓣㓁ٓ員品ٓ铞瓁ٓ득铁ٓ꓁ړ퓁ړړۓܓܓޓߓⓁߓ㓁ޓ品ܓ蓁ܓ꓁ۓړړړٓٓٓٓٓٓٓړړړܓܓݓޓ쓁铁ᓴ品铁⓳㓁瓁䓱Ⓛ品擯Ⓛ㓁铮ᓁᓁ쓫ᓁߓߓޓߓݓݓܓݓۓۓۓۓړړٓٓٓٓٓٓٓٓٓٓٓٓٓٓٓٓٓٓړړۓۓۓۓܓۓݓܓޓۓߓۓᓁۓ㓁ړ䓁ړ瓁ٓ铁ٓ쓁듁퓁듁蓁擁䓁דⓁדדߓדޓדݓדܓٓۓٓړٓؓד͓ד“֓ՓՓ퓁퓁퓁ړۓܓݓȓޓӓߓ֓דⓁד䓁ٓ擁ٓ蓁ۓ쓁ܓݓߓᓪ㓩ߓޓݓܓۓۓړٓٓٓٓٓٓٓٓٓړۓۓܓݓޓߓ듁듁铁瓁品㓄ޓݓܓܓۓړړٓٓٓٓٓٓٓٓړړۓܓܓݓߓᓁ㓁品瓁꓁퓁퓁꓁蓁品㓁Ⓛߓޓݓܓۓړړٓٓꓛ쓁ٓ蓞蓁ٓ䓡擁ٓⓣ䓁ٓⓁٓޓٓݓړܓޓړړܓۓړܓۓؓܓܓؓړݓ֓ړޓ֓ړ֓ړړړړړړᓁۓⓁۓۓۓܓݓ֓ܓ֓ۓ֓ۓؓٓؓٓړٓۓؓܓؓޓⓛ䓕擔ꓒړٓٓٓٓړړۓۓܓۓړ꓁ړ蓁ړ֓ٓ֓ٓ֓ٓדٓדٓؓٓٓٓړדۓՓړӓړѓړϓړΓړ˓ؓʓؓɓٓ㓁ړ品֓品ғ品ϓ퓦瓁˓蓁ɓꓲœ蓳Ó擴䓵⓶ߓٓݓߓړݓƓޓǓܓɓܓ˓ܓ͓ړÓϓړƓѓړʓӓړΓՓٓѓדٓՓٓؓؓٓٓٓٓړړٓړړ֓ړۓӓړۓГړܓΓܓݓ˓ܓߓɓܓߓǓۓēړ“ٓᓿؓؓד֓蓁品ꓔ品蓖䓁擗㓁䓙Ⓛⓛޓޓܓޓۓܓܓړ“ړēړǓړɓړ˓ؓΓؓѓؓԓؓԓړԓړԓړԓߓړԓɓړԓɓܓՓʓۓ֓˓ۓ֓̓ړ֓Γړؓϓؓ֓ГؓՓғؓӓӓؓӓՓѓԓГԓϓԓΓԓ擁ԓ蓁ԓ쓁ՓՓՓ듁֓꓁֓꓁ד꓁ؓ듁ٓ듁ړ듁ۓ듁ܓ듁ޓ꓁꓁⓵듁䓳듁擱듁铭擏듁퓪哐듁䓑꓁ⓓ꓁ⓓ듁ᓕ듁Γ듁Γ듁Γ듁ϓ듁Г듁ѓ듁ѓ듁ғ듁ӓɓԓ˓Փ̓ՓΓՓΓԓГԓғԓӓԓՓԓדՓٓՓٓՓٓՓٓ֓ٓדٓדٓٓۓٓۓۓۓܓݓݓݓߓߓߓᓁ㓁品쓿瓁铁퓁݁݁ݷ݁ݻ݁ݾ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݽ݁ݖ݁ݔ݁ݒݡ݁ݑݦ݁ݐݫ݁ݎݮ݁ݎݰ݁݌ݲ݁݋ݶ݁݊ݷ݈݁ݹ݁݇ݻ݁݅ݽ݁݅ݿ݂݁݃݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁݁ݼ݁݁ݺ݁݁ݹ݁݁ݷ݁݁ݶ݁݁ݳ݁݁ݲ݁݁ݰ݁݁ݮ݁݁ݬ݁݁ݪ݁݁ݪ݁݁ݩ݁݁ݨ݁݁ݩ݁݁ݨ݁݁ݨ݁݁ݧ݁݁ݧ݁݁ݦ݁݁ݥ݁݁ݥ݁݁ݤ݁݅ݤ݈݁ݢ݁݌ݢ݁ݎݡ݁ݒݟ݁ݖݟ݁ݙݝ݁ݝݝ݁ݠݛ݁ݤݙ݁ݦݘ݁ݪݖ݁ݮݔ݁ݱݒ݁ݵݐ݁ݸ݋݁ݼ݁݁ݾ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݤ݁ݧݿ݁ݪݾ݁ݫݼ݁ݮݺ݁ݿݯݹ݁ݽݱݸ݁ݻݲݶ݁ݹݲݵ݁ݶݴݴ݁ݴݴݵ݁ݲݶݶ݁ݰݶݶ݁ݮݸݷ݁ݬݸݷ݁ݪݹݷ݁ݩݹݸ݁ݪݻݸ݁ݫݿݸ݁ݭݸ݁ݰݸ݁ݴݸ݁ݷݹ݁ݹݸ݁ݽݸ݁ݸ݁ݷ݁ݸ݁ݷ݁ݶ݁ݶ݁ݵ݁ݵ݁ݳ݁ݲ݁ݰ݁ݮ݁ݥݪ݁ݡ݁ݜ݁݁݁݁݁݁݁݁݁ݩ݁݁ݧ݁݁ݤ݁݁ݣ݁݁ݠ݁݁ݝ݁݁ݜ݁݁ݙ݁݁ݖ݁݁ݔ݁݁ݑ݁݁ݎ݁݁݌݁݁݊݁݁݊݁݁݊݁݁݊݁݁݊݁݁݊݁݁݊݁݁݋݁݁݌݁݁ݍ݁݁ݎ݁݁ݏ݁݁ݐ݁݁ݐ݁݁ݑ݁݁ݒ݁݁ݒ݁݁ݓ݁݁ݓ݁݉݁ݔ݁ݍ݁ݔ݁ݐ݁ݔ݁ݒ݁ݕ݁ݓ݁ݖ݁ݖ݁ݖ݁ݜ݁ݘ݁ݟ݁ݙ݁ݢ݁ݛ݁ݣ݁ݜ݁ݥ݁ݞ݁ݧ݁ݠ݁ݨ݁ݡ݁ݪ݁ݣ݁ݫ݁ݤ݁ݫ݁ݦ݁ݭ݁ݨ݁݁ݩ݁݁ݫ݁݁ݬ݁݁ݼ݁݁ݿ݁݁݁݁݁݁݁݁݁݁݁݁݁ݯ݁݁ݯ݁݁ݭ݁݁ݭ݁݁ݬ݁݁ݫ݁݁ݪ݁݁ݪ݁݁ݨ݁݁ݦ݁݁ݤ݁݁ݣ݁݁ݡ݁݁ݞ݁݁ݝ݁݁ݜ݁݁ݚ݁ݺ݁ݖ݁ݽ݁݁ݿ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁݁ݿ݁݁ݾ݁݁ݽ݁݁ݼ݁݁ݻ݁݁ݻ݁݁ݹ݁݁ݹ݁݁ݸ݁݁ݶ݁݁ݵ݁݁ݴ݁݁ݳ݁݁ݱ݁݁ݯ݁݁ݮ݁݁ݫ݁݁ݩ݁݁ݧ݁݁ݢ݁݁ݣ݁݁ݣ݁݁ݤ݁݁ݤ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݥ݁݁ݤ݁݁ݤ݁݁ݤ݁݁ݣ݁݁ݤ݁݁ݥ݁݁ݦ݁݁ݧ݁݁ݨ݁݁ݪ݁݁ݫ݁݁ݬ݁݁ݭ݁݁ݮ݁ݯ݁ݱ݁ݲ݁ݳ݁ݴ݁ݵ݁ݶ݁ݸ݁ݹ݁ݺ݁ݻ݁ݼ݁ݽ݁ݾ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݽ݁ݼ݁ݻ݁ݹ݁ݸ݁ݵ݁ݳ݁ݰ݁ݫ݁ݪ݁ݩ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݾ݁ݾ݁ݾ݁ݽ݁ݽ݁ݽ݁ݾ݁ݿ݄ݿݾݾݽ݁ݼ݁ݼ݁ݻ݁ݹ݁ݸ݁ݶ݁ݵ݁ݳ݁ݯ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݌݁݁ݒ݁݁ݗ݁݁ݛ݁݁ݞ݁݁ݡ݁݁ݣ݁݁ݥ݁݁ݧ݁݁ݨ݁݁ݩ݁݁ݫ݁݁ݫ݁݁ݬ݁݁ݭ݁݁ݮ݁݁ݮ݁݁ݮ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݭ݁݁ݬ݁݁ݫ݁݁ݪ݁݁ݩ݁݁ݧ݁݁ݥ݁݁ݤ݁݁ݡ݁݁ݟ݁݁ݛ݁݁ݕ݁݁ݔ݁݁ݒ݁݁ݐ݁݁ݍ݈݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݛ݁ݢ݁ݪݦ݁ݯ݁ݱ݁ݲ݁ݳ݁ݴ݁ݵ݁ݶ݁ݷ݁ݶ݁ݷ݁ݸ݁ݸݿ݁ݸݼ݁ݸݺ݁ݸݶ݁ݸݳ݁ݸݱ݁ݸݮ݁ݷݿݫ݁ݸݻݫ݁ݷݺݪ݁ݷݹݩ݁ݷݹݫ݁ݷݷݮ݁ݶݷݰ݁ݶݶݲ݁ݵݵݵ݁ݵݳݶ݁ݵݳݸ݁ݶݱݻ݁ݸݰݽ݁ݹݯݿ݁ݻݭ݁ݼݬ݁ݽݩ݁ݿݧ݁ݣ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁݁ݻ݁݌ݸ݁ݏݴ݁ݒݲ݁ݔݮ݁ݖݪ݁ݗݧ݁ݙݣ݁ݛݠ݁ݜݜ݁ݞݚ݁ݞݖ݁ݠݒ݁ݠݏ݁ݢ݋݁ݣ݈݁ݣ݄݁ݤ݂݁ݤ݁݁ݦ݁݁ݧ݁݁ݧ݁݁ݨ݁݁ݧ݁݁ݨ݁݁ݨ݁݁ݩ݁݁ݪ݁݁ݩ݁݁ݫ݁݁ݬ݁݁ݮ݁݁ݯ݁݁ݲ݁݁ݴ݁݁ݵ݁݁ݷ݁݁ݸ݁݁ݻ݁݁ݽ݁݁ݾ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݃݁ݿ݄݁ݽ݆݁ݼ݇݁ݺ݉݁ݷ݊݁ݵ݊݁ݳ݌݁ݱݍ݁ݭݏ݁ݪݐ݁ݧݑ݁ݡݓ݁ݓ݁ݕ݁ݽ݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁݁ݿ݁ݻ݁ݶ݁݁݁jgrapht-jgrapht-1.5.1/etc/org.eclipse.jdt.core.prefs000066400000000000000000000720161402514743400223610ustar00rootroot00000000000000eclipse.preferences.version=1 org.eclipse.jdt.core.formatter.align_assignment_statements_on_columns=false org.eclipse.jdt.core.formatter.align_fields_grouping_blank_lines=2147483647 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false org.eclipse.jdt.core.formatter.align_variable_declarations_on_columns=false org.eclipse.jdt.core.formatter.align_with_spaces=false org.eclipse.jdt.core.formatter.alignment_for_additive_operator=16 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=32 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=48 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=32 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=32 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=32 org.eclipse.jdt.core.formatter.alignment_for_assignment=16 org.eclipse.jdt.core.formatter.alignment_for_bitwise_operator=16 org.eclipse.jdt.core.formatter.alignment_for_compact_if=16 org.eclipse.jdt.core.formatter.alignment_for_compact_loops=16 org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=16 org.eclipse.jdt.core.formatter.alignment_for_conditional_expression_chain=0 org.eclipse.jdt.core.formatter.alignment_for_enum_constants=49 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16 org.eclipse.jdt.core.formatter.alignment_for_expressions_in_for_loop_header=16 org.eclipse.jdt.core.formatter.alignment_for_logical_operator=16 org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0 org.eclipse.jdt.core.formatter.alignment_for_module_statements=16 org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16 org.eclipse.jdt.core.formatter.alignment_for_multiplicative_operator=16 org.eclipse.jdt.core.formatter.alignment_for_parameterized_type_references=16 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=32 org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=32 org.eclipse.jdt.core.formatter.alignment_for_relational_operator=0 org.eclipse.jdt.core.formatter.alignment_for_resources_in_try=32 org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=32 org.eclipse.jdt.core.formatter.alignment_for_shift_operator=0 org.eclipse.jdt.core.formatter.alignment_for_string_concatenation=16 org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=17 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=17 org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=17 org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=17 org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=17 org.eclipse.jdt.core.formatter.alignment_for_type_arguments=16 org.eclipse.jdt.core.formatter.alignment_for_type_parameters=16 org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch=32 org.eclipse.jdt.core.formatter.blank_lines_after_imports=1 org.eclipse.jdt.core.formatter.blank_lines_after_last_class_body_declaration=0 org.eclipse.jdt.core.formatter.blank_lines_after_package=1 org.eclipse.jdt.core.formatter.blank_lines_before_abstract_method=1 org.eclipse.jdt.core.formatter.blank_lines_before_field=0 org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0 org.eclipse.jdt.core.formatter.blank_lines_before_imports=1 org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1 org.eclipse.jdt.core.formatter.blank_lines_before_method=1 org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1 org.eclipse.jdt.core.formatter.blank_lines_before_package=0 org.eclipse.jdt.core.formatter.blank_lines_between_import_groups=1 org.eclipse.jdt.core.formatter.blank_lines_between_statement_group_in_switch=0 org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations=1 org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration=next_line org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration=next_line org.eclipse.jdt.core.formatter.brace_position_for_array_initializer=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_block=next_line_on_wrap org.eclipse.jdt.core.formatter.brace_position_for_block_in_case=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration=next_line org.eclipse.jdt.core.formatter.brace_position_for_enum_constant=next_line org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration=next_line org.eclipse.jdt.core.formatter.brace_position_for_lambda_body=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_method_declaration=next_line org.eclipse.jdt.core.formatter.brace_position_for_switch=end_of_line org.eclipse.jdt.core.formatter.brace_position_for_type_declaration=next_line org.eclipse.jdt.core.formatter.comment.align_tags_descriptions_grouped=false org.eclipse.jdt.core.formatter.comment.align_tags_names_descriptions=false org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment=false org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment=false org.eclipse.jdt.core.formatter.comment.count_line_length_from_starting_position=false org.eclipse.jdt.core.formatter.comment.format_block_comments=true org.eclipse.jdt.core.formatter.comment.format_header=false org.eclipse.jdt.core.formatter.comment.format_html=true org.eclipse.jdt.core.formatter.comment.format_javadoc_comments=true org.eclipse.jdt.core.formatter.comment.format_line_comments=true org.eclipse.jdt.core.formatter.comment.format_source_code=true org.eclipse.jdt.core.formatter.comment.indent_parameter_description=false org.eclipse.jdt.core.formatter.comment.indent_root_tags=true org.eclipse.jdt.core.formatter.comment.indent_tag_description=false org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags=insert org.eclipse.jdt.core.formatter.comment.insert_new_line_between_different_tags=do not insert org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter=do not insert org.eclipse.jdt.core.formatter.comment.line_length=100 org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries=true org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries=true org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments=false org.eclipse.jdt.core.formatter.compact_else_if=true org.eclipse.jdt.core.formatter.continuation_indentation=1 org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer=1 org.eclipse.jdt.core.formatter.disabling_tag=@formatter\:off org.eclipse.jdt.core.formatter.enabling_tag=@formatter\:on org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line=false org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header=true org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header=true org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases=true org.eclipse.jdt.core.formatter.indent_empty_lines=false org.eclipse.jdt.core.formatter.indent_statements_compare_to_block=true org.eclipse.jdt.core.formatter.indent_statements_compare_to_body=true org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases=true org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch=false org.eclipse.jdt.core.formatter.indentation.size=4 org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method=insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type=insert org.eclipse.jdt.core.formatter.insert_new_line_after_label=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation=do not insert org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing=insert org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement=do not insert org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement=do not insert org.eclipse.jdt.core.formatter.insert_space_after_additive_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter=insert org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_case=insert org.eclipse.jdt.core.formatter.insert_space_after_arrow_in_switch_default=insert org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_bitwise_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block=insert org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_switch_case_expressions=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments=insert org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters=insert org.eclipse.jdt.core.formatter.insert_space_after_ellipsis=insert org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow=insert org.eclipse.jdt.core.formatter.insert_space_after_logical_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_multiplicative_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_not_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try=do not insert org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while=do not insert org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard=do not insert org.eclipse.jdt.core.formatter.insert_space_after_relational_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources=insert org.eclipse.jdt.core.formatter.insert_space_after_shift_operator=insert org.eclipse.jdt.core.formatter.insert_space_after_string_concatenation=insert org.eclipse.jdt.core.formatter.insert_space_after_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_additive_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter=insert org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_case=insert org.eclipse.jdt.core.formatter.insert_space_before_arrow_in_switch_default=insert org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_bitwise_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try=do not insert org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default=do not insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for=insert org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_switch_case_expressions=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_ellipsis=do not insert org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow=insert org.eclipse.jdt.core.formatter.insert_space_before_logical_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_multiplicative_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try=insert org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while=insert org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return=insert org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw=insert org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional=insert org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard=do not insert org.eclipse.jdt.core.formatter.insert_space_before_relational_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for=do not insert org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources=do not insert org.eclipse.jdt.core.formatter.insert_space_before_shift_operator=insert org.eclipse.jdt.core.formatter.insert_space_before_string_concatenation=insert org.eclipse.jdt.core.formatter.insert_space_before_unary_operator=do not insert org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration=do not insert org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation=do not insert org.eclipse.jdt.core.formatter.join_lines_in_comments=true org.eclipse.jdt.core.formatter.join_wrapped_lines=true org.eclipse.jdt.core.formatter.keep_annotation_declaration_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_anonymous_type_declaration_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_code_block_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line=false org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line=false org.eclipse.jdt.core.formatter.keep_enum_constant_declaration_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_enum_declaration_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_if_then_body_block_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line=false org.eclipse.jdt.core.formatter.keep_lambda_body_block_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_loop_body_block_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_method_body_on_one_line=one_line_never org.eclipse.jdt.core.formatter.keep_simple_do_while_body_on_same_line=false org.eclipse.jdt.core.formatter.keep_simple_for_body_on_same_line=false org.eclipse.jdt.core.formatter.keep_simple_getter_setter_on_one_line=false org.eclipse.jdt.core.formatter.keep_simple_while_body_on_same_line=false org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line=false org.eclipse.jdt.core.formatter.keep_type_declaration_on_one_line=one_line_never org.eclipse.jdt.core.formatter.lineSplit=100 org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column=false org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column=false org.eclipse.jdt.core.formatter.number_of_blank_lines_after_code_block=0 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_code_block=0 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body=0 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_code_block=0 org.eclipse.jdt.core.formatter.number_of_blank_lines_at_end_of_method_body=0 org.eclipse.jdt.core.formatter.number_of_blank_lines_before_code_block=0 org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve=1 org.eclipse.jdt.core.formatter.parentheses_positions_in_annotation=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_catch_clause=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_enum_constant_declaration=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_for_statment=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_if_while_statement=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_lambda_declaration=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_method_delcaration=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_method_invocation=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_switch_statement=common_lines org.eclipse.jdt.core.formatter.parentheses_positions_in_try_clause=common_lines org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line=true org.eclipse.jdt.core.formatter.tabulation.char=space org.eclipse.jdt.core.formatter.tabulation.size=4 org.eclipse.jdt.core.formatter.text_block_indentation=0 org.eclipse.jdt.core.formatter.use_on_off_tags=true org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations=false org.eclipse.jdt.core.formatter.wrap_before_additive_operator=true org.eclipse.jdt.core.formatter.wrap_before_assignment_operator=false org.eclipse.jdt.core.formatter.wrap_before_bitwise_operator=true org.eclipse.jdt.core.formatter.wrap_before_conditional_operator=true org.eclipse.jdt.core.formatter.wrap_before_logical_operator=true org.eclipse.jdt.core.formatter.wrap_before_multiplicative_operator=true org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch=true org.eclipse.jdt.core.formatter.wrap_before_relational_operator=true org.eclipse.jdt.core.formatter.wrap_before_shift_operator=true org.eclipse.jdt.core.formatter.wrap_before_string_concatenation=true org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested=true org.eclipse.jdt.core.javaFormatter=org.eclipse.jdt.core.defaultJavaFormatter jgrapht-jgrapht-1.5.1/etc/prepareDocs.sh000077500000000000000000000005611402514743400202010ustar00rootroot00000000000000#!/bin/bash # Prepare docs directory for deployment to gh-pages branch after build on master set -e : ${GITHUB_WORKSPACE?"variable value required"} ${GITHUB_WORKSPACE}/etc/expandMarkdown.sh rm -f ${GITHUB_WORKSPACE}/docs/guide/.gitignore ${GITHUB_WORKSPACE}/etc/downloadJavadoc.sh mv ${GITHUB_WORKSPACE}/target/site/apidocs ${GITHUB_WORKSPACE}/docs/javadoc-SNAPSHOT jgrapht-jgrapht-1.5.1/etc/snapshot-settings.xml000066400000000000000000000007601402514743400216130ustar00rootroot00000000000000 sonatype-nexus-snapshots ${SONATYPE_USER} ${SONATYPE_PASSWORD} jgrapht-jgrapht-1.5.1/etc/updateCopyRightYear.sh000077500000000000000000000013021402514743400216600ustar00rootroot00000000000000#!/bin/bash #Updates the year in our copyright statement. i.e. "* (C) Copyright 2003-2016," gets replaced by "* (C) Copyright 2003-[current_year]," set -e SRC_DIR=`dirname "$BASH_SOURCE"`/.. #get the current year year=$(date +'%Y') function updateOneFile() { file="$1" sed -i "s/\(\*\s(C)\sCopyright\s[0-9]\{4\}-\)[0-9]\{4\},/\1"$year",/" $file } function updateOneModule() { module="$1" find "$module" -name '*.java' -print0 | while IFS= read -r -d '' file; do updateOneFile "$file"; done } pushd $SRC_DIR updateOneModule jgrapht-core updateOneModule jgrapht-demo updateOneModule jgrapht-ext updateOneModule jgrapht-guava updateOneModule jgrapht-io updateOneModule jgrapht-opt popd jgrapht-jgrapht-1.5.1/etc/updateHeader.sh000077500000000000000000000017011402514743400203220ustar00rootroot00000000000000#!/bin/bash # Updates the headers for all source files to match our current boilerplate set -e SRC_DIR=`dirname "$BASH_SOURCE"`/.. function updateOneFile() { file="$1" if [ -n "$file" ]; then echo "Updating $file" sed -i '/@since/d' "$file" sed -i '/^\/\/ End/d' "$file" sed -i'' '/(C) Copyright/,/\*\// { /(C) Copyright/n /\*\//r etc/header-boilerplate-tail.txt d }' "$file" fi } function updateOneModule() { module="$1" find "$module" -name '*.java' -print0 | while IFS= read -r -d '' file; do updateOneFile "$file"; done } pushd $SRC_DIR tail -n +3 etc/header-boilerplate.txt > etc/header-boilerplate-tail.txt updateOneModule jgrapht-core updateOneModule jgrapht-demo updateOneModule jgrapht-ext updateOneModule jgrapht-guava updateOneModule jgrapht-io updateOneModule jgrapht-opt rm -f etc/header-boilerplate-tail.txt popd jgrapht-jgrapht-1.5.1/jgrapht-core/000077500000000000000000000000001402514743400172035ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/pom.xml000066400000000000000000000071641402514743400205300ustar00rootroot00000000000000 4.0.0 org.jgrapht jgrapht 1.5.1 jgrapht-core JGraphT - Core ${project.parent.basedir} org.jgrapht.SlowTests,org.jgrapht.OptionalTests GNU Lesser General Public License Version 2.1, February 1999 http://jgrapht.sourceforge.net/LGPL.html repo Eclipse Public License (EPL) 2.0 http://www.eclipse.org/legal/epl-v20.html repo org.jheaps jheaps 0.13 com.googlecode.junit-toolbox junit-toolbox test org.openjdk.jmh jmh-core 1.23 test org.openjdk.jmh jmh-generator-annprocess 1.23 test org.apache.maven.plugins maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.felix maven-bundle-plugin org.apache.maven.plugins maven-surefire-plugin default-test **/perf/** ${excludedGroups} org.apache.maven.plugins maven-failsafe-plugin **/*Test.java org.jgrapht.SlowTests org.jgrapht.OptionalTests jgrapht-jgrapht-1.5.1/jgrapht-core/src/000077500000000000000000000000001402514743400177725ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/000077500000000000000000000000001402514743400207165ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/000077500000000000000000000000001402514743400216375ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/module-info.java000066400000000000000000000031371402514743400247240ustar00rootroot00000000000000module org.jgrapht.core { exports org.jgrapht; exports org.jgrapht.alg; exports org.jgrapht.alg.clique; exports org.jgrapht.alg.clustering; exports org.jgrapht.alg.color; exports org.jgrapht.alg.connectivity; exports org.jgrapht.alg.cycle; exports org.jgrapht.alg.decomposition; exports org.jgrapht.alg.densesubgraph; exports org.jgrapht.alg.drawing; exports org.jgrapht.alg.drawing.model; exports org.jgrapht.alg.flow; exports org.jgrapht.alg.flow.mincost; exports org.jgrapht.alg.independentset; exports org.jgrapht.alg.interfaces; exports org.jgrapht.alg.isomorphism; exports org.jgrapht.alg.lca; exports org.jgrapht.alg.linkprediction; exports org.jgrapht.alg.matching; exports org.jgrapht.alg.matching.blossom.v5; exports org.jgrapht.alg.partition; exports org.jgrapht.alg.planar; exports org.jgrapht.alg.scoring; exports org.jgrapht.alg.shortestpath; exports org.jgrapht.alg.similarity; exports org.jgrapht.alg.spanning; exports org.jgrapht.alg.tour; exports org.jgrapht.alg.transform; exports org.jgrapht.alg.util; exports org.jgrapht.alg.util.extension; exports org.jgrapht.alg.vertexcover; exports org.jgrapht.alg.vertexcover.util; exports org.jgrapht.event; exports org.jgrapht.generate; exports org.jgrapht.generate.netgen; exports org.jgrapht.graph; exports org.jgrapht.graph.builder; exports org.jgrapht.graph.concurrent; exports org.jgrapht.graph.specifics; exports org.jgrapht.traverse; exports org.jgrapht.util; requires transitive org.jheaps; } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/000077500000000000000000000000001402514743400224265ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/000077500000000000000000000000001402514743400240655ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/Graph.java000066400000000000000000000647051402514743400260050ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import java.util.Collection; import java.util.Set; import java.util.function.Supplier; import org.jgrapht.graph.DefaultGraphIterables; /** * The root interface in the graph hierarchy. A mathematical graph-theory graph object * G(V,E) contains a set V of vertices and a set * E of edges. Each edge e=(v1,v2) in E connects vertex v1 to vertex v2. for more information * about graphs and their related definitions see * http://mathworld.wolfram.com/Graph.html. * *

* This library generally follows the terminology found at: * * http://mathworld.wolfram.com/topics/GraphTheory.html. Implementation of this interface can * provide simple-graphs, multigraphs, pseudographs etc. The package org.jgrapht.graph * provides a gallery of abstract and concrete graph implementations. *

* *

* This library works best when vertices represent arbitrary objects and edges represent the * relationships between them. Vertex and edge instances may be shared by more than one graph. *

* *

* Through generics, a graph can be typed to specific classes for vertices V and edges * E<T>. Such a graph can contain vertices of type V and all * sub-types and Edges of type * E and all sub-types. *

* *

* For guidelines on vertex and edge classes, see * this wiki page. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public interface Graph { /** * Returns a set of all edges connecting source vertex to target vertex if such vertices exist * in this graph. If any of the vertices does not exist or is null, returns * null. If both vertices exist but no edges found, returns an empty set. * *

* In undirected graphs, some of the returned edges may have their source and target vertices in * the opposite order. In simple graphs the returned set is either singleton set or empty set. *

* * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return a set of all edges connecting source vertex to target vertex. */ Set getAllEdges(V sourceVertex, V targetVertex); /** * Returns an edge connecting source vertex to target vertex if such vertices and such edge * exist in this graph. Otherwise returns * null. If any of the specified vertices is null returns null * *

* In undirected graphs, the returned edge may have its source and target vertices in the * opposite order. *

* * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return an edge connecting source vertex to target vertex. */ E getEdge(V sourceVertex, V targetVertex); /** * Return the vertex supplier that the graph uses whenever it needs to create new vertices. * *

* A graph uses the vertex supplier to create new vertex objects whenever a user calls method * {@link Graph#addVertex()}. Users can also create the vertex in user code and then use method * {@link Graph#addVertex(Object)} to add the vertex. * *

* In contrast with the {@link Supplier} interface, the vertex supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new vertex to be added in a graph v must not be equal * to any other vertex in the graph. More formally, the graph must not contain any vertex * v2 such that v2.equals(v). * *

* Care must also be taken when interchanging calls to methods {@link Graph#addVertex(Object)} * and {@link Graph#addVertex()}. In such a case the user must make sure never to add vertices * in the graph using method {@link Graph#addVertex(Object)}, which are going to be returned in * the future by the supplied vertex supplier. Such a sequence will result into an * {@link IllegalArgumentException} when calling method {@link Graph#addVertex()}. * * @return the vertex supplier or null if the graph has no such supplier */ Supplier getVertexSupplier(); /** * Return the edge supplier that the graph uses whenever it needs to create new edges. * *

* A graph uses the edge supplier to create new edge objects whenever a user calls method * {@link Graph#addEdge(Object, Object)}. Users can also create the edge in user code and then * use method {@link Graph#addEdge(Object, Object, Object)} to add the edge. * *

* In contrast with the {@link Supplier} interface, the edge supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new edge to be added in a graph e must not be equal to * any other edge in the graph (even if the graph allows edge-multiplicity). More formally, the * graph must not contain any edge e2 such that e2.equals(e). * * @return the edge supplier null if the graph has no such supplier */ Supplier getEdgeSupplier(); /** * Creates a new edge in this graph, going from the source vertex to the target vertex, and * returns the created edge. Some graphs do not allow edge-multiplicity. In such cases, if the * graph already contains an edge from the specified source to the specified target, then this * method does not change the graph and returns null. * *

* The source and target vertices must already be contained in this graph. If they are not found * in graph {@link IllegalArgumentException} is thrown. * *

* This method creates the new edge e using this graph's edge supplier (see * {@link #getEdgeSupplier()}). For the new edge to be added e must not be * equal to any other edge the graph (even if the graph allows edge-multiplicity). More * formally, the graph must not contain any edge e2 such that * e2.equals(e). If such * e2 is found then the newly created edge e is abandoned, the method leaves * this graph unchanged and returns null. * *

* If the underlying graph implementation's {@link #getEdgeSupplier()} returns * null, then this method cannot create edges and throws an * {@link UnsupportedOperationException}. * * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return The newly created edge if added to the graph, otherwise * null. * * @throws IllegalArgumentException if source or target vertices are not found in the graph. * @throws NullPointerException if any of the specified vertices is null. * @throws UnsupportedOperationException if the graph was not initialized with an edge supplier * * @see #getEdgeSupplier() */ E addEdge(V sourceVertex, V targetVertex); /** * Adds the specified edge to this graph, going from the source vertex to the target vertex. * More formally, adds the specified edge, * e, to this graph if this graph contains no edge e2 such that * e2.equals(e). If this graph already contains such an edge, the call leaves this * graph unchanged and returns false. Some graphs do not allow edge-multiplicity. * In such cases, if the graph already contains an edge from the specified source to the * specified target, then this method does not change the graph and returns * false. If the edge was added to the graph, returns * true. * *

* The source and target vertices must already be contained in this graph. If they are not found * in graph IllegalArgumentException is thrown. *

* * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * @param e edge to be added to this graph. * * @return true if this graph did not already contain the specified edge. * * @throws IllegalArgumentException if source or target vertices are not found in the graph. * @throws ClassCastException if the specified edge is not assignment compatible with the class * of edges produced by the edge factory of this graph. * @throws NullPointerException if any of the specified vertices is * null. * * @see #addEdge(Object, Object) * @see #getEdgeSupplier() */ boolean addEdge(V sourceVertex, V targetVertex, E e); /** * Creates a new vertex in this graph and returns it. * *

* This method creates the new vertex v using this graph's vertex supplier (see * {@link #getVertexSupplier()}). For the new vertex to be added v must not * be equal to any other vertex in the graph. More formally, the graph must not contain any * vertex v2 such that v2.equals(v). If such * v2 is found then the newly created vertex v is abandoned, the method * leaves this graph unchanged and throws an {@link IllegalArgumentException}. * *

* If the underlying graph implementation's {@link #getVertexSupplier()} returns * null, then this method cannot create vertices and throws an * {@link UnsupportedOperationException}. * *

* Care must also be taken when interchanging calls to methods {@link Graph#addVertex(Object)} * and {@link Graph#addVertex()}. In such a case the user must make sure never to add vertices * in the graph using method {@link Graph#addVertex(Object)}, which are going to be returned in * the future by the supplied vertex supplier. Such a sequence will result into an * {@link IllegalArgumentException} when calling method {@link Graph#addVertex()}. * * @return The newly created vertex if added to the graph. * * @throws IllegalArgumentException if the graph supplier returns a vertex which is already in * the graph * @throws UnsupportedOperationException if the graph was not initialized with a vertex supplier * * @see #getVertexSupplier() */ V addVertex(); /** * Adds the specified vertex to this graph if not already present. More formally, adds the * specified vertex, v, to this graph if this graph contains no vertex * u such that * u.equals(v). If this graph already contains such vertex, the call leaves this graph * unchanged and returns false. In combination with the restriction on * constructors, this ensures that graphs never contain duplicate vertices. * * @param v vertex to be added to this graph. * * @return true if this graph did not already contain the specified vertex. * * @throws NullPointerException if the specified vertex is * null. */ boolean addVertex(V v); /** * Returns true if and only if this graph contains an edge going from the source * vertex to the target vertex. In undirected graphs the same result is obtained when source and * target are inverted. If any of the specified vertices does not exist in the graph, or if is * * null, returns false. * * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return true if this graph contains the specified edge. */ boolean containsEdge(V sourceVertex, V targetVertex); /** * Returns true if this graph contains the specified edge. More formally, returns * true if and only if this graph contains an edge e2 such that * e.equals(e2). If the specified edge is null returns * false. * * @param e edge whose presence in this graph is to be tested. * * @return true if this graph contains the specified edge. */ boolean containsEdge(E e); /** * Returns true if this graph contains the specified vertex. More formally, returns * true if and only if this graph contains a vertex u such that * u.equals(v). If the specified vertex is null returns * false. * * @param v vertex whose presence in this graph is to be tested. * * @return true if this graph contains the specified vertex. */ boolean containsVertex(V v); /** * Returns a set of the edges contained in this graph. The set is backed by the graph, so * changes to the graph are reflected in the set. If the graph is modified while an iteration * over the set is in progress, the results of the iteration are undefined. * *

* The graph implementation may maintain a particular set ordering (e.g. via * {@link java.util.LinkedHashSet}) for deterministic iteration, but this is not required. It is * the responsibility of callers who rely on this behavior to only use graph implementations * which support it. *

* * @return a set of the edges contained in this graph. */ Set edgeSet(); /** * Returns the degree of the specified vertex. * *

* A degree of a vertex in an undirected graph is the number of edges touching that vertex. * Edges with same source and target vertices (self-loops) are counted twice. * *

* In directed graphs this method returns the sum of the "in degree" and the "out degree". * * @param vertex vertex whose degree is to be calculated. * @return the degree of the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. * @throws ArithmeticException if the result overflows an int */ int degreeOf(V vertex); /** * Returns a set of all edges touching the specified vertex. If no edges are touching the * specified vertex returns an empty set. * * @param vertex the vertex for which a set of touching edges is to be returned. * @return a set of all edges touching the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ Set edgesOf(V vertex); /** * Returns the "in degree" of the specified vertex. * *

* The "in degree" of a vertex in a directed graph is the number of inward directed edges from * that vertex. See * http://mathworld.wolfram.com/Indegree.html. * *

* In the case of undirected graphs this method returns the number of edges touching the vertex. * Edges with same source and target vertices (self-loops) are counted twice. * * @param vertex vertex whose degree is to be calculated. * @return the degree of the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. * @throws ArithmeticException if the result overflows an int */ int inDegreeOf(V vertex); /** * Returns a set of all edges incoming into the specified vertex. * *

* In the case of undirected graphs this method returns all edges touching the vertex, thus, * some of the returned edges may have their source and target vertices in the opposite order. * * @param vertex the vertex for which the list of incoming edges to be returned. * @return a set of all edges incoming into the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ Set incomingEdgesOf(V vertex); /** * Returns the "out degree" of the specified vertex. * *

* The "out degree" of a vertex in a directed graph is the number of outward directed edges from * that vertex. See * http://mathworld.wolfram.com/Outdegree.html. * *

* In the case of undirected graphs this method returns the number of edges touching the vertex. * Edges with same source and target vertices (self-loops) are counted twice. * * @param vertex vertex whose degree is to be calculated. * @return the degree of the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. * @throws ArithmeticException if the result overflows an int */ int outDegreeOf(V vertex); /** * Returns a set of all edges outgoing from the specified vertex. * *

* In the case of undirected graphs this method returns all edges touching the vertex, thus, * some of the returned edges may have their source and target vertices in the opposite order. * * @param vertex the vertex for which the list of outgoing edges to be returned. * @return a set of all edges outgoing from the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ Set outgoingEdgesOf(V vertex); /** * Removes all the edges in this graph that are also contained in the specified edge collection. * After this call returns, this graph will contain no edges in common with the specified edges. * This method will invoke the {@link #removeEdge(Object)} method. * * @param edges edges to be removed from this graph. * * @return true if this graph changed as a result of the call * * @throws NullPointerException if the specified edge collection is * null. * * @see #removeEdge(Object) * @see #containsEdge(Object) */ boolean removeAllEdges(Collection edges); /** * Removes all the edges going from the specified source vertex to the specified target vertex, * and returns a set of all removed edges. Returns null if any of the specified * vertices does not exist in the graph. If both vertices exist but no edge is found, returns an * empty set. This method will either invoke the {@link #removeEdge(Object)} method, or the * {@link #removeEdge(Object, Object)} method. * * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return the removed edges, or null if either vertex is not part of graph */ Set removeAllEdges(V sourceVertex, V targetVertex); /** * Removes all the vertices in this graph that are also contained in the specified vertex * collection. After this call returns, this graph will contain no vertices in common with the * specified vertices. This method will invoke the {@link #removeVertex(Object)} method. * * @param vertices vertices to be removed from this graph. * * @return true if this graph changed as a result of the call * * @throws NullPointerException if the specified vertex collection is * null. * * @see #removeVertex(Object) * @see #containsVertex(Object) */ boolean removeAllVertices(Collection vertices); /** * Removes an edge going from source vertex to target vertex, if such vertices and such edge * exist in this graph. Returns the edge if removed or null otherwise. * * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return The removed edge, or null if no edge removed. */ E removeEdge(V sourceVertex, V targetVertex); /** * Removes the specified edge from the graph. Removes the specified edge from this graph if it * is present. More formally, removes an edge * e2 such that e2.equals(e), if the graph contains such edge. Returns * true if the graph contained the specified edge. (The graph will not contain the * specified edge once the call returns). * *

* If the specified edge is null returns * false. *

* * @param e edge to be removed from this graph, if present. * * @return true if and only if the graph contained the specified edge. */ boolean removeEdge(E e); /** * Removes the specified vertex from this graph including all its touching edges if present. * More formally, if the graph contains a vertex * u such that u.equals(v), the call removes all edges that touch * u and then removes u itself. If no such u is found, * the call leaves the graph unchanged. Returns true if the graph contained the * specified vertex. (The graph will not contain the specified vertex once the call returns). * *

* If the specified vertex is null returns * false. *

* * @param v vertex to be removed from this graph, if present. * * @return true if the graph contained the specified vertex; false * otherwise. */ boolean removeVertex(V v); /** * Returns a set of the vertices contained in this graph. The set is backed by the graph, so * changes to the graph are reflected in the set. If the graph is modified while an iteration * over the set is in progress, the results of the iteration are undefined. * *

* The graph implementation may maintain a particular set ordering (e.g. via * {@link java.util.LinkedHashSet}) for deterministic iteration, but this is not required. It is * the responsibility of callers who rely on this behavior to only use graph implementations * which support it. *

* * @return a set view of the vertices contained in this graph. */ Set vertexSet(); /** * Returns the source vertex of an edge. For an undirected graph, source and target are * distinguishable designations (but without any mathematical meaning). * * @param e edge of interest * * @return source vertex */ V getEdgeSource(E e); /** * Returns the target vertex of an edge. For an undirected graph, source and target are * distinguishable designations (but without any mathematical meaning). * * @param e edge of interest * * @return target vertex */ V getEdgeTarget(E e); /** * Get the graph type. The graph type can be used to query for additional metadata such as * whether the graph supports directed or undirected edges, self-loops, multiple (parallel) * edges, weights, etc. * * @return the graph type */ GraphType getType(); /** * The default weight for an edge. */ double DEFAULT_EDGE_WEIGHT = 1.0; /** * Returns the weight assigned to a given edge. Unweighted graphs return 1.0 (as defined by * {@link #DEFAULT_EDGE_WEIGHT}), allowing weighted-graph algorithms to apply to them when * meaningful. * * @param e edge of interest * @return edge weight */ double getEdgeWeight(E e); /** * Assigns a weight to an edge. * * @param e edge on which to set weight * @param weight new weight for edge * @throws UnsupportedOperationException if the graph does not support weights */ void setEdgeWeight(E e, double weight); /** * Assigns a weight to an edge between sourceVertex and targetVertex. * If no edge exists between sourceVertex and targetVertex or either * of these vertices is null, a NullPointerException is thrown. *

* When there exist multiple edges between sourceVertex and * targetVertex, consider using {@link #setEdgeWeight(Object, double)} instead. * * @param sourceVertex source vertex of the edge * @param targetVertex target vertex of the edge * @param weight new weight for edge * @throws UnsupportedOperationException if the graph does not support weights */ default void setEdgeWeight(V sourceVertex, V targetVertex, double weight) { this.setEdgeWeight(this.getEdge(sourceVertex, targetVertex), weight); } /** * Access the graph using the {@link GraphIterables} interface. This allows accessing graphs * without the restrictions imposed by 32-bit arithmetic. Moreover, graph implementations are * free to implement this interface without explicitly materializing intermediate results. * * @return the graph iterables */ default GraphIterables iterables() { return new DefaultGraphIterables(this); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/GraphIterables.java000066400000000000000000000223611402514743400276300ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import org.jgrapht.util.LiveIterableWrapper; /** * Presents a graph as a collection of views suitable for graphs which contain a very large number * of vertices or edges. Graph algorithms written these methods can work with graphs without the * restrictions imposed by 32-bit arithmetic. * *

* Whether the returned iterators support removal of elements is left to the graph implementation. * It is the responsibility of callers who rely on this behavior to only use graph implementations * which support it. *

* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public interface GraphIterables { /** * Get the underlying graph. * * @return the underlying graph */ Graph getGraph(); /** * Returns an iterable over the edges of the graph. * *

* Whether the ordering is deterministic, depends on the actual graph implementation. It is the * responsibility of callers who rely on this behavior to only use graph implementations which * support it. * * @return an iterable over the edges of the graph. */ default Iterable edges() { return new LiveIterableWrapper<>(() -> getGraph().edgeSet()); } /** * Return the number of edges in the graph. * * @return the number of edges. */ default long edgeCount() { return getGraph().edgeSet().size(); } /** * Returns an iterable view over the vertices contained in this graph. The returned iterator is * a live view of the vertices. If the graph is modified while an iteration is in progress, the * results of the iteration are undefined. * *

* The graph implementation may maintain a particular ordering for deterministic iteration, but * this is not required. It is the responsibility of callers who rely on this behavior to only * use graph implementations which support it. *

* * @return an iterable view of the vertices contained in this graph */ default Iterable vertices() { return new LiveIterableWrapper<>(() -> getGraph().vertexSet()); } /** * Return the number of vertices in the graph. * * @return the number of vertices */ default long vertexCount() { return getGraph().vertexSet().size(); } /** * Returns an iterable view over all edges touching the specified vertex. The returned iterators * are live views. If the graph is modified while an iteration is in progress, the results of * the iteration are undefined. If no edges are touching the specified vertex, the returned * iterators are already exhausted. * * @param vertex input vertex * @return an iterable view of the vertices contained in this graph * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ default Iterable edgesOf(V vertex) { return new LiveIterableWrapper<>(() -> getGraph().edgesOf(vertex)); } /** * Returns the degree of the specified vertex. * *

* A degree of a vertex in an undirected graph is the number of edges touching that vertex. * Edges with same source and target vertices (self-loops) are counted twice. * *

* In directed graphs this method returns the sum of the "in degree" and the "out degree". * * @param vertex vertex whose degree is to be calculated. * @return the degree of the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ default long degreeOf(V vertex) { return getGraph().degreeOf(vertex); } /** * Returns an iterable view over all edges incoming into the specified vertex. The returned * iterators are live views. If the graph is modified while an iteration is in progress, the * results of the iteration are undefined. * *

* In the case of undirected graphs the returned iterators return all edges touching the vertex, * thus, some of the returned edges may have their source and target vertices in the opposite * order. * * @param vertex input vertex * @return an iterable view of all edges incoming into the specified vertex * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ default Iterable incomingEdgesOf(V vertex) { return new LiveIterableWrapper<>(() -> getGraph().incomingEdgesOf(vertex)); } /** * Returns the "in degree" of the specified vertex. * *

* The "in degree" of a vertex in a directed graph is the number of inward directed edges from * that vertex. See * http://mathworld.wolfram.com/Indegree.html. * *

* In the case of undirected graphs this method returns the number of edges touching the vertex. * Edges with same source and target vertices (self-loops) are counted twice. * * @param vertex vertex whose degree is to be calculated. * @return the degree of the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ default long inDegreeOf(V vertex) { return getGraph().inDegreeOf(vertex); } /** * Returns an iterable view over all edges outgoing into the specified vertex. The returned * iterators are live views. If the graph is modified while an iteration is in progress, the * results of the iteration are undefined. * *

* In the case of undirected graphs the returned iterators return all edges touching the vertex, * thus, some of the returned edges may have their source and target vertices in the opposite * order. * * @param vertex input vertex * @return an iterable view of all edges outgoing from the specified vertex * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ default Iterable outgoingEdgesOf(V vertex) { return new LiveIterableWrapper<>(() -> getGraph().outgoingEdgesOf(vertex)); } /** * Returns the "out degree" of the specified vertex. * *

* The "out degree" of a vertex in a directed graph is the number of outward directed edges from * that vertex. See * http://mathworld.wolfram.com/Outdegree.html. * *

* In the case of undirected graphs this method returns the number of edges touching the vertex. * Edges with same source and target vertices (self-loops) are counted twice. * * @param vertex vertex whose degree is to be calculated. * @return the degree of the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ default long outDegreeOf(V vertex) { return getGraph().outDegreeOf(vertex); } /** * Returns an iterable view over all edges connecting source vertex to target vertex if such * vertices exist in this graph. The returned iterators are live views. If the graph is modified * while an iteration is in progress, the results of the iteration are undefined. * * If any of the vertices does not exist or is null, returns null. If * both vertices exist but no edges found, returns an iterable which returns exhausted * iterators. * *

* In undirected graphs, some of the returned edges may have their source and target vertices in * the opposite order. In simple graphs the returned set is either singleton set or empty set. *

* * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return an iterable view of all edges connecting source to target vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ default Iterable allEdges(V sourceVertex, V targetVertex) { return new LiveIterableWrapper<>(() -> getGraph().getAllEdges(sourceVertex, targetVertex)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/GraphMapping.java000066400000000000000000000036111402514743400273060ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by Assaf Lehr and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; /** * GraphMapping represents a bidirectional mapping between two graphs (called graph1 and graph2), * which allows the caller to obtain the matching vertex or edge in either direction, from graph1 to * graph2, or from graph2 to graph1. It does not have to always be a complete bidirectional mapping * (it could return null for some lookups). * * @param the graph vertex type * @param the graph edge type * * @author Assaf Lehr */ public interface GraphMapping { /** * Gets the mapped value where the key is vertex * * @param vertex vertex in one of the graphs * @param forward if true, uses mapping from graph1 to graph2; if false, use mapping from graph2 * to graph1 * * @return corresponding vertex in other graph, or null if none */ V getVertexCorrespondence(V vertex, boolean forward); /** * Gets the mapped value where the key is edge * * @param edge edge in one of the graphs * @param forward if true, uses mapping from graph1 to graph2; if false, use mapping from graph2 * to graph1 * * @return corresponding edge in other graph, or null if none */ E getEdgeCorrespondence(E edge, boolean forward); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/GraphMetrics.java000066400000000000000000000350021402514743400273200ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.alg.util.*; import org.jgrapht.util.*; import java.util.*; import java.util.stream.*; /** * Collection of methods which provide numerical graph information. * * @author Joris Kinable * @author Alexandru Valeanu */ public abstract class GraphMetrics { /** * Compute the diameter of the * graph. The diameter of a graph is defined as $\max_{v\in V}\epsilon(v)$, where $\epsilon(v)$ * is the eccentricity of vertex $v$. In other words, this method computes the 'longest shortest * path'. Two special cases exist. If the graph has no vertices, the diameter is 0. If the graph * is disconnected, the diameter is {@link Double#POSITIVE_INFINITY}. *

* For more fine-grained control over this method, or if you need additional distance metrics * such as the graph radius, consider using {@link org.jgrapht.alg.shortestpath.GraphMeasurer} * instead. * * @param graph input graph * @param graph vertex type * @param graph edge type * @return the diameter of the graph. */ public static double getDiameter(Graph graph) { return new GraphMeasurer<>(graph).getDiameter(); } /** * Compute the radius of the graph. * The radius of a graph is defined as $\min_{v\in V}\epsilon(v)$, where $\epsilon(v)$ is the * eccentricity of vertex $v$. Two special cases exist. If the graph has no vertices, the radius * is 0. If the graph is disconnected, the diameter is {@link Double#POSITIVE_INFINITY}. *

* For more fine-grained control over this method, or if you need additional distance metrics * such as the graph diameter, consider using {@link org.jgrapht.alg.shortestpath.GraphMeasurer} * instead. * * @param graph input graph * @param graph vertex type * @param graph edge type * @return the diameter of the graph. */ public static double getRadius(Graph graph) { return new GraphMeasurer<>(graph).getRadius(); } /** * Compute the girth of the graph. The * girth of a graph is the length (number of edges) of the smallest cycle in the graph. Acyclic * graphs are considered to have infinite girth. For directed graphs, the length of the shortest * directed cycle is returned (see Bang-Jensen, J., Gutin, G., Digraphs: Theory, Algorithms and * Applications, Springer Monographs in Mathematics, ch 1, ch 8.4.). Simple undirected graphs * have a girth of at least 3 (triangle cycle). Directed graphs and Multigraphs have a girth of * at least 2 (parallel edges/arcs), and in Pseudo graphs have a girth of at least 1 * (self-loop). *

* This implementation is loosely based on these notes. * In essence, this method invokes a Breadth-First search from every vertex in the graph. A * single Breadth-First search takes $O(n+m)$ time, where $n$ is the number of vertices in the * graph, and $m$ the number of edges. Consequently, the runtime complexity of this method is * $O(n(n+m))=O(mn)$. *

* An algorithm with the same worst case runtime complexity, but a potentially better average * runtime complexity of $O(n^2)$ is described in: Itai, A. Rodeh, M. Finding a minimum circuit * in a graph. SIAM J. Comput. Vol 7, No 4, 1987. * * @param graph input graph * @param graph vertex type * @param graph edge type * @return girth of the graph, or {@link Integer#MAX_VALUE} if the graph is acyclic. */ public static int getGirth(Graph graph) { final int NIL = -1; final boolean isAllowingMultipleEdges = graph.getType().isAllowingMultipleEdges(); // Ordered sequence of vertices List vertices = new ArrayList<>(graph.vertexSet()); // Index map of vertices in ordered sequence Map indexMap = new HashMap<>(); for (int i = 0; i < vertices.size(); i++) indexMap.put(vertices.get(i), i); // Objective int girth = Integer.MAX_VALUE; // Array storing the depth of each vertex in the search tree int[] depth = new int[vertices.size()]; // Queue for BFS Queue queue = new ArrayDeque<>(); // Check whether the graph has self-loops if (graph.getType().isAllowingSelfLoops()) for (V v : vertices) if (graph.containsEdge(v, v)) return 1; NeighborCache neighborIndex = new NeighborCache<>(graph); if (graph.getType().isUndirected()) { // Array which keeps track of the search tree structure to prevent revisiting parent // nodes int[] parent = new int[vertices.size()]; // Start a BFS search tree from each vertex. The search stops when a triangle (smallest // possible cycle) is found. // The last two vertices can be ignored. for (int i = 0; i < vertices.size() - 2 && girth > 3; i++) { // Reset data structures Arrays.fill(depth, NIL); Arrays.fill(parent, NIL); queue.clear(); depth[i] = 0; queue.add(vertices.get(i)); int depthU; do { V u = queue.poll(); int indexU = indexMap.get(u); depthU = depth[indexU]; // Visit all neighbors of vertex u for (V v : neighborIndex.neighborsOf(u)) { int indexV = indexMap.get(v); if (parent[indexU] == indexV) { // Skip the parent of vertex u, unless there // are multiple edges between u and v if (!isAllowingMultipleEdges || graph.getAllEdges(u, v).size() == 1) continue; } int depthV = depth[indexV]; if (depthV == NIL) { // New neighbor discovered queue.add(v); depth[indexV] = depthU + 1; parent[indexV] = indexU; } else { // Rediscover neighbor: found cycle. girth = Math.min(girth, depthU + depthV + 1); } } } while (!queue.isEmpty() && 2 * (depthU + 1) - 1 < girth); } } else { // Directed case for (int i = 0; i < vertices.size() - 1 && girth > 2; i++) { // Reset data structures Arrays.fill(depth, NIL); queue.clear(); depth[i] = 0; queue.add(vertices.get(i)); int depthU; do { V u = queue.poll(); int indexU = indexMap.get(u); depthU = depth[indexU]; // Visit all neighbors of vertex u for (V v : neighborIndex.successorsOf(u)) { int indexV = indexMap.get(v); int depthV = depth[indexV]; if (depthV == NIL) { // New neighbor discovered queue.add(v); depth[indexV] = depthU + 1; } else if (depthV == 0) { // Rediscover root: found cycle. girth = Math.min(girth, depthU + depthV + 1); } } } while (!queue.isEmpty() && depthU + 1 < girth); } } assert graph.getType().isUndirected() && graph.getType().isSimple() && girth >= 3 || graph.getType().isAllowingSelfLoops() && girth >= 1 || girth >= 2 && (graph.getType().isDirected() || graph.getType().isAllowingMultipleEdges()); return girth; } /** * An $O(|V|^3)$ (assuming vertexSubset provides constant time indexing) naive implementation * for counting non-trivial triangles in an undirected graph induced by the subset of vertices. * * @param graph the input graph * @param vertexSubset the vertex subset * @param the graph vertex type * @param the graph edge type * @return the number of triangles in the graph induced by vertexSubset */ static long naiveCountTriangles(Graph graph, List vertexSubset) { long total = 0; if (graph.getType().isAllowingMultipleEdges()) { for (int i = 0; i < vertexSubset.size(); i++) { for (int j = i + 1; j < vertexSubset.size(); j++) { for (int k = j + 1; k < vertexSubset.size(); k++) { V u = vertexSubset.get(i); V v = vertexSubset.get(j); V w = vertexSubset.get(k); int uvEdgeCount = graph.getAllEdges(u, v).size(); if (uvEdgeCount == 0) { continue; } int vwEdgeCount = graph.getAllEdges(v, w).size(); if (vwEdgeCount == 0) { continue; } int wuEdgeCount = graph.getAllEdges(w, u).size(); if (wuEdgeCount == 0) { continue; } total += uvEdgeCount * vwEdgeCount * wuEdgeCount; } } } } else { for (int i = 0; i < vertexSubset.size(); i++) { for (int j = i + 1; j < vertexSubset.size(); j++) { for (int k = j + 1; k < vertexSubset.size(); k++) { V u = vertexSubset.get(i); V v = vertexSubset.get(j); V w = vertexSubset.get(k); if (graph.containsEdge(u, v) && graph.containsEdge(v, w) && graph.containsEdge(w, u)) { total++; } } } } } return total; } /** * An $O(|E|^{3/2})$ algorithm for counting the number of non-trivial triangles in an undirected * graph. A non-trivial triangle is formed by three distinct vertices all connected to each * other. * *

* For more details of this algorithm see Ullman, Jeffrey: "Mining of Massive Datasets", * Cambridge University Press, Chapter 10 * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return the number of triangles in the graph * @throws NullPointerException if {@code graph} is {@code null} * @throws IllegalArgumentException if {@code graph} is not undirected */ public static long getNumberOfTriangles(Graph graph) { GraphTests.requireUndirected(graph); final int sqrtV = (int) Math.sqrt(graph.vertexSet().size()); List vertexList = new ArrayList<>(graph.vertexSet()); /* * The book suggest the following comparator: "Compare vertices based on their degree. If * equal compare them of their actual value, since they are all integers". */ // Fix vertex order for unique comparison of vertices Map vertexOrder = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); int k = 0; for (V v : graph.vertexSet()) { vertexOrder.put(v, k++); } Comparator comparator = Comparator .comparingInt(graph::degreeOf).thenComparingInt(System::identityHashCode) .thenComparingInt(vertexOrder::get); vertexList.sort(comparator); // vertex v is a heavy-hitter iff degree(v) >= sqrtV List heavyHitterVertices = vertexList .stream().filter(x -> graph.degreeOf(x) >= sqrtV) .collect(Collectors.toCollection(ArrayList::new)); // count the number of triangles formed from only heavy-hitter vertices long numberTriangles = naiveCountTriangles(graph, heavyHitterVertices); for (E edge : graph.edgeSet()) { V v1 = graph.getEdgeSource(edge); V v2 = graph.getEdgeTarget(edge); if (v1 == v2) { continue; } if (graph.degreeOf(v1) < sqrtV || graph.degreeOf(v2) < sqrtV) { // ensure that v1 <= v2 (swap them otherwise) if (comparator.compare(v1, v2) > 0) { V tmp = v1; v1 = v2; v2 = tmp; } for (E e : graph.edgesOf(v1)) { V u = Graphs.getOppositeVertex(graph, e, v1); // check if the triangle is non-trivial: u, v1, v2 are distinct vertices if (u == v1 || u == v2) { continue; } /* * Check if v2 <= u and if (u, v2) is a valid edge. If both of them are true, * then we have a new triangle (v1, v2, u) and all three vertices in the * triangle are ordered (v1 <= v2 <= u) so we count it only once. */ if (comparator.compare(v2, u) <= 0 && graph.containsEdge(u, v2)) { numberTriangles++; } } } } return numberTriangles; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/GraphPath.java000066400000000000000000000076551402514743400266230ustar00rootroot00000000000000/* * (C) Copyright 2008-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import java.util.*; /** * A GraphPath represents a path in a * {@link Graph}. Unlike some definitions, the path is not required to be a * Simple Path. * * @param the graph vertex type * @param the graph edge type * * @author John Sichi */ public interface GraphPath { /** * Returns the graph over which this path is defined. The path may also be valid with respect to * other graphs. * * @return the containing graph */ Graph getGraph(); /** * Returns the start vertex in the path. * * @return the start vertex */ V getStartVertex(); /** * Returns the end vertex in the path. * * @return the end vertex */ V getEndVertex(); /** * Returns the edges making up the path. The first edge in this path is incident to the start * vertex. The last edge is incident to the end vertex. The vertices along the path can be * obtained by traversing from the start vertex, finding its opposite across the first edge, and * then doing the same successively across subsequent edges; see {@link #getVertexList()}. * *

* Whether or not the returned edge list is modifiable depends on the path implementation. * * @return list of edges traversed by the path */ default List getEdgeList() { List vertexList = this.getVertexList(); if (vertexList.size() < 2) return Collections.emptyList(); Graph g = this.getGraph(); List edgeList = new ArrayList<>(); Iterator vertexIterator = vertexList.iterator(); V u = vertexIterator.next(); while (vertexIterator.hasNext()) { V v = vertexIterator.next(); edgeList.add(g.getEdge(u, v)); u = v; } return edgeList; } /** * Returns the path as a sequence of vertices. * * @return path, denoted by a list of vertices */ default List getVertexList() { List edgeList = this.getEdgeList(); if (edgeList.isEmpty()) { V startVertex = getStartVertex(); if (startVertex != null && startVertex.equals(getEndVertex())) { return Collections.singletonList(startVertex); } else { return Collections.emptyList(); } } Graph g = this.getGraph(); List list = new ArrayList<>(); V v = this.getStartVertex(); list.add(v); for (E e : edgeList) { v = Graphs.getOppositeVertex(g, e, v); list.add(v); } return list; } /** * Returns the weight assigned to the path. Typically, this will be the sum of the weights of * the edge list entries (as defined by the containing graph), but some path implementations may * use other definitions. * * @return the weight of the path */ double getWeight(); /** * Returns the length of the path, measured in the number of edges. * * @return the length of the path, measured in the number of edges */ default int getLength() { return getEdgeList().size(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/GraphTests.java000066400000000000000000001001641402514743400270160ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh, Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.partition.*; import org.jgrapht.alg.planar.*; import java.util.*; import java.util.stream.*; /** * A collection of utilities to test for various graph properties. * * @author Barak Naveh * @author Dimitrios Michail * @author Joris Kinable * @author Alexandru Valeanu */ public abstract class GraphTests { private static final String GRAPH_CANNOT_BE_NULL = "Graph cannot be null"; private static final String GRAPH_MUST_BE_DIRECTED_OR_UNDIRECTED = "Graph must be directed or undirected"; private static final String GRAPH_MUST_BE_UNDIRECTED = "Graph must be undirected"; private static final String GRAPH_MUST_BE_DIRECTED = "Graph must be directed"; private static final String GRAPH_MUST_BE_WEIGHTED = "Graph must be weighted"; /** * Test whether a graph is empty. An empty graph on n nodes consists of n isolated vertices with * no edges. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is empty, false otherwise */ public static boolean isEmpty(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); return graph.edgeSet().isEmpty(); } /** * Check if a graph is simple. A graph is simple if it has no self-loops and multiple (parallel) * edges. * * @param graph a graph * @param the graph vertex type * @param the graph edge type * @return true if a graph is simple, false otherwise */ public static boolean isSimple(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); GraphType type = graph.getType(); if (type.isSimple()) { return true; } // no luck, we have to check for (V v : graph.vertexSet()) { Set neighbors = new HashSet<>(); for (E e : graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (u.equals(v) || !neighbors.add(u)) { return false; } } } return true; } /** * Check if a graph has self-loops. A self-loop is an edge with the same source and target * vertices. * * @param graph a graph * @param the graph vertex type * @param the graph edge type * @return true if a graph has self-loops, false otherwise */ public static boolean hasSelfLoops(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); if (!graph.getType().isAllowingSelfLoops()) { return false; } // no luck, we have to check for (E e : graph.edgeSet()) { if (graph.getEdgeSource(e).equals(graph.getEdgeTarget(e))) { return true; } } return false; } /** * Check if a graph has multiple edges (parallel edges), that is, whether the graph contains two * or more edges connecting the same pair of vertices. * * @param graph a graph * @param the graph vertex type * @param the graph edge type * @return true if a graph has multiple edges, false otherwise */ public static boolean hasMultipleEdges(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); if (!graph.getType().isAllowingMultipleEdges()) { return false; } // no luck, we have to check for (V v : graph.vertexSet()) { Set neighbors = new HashSet<>(); for (E e : graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (!neighbors.add(u)) { return true; } } } return false; } /** * Test whether a graph is complete. A complete undirected graph is a simple graph in which * every pair of distinct vertices is connected by a unique edge. A complete directed graph is a * directed graph in which every pair of distinct vertices is connected by a pair of unique * edges (one in each direction). * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is complete, false otherwise */ public static boolean isComplete(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); int n = graph.vertexSet().size(); int allEdges; if (graph.getType().isDirected()) { allEdges = Math.multiplyExact(n, n - 1); } else if (graph.getType().isUndirected()) { if (n % 2 == 0) { allEdges = Math.multiplyExact(n / 2, n - 1); } else { allEdges = Math.multiplyExact(n, (n - 1) / 2); } } else { throw new IllegalArgumentException(GRAPH_MUST_BE_DIRECTED_OR_UNDIRECTED); } return graph.edgeSet().size() == allEdges && isSimple(graph); } /** * Test if the inspected graph is connected. A graph is connected when, while ignoring edge * directionality, there exists a path between every pair of vertices. In a connected graph, * there are no unreachable vertices. When the inspected graph is a directed graph, this * method returns true if and only if the inspected graph is weakly connected. An empty * graph is not considered connected. * *

* This method does not performing any caching, instead recomputes everything from scratch. In * case more control is required use {@link ConnectivityInspector} directly. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is connected, false otherwise * @see ConnectivityInspector */ public static boolean isConnected(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); return new ConnectivityInspector<>(graph).isConnected(); } /** * Tests if the inspected graph is biconnected. A biconnected graph is a connected graph on two * or more vertices having no cutpoints. * *

* This method does not performing any caching, instead recomputes everything from scratch. In * case more control is required use * {@link org.jgrapht.alg.connectivity.BiconnectivityInspector} directly. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is biconnected, false otherwise * @see org.jgrapht.alg.connectivity.BiconnectivityInspector */ public static boolean isBiconnected(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); return new BiconnectivityInspector<>(graph).isBiconnected(); } /** * Test whether a directed graph is weakly connected. * *

* This method does not performing any caching, instead recomputes everything from scratch. In * case more control is required use {@link ConnectivityInspector} directly. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is weakly connected, false otherwise * @see ConnectivityInspector */ public static boolean isWeaklyConnected(Graph graph) { return isConnected(graph); } /** * Test whether a graph is strongly connected. * *

* This method does not performing any caching, instead recomputes everything from scratch. In * case more control is required use {@link KosarajuStrongConnectivityInspector} directly. * *

* In case of undirected graphs this method delegated to {@link #isConnected(Graph)}. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is strongly connected, false otherwise * @see KosarajuStrongConnectivityInspector */ public static boolean isStronglyConnected(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); if (graph.getType().isUndirected()) { return isConnected(graph); } else { return new KosarajuStrongConnectivityInspector<>(graph).isStronglyConnected(); } } /** * Test whether an undirected graph is a tree. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is tree, false otherwise */ public static boolean isTree(Graph graph) { if (!graph.getType().isUndirected()) { throw new IllegalArgumentException(GRAPH_MUST_BE_UNDIRECTED); } return (graph.edgeSet().size() == (graph.vertexSet().size() - 1)) && isConnected(graph); } /** * Test whether an undirected graph is a forest. A forest is a set of disjoint trees. By * definition, any acyclic graph is a forest. This includes the empty graph and the class of * tree graphs. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is forest, false otherwise */ public static boolean isForest(Graph graph) { if (!graph.getType().isUndirected()) { throw new IllegalArgumentException(GRAPH_MUST_BE_UNDIRECTED); } if (graph.vertexSet().isEmpty()) // null graph is not a forest return false; int nrConnectedComponents = new ConnectivityInspector<>(graph).connectedSets().size(); return graph.edgeSet().size() + nrConnectedComponents == graph.vertexSet().size(); } /** * Test whether a graph is overfull. * A graph is overfull if $|E|>\Delta(G)\lfloor |V|/2 \rfloor$, where $\Delta(G)$ is the * maximum degree of the graph. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is overfull, false otherwise */ public static boolean isOverfull(Graph graph) { int maxDegree = graph.vertexSet().stream().mapToInt(graph::degreeOf).max().getAsInt(); return graph.edgeSet().size() > maxDegree * Math.floor(graph.vertexSet().size() / 2.0); } /** * Test whether an undirected graph is a * split graph. A split graph is a graph * in which the vertices can be partitioned into a clique and an independent set. Split graphs * are a special class of chordal graphs. Given the degree sequence $d_1 \geq,\dots,\geq d_n$ of * $G$, a graph is a split graph if and only if : \[\sum_{i=1}^m d_i = m (m - 1) + \sum_{i=m + * 1}^nd_i\], where $m = \max_i \{d_i\geq i-1\}$. If the graph is a split graph, then the $m$ * vertices with the largest degrees form a maximum clique in $G$, and the remaining vertices * constitute an independent set. See Brandstadt, A., Le, V., Spinrad, J. Graph Classes: A * Survey. Philadelphia, PA: SIAM, 1999. for details. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is a split graph, false otherwise */ public static boolean isSplit(Graph graph) { requireUndirected(graph); if (!isSimple(graph) || graph.vertexSet().isEmpty()) return false; List degrees = new ArrayList<>(graph.vertexSet().size()); degrees .addAll(graph.vertexSet().stream().map(graph::degreeOf).collect(Collectors.toList())); Collections.sort(degrees, Collections.reverseOrder()); // sort degrees descending order // Find m = \max_i \{d_i\geq i-1\} int m = 1; for (; m < degrees.size() && degrees.get(m) >= m; m++) { } m--; int left = 0; for (int i = 0; i <= m; i++) left += degrees.get(i); int right = m * (m + 1); for (int i = m + 1; i < degrees.size(); i++) right += degrees.get(i); return left == right; } /** * Test whether a graph is bipartite. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is bipartite, false otherwise * @see BipartitePartitioning#isBipartite() */ public static boolean isBipartite(Graph graph) { return new BipartitePartitioning<>(graph).isBipartite(); } /** * Test whether a partition of the vertices into two sets is a bipartite partition. * * @param graph the input graph * @param firstPartition the first vertices partition * @param secondPartition the second vertices partition * @return true if the partition is a bipartite partition, false otherwise * @param the graph vertex type * @param the graph edge type * @see BipartitePartitioning#isValidPartitioning(PartitioningAlgorithm.Partitioning) */ @SuppressWarnings("unchecked") public static boolean isBipartitePartition( Graph graph, Set firstPartition, Set secondPartition) { return new BipartitePartitioning<>(graph) .isValidPartitioning( new PartitioningAlgorithm.PartitioningImpl<>( Arrays.asList((Set) firstPartition, (Set) secondPartition))); } /** * Tests whether a graph is cubic. A * graph is cubic if all vertices have degree 3. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is cubic, false otherwise */ public static boolean isCubic(Graph graph) { for (V v : graph.vertexSet()) if (graph.degreeOf(v) != 3) return false; return true; } /** * Test whether a graph is Eulerian. An undirected graph is Eulerian if it is connected and each * vertex has an even degree. A directed graph is Eulerian if it is strongly connected and each * vertex has the same incoming and outgoing degree. Test whether a graph is Eulerian. An * Eulerian graph is a graph * containing an Eulerian cycle. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * * @return true if the graph is Eulerian, false otherwise * @see HierholzerEulerianCycle#isEulerian(Graph) */ public static boolean isEulerian(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); return new HierholzerEulerianCycle().isEulerian(graph); } /** * Checks whether a graph is chordal. A * chordal graph is one in which all cycles of four or more vertices have a chord, which is * an edge that is not part of the cycle but connects two vertices of the cycle. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is chordal, false otherwise * @see ChordalityInspector#isChordal() */ public static boolean isChordal(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); return new ChordalityInspector<>(graph).isChordal(); } /** * Checks whether a graph is weakly * chordal. *

* The following definitions are equivalent: *

    *
  1. A graph is weakly chordal (weakly triangulated) if neither it nor its complement contains * a chordless cycles with five * or more vertices.
  2. *
  3. A 2-pair in a graph is a pair of non-adjacent vertices $x$, $y$ such that every chordless * path has exactly two edges. A graph is weakly chordal if every connected * induced subgraph $H$ that is not * a complete graph, contains a 2-pair.
  4. *
* * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is weakly chordal, false otherwise * @see WeakChordalityInspector#isWeaklyChordal() */ public static boolean isWeaklyChordal(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); return new WeakChordalityInspector<>(graph).isWeaklyChordal(); } /** * Tests whether an undirected graph meets Ore's condition to be Hamiltonian. * * Let $G$ be a (finite and simple) graph with $n \geq 3$ vertices. We denote by $deg(v)$ the * degree of a vertex $v$ in $G$, i.e. the number of incident edges in $G$ to $v$. Then, Ore's * theorem states that if $deg(v) + deg(w) \geq n$ for every pair of distinct non-adjacent * vertices $v$ and $w$ of $G$, then $G$ is Hamiltonian. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph meets Ore's condition, false otherwise * @see org.jgrapht.alg.tour.PalmerHamiltonianCycle */ public static boolean hasOreProperty(Graph graph) { requireUndirected(graph); final int n = graph.vertexSet().size(); if (!graph.getType().isSimple() || n < 3) return false; List vertexList = new ArrayList<>(graph.vertexSet()); for (int i = 0; i < vertexList.size(); i++) { for (int j = i + 1; j < vertexList.size(); j++) { V v = vertexList.get(i); V w = vertexList.get(j); if (!v.equals(w) && !graph.containsEdge(v, w) && graph.degreeOf(v) + graph.degreeOf(w) < n) return false; } } return true; } /** * Tests whether an undirected graph is triangle-free (i.e. no three distinct vertices form a * triangle of edges). * * The implementation of this method uses {@link GraphMetrics#getNumberOfTriangles(Graph)}. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return true if the graph is triangle-free, false otherwise */ public static boolean isTriangleFree(Graph graph) { return GraphMetrics.getNumberOfTriangles(graph) == 0; } /** * Checks that the specified graph is perfect. Due to the Strong Perfect Graph Theorem Berge * Graphs are the same as perfect Graphs. The implementation of this method is delegated to * {@link org.jgrapht.alg.cycle.BergeGraphInspector} * * @param graph the graph reference to check for being perfect or not * @param the graph vertex type * @param the graph edge type * @return true if the graph is perfect, false otherwise */ public static boolean isPerfect(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); return new BergeGraphInspector().isBerge(graph); } /** * Checks that the specified graph is planar. A graph is * planar if it can be drawn on a * two-dimensional plane without any of its edges crossing. The implementation of the method is * delegated to the {@link org.jgrapht.alg.planar.BoyerMyrvoldPlanarityInspector}. Also, use * this class to get a planar embedding of the graph in case it is planar, or a Kuratowski * subgraph as a certificate of nonplanarity. * * @param graph the graph to test planarity of * @param the graph vertex type * @param the graph edge type * @return true if the graph is planar, false otherwise * @see PlanarityTestingAlgorithm * @see BoyerMyrvoldPlanarityInspector */ public static boolean isPlanar(Graph graph) { Objects.requireNonNull(graph, GRAPH_CANNOT_BE_NULL); return new BoyerMyrvoldPlanarityInspector<>(graph).isPlanar(); } /** * Checks whether the {@code graph} is a Kuratowski * subdivision. Effectively checks whether the {@code graph} is a $K_{3,3}$ subdivision or * $K_{5}$ subdivision * * @param graph the graph to test * @param the graph vertex type * @param the graph edge type * @return true if the {@code graph} is a Kuratowski subdivision, false otherwise */ public static boolean isKuratowskiSubdivision(Graph graph) { return isK33Subdivision(graph) || isK5Subdivision(graph); } /** * Checks whether the {@code graph} is a $K_{3,3}$ subdivision. * * @param graph the graph to test * @param the graph vertex type * @param the graph edge type * @return true if the {@code graph} is a $K_{3,3}$ subdivision, false otherwise */ public static boolean isK33Subdivision(Graph graph) { List degree3 = new ArrayList<>(); // collect all vertices with degree 3 for (V vertex : graph.vertexSet()) { int degree = graph.degreeOf(vertex); if (degree == 3) { degree3.add(vertex); } else if (degree != 2) { return false; } } if (degree3.size() != 6) { return false; } V vertex = degree3.remove(degree3.size() - 1); Set reachable = reachableWithDegree(graph, vertex, 3); if (reachable.size() != 3) { return false; } degree3.removeAll(reachable); return reachable.equals(reachableWithDegree(graph, degree3.get(0), 3)) && reachable.equals(reachableWithDegree(graph, degree3.get(1), 3)); } /** * Checks whether the {@code graph} is a $K_5$ subdivision. * * @param graph the graph to test * @param the graph vertex type * @param the graph edge type * @return true if the {@code graph} is a $K_5$ subdivision, false otherwise */ public static boolean isK5Subdivision(Graph graph) { Set degree5 = new HashSet<>(); for (V vertex : graph.vertexSet()) { int degree = graph.degreeOf(vertex); if (degree == 4) { degree5.add(vertex); } else if (degree != 2) { return false; } } if (degree5.size() != 5) { return false; } for (V vertex : degree5) { Set reachable = reachableWithDegree(graph, vertex, 4); if (reachable.size() != 4 || !degree5.containsAll(reachable) || reachable.contains(vertex)) { return false; } } return true; } /** * Uses BFS to find all vertices of the {@code graph} which have a degree {@code degree}. This * method doesn't advance to new nodes after it finds a node with a degree {@code degree} * * @param graph the graph to search in * @param startVertex the start vertex * @param degree the degree of desired vertices * @param the graph vertex type * @param the graph edge type * @return all vertices of the {@code graph} reachable from {@code startVertex}, which have * degree {@code degree} */ private static Set reachableWithDegree(Graph graph, V startVertex, int degree) { Set visited = new HashSet<>(); Set reachable = new HashSet<>(); Queue queue = new ArrayDeque<>(); queue.add(startVertex); while (!queue.isEmpty()) { V current = queue.poll(); visited.add(current); for (E e : graph.edgesOf(current)) { V opposite = Graphs.getOppositeVertex(graph, e, current); if (visited.contains(opposite)) { continue; } if (graph.degreeOf(opposite) == degree) { reachable.add(opposite); } else { queue.add(opposite); } } } return reachable; } /** * Checks that the specified graph is directed and throws a customized * {@link IllegalArgumentException} if it is not. Also checks that the graph reference is not * {@code null} and throws a {@link NullPointerException} if it is. * * @param graph the graph reference to check for beeing directed and not null * @param message detail message to be used in the event that an exception is thrown * @param the graph vertex type * @param the graph edge type * @return {@code graph} if directed and not {@code null} * @throws NullPointerException if {@code graph} is {@code null} * @throws IllegalArgumentException if {@code graph} is not directed */ public static Graph requireDirected(Graph graph, String message) { if (graph == null) throw new NullPointerException(GRAPH_CANNOT_BE_NULL); if (!graph.getType().isDirected()) { throw new IllegalArgumentException(message); } return graph; } /** * Checks that the specified graph is directed and throws an {@link IllegalArgumentException} if * it is not. Also checks that the graph reference is not {@code null} and throws a * {@link NullPointerException} if it is. * * @param graph the graph reference to check for beeing directed and not null * @param the graph vertex type * @param the graph edge type * @return {@code graph} if directed and not {@code null} * @throws NullPointerException if {@code graph} is {@code null} * @throws IllegalArgumentException if {@code graph} is not directed */ public static Graph requireDirected(Graph graph) { return requireDirected(graph, GRAPH_MUST_BE_DIRECTED); } /** * Checks that the specified graph is undirected and throws a customized * {@link IllegalArgumentException} if it is not. Also checks that the graph reference is not * {@code null} and throws a {@link NullPointerException} if it is. * * @param graph the graph reference to check for being undirected and not null * @param message detail message to be used in the event that an exception is thrown * @param the graph vertex type * @param the graph edge type * @return {@code graph} if undirected and not {@code null} * @throws NullPointerException if {@code graph} is {@code null} * @throws IllegalArgumentException if {@code graph} is not undirected */ public static Graph requireUndirected(Graph graph, String message) { if (graph == null) throw new NullPointerException(GRAPH_CANNOT_BE_NULL); if (!graph.getType().isUndirected()) { throw new IllegalArgumentException(message); } return graph; } /** * Checks that the specified graph is undirected and throws an {@link IllegalArgumentException} * if it is not. Also checks that the graph reference is not {@code null} and throws a * {@link NullPointerException} if it is. * * @param graph the graph reference to check for being undirected and not null * @param the graph vertex type * @param the graph edge type * @return {@code graph} if undirected and not {@code null} * @throws NullPointerException if {@code graph} is {@code null} * @throws IllegalArgumentException if {@code graph} is not undirected */ public static Graph requireUndirected(Graph graph) { return requireUndirected(graph, GRAPH_MUST_BE_UNDIRECTED); } /** * Checks that the specified graph is directed or undirected and throws a customized * {@link IllegalArgumentException} if it is not. Also checks that the graph reference is not * {@code null} and throws a {@link NullPointerException} if it is. * * @param graph the graph reference to check for beeing directed or undirected and not null * @param message detail message to be used in the event that an exception is thrown * @param the graph vertex type * @param the graph edge type * @return {@code graph} if directed and not {@code null} * @throws NullPointerException if {@code graph} is {@code null} * @throws IllegalArgumentException if {@code graph} is mixed */ public static Graph requireDirectedOrUndirected(Graph graph, String message) { if (graph == null) throw new NullPointerException(GRAPH_CANNOT_BE_NULL); if (!graph.getType().isDirected() && !graph.getType().isUndirected()) { throw new IllegalArgumentException(message); } return graph; } /** * Checks that the specified graph is directed and throws an {@link IllegalArgumentException} if * it is not. Also checks that the graph reference is not {@code null} and throws a * {@link NullPointerException} if it is. * * @param graph the graph reference to check for beeing directed and not null * @param the graph vertex type * @param the graph edge type * @return {@code graph} if directed and not {@code null} * @throws NullPointerException if {@code graph} is {@code null} * @throws IllegalArgumentException if {@code graph} is mixed */ public static Graph requireDirectedOrUndirected(Graph graph) { return requireDirectedOrUndirected(graph, GRAPH_MUST_BE_DIRECTED_OR_UNDIRECTED); } /** * Checks that the specified graph is weighted and throws a customized * {@link IllegalArgumentException} if it is not. Also checks that the graph reference is not * {@code null} and throws a {@link NullPointerException} if it is. * * @param graph the graph reference to check for being weighted and not null * @param the graph vertex type * @param the graph edge type * @return {@code graph} if directed and not {@code null} * @throws NullPointerException if {@code graph} is {@code null} * @throws IllegalArgumentException if {@code graph} is not weighted */ public static Graph requireWeighted(Graph graph) { if (graph == null) throw new NullPointerException(GRAPH_CANNOT_BE_NULL); if (!graph.getType().isWeighted()) { throw new IllegalArgumentException(GRAPH_MUST_BE_WEIGHTED); } return graph; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/GraphType.java000066400000000000000000000122601402514743400266340ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; /** * A graph type. * *

* The graph type describes various properties of a graph such as whether it is directed, undirected * or mixed, whether it contain self-loops (a self-loop is an edge where the source vertex is the * same as the target vertex), whether it contain multiple (parallel) edges (multiple edges which * connect the same pair of vertices) and whether it is weighted or not. * *

* The type of a graph can be queried on runtime using method {@link Graph#getType()}. This way, for * example, an algorithm can have different behavior based on whether the input graph is directed or * undirected, etc. * * @author Dimitrios Michail */ public interface GraphType { /** * Returns true if all edges of the graph are directed, false otherwise. * * @return true if all edges of the graph are directed, false otherwise */ boolean isDirected(); /** * Returns true if all edges of the graph are undirected, false otherwise. * * @return true if all edges of the graph are undirected, false otherwise */ boolean isUndirected(); /** * Returns true if the graph contain both directed and undirected edges, false otherwise. * * @return true if the graph contain both directed and undirected edges, false otherwise */ boolean isMixed(); /** * Returns true if and only if multiple (parallel) edges are allowed in this graph. * The meaning of multiple edges is that there can be many edges going from vertex v1 to vertex * v2. * * @return true if and only if multiple (parallel) edges are allowed. */ boolean isAllowingMultipleEdges(); /** * Returns true if and only if self-loops are allowed in this graph. A self loop is * an edge that its source and target vertices are the same. * * @return true if and only if graph self-loops are allowed. */ boolean isAllowingSelfLoops(); /** * Returns true if and only if cycles are allowed in this graph. * * @return true if and only if graph cycles are allowed. */ boolean isAllowingCycles(); /** * Returns true if and only if the graph supports edge weights. * * @return true if the graph supports edge weights, false otherwise. */ boolean isWeighted(); /** * Returns true if the graph is simple, false otherwise. * * @return true if the graph is simple, false otherwise */ boolean isSimple(); /** * Returns true if the graph is a pseudograph, false otherwise. * * @return true if the graph is a pseudograph, false otherwise */ boolean isPseudograph(); /** * Returns true if the graph is a multigraph, false otherwise. * * @return true if the graph is a multigraph, false otherwise */ boolean isMultigraph(); /** * Returns true if the graph is modifiable, false otherwise. * * @return true if the graph is modifiable, false otherwise */ boolean isModifiable(); /** * Create a directed variant of the current graph type. * * @return a directed variant of the current graph type */ GraphType asDirected(); /** * Create an undirected variant of the current graph type. * * @return an undirected variant of the current graph type */ GraphType asUndirected(); /** * Create a mixed variant of the current graph type. * * @return a mixed variant of the current graph type */ GraphType asMixed(); /** * Create an unweighted variant of the current graph type. * * @return an unweighted variant of the current graph type */ GraphType asUnweighted(); /** * Create a weighted variant of the current graph type. * * @return a weighted variant of the current graph type */ GraphType asWeighted(); /** * Create a modifiable variant of the current graph type. * * @return a modifiable variant of the current graph type */ GraphType asModifiable(); /** * Create an unmodifiable variant of the current graph type. * * @return a unmodifiable variant of the current graph type */ GraphType asUnmodifiable(); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/Graphs.java000066400000000000000000000517331402514743400261650ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; import java.util.function.*; /** * A collection of utilities to assist with graph manipulation. * * @author Barak Naveh */ public abstract class Graphs { /** * Creates a new edge and adds it to the specified graph similarly to the * {@link Graph#addEdge(Object, Object)} method. * * @param g the graph for which the edge to be added * @param sourceVertex source vertex of the edge * @param targetVertex target vertex of the edge * @param weight weight of the edge * @param the graph vertex type * @param the graph edge type * * @return The newly created edge if added to the graph, otherwise * null. * * @throws UnsupportedOperationException if the graph has no edge supplier * * @see Graph#addEdge(Object, Object) */ public static E addEdge(Graph g, V sourceVertex, V targetVertex, double weight) { Supplier edgeSupplier = g.getEdgeSupplier(); if (edgeSupplier == null) { throw new UnsupportedOperationException("Graph contains no edge supplier"); } E e = edgeSupplier.get(); if (g.addEdge(sourceVertex, targetVertex, e)) { g.setEdgeWeight(e, weight); return e; } else { return null; } } /** * Adds the specified source and target vertices to the graph, if not already included, and * creates a new edge and adds it to the specified graph similarly to the * {@link Graph#addEdge(Object, Object)} method. * * @param g the graph for which the specified edge to be added * @param sourceVertex source vertex of the edge * @param targetVertex target vertex of the edge * @param the graph vertex type * @param the graph edge type * * @return The newly created edge if added to the graph, otherwise * null. */ public static E addEdgeWithVertices(Graph g, V sourceVertex, V targetVertex) { g.addVertex(sourceVertex); g.addVertex(targetVertex); return g.addEdge(sourceVertex, targetVertex); } /** * Adds the specified edge to the graph, including its vertices if not already included. * * @param targetGraph the graph for which the specified edge to be added * @param sourceGraph the graph in which the specified edge is already present * @param edge edge to add * @param the graph vertex type * @param the graph edge type * * @return true if the target graph did not already contain the specified edge. */ public static boolean addEdgeWithVertices(Graph targetGraph, Graph sourceGraph, E edge) { V sourceVertex = sourceGraph.getEdgeSource(edge); V targetVertex = sourceGraph.getEdgeTarget(edge); targetGraph.addVertex(sourceVertex); targetGraph.addVertex(targetVertex); return targetGraph.addEdge(sourceVertex, targetVertex, edge); } /** * Adds the specified source and target vertices to the graph, if not already included, and * creates a new weighted edge and adds it to the specified graph similarly to the * {@link Graph#addEdge(Object, Object)} method. * * @param g the graph for which the specified edge to be added * @param sourceVertex source vertex of the edge * @param targetVertex target vertex of the edge * @param weight weight of the edge * @param the graph vertex type * @param the graph edge type * * @return The newly created edge if added to the graph, otherwise * null. */ public static E addEdgeWithVertices(Graph g, V sourceVertex, V targetVertex, double weight) { g.addVertex(sourceVertex); g.addVertex(targetVertex); return addEdge(g, sourceVertex, targetVertex, weight); } /** * Adds all the vertices and all the edges of the specified source graph to the specified * destination graph. First all vertices of the source graph are added to the destination graph. * Then every edge of the source graph is added to the destination graph. This method returns * true if the destination graph has been modified as a result of this operation, * otherwise it returns false. * *

* The behavior of this operation is undefined if any of the specified graphs is modified while * operation is in progress. *

* * @param destination the graph to which vertices and edges are added * @param source the graph used as source for vertices and edges to add * @param the graph vertex type * @param the graph edge type * * @return true if and only if the destination graph has been changed as a result * of this operation. */ public static boolean addGraph(Graph destination, Graph source) { boolean modified = addAllVertices(destination, source.vertexSet()); modified |= addAllEdges(destination, source, source.edgeSet()); return modified; } /** * Adds all the vertices and all the edges of the specified source digraph to the specified * destination digraph, reversing all of the edges. If you want to do this as a linked view of * the source graph (rather than by copying to a destination graph), use * {@link EdgeReversedGraph} instead. * *

* The behavior of this operation is undefined if any of the specified graphs is modified while * operation is in progress. * * @param destination the graph to which vertices and edges are added * @param source the graph used as source for vertices and edges to add * @param the graph vertex type * @param the graph edge type * * @see EdgeReversedGraph */ public static void addGraphReversed(Graph destination, Graph source) { if (!source.getType().isDirected() || !destination.getType().isDirected()) { throw new IllegalArgumentException("graph must be directed"); } addAllVertices(destination, source.vertexSet()); for (E edge : source.edgeSet()) { destination.addEdge(source.getEdgeTarget(edge), source.getEdgeSource(edge)); } } /** * Adds a subset of the edges of the specified source graph to the specified destination graph. * The behavior of this operation is undefined if either of the graphs is modified while the * operation is in progress. {@link #addEdgeWithVertices} is used for the transfer, so source * vertexes will be added automatically to the target graph. * * @param destination the graph to which edges are to be added * @param source the graph used as a source for edges to add * @param edges the edges to be added * @param the graph vertex type * @param the graph edge type * * @return true if this graph changed as a result of the call */ public static boolean addAllEdges( Graph destination, Graph source, Collection edges) { boolean modified = false; for (E e : edges) { V s = source.getEdgeSource(e); V t = source.getEdgeTarget(e); destination.addVertex(s); destination.addVertex(t); modified |= destination.addEdge(s, t, e); } return modified; } /** * Adds all of the specified vertices to the destination graph. The behavior of this operation * is undefined if the specified vertex collection is modified while the operation is in * progress. This method will invoke the {@link Graph#addVertex(Object)} method. * * @param destination the graph to which edges are to be added * @param vertices the vertices to be added to the graph * @param the graph vertex type * @param the graph edge type * * @return true if graph changed as a result of the call * * @throws NullPointerException if the specified vertices contains one or more null vertices, or * if the specified vertex collection is * null. * * @see Graph#addVertex(Object) */ public static boolean addAllVertices( Graph destination, Collection vertices) { boolean modified = false; for (V v : vertices) { modified |= destination.addVertex(v); } return modified; } /** * Returns a list of vertices that are the neighbors of a specified vertex. If the graph is a * multigraph vertices may appear more than once in the returned list. * *

* The method uses {@link Graph#edgesOf(Object)} to traverse the graph. * * @param g the graph to look for neighbors in * @param vertex the vertex to get the neighbors of * @param the graph vertex type * @param the graph edge type * * @return a list of the vertices that are the neighbors of the specified vertex. */ public static List neighborListOf(Graph g, V vertex) { List neighbors = new ArrayList<>(); for (E e : g.iterables().edgesOf(vertex)) { neighbors.add(getOppositeVertex(g, e, vertex)); } return neighbors; } /** * Returns a set of vertices that are neighbors of a specified vertex. * * @param g the graph to look for neighbors in * @param vertex the vertex to get the neighbors of * @param the graph vertex type * @param the graph edge type * @return a set of the vertices that are neighbors of the specified vertex */ public static Set neighborSetOf(Graph g, V vertex) { Set neighbors = new LinkedHashSet<>(); for (E e : g.iterables().edgesOf(vertex)) { neighbors.add(Graphs.getOppositeVertex(g, e, vertex)); } return neighbors; } /** * Returns a list of vertices that are the direct predecessors of a specified vertex. If the * graph is a multigraph, vertices may appear more than once in the returned list. * *

* The method uses {@link Graph#incomingEdgesOf(Object)} to traverse the graph. * * @param g the graph to look for predecessors in * @param vertex the vertex to get the predecessors of * @param the graph vertex type * @param the graph edge type * * @return a list of the vertices that are the direct predecessors of the specified vertex. */ public static List predecessorListOf(Graph g, V vertex) { List predecessors = new ArrayList<>(); for (E e : g.iterables().incomingEdgesOf(vertex)) { predecessors.add(getOppositeVertex(g, e, vertex)); } return predecessors; } /** * Returns a list of vertices that are the direct successors of a specified vertex. If the graph * is a multigraph vertices may appear more than once in the returned list. * *

* The method uses {@link Graph#outgoingEdgesOf(Object)} to traverse the graph. * * @param g the graph to look for successors in * @param vertex the vertex to get the successors of * @param the graph vertex type * @param the graph edge type * * @return a list of the vertices that are the direct successors of the specified vertex. */ public static List successorListOf(Graph g, V vertex) { List successors = new ArrayList<>(); for (E e : g.iterables().outgoingEdgesOf(vertex)) { successors.add(getOppositeVertex(g, e, vertex)); } return successors; } /** * Returns an undirected view of the specified graph. If the specified graph is directed, * returns an undirected view of it. If the specified graph is already undirected, just returns * it. * * @param g the graph for which an undirected view is to be returned * @param the graph vertex type * @param the graph edge type * * @return an undirected view of the specified graph, if it is directed, or or the specified * graph itself if it is already undirected. * * @throws IllegalArgumentException if the graph is neither directed nor undirected * @see AsUndirectedGraph */ public static Graph undirectedGraph(Graph g) { if (g.getType().isDirected()) { return new AsUndirectedGraph<>(g); } else if (g.getType().isUndirected()) { return g; } else { throw new IllegalArgumentException("graph must be either directed or undirected"); } } /** * Tests whether an edge is incident to a vertex. * * @param g graph containing e and v * @param e edge in g * @param v vertex in g * @param the graph vertex type * @param the graph edge type * * @return true iff e is incident on v */ public static boolean testIncidence(Graph g, E e, V v) { return (g.getEdgeSource(e).equals(v)) || (g.getEdgeTarget(e).equals(v)); } /** * Gets the vertex opposite another vertex across an edge. * * @param g graph containing e and v * @param e edge in g * @param v vertex in g * @param the graph vertex type * @param the graph edge type * * @return vertex opposite to v across e */ public static V getOppositeVertex(Graph g, E e, V v) { V source = g.getEdgeSource(e); V target = g.getEdgeTarget(e); if (v.equals(source)) { return target; } else if (v.equals(target)) { return source; } else { throw new IllegalArgumentException("no such vertex: " + v.toString()); } } /** * Removes the given vertex from the given graph. If the vertex to be removed has one or more * predecessors, the predecessors will be connected directly to the successors of the vertex to * be removed. * * @param graph graph to be mutated * @param vertex vertex to be removed from this graph, if present * @param the graph vertex type * @param the graph edge type * * @return true if the graph contained the specified vertex; false otherwise. */ public static boolean removeVertexAndPreserveConnectivity(Graph graph, V vertex) { if (!graph.containsVertex(vertex)) { return false; } if (vertexHasPredecessors(graph, vertex)) { List predecessors = Graphs.predecessorListOf(graph, vertex); List successors = Graphs.successorListOf(graph, vertex); for (V predecessor : predecessors) { addOutgoingEdges(graph, predecessor, successors); } } graph.removeVertex(vertex); return true; } /** * Filters vertices from the given graph and subsequently removes them. If the vertex to be * removed has one or more predecessors, the predecessors will be connected directly to the * successors of the vertex to be removed. * * @param graph graph to be mutated * @param predicate a non-interfering stateless predicate to apply to each vertex to determine * if it should be removed from the graph * @param the graph vertex type * @param the graph edge type * * @return true if at least one vertex has been removed; false otherwise. */ public static boolean removeVerticesAndPreserveConnectivity(Graph graph, Predicate predicate) { List verticesToRemove = new ArrayList<>(); for (V node : graph.vertexSet()) { if (predicate.test(node)) { verticesToRemove.add(node); } } return removeVertexAndPreserveConnectivity(graph, verticesToRemove); } /** * Removes all the given vertices from the given graph. If the vertex to be removed has one or * more predecessors, the predecessors will be connected directly to the successors of the * vertex to be removed. * * @param graph to be mutated * @param vertices vertices to be removed from this graph, if present * @param the graph vertex type * @param the graph edge type * * @return true if at least one vertex has been removed; false otherwise. */ public static boolean removeVertexAndPreserveConnectivity(Graph graph, Iterable vertices) { boolean atLeastOneVertexHasBeenRemoved = false; for (V vertex : vertices) { if (removeVertexAndPreserveConnectivity(graph, vertex)) { atLeastOneVertexHasBeenRemoved = true; } } return atLeastOneVertexHasBeenRemoved; } /** * Add edges from one source vertex to multiple target vertices. Whether duplicates are created * depends on the underlying {@link Graph} implementation. * * @param graph graph to be mutated * @param source source vertex of the new edges * @param targets target vertices for the new edges * @param the graph vertex type * @param the graph edge type */ public static void addOutgoingEdges(Graph graph, V source, Iterable targets) { if (!graph.containsVertex(source)) { graph.addVertex(source); } for (V target : targets) { if (!graph.containsVertex(target)) { graph.addVertex(target); } graph.addEdge(source, target); } } /** * Add edges from multiple source vertices to one target vertex. Whether duplicates are created * depends on the underlying {@link Graph} implementation. * * @param graph graph to be mutated * @param target target vertex for the new edges * @param sources source vertices for the new edges * @param the graph vertex type * @param the graph edge type */ public static void addIncomingEdges(Graph graph, V target, Iterable sources) { if (!graph.containsVertex(target)) { graph.addVertex(target); } for (V source : sources) { if (!graph.containsVertex(source)) { graph.addVertex(source); } graph.addEdge(source, target); } } /** * Check if a vertex has any direct successors. * * @param graph the graph to look for successors * @param vertex the vertex to look for successors * @param the graph vertex type * @param the graph edge type * * @return true if the vertex has any successors, false otherwise */ public static boolean vertexHasSuccessors(Graph graph, V vertex) { return !graph.outgoingEdgesOf(vertex).isEmpty(); } /** * Check if a vertex has any direct predecessors. * * @param graph the graph to look for predecessors * @param vertex the vertex to look for predecessors * @param the graph vertex type * @param the graph edge type * * @return true if the vertex has any predecessors, false otherwise */ public static boolean vertexHasPredecessors(Graph graph, V vertex) { return !graph.incomingEdgesOf(vertex).isEmpty(); } /** * Compute a new mapping from the vertices of a graph to the integer range $[0, n)$ where $n$ is * the number of vertices in the graph. * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @throws NullPointerException if {@code graph} is {@code null} * * @return the mapping as an object containing the {@code vertexMap} and the {@code indexList} * * @see VertexToIntegerMapping */ public static VertexToIntegerMapping getVertexToIntegerMapping(Graph graph) { return new VertexToIntegerMapping<>(Objects.requireNonNull(graph).vertexSet()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/ListenableGraph.java000066400000000000000000000034221402514743400277750ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import org.jgrapht.event.*; /** * A graph that supports listeners on structural change events. * * @param the graph vertex type * @param the graph edge type * * @see GraphListener * @see VertexSetListener * * @author Barak Naveh */ public interface ListenableGraph extends Graph { /** * Adds the specified graph listener to this graph, if not already present. * * @param l the listener to be added. */ public void addGraphListener(GraphListener l); /** * Adds the specified vertex set listener to this graph, if not already present. * * @param l the listener to be added. */ public void addVertexSetListener(VertexSetListener l); /** * Removes the specified graph listener from this graph, if present. * * @param l the listener to be removed. */ public void removeGraphListener(GraphListener l); /** * Removes the specified vertex set listener from this graph, if present. * * @param l the listener to be removed. */ public void removeVertexSetListener(VertexSetListener l); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/000077500000000000000000000000001402514743400246305ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/StoerWagnerMinimumCut.java000066400000000000000000000217671402514743400317600ustar00rootroot00000000000000/* * (C) Copyright 2011-2021, by Robby McKilliam, Ernst de Ridder and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; /** * Implements the Stoer and Wagner minimum cut * algorithm. Deterministically computes the minimum cut in $O(|V||E| + |V| \log |V|)$ time. * This implementation uses Java's PriorityQueue and requires $O(|V||E| \log |E|)$ time. M. Stoer * and F. Wagner, "A Simple Min-Cut Algorithm", Journal of the ACM, volume 44, number 4. pp 585-591, * 1997. * * @param the graph vertex type * @param the graph edge type * * @author Robby McKilliam * @author Ernst de Ridder */ public class StoerWagnerMinimumCut { final Graph, DefaultWeightedEdge> workingGraph; protected double bestCutWeight = Double.POSITIVE_INFINITY; protected Set bestCut; /** * Will compute the minimum cut in graph. * * @param graph graph over which to run algorithm * * @throws IllegalArgumentException if a negative weight edge is found * @throws IllegalArgumentException if graph has less than 2 vertices */ public StoerWagnerMinimumCut(Graph graph) { GraphTests.requireUndirected(graph, "Graph must be undirected"); if (graph.vertexSet().size() < 2) { throw new IllegalArgumentException("Graph has less than 2 vertices"); } // get a version of this graph where each vertex is wrapped with a list workingGraph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Map> vertexMap = new HashMap<>(); for (V v : graph.vertexSet()) { Set list = new HashSet<>(); list.add(v); vertexMap.put(v, list); workingGraph.addVertex(list); } for (E e : graph.edgeSet()) { if (graph.getEdgeWeight(e) < 0.0) { throw new IllegalArgumentException("Negative edge weights not allowed"); } V s = graph.getEdgeSource(e); Set sNew = vertexMap.get(s); V t = graph.getEdgeTarget(e); Set tNew = vertexMap.get(t); // For multigraphs, we sum the edge weights (either all are // contained in a cut, or none) DefaultWeightedEdge eNew = workingGraph.getEdge(sNew, tNew); if (eNew == null) { eNew = workingGraph.addEdge(sNew, tNew); workingGraph.setEdgeWeight(eNew, graph.getEdgeWeight(e)); } else { workingGraph .setEdgeWeight(eNew, workingGraph.getEdgeWeight(eNew) + graph.getEdgeWeight(e)); } } // arbitrary vertex used to seed the algorithm. Set a = workingGraph.vertexSet().iterator().next(); while (workingGraph.vertexSet().size() > 1) { minimumCutPhase(a); } } /** * Implements the MinimumCutPhase function of Stoer and Wagner. * * @param a the vertex */ protected void minimumCutPhase(Set a) { // The last and before last vertices added to A. Set last = a, beforelast = null; // queue contains vertices not in A ordered by max weight of edges to A. PriorityQueue queue = new PriorityQueue<>(); // Maps vertices to elements of queue Map, VertexAndWeight> dmap = new HashMap<>(); // Initialize queue for (Set v : workingGraph.vertexSet()) { if (v == a) { continue; } DefaultWeightedEdge e = workingGraph.getEdge(v, a); Double w = (e == null) ? 0.0 : workingGraph.getEdgeWeight(e); VertexAndWeight vandw = new VertexAndWeight(v, w, e != null); queue.add(vandw); dmap.put(v, vandw); } // Now iteratively update the queue to get the required vertex ordering while (!queue.isEmpty()) { Set v = queue.poll().vertex; dmap.remove(v); beforelast = last; last = v; for (DefaultWeightedEdge e : workingGraph.edgesOf(v)) { Set vc = Graphs.getOppositeVertex(workingGraph, e, v); VertexAndWeight vcandw = dmap.get(vc); if (vcandw != null) { queue.remove(vcandw); // this is O(log n) but could be O(1)? vcandw.active = true; vcandw.weight += workingGraph.getEdgeWeight(e); queue.add(vcandw); // this is O(log n) but could be O(1)? } } } // Update the best cut double w = vertexWeight(last); if (w < bestCutWeight) { bestCutWeight = w; bestCut = last; } // merge the last added vertices mergeVertices(beforelast, last); } /** * Return the weight of the minimum cut * * @return the weight of the minimum cut */ public double minCutWeight() { return bestCutWeight; } /** * Return a set of vertices on one side of the cut * * @return a set of vertices on one side of the cut */ public Set minCut() { return bestCut; } /** * Merges vertex $t$ into vertex $s$, summing the weights as required. Returns the merged vertex * and the sum of its weights * * @param s the first vertex * @param t the second vertex * * @return the merged vertex and its weight */ protected VertexAndWeight mergeVertices(Set s, Set t) { // construct the new combinedvertex Set set = new HashSet<>(); set.addAll(s); set.addAll(t); workingGraph.addVertex(set); // add edges and weights to the combined vertex double wsum = 0.0; for (Set v : workingGraph.vertexSet()) { if ((s != v) && (t != v)) { double neww = 0.0; DefaultWeightedEdge etv = workingGraph.getEdge(t, v); DefaultWeightedEdge esv = workingGraph.getEdge(s, v); if (etv != null) { neww += workingGraph.getEdgeWeight(etv); } if (esv != null) { neww += workingGraph.getEdgeWeight(esv); } if ((etv != null) || (esv != null)) { wsum += neww; workingGraph.setEdgeWeight(workingGraph.addEdge(set, v), neww); } } } // remove original vertices workingGraph.removeVertex(t); workingGraph.removeVertex(s); return new VertexAndWeight(set, wsum, false); } /** * Compute the sum of the weights entering a vertex * * @param v the vertex * @return the sum of the weights entering a vertex */ public double vertexWeight(Set v) { double wsum = 0.0; for (DefaultWeightedEdge e : workingGraph.edgesOf(v)) { wsum += workingGraph.getEdgeWeight(e); } return wsum; } /** * Class for weighted vertices */ protected class VertexAndWeight implements Comparable { public Set vertex; public Double weight; public boolean active; // active == neighbour in A /** * Construct a new weighted vertex. * * @param v the vertex * @param w the weight of the vertex * @param active whether it is active */ public VertexAndWeight(Set v, double w, boolean active) { this.vertex = v; this.weight = w; this.active = active; } /** * compareTo that sorts in reverse order because we need extract-max and queue provides * extract-min. */ @Override public int compareTo(VertexAndWeight that) { if (this.active && that.active) { return -Double.compare(weight, that.weight); } if (this.active && !that.active) { return -1; } if (!this.active && that.active) { return +1; } // both inactive return 0; } @Override public String toString() { return "(" + vertex + ", " + weight + ")"; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/TransitiveClosure.java000066400000000000000000000076671402514743400312000ustar00rootroot00000000000000/* * (C) Copyright 2007-2021, by Vinayak R Borkar and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import java.util.*; /** * Constructs the transitive closure of the input graph. * * @author Vinayak R. Borkar */ public class TransitiveClosure { /** * Singleton instance. */ public static final TransitiveClosure INSTANCE = new TransitiveClosure(); /** * Private Constructor. */ private TransitiveClosure() { } /** * Computes the transitive closure of the given graph. * * @param graph - Graph to compute transitive closure for. * @param the graph vertex type * @param the graph edge type */ public void closeSimpleDirectedGraph(SimpleDirectedGraph graph) { Set vertexSet = graph.vertexSet(); Set newEdgeTargets = new HashSet<>(); // At every iteration of the outer loop, we add a path of length 1 // between nodes that originally had a path of length 2. In the worst // case, we need to make floor(log |V|) + 1 iterations. We stop earlier // if there is no change to the output graph. int bound = computeBinaryLog(vertexSet.size()); boolean done = false; for (int i = 0; !done && (i < bound); ++i) { done = true; for (V v1 : vertexSet) { newEdgeTargets.clear(); for (E v1OutEdge : graph.outgoingEdgesOf(v1)) { V v2 = graph.getEdgeTarget(v1OutEdge); for (E v2OutEdge : graph.outgoingEdgesOf(v2)) { V v3 = graph.getEdgeTarget(v2OutEdge); if (v1.equals(v3)) { // Its a simple graph, so no self loops. continue; } if (graph.getEdge(v1, v3) != null) { // There is already an edge from v1 ---> v3, skip; continue; } newEdgeTargets.add(v3); done = false; } } for (V v3 : newEdgeTargets) { graph.addEdge(v1, v3); } } } } /** * Computes floor($\log_2 (n)$) $+ 1$ */ private int computeBinaryLog(int n) { assert n >= 0; int result = 0; while (n > 0) { n >>= 1; ++result; } return result; } /** * Computes the transitive closure of a directed acyclic graph in $O(nm)$ * * @param graph - Graph to compute transitive closure for. * @param the graph vertex type * @param the graph edge type */ public void closeDirectedAcyclicGraph(DirectedAcyclicGraph graph) { Deque orderedVertices = new ArrayDeque<>(graph.vertexSet().size()); new TopologicalOrderIterator<>(graph).forEachRemaining(orderedVertices::addFirst); for (V vertex : orderedVertices) { for (V successor : Graphs.successorListOf(graph, vertex)) { for (V closureVertex : Graphs.successorListOf(graph, successor)) { graph.addEdge(vertex, closureVertex); } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/TransitiveReduction.java000066400000000000000000000132671402514743400315110ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Christophe Thiebaud and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg; import org.jgrapht.*; import java.util.*; /** * An implementation of Harry Hsu's * transitive reduction algorithm. * *

* cf. Harry Hsu. "An * algorithm for finding a minimal equivalent graph of a digraph.", Journal of the ACM, 22(1):11-16, * January 1975. *

* *

* This is a port from a python example by Michael Clerx, posted as an answer to a question about * * transitive reduction algorithm pseudocode on Stack * Overflow *

* * @author Christophe Thiebaud */ public class TransitiveReduction { /** * Singleton instance. */ public static final TransitiveReduction INSTANCE = new TransitiveReduction(); /** * Private Constructor. */ private TransitiveReduction() { } /** * The matrix passed as input parameter will be transformed into a path matrix. * *

* This method is package visible for unit testing, but it is meant as a private method. *

* * @param matrix the original matrix to transform into a path matrix */ static void transformToPathMatrix(BitSet[] matrix) { // compute path matrix for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix.length; j++) { if (i == j) { continue; } if (matrix[j].get(i)) { for (int k = 0; k < matrix.length; k++) { if (!matrix[j].get(k)) { matrix[j].set(k, matrix[i].get(k)); } } } } } } /** * The path matrix passed as input parameter will be transformed into a transitively reduced * matrix. * *

* This method is package visible for unit testing, but it is meant as a private method. *

* * @param pathMatrix the path matrix to reduce */ static void transitiveReduction(BitSet[] pathMatrix) { // transitively reduce for (int j = 0; j < pathMatrix.length; j++) { for (int i = 0; i < pathMatrix.length; i++) { if (pathMatrix[i].get(j)) { for (int k = 0; k < pathMatrix.length; k++) { if (pathMatrix[j].get(k)) { pathMatrix[i].set(k, false); } } } } } } /** * This method will remove all transitive edges from the graph passed as input parameter. * *

* You may want to clone the graph before, as transitive edges will be pitilessly removed. *

* * e.g. * *
     * {
     *     @code DirectedGraph<V, T> soonToBePrunedDirectedGraph;
     *
     *     TransitiveReduction.INSTANCE.reduce(soonToBePrunedDirectedGraph);
     *
     *     // pruned !
     * }
     * 
* * @param directedGraph the directed graph that will be reduced transitively * @param the graph vertex type * @param the graph edge type */ public void reduce(final Graph directedGraph) { GraphTests.requireDirected(directedGraph, "Graph must be directed"); final List vertices = new ArrayList<>(directedGraph.vertexSet()); final int n = vertices.size(); BitSet[] originalMatrix = new BitSet[n]; for (int i = 0; i < originalMatrix.length; i++) { originalMatrix[i] = new BitSet(n); } // initialize matrix with zeros // 'By default, all bits in the set initially have the value false.' // cf. http://docs.oracle.com/javase/7/docs/api/java/util/BitSet.html // initialize matrix with edges for (final E edge : directedGraph.edgeSet()) { final V v1 = directedGraph.getEdgeSource(edge); final V v2 = directedGraph.getEdgeTarget(edge); final int v_1 = vertices.indexOf(v1); final int v_2 = vertices.indexOf(v2); originalMatrix[v_1].set(v_2); } // create path matrix from original matrix final BitSet[] pathMatrix = originalMatrix; transformToPathMatrix(pathMatrix); // create reduced matrix from path matrix final BitSet[] transitivelyReducedMatrix = pathMatrix; transitiveReduction(transitivelyReducedMatrix); // remove edges from the DirectedGraph which are not in the reduced // matrix for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (!transitivelyReducedMatrix[i].get(j)) { directedGraph .removeEdge(directedGraph.getEdge(vertices.get(i), vertices.get(j))); } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clique/000077500000000000000000000000001402514743400261125ustar00rootroot00000000000000BaseBronKerboschCliqueFinder.java000066400000000000000000000062151402514743400343510ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clique/* * (C) Copyright 2005-2021, by Ewgenij Proschak and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; import java.util.concurrent.*; /** * Base implementation of the Bron-Kerbosch algorithm. * * @param the graph vertex type * @param the graph edge type * * @author Ewgenij Proschak */ abstract class BaseBronKerboschCliqueFinder implements MaximalCliqueEnumerationAlgorithm { /** * The underlying graph */ protected final Graph graph; /** * Timeout in nanoseconds */ protected final long nanos; /** * Whether the last computation terminated due to a time limit. */ protected boolean timeLimitReached; /** * The result */ protected List> allMaximalCliques; /** * Size of biggest maximal clique found. */ protected int maxSize; /** * Constructor * * @param graph the input graph; must be simple * @param timeout the maximum time to wait, if zero no timeout * @param unit the time unit of the timeout argument */ public BaseBronKerboschCliqueFinder(Graph graph, long timeout, TimeUnit unit) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); if (timeout == 0L) { this.nanos = Long.MAX_VALUE; } else { this.nanos = unit.toNanos(timeout); } if (this.nanos < 1L) { throw new IllegalArgumentException("Invalid timeout, must be positive"); } this.timeLimitReached = false; } @Override public Iterator> iterator() { lazyRun(); return allMaximalCliques.iterator(); } /** * Create an iterator which returns only the maximum cliques of a graph. The iterator computes * all maximal cliques and then filters them by the size of the maximum found clique. * * @return an iterator which returns only the maximum cliques of a graph */ public Iterator> maximumIterator() { lazyRun(); return allMaximalCliques.stream().filter(c -> c.size() == maxSize).iterator(); } /** * Check the computation has stopped due to a time limit or due to computing all maximal * cliques. * * @return true if the computation has stopped due to a time limit, false otherwise */ public boolean isTimeLimitReached() { return timeLimitReached; } /** * Lazily start the computation. */ protected abstract void lazyRun(); } BronKerboschCliqueFinder.java000066400000000000000000000122111402514743400335470ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clique/* * (C) Copyright 2005-2021, by Ewgenij Proschak and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import java.util.*; import java.util.concurrent.*; /** * Bron-Kerbosch maximal clique enumeration algorithm. * *

* Implementation of the Bron-Kerbosch clique enumeration algorithm as described in: *

    *
  • R. Samudrala and J. Moult. A graph-theoretic algorithm for comparative modeling of protein * structure. Journal of Molecular Biology, 279(1):287--302, 1998.
  • *
* *

* The algorithm first computes all maximal cliques and then returns the result to the user. A * timeout can be set using the constructor parameters. * * @param the graph vertex type * @param the graph edge type * * @see PivotBronKerboschCliqueFinder * @see DegeneracyBronKerboschCliqueFinder * * @author Ewgenij Proschak */ public class BronKerboschCliqueFinder extends BaseBronKerboschCliqueFinder { /** * Constructs a new clique finder. * * @param graph the input graph; must be simple */ public BronKerboschCliqueFinder(Graph graph) { this(graph, 0L, TimeUnit.SECONDS); } /** * Constructs a new clique finder. * * @param graph the input graph; must be simple * @param timeout the maximum time to wait, if zero no timeout * @param unit the time unit of the timeout argument */ public BronKerboschCliqueFinder(Graph graph, long timeout, TimeUnit unit) { super(graph, timeout, unit); } /** * Lazily execute the enumeration algorithm. */ @Override protected void lazyRun() { if (allMaximalCliques == null) { if (!GraphTests.isSimple(graph)) { throw new IllegalArgumentException("Graph must be simple"); } allMaximalCliques = new ArrayList<>(); long nanosTimeLimit; try { nanosTimeLimit = Math.addExact(System.nanoTime(), nanos); } catch (ArithmeticException ignore) { nanosTimeLimit = Long.MAX_VALUE; } findCliques( new ArrayList<>(), new ArrayList<>(graph.vertexSet()), new ArrayList<>(), nanosTimeLimit); } } private void findCliques( List potentialClique, List candidates, List alreadyFound, final long nanosTimeLimit) { /* * Termination condition: check if any already found node is connected to all candidate * nodes. */ for (V v : alreadyFound) { if (candidates.stream().allMatch(c -> graph.containsEdge(v, c))) { return; } } /* * Check each candidate */ for (V candidate : new ArrayList<>(candidates)) { /* * Check if timeout */ if (nanosTimeLimit - System.nanoTime() < 0) { timeLimitReached = true; return; } List newCandidates = new ArrayList<>(); List newAlreadyFound = new ArrayList<>(); // move candidate node to potentialClique potentialClique.add(candidate); candidates.remove(candidate); // create newCandidates by removing nodes in candidates not // connected to candidate node for (V newCandidate : candidates) { if (graph.containsEdge(candidate, newCandidate)) { newCandidates.add(newCandidate); } } // create newAlreadyFound by removing nodes in alreadyFound // not connected to candidate node for (V newFound : alreadyFound) { if (graph.containsEdge(candidate, newFound)) { newAlreadyFound.add(newFound); } } // if newCandidates and newAlreadyFound are empty if (newCandidates.isEmpty() && newAlreadyFound.isEmpty()) { // potential clique is maximal clique Set maximalClique = new HashSet<>(potentialClique); allMaximalCliques.add(maximalClique); maxSize = Math.max(maxSize, maximalClique.size()); } else { // recursive call findCliques(potentialClique, newCandidates, newAlreadyFound, nanosTimeLimit); } // move candidate node from potentialClique to alreadyFound alreadyFound.add(candidate); potentialClique.remove(candidate); } } } ChordalGraphMaxCliqueFinder.java000066400000000000000000000152151402514743400342010ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clique/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.alg.color.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.util.*; /** * Calculates a maximum cardinality * clique in a chordal graph. A * chordal graph is a simple graph in which all * cycles of four or more vertices have * a chord. A chord is an edge that is * not part of the cycle but connects two vertices of the cycle. * * To compute the clique, this implementation relies on the {@link ChordalityInspector} to compute a * * perfect elimination order. * * The maximum clique for a chordal graph is computed in $\mathcal{O}(|V| + |E|)$ time. * * All the methods in this class are invoked in a lazy fashion, meaning that computations are only * started once the method gets invoked. * * @param the graph vertex type. * @param the graph edge type. * * @author Timofey Chudakov */ public class ChordalGraphMaxCliqueFinder implements CliqueAlgorithm { private final Graph graph; private final ChordalityInspector.IterationOrder iterationOrder; private Clique maximumClique; private boolean isChordal = true; /** * Creates a new ChordalGraphMaxCliqueFinder instance. The {@link ChordalityInspector} used in * this implementation uses the default {@link MaximumCardinalityIterator} iterator. * * @param graph graph */ public ChordalGraphMaxCliqueFinder(Graph graph) { this(graph, ChordalityInspector.IterationOrder.MCS); } /** * Creates a new ChordalGraphMaxCliqueFinder instance. The {@link ChordalityInspector} used in * this implementation uses either the {@link MaximumCardinalityIterator} iterator or the * {@link LexBreadthFirstIterator} iterator, depending on the parameter {@code iterationOrder}. * * @param graph graph * @param iterationOrder constant which defines iterator to be used by the * {@code ChordalityInspector} in this implementation. */ public ChordalGraphMaxCliqueFinder( Graph graph, ChordalityInspector.IterationOrder iterationOrder) { this.graph = Objects.requireNonNull(graph); this.iterationOrder = Objects.requireNonNull(iterationOrder); } /** * Lazily computes some maximum clique of the {@code graph}. */ private void lazyComputeMaximumClique() { if (maximumClique == null && isChordal) { ChordalGraphColoring cgc = new ChordalGraphColoring<>(graph, iterationOrder); VertexColoringAlgorithm.Coloring coloring = cgc.getColoring(); List perfectEliminationOrder = cgc.getPerfectEliminationOrder(); if (coloring == null) { isChordal = false; // Graph isn't chordal return; } // finds the vertex with the maximum cardinality predecessor list Map vertexInOrder = getVertexInOrder(perfectEliminationOrder); Map.Entry maxEntry = coloring .getColors().entrySet().stream().max(Comparator.comparing(Map.Entry::getValue)) .orElse(null); if (maxEntry == null) { maximumClique = new CliqueImpl<>(Collections.emptySet()); } else { Set cliqueSet = getPredecessors(vertexInOrder, maxEntry.getKey()); cliqueSet.add(maxEntry.getKey()); maximumClique = new CliqueImpl<>(cliqueSet); } } } /** * Returns a map containing vertices from the {@code vertexOrder} mapped to their indices in * {@code vertexOrder}. * * @param vertexOrder a list with vertices. * @return a mapping of vertices from {@code vertexOrder} to their indices in * {@code vertexOrder}. */ private Map getVertexInOrder(List vertexOrder) { Map vertexInOrder = CollectionUtil.newHashMapWithExpectedSize(vertexOrder.size()); int i = 0; for (V vertex : vertexOrder) { vertexInOrder.put(vertex, i++); } return vertexInOrder; } /** * Returns the predecessors of {@code vertex} in the order defined by {@code map}. More * precisely, returns those of {@code vertex}, whose mapped index in {@code map} is less then * the index of {@code vertex}. * * @param vertexInOrder defines the mapping of vertices in {@code graph} to their indices in * order. * @param vertex the vertex whose predecessors in order are to be returned. * @return the predecessors of {@code vertex} in order defines by {@code map}. */ private Set getPredecessors(Map vertexInOrder, V vertex) { Set predecessors = new HashSet<>(); Integer vertexPosition = vertexInOrder.get(vertex); Set edges = graph.edgesOf(vertex); for (E edge : edges) { V oppositeVertex = Graphs.getOppositeVertex(graph, edge, vertex); Integer destPosition = vertexInOrder.get(oppositeVertex); if (destPosition < vertexPosition) predecessors.add(oppositeVertex); } return predecessors; } /** * Returns a maximum cardinality * clique of the inspected {@code graph}. If the graph isn't chordal, returns null. * * @return a maximum clique of the {@code graph} if it is chordal, null otherwise. */ public Clique getClique() { lazyComputeMaximumClique(); return maximumClique; } } CliqueMinimalSeparatorDecomposition.java000066400000000000000000000337061402514743400360560ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clique/* * (C) Copyright 2015-2021, by Florian Buenzli and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.graph.builder.*; import java.util.*; import java.util.Map.*; /** * Clique Minimal Separator Decomposition using MCS-M+ and Atoms algorithm as described in Berry et * al. An Introduction to Clique Minimal Separator Decomposition (2010), DOI:10.3390/a3020197, * http://www.mdpi.com/1999-4893/3/2/197 * *

* The Clique Minimal Separator (CMS) Decomposition is a procedure that splits a graph into a set of * subgraphs separated by minimal clique separators, adding the separating clique to each component * produced by the separation. At the end we have a set of atoms. The CMS decomposition is unique * and yields the set of the atoms independent of the order of the decomposition. * * @param the graph vertex type * @param the graph edge type * * @author Florian Buenzli (fbuenzli@student.ethz.ch) * @author Thomas Tschager (thomas.tschager@inf.ethz.ch) * @author Tomas Hruz (tomas.hruz@inf.ethz.ch) * @author Philipp Hoppen */ public class CliqueMinimalSeparatorDecomposition { /** * Source graph to operate on */ private Graph graph; /** * Minimal triangulation of graph */ private Graph chordalGraph; /** * Fill edges */ private Set fillEdges; /** * Minimal elimination ordering on the vertices of graph */ private LinkedList meo; /** * List of all vertices that generate a minimal separator of * chordGraph */ private List generators; /** * Set of clique minimal separators */ private Set> separators; /** * The atoms generated by the decomposition */ private Set> atoms; /** * Map for each separator how many components it produces. */ private Map, Integer> fullComponentCount = new HashMap<>(); /** * Setup a clique minimal separator decomposition on undirected graph * g. Loops and multiple (parallel) edges are removed, i.e. the graph is transformed to a * simple graph. * * @param g The graph to decompose. */ public CliqueMinimalSeparatorDecomposition(Graph g) { this.graph = GraphTests.requireUndirected(g); this.fillEdges = new HashSet<>(); } /** * Compute the minimal triangulation of the graph. Implementation of Algorithm MCS-M+ as * described in Berry et al. (2010), DOI:10.3390/a3020197 * http://www.mdpi.com/1999-4893/3/2/197 */ private void computeMinimalTriangulation() { // initialize chordGraph with same vertices as graph chordalGraph = GraphTypeBuilder . undirected().edgeSupplier(graph.getEdgeSupplier()) .vertexSupplier(graph.getVertexSupplier()).allowingMultipleEdges(false) .allowingSelfLoops(false).buildGraph(); for (V v : graph.vertexSet()) { chordalGraph.addVertex(v); } // initialize g' as subgraph of graph (same vertices and edges) final Graph gprime = copyAsSimpleGraph(graph); int s = -1; generators = new ArrayList<>(); meo = new LinkedList<>(); final Map vertexLabels = new HashMap<>(); for (V v : gprime.vertexSet()) { vertexLabels.put(v, 0); } for (int i = 1, n = graph.vertexSet().size(); i <= n; i++) { V v = getMaxLabelVertex(vertexLabels); LinkedList Y = new LinkedList<>(Graphs.neighborListOf(gprime, v)); if (vertexLabels.get(v) <= s) { generators.add(v); } s = vertexLabels.get(v); // Mark x reached and all other vertices of gprime unreached HashSet reached = new HashSet<>(); reached.add(v); // mark neighborhood of x reached and add to reach(label(y)) HashMap> reach = new HashMap<>(); // mark y reached and add y to reach for (V y : Y) { reached.add(y); addToReach(vertexLabels.get(y), y, reach); } for (int j = 0; j < graph.vertexSet().size(); j++) { if (!reach.containsKey(j)) { continue; } while (reach.get(j).size() > 0) { // remove a vertex y from reach(j) V y = reach.get(j).iterator().next(); reach.get(j).remove(y); for (V z : Graphs.neighborListOf(gprime, y)) { if (!reached.contains(z)) { reached.add(z); if (vertexLabels.get(z) > j) { Y.add(z); E fillEdge = graph.getEdgeSupplier().get(); fillEdges.add(fillEdge); addToReach(vertexLabels.get(z), z, reach); } else { addToReach(j, z, reach); } } } } } for (V y : Y) { chordalGraph.addEdge(v, y); vertexLabels.put(y, vertexLabels.get(y) + 1); } meo.addLast(v); gprime.removeVertex(v); vertexLabels.remove(v); } } /** * Get the vertex with the maximal label. * * @param vertexLabels Map that gives a label for each vertex. * * @return Vertex with the maximal label. */ private V getMaxLabelVertex(Map vertexLabels) { Iterator> iterator = vertexLabels.entrySet().iterator(); Entry max = iterator.next(); while (iterator.hasNext()) { Entry e = iterator.next(); if (e.getValue() > max.getValue()) { max = e; } } return max.getKey(); } /** * Add a vertex to reach. * * @param k vertex' label * @param v the vertex * @param r the reach structure. */ private void addToReach(Integer k, V v, HashMap> r) { if (r.containsKey(k)) { r.get(k).add(v); } else { HashSet set = new HashSet<>(); set.add(v); r.put(k, set); } } /** * Compute the unique decomposition of the input graph $G$ (atoms of $G$). Implementation of * algorithm Atoms as described in Berry et al. (2010), DOI:10.3390/a3020197, * http://www.mdpi.com/1999-4893/3/2/197 */ private void computeAtoms() { if (chordalGraph == null) { computeMinimalTriangulation(); } separators = new HashSet<>(); // initialize g' as subgraph of graph (same vertices and edges) Graph gprime = copyAsSimpleGraph(graph); // initialize h' as subgraph of chordalGraph (same vertices and edges) Graph hprime = copyAsSimpleGraph(chordalGraph); atoms = new HashSet<>(); Iterator iterator = meo.descendingIterator(); while (iterator.hasNext()) { V v = iterator.next(); if (generators.contains(v)) { Set separator = new HashSet<>(Graphs.neighborListOf(hprime, v)); if (isClique(graph, separator)) { if (separator.size() > 0) { if (separators.contains(separator)) { fullComponentCount .put(separator, fullComponentCount.get(separator) + 1); } else { fullComponentCount.put(separator, 2); separators.add(separator); } } Graph tmpGraph = copyAsSimpleGraph(gprime); tmpGraph.removeAllVertices(separator); ConnectivityInspector con = new ConnectivityInspector<>(tmpGraph); if (con.isConnected()) { throw new RuntimeException("separator did not separate the graph"); } for (Set component : con.connectedSets()) { if (component.contains(v)) { gprime.removeAllVertices(component); component.addAll(separator); atoms.add(new HashSet<>(component)); assert (component.size() > 0); break; } } } } hprime.removeVertex(v); } if (gprime.vertexSet().size() > 0) { atoms.add(new HashSet<>(gprime.vertexSet())); } } /** * Check whether the subgraph of graph induced by the given vertices * is complete, i.e. a clique. * * @param graph the graph. * @param vertices the vertices to induce the subgraph from. * * @return true if the induced subgraph is a clique. */ private static boolean isClique(Graph graph, Set vertices) { for (V v1 : vertices) { for (V v2 : vertices) { if (!v1.equals(v2) && (graph.getEdge(v1, v2) == null)) { return false; } } } return true; } /** * Create a copy of a graph for internal use. * * @param graph the graph to copy. * * @return A copy of the graph projected to a SimpleGraph. */ private static Graph copyAsSimpleGraph(Graph graph) { Graph copy = GraphTypeBuilder . undirected().edgeSupplier(graph.getEdgeSupplier()) .vertexSupplier(graph.getVertexSupplier()).allowingMultipleEdges(false) .allowingSelfLoops(false).buildGraph(); if (graph.getType().isSimple()) { Graphs.addGraph(copy, graph); } else { // project graph to SimpleGraph Graphs.addAllVertices(copy, graph.vertexSet()); for (E e : graph.edgeSet()) { V v1 = graph.getEdgeSource(e); V v2 = graph.getEdgeTarget(e); if (!v1.equals(v2) && !copy.containsEdge(e)) { copy.addEdge(v1, v2); } } } return copy; } /** * Check if the graph is chordal. * * @return true if the graph is chordal, false otherwise. */ public boolean isChordal() { if (chordalGraph == null) { computeMinimalTriangulation(); } return (chordalGraph.edgeSet().size() == graph.edgeSet().size()); } /** * Get the fill edges generated by the triangulation. * * @return Set of fill edges. */ public Set getFillEdges() { if (fillEdges == null) { computeMinimalTriangulation(); } return fillEdges; } /** * Get the minimal triangulation of the graph. * * @return Triangulated graph. */ public Graph getMinimalTriangulation() { if (chordalGraph == null) { computeMinimalTriangulation(); } return chordalGraph; } /** * Get the generators of the separators of the triangulated graph, i.e. all vertices that * generate a minimal separator of triangulated graph. * * @return List of generators. */ public List getGenerators() { if (generators == null) { computeMinimalTriangulation(); } return generators; } /** * Get the minimal elimination ordering produced by the triangulation. * * @return The minimal elimination ordering. */ public LinkedList getMeo() { if (meo == null) { computeMinimalTriangulation(); } return meo; } /** * Get a map to know for each separator how many components it produces. * * @return A map from separators to integers (component count). */ public Map, Integer> getFullComponentCount() { if (fullComponentCount == null) { computeAtoms(); } return fullComponentCount; } /** * Get the atoms generated by the decomposition. * * @return Set of atoms, where each atom is described as the set of its vertices. */ public Set> getAtoms() { if (atoms == null) { computeAtoms(); } return atoms; } /** * Get the clique minimal separators. * * @return Set of separators, where each separator is described as the set of its vertices. */ public Set> getSeparators() { if (separators == null) { computeAtoms(); } return separators; } /** * Get the original graph. * * @return Original graph. */ public Graph getGraph() { return graph; } } DegeneracyBronKerboschCliqueFinder.java000066400000000000000000000110051402514743400355360ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clique/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.traverse.*; import java.util.*; import java.util.concurrent.*; /** * Bron-Kerbosch maximal clique enumeration algorithm with pivot and degeneracy ordering. * *

* The algorithm is a variant of the Bron-Kerbosch algorithm which apart from the pivoting uses a * degeneracy ordering of the vertices. The algorithm is described in *

    *
  • David Eppstein, Maarten Löffler and Darren Strash. Listing All Maximal Cliques in Sparse * Graphs in Near-Optimal Time. Algorithms and Computation: 21st International Symposium (ISSAC), * 403--414, 2010.
  • *
* *

* and has running time $O(d n 3^{d/3})$ where $n$ is the number of vertices of the graph and $d$ is * the degeneracy of the graph. The algorithm looks for a maximal clique parameterized by * degeneracy, a frequently-used measure of the sparseness of a graph that is closely related to * other common sparsity measures such as arboricity and thickness, and that has previously been * used for other fixed-parameter problems. * *

* The algorithm first computes all maximal cliques and then returns the result to the user. A * timeout can be set using the constructor parameters. * * @param the graph vertex type * @param the graph edge type * * @see BronKerboschCliqueFinder * @see PivotBronKerboschCliqueFinder * * @author Dimitrios Michail */ public class DegeneracyBronKerboschCliqueFinder extends PivotBronKerboschCliqueFinder { /** * Constructs a new clique finder. * * @param graph the input graph; must be simple */ public DegeneracyBronKerboschCliqueFinder(Graph graph) { this(graph, 0L, TimeUnit.SECONDS); } /** * Constructs a new clique finder. * * @param graph the input graph; must be simple * @param timeout the maximum time to wait, if zero no timeout * @param unit the time unit of the timeout argument */ public DegeneracyBronKerboschCliqueFinder(Graph graph, long timeout, TimeUnit unit) { super(graph, timeout, unit); } /** * Lazily execute the enumeration algorithm. */ @Override protected void lazyRun() { if (allMaximalCliques == null) { if (!GraphTests.isSimple(graph)) { throw new IllegalArgumentException("Graph must be simple"); } allMaximalCliques = new ArrayList<>(); long nanosTimeLimit; try { nanosTimeLimit = Math.addExact(System.nanoTime(), nanos); } catch (ArithmeticException ignore) { nanosTimeLimit = Long.MAX_VALUE; } List ordering = new ArrayList<>(); new DegeneracyOrderingIterator(graph).forEachRemaining(ordering::add); int n = ordering.size(); for (int i = 0; i < n; i++) { V vi = ordering.get(i); Set viNeighbors = new HashSet<>(); for (E e : graph.edgesOf(vi)) { viNeighbors.add(Graphs.getOppositeVertex(graph, e, vi)); } Set P = new HashSet<>(); for (int j = i + 1; j < n; j++) { V vj = ordering.get(j); if (viNeighbors.contains(vj)) { P.add(vj); } } Set R = new HashSet<>(); R.add(vi); Set X = new HashSet<>(); for (int j = 0; j < i; j++) { V vj = ordering.get(j); if (viNeighbors.contains(vj)) { X.add(vj); } } /* * Call the pivot version */ findCliques(P, R, X, nanosTimeLimit); } } } } PivotBronKerboschCliqueFinder.java000066400000000000000000000134421402514743400346000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clique/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import java.util.*; import java.util.concurrent.*; import java.util.stream.*; /** * Bron-Kerbosch maximal clique enumeration algorithm with pivot. * *

* The pivoting follows the rule from the paper *

    *
  • E. Tomita, A. Tanaka, and H. Takahashi. The worst-case time complexity for generating all * maximal cliques and computational experiments. Theor. Comput. Sci. 363(1):28–42, 2006.
  • *
* *

* where the authors show that using that rule guarantees that the Bron-Kerbosch algorithm has * worst-case running time $O(3^{n/3})$ where $n$ is the number of vertices of the graph, excluding * time to write the output, which is worst-case optimal. * *

* The algorithm first computes all maximal cliques and then returns the result to the user. A * timeout can be set using the constructor parameters. * * @param the graph vertex type * @param the graph edge type * * @see BronKerboschCliqueFinder * @see DegeneracyBronKerboschCliqueFinder * * @author Dimitrios Michail */ public class PivotBronKerboschCliqueFinder extends BaseBronKerboschCliqueFinder { /** * Constructs a new clique finder. * * @param graph the input graph; must be simple */ public PivotBronKerboschCliqueFinder(Graph graph) { this(graph, 0L, TimeUnit.SECONDS); } /** * Constructs a new clique finder. * * @param graph the input graph; must be simple * @param timeout the maximum time to wait, if zero no timeout * @param unit the time unit of the timeout argument */ public PivotBronKerboschCliqueFinder(Graph graph, long timeout, TimeUnit unit) { super(graph, timeout, unit); } /** * Lazily execute the enumeration algorithm. */ @Override protected void lazyRun() { if (allMaximalCliques == null) { if (!GraphTests.isSimple(graph)) { throw new IllegalArgumentException("Graph must be simple"); } allMaximalCliques = new ArrayList<>(); long nanosTimeLimit; try { nanosTimeLimit = Math.addExact(System.nanoTime(), nanos); } catch (ArithmeticException ignore) { nanosTimeLimit = Long.MAX_VALUE; } findCliques( new HashSet<>(graph.vertexSet()), new HashSet<>(), new HashSet<>(), nanosTimeLimit); } } /** * Choose a pivot. * * @param P vertices to consider adding to the clique * @param X vertices which must be excluded from the clique * @return a pivot */ private V choosePivot(Set P, Set X) { int max = -1; V pivot = null; Iterator it = Stream.concat(P.stream(), X.stream()).iterator(); while (it.hasNext()) { V u = it.next(); int count = 0; for (E e : graph.edgesOf(u)) { if (P.contains(Graphs.getOppositeVertex(graph, e, u))) { count++; } } if (count > max) { max = count; pivot = u; } } return pivot; } /** * Recursive implementation of the Bron-Kerbosch with pivot. * * @param P vertices to consider adding to the clique * @param R a possibly non-maximal clique * @param X vertices which must be excluded from the clique * @param nanosTimeLimit time limit */ protected void findCliques(Set P, Set R, Set X, final long nanosTimeLimit) { /* * Check if maximal clique */ if (P.isEmpty() && X.isEmpty()) { Set maximalClique = new HashSet<>(R); allMaximalCliques.add(maximalClique); maxSize = Math.max(maxSize, maximalClique.size()); return; } /* * Check if timeout */ if (nanosTimeLimit - System.nanoTime() < 0) { timeLimitReached = true; return; } /* * Choose pivot */ V u = choosePivot(P, X); /* * Find candidates for addition */ Set uNeighbors = new HashSet<>(); for (E e : graph.edgesOf(u)) { uNeighbors.add(Graphs.getOppositeVertex(graph, e, u)); } Set candidates = new HashSet<>(); for (V v : P) { if (!uNeighbors.contains(v)) { candidates.add(v); } } /* * Main loop */ for (V v : candidates) { Set vNeighbors = new HashSet<>(); for (E e : graph.edgesOf(v)) { vNeighbors.add(Graphs.getOppositeVertex(graph, e, v)); } Set newP = P.stream().filter(vNeighbors::contains).collect(Collectors.toSet()); Set newX = X.stream().filter(vNeighbors::contains).collect(Collectors.toSet()); Set newR = new HashSet<>(R); newR.add(v); findCliques(newP, newR, newX, nanosTimeLimit); P.remove(v); X.add(v); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clique/package-info.java000066400000000000000000000001061402514743400312760ustar00rootroot00000000000000/** * Clique related algorithms. */ package org.jgrapht.alg.clique; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/000077500000000000000000000000001402514743400270075ustar00rootroot00000000000000GirvanNewmanClustering.java000066400000000000000000000114611402514743400342320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/* * (C) Copyright 2021-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clustering; import java.util.List; import java.util.Map.Entry; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.alg.connectivity.ConnectivityInspector; import org.jgrapht.alg.interfaces.ClusteringAlgorithm; import org.jgrapht.alg.scoring.EdgeBetweennessCentrality; import org.jgrapht.alg.scoring.EdgeBetweennessCentrality.OverflowStrategy; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; /** * The Girvan-Newman clustering algorithm. * *

* The algorithm is described in: Girvan, Michelle, and Mark EJ Newman. "Community structure in * social and biological networks." Proceedings of the national academy of sciences 99.12 (2002): * 7821-7826. * *

* Running time is $O(m^2 n)$ or $O(m^2n + m n^2 \log n)$ for weighted graphs. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class GirvanNewmanClustering implements ClusteringAlgorithm { private Graph graph; private int k; private final Iterable startVertices; private final OverflowStrategy overflowStrategy; /** * Create a new clustering algorithm. * * @param graph the graph * @param k the desired number of clusters */ public GirvanNewmanClustering(Graph graph, int k) { this(graph, k, OverflowStrategy.THROW_EXCEPTION_ON_OVERFLOW, graph.vertexSet()); } /** * Create a new clustering algorithm. * * @param graph the graph * @param k the desired number of clusters * @param overflowStrategy strategy to use if overflow is detected * @param startVertices vertices from which to start shortest path computations when computing * edge centralities. This parameter allows the user to compute edge centrality * contributions only from a subset of the vertices of the graph. If null the whole graph * vertex set is used. */ public GirvanNewmanClustering( Graph graph, int k, OverflowStrategy overflowStrategy, Iterable startVertices) { this.graph = Objects.requireNonNull(graph); if (k < 1 || k > graph.vertexSet().size()) { throw new IllegalArgumentException("Illegal number of clusters"); } this.k = k; this.overflowStrategy = overflowStrategy; if (startVertices == null) { this.startVertices = graph.vertexSet(); } else { this.startVertices = startVertices; } } @Override public Clustering getClustering() { // copy graph Graph graphCopy = GraphTypeBuilder .forGraphType(graph.getType()).edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(graph.getVertexSupplier()).buildGraph(); for (V v : graph.iterables().vertices()) { graphCopy.addVertex(v); } for (E e : graph.iterables().edges()) { V sourceVertex = graph.getEdgeSource(e); V targetVertex = graph.getEdgeTarget(e); graphCopy.addEdge(sourceVertex, targetVertex); } // main algorithm while (true) { List> ccs = new ConnectivityInspector<>(graphCopy).connectedSets(); if (ccs.size() == k) { return new ClusteringImpl<>(ccs); } // compute edge centralities EdgeBetweennessCentrality bc = new EdgeBetweennessCentrality<>(graphCopy, overflowStrategy, startVertices); // find edge with max centrality DefaultEdge maxEdge = null; double maxCentrality = 0d; for (Entry entry : bc.getScores().entrySet()) { if (Double.compare(entry.getValue(), maxCentrality) > 0 || maxEdge == null) { maxEdge = entry.getKey(); maxCentrality = entry.getValue(); } } // remove edge with max centrality graphCopy.removeEdge(maxEdge); } } } KSpanningTreeClustering.java000066400000000000000000000070601402514743400343460ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clustering; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.SpanningTreeAlgorithm.*; import org.jgrapht.alg.spanning.*; import org.jgrapht.alg.util.*; import java.util.*; /** * The k spanning tree clustering algorithm. * *

* The algorithm finds a minimum spanning tree $T$ using Prim's algorithm, then executes Kruskal's * algorithm only on the edges of $T$ until $k$ trees are formed. The resulting trees are the final * clusters. The total running time is $O(m + n \log n)$. * *

* The algorithm is strongly related to single linkage cluster analysis, also known as single-link * clustering. For more information see: J. C. Gower and G. J. S. Ross. Minimum Spanning Trees and * Single Linkage Cluster Analysis. Journal of the Royal Statistical Society. Series C (Applied * Statistics), 18(1):54--64, 1969. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class KSpanningTreeClustering implements ClusteringAlgorithm { private Graph graph; private int k; /** * Create a new clustering algorithm. * * @param graph the graph (needs to be undirected) * @param k the desired number of clusters */ public KSpanningTreeClustering(Graph graph, int k) { this.graph = GraphTests.requireUndirected(graph); if (k < 1 || k > graph.vertexSet().size()) { throw new IllegalArgumentException("Illegal number of clusters"); } this.k = k; } @Override public Clustering getClustering() { /* * Compute an MST */ SpanningTree mst = new PrimMinimumSpanningTree<>(graph).getSpanningTree(); /* * Run Kruskal only on MST edges until we get k clusters */ UnionFind forest = new UnionFind<>(graph.vertexSet()); ArrayList allEdges = new ArrayList<>(mst.getEdges()); allEdges.sort(Comparator.comparingDouble(graph::getEdgeWeight)); for (E edge : allEdges) { if (forest.numberOfSets() == k) { break; } V source = graph.getEdgeSource(edge); V target = graph.getEdgeTarget(edge); if (forest.find(source).equals(forest.find(target))) { continue; } forest.union(source, target); } /* * Transform and return result */ Map> clusterMap = new LinkedHashMap<>(); for (V v : graph.vertexSet()) { V rv = forest.find(v); Set cluster = clusterMap.get(rv); if (cluster == null) { cluster = new LinkedHashSet<>(); clusterMap.put(rv, cluster); } cluster.add(v); } return new ClusteringImpl<>(new ArrayList<>(clusterMap.values())); } } LabelPropagationClustering.java000066400000000000000000000277321402514743400350710ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clustering; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; import java.util.stream.*; /** * A label propagation clustering algorithm. * *

* The algorithm is a near linear time algorithm capable of discovering communities in large graphs. * It is described in detail in the following * paper: *

    *
  • Raghavan, U. N., Albert, R., and Kumara, S. (2007). Near linear time algorithm to detect * community structures in large-scale networks. Physical review E, 76(3), 036106.
  • *
* *

* As the paper title suggests the running time is close to linear. The algorithm runs in * iterations, each of which runs in $O(n + m)$ where $n$ is the number of vertices and $m$ is the * number of edges. The authors found experimentally that in most cases, 95% of the nodes or more * are classified correctly by the end of iteration 5. See the paper for more details. * *

* The algorithm is randomized, meaning that two runs on the same graph may return different * results. If the user requires deterministic behavior, the random number generator can be provided * by the constructor. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class LabelPropagationClustering implements ClusteringAlgorithm { private Graph graph; private int maxIterations; private Random rng; private Clustering result; /** * Create a new clustering algorithm. * * @param graph the graph (needs to be undirected) */ public LabelPropagationClustering(Graph graph) { this(graph, 0, new Random()); } /** * Create a new clustering algorithm. * * @param graph the graph (needs to be undirected) * @param rng random number generator */ public LabelPropagationClustering(Graph graph, Random rng) { this(graph, 0, rng); } /** * Create a new clustering algorithm. * * @param graph the graph (needs to be undirected) * @param maxIterations maximum number of iterations (zero means no limit) */ public LabelPropagationClustering(Graph graph, int maxIterations) { this(graph, maxIterations, new Random()); } /** * Create a new clustering algorithm. * * @param graph the graph (needs to be undirected) * @param maxIterations maximum number of iterations (zero means no limit) * @param rng random number generator */ public LabelPropagationClustering(Graph graph, int maxIterations, Random rng) { this.graph = GraphTests.requireUndirected(graph); this.maxIterations = maxIterations; this.rng = Objects.requireNonNull(rng); if (maxIterations < 0) { throw new IllegalArgumentException("Max iterations cannot be negative"); } } @Override public Clustering getClustering() { if (result == null) { result = new ClusteringImpl<>(new Implementation<>(graph, rng, maxIterations).compute()); } return result; } /** * The actual implementation * * @param the graph vertex type * @param the graph edge type */ private static class Implementation { private Graph graph; private Random rng; private int maxIterations; private Map labels; /** * Initialize the computation * * @param graph the graph * @param rng the random number generator * @param maxIterations maximum iterations */ public Implementation(Graph graph, Random rng, int maxIterations) { this.graph = graph; this.rng = rng; this.maxIterations = maxIterations; this.labels = new HashMap<>(); int i = 0; for (V v : graph.vertexSet()) { labels.put(v, String.valueOf(i++)); } } /** * Main loop of the algorithm * * @return the clusters */ public List> compute() { int currentIteration = 0; while (true) { // is there a limit on the number of iterations? if (maxIterations > 0 && currentIteration > maxIterations) { break; } // perform synchronous label update (to avoid oscillations) boolean anyChange = false; List allVertices = new ArrayList<>(graph.vertexSet()); Collections.shuffle(allVertices, rng); for (V v : allVertices) { if (updateLabel(v)) { anyChange = true; } } // stopping criterion if (anyChange == false || shouldStop()) { break; } currentIteration++; } return computeCommunities(); } /** * Stopping criterion. Perform the iterative process until every node in the network has a * label equal to a label that the maximum number of its neighbors belong to. * * @return true whether we should stop, false otherwise */ private boolean shouldStop() { for (V v : graph.vertexSet()) { Pair, Integer> labelCountsAndMaximum = getNeighborLabelCountsAndMaximum(v); Map counts = labelCountsAndMaximum.getFirst(); String vLabel = labels.get(v); int vLabelCount = counts.getOrDefault(vLabel, 0); int maxCount = labelCountsAndMaximum.getSecond(); if (maxCount > vLabelCount) { return false; } } return true; } /** * Compute the frequency of the labels of all neighbors of a vertex and the maximum * frequency of the vertices, which have a label not equal to the input vertex label. * * @param v the input vertex * @return the frequency of the labels of all neighbors of a vertex and the maximum label * frequency of the vertices with a label not equal to the input vertex label */ private Pair, Integer> getNeighborLabelCountsAndMaximum(V v) { Map counts = new HashMap<>(); String vLabel = labels.get(v); int maxCount = 0; for (E e : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); String uLabel = labels.get(u); int newCount = counts.getOrDefault(uLabel, 0) + 1; counts.put(uLabel, newCount); if (newCount > maxCount && !uLabel.equals(vLabel)) { maxCount = newCount; } } return Pair.of(counts, maxCount); } /** * Update the label of a vertex. * * @param v the vertex * @return true if a label change occurred */ private boolean updateLabel(V v) { if (graph.degreeOf(v) == 0) { return false; } Pair, Integer> labelCountsAndMaximum = getNeighborLabelCountsAndMaximum(v); Map counts = labelCountsAndMaximum.getFirst(); String oldLabel = labels.get(v); int vLabelCount = counts.getOrDefault(oldLabel, 0); final int maxCount = Math.max(labelCountsAndMaximum.getSecond(), vLabelCount); ArrayList maxLabels = counts .entrySet().stream().filter(e -> e.getValue() == maxCount).map(Map.Entry::getKey) .collect(Collectors.toCollection(ArrayList::new)); String newLabel = maxLabels.get(rng.nextInt(maxLabels.size())); if (oldLabel.equals(newLabel)) { return false; } else { labels.put(v, newLabel); return true; } } /** * Compute the final communities from the labels. We need to do some extra work due to the * way the algorithm works, as described in the following paragraph from the original paper. * * "When the algorithm terminates it is possible that two or more disconnected groups of * nodes have the same label (the groups are connected in the network via other nodes of * different labels). This happens when two or more neighbors of a node receive its label * and pass the labels in different directions, which ultimately leads to different * communities adopting the same label. In such cases, after the algorithm terminates one * can run a simple breadth-first search on the sub-networks of each individual groups to * separate the disconnected communities." * * @return the clustering */ private List> computeCommunities() { Map finalLabels = new HashMap<>(); int nextLabel = 0; for (V v : graph.vertexSet()) { if (finalLabels.containsKey(v)) { continue; } // start a BFS Deque frontier = new ArrayDeque<>(); String currentLabel = String.valueOf(nextLabel++); finalLabels.put(v, currentLabel); frontier.addLast(v); while (!frontier.isEmpty()) { V u = frontier.removeFirst(); String uLabel = labels.get(u); for (E e : graph.edgesOf(u)) { V w = Graphs.getOppositeVertex(graph, e, u); String wLabel = labels.get(w); if (!wLabel.equals(uLabel) || finalLabels.containsKey(w)) { continue; } finalLabels.put(w, currentLabel); frontier.addLast(w); } } } return convert(graph, finalLabels); } /** * Convert from a map representation to a list of sets. * * @param graph the graph * @param labels the map representation * @return the list of sets */ private List> convert(Graph graph, Map labels) { Map> clusterMap = new LinkedHashMap<>(); for (V v : graph.vertexSet()) { String rv = labels.get(v); if (rv == null) { throw new IllegalArgumentException("Not all vertices have labels."); } Set cluster = clusterMap.get(rv); if (cluster == null) { cluster = new LinkedHashSet<>(); clusterMap.put(rv, cluster); } cluster.add(v); } return new ArrayList<>(clusterMap.values()); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/clustering/package-info.java000066400000000000000000000001141402514743400321720ustar00rootroot00000000000000/** * Graph clustering algorithms. */ package org.jgrapht.alg.clustering; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/000077500000000000000000000000001402514743400257465ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/BrownBacktrackColoring.java000066400000000000000000000145031402514743400332060ustar00rootroot00000000000000/* * (C) Copyright 2010-2021, by Michael Behrisch and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.util.*; import java.util.*; /** * Brown graph coloring algorithm. * * @param the graph vertex type * @param the graph edge type * * @author Michael Behrisch */ public class BrownBacktrackColoring implements VertexColoringAlgorithm { private final List vertexList; // list of all vertices private final int[][] neighbors; // for every vertex v, neighbors[v] stores the neighbors of v private final Map indexMap; // assigned unique index to each vertex. maps to vertex // list private int[] partialColorAssignment; // color assigned to a specific vertex private int[] colorCount; // Number of colors used up to the ith vertex that has been colored private BitSet[] allowedColors; private int chi; // chromatic number private int[] completeColorAssignment; private Coloring vertexColoring; /** * Construct a new Brown backtracking algorithm. * * @param graph the input graph */ public BrownBacktrackColoring(Graph graph) { Objects.requireNonNull(graph, "Graph cannot be null"); final int numVertices = graph.vertexSet().size(); vertexList = new ArrayList<>(numVertices); neighbors = new int[numVertices][]; indexMap = CollectionUtil.newHashMapWithExpectedSize(numVertices); for (V vertex : graph.vertexSet()) { neighbors[vertexList.size()] = new int[graph.edgesOf(vertex).size()]; indexMap.put(vertex, vertexList.size()); vertexList.add(vertex); } for (int i = 0; i < numVertices; i++) { int nbIndex = 0; final V vertex = vertexList.get(i); for (E e : graph.edgesOf(vertex)) { neighbors[i][nbIndex++] = indexMap.get(Graphs.getOppositeVertex(graph, e, vertex)); } } } private void recursiveColor(int pos) { colorCount[pos] = colorCount[pos - 1]; allowedColors[pos].set(0, colorCount[pos] + 1); // To color the ith vertex, one can use the // number of colors needed to color the // i-1th vertex plus 1 // Determine which colors have been used by the neighbors of the ith vertex for (int i = 0; i < neighbors[pos].length; i++) { final int nb = neighbors[pos][i]; if (partialColorAssignment[nb] > 0) { allowedColors[pos].clear(partialColorAssignment[nb]); } } // Try to assign each of the already used colors to vertex i. Prune search if partial // coloring will never be better than chromatic number of best solution found thus far for (int i = 1; (i <= colorCount[pos]) && (colorCount[pos] < chi); i++) { if (allowedColors[pos].get(i)) { // Try all available colors for vertex i. A color is // available if its not used by its neighbor partialColorAssignment[pos] = i; if (pos < (neighbors.length - 1)) { // If not all vertices have been colored, // proceed with the next uncolored vertex recursiveColor(pos + 1); } else { // Otherwise we have found a feasible coloring chi = colorCount[pos]; System .arraycopy( partialColorAssignment, 0, completeColorAssignment, 0, partialColorAssignment.length); } } } // consider using a new color for vertex i if ((colorCount[pos] + 1) < chi) { colorCount[pos]++; partialColorAssignment[pos] = colorCount[pos]; if (pos < (neighbors.length - 1)) { recursiveColor(pos + 1); } else { chi = colorCount[pos]; System .arraycopy( partialColorAssignment, 0, completeColorAssignment, 0, partialColorAssignment.length); } } partialColorAssignment[pos] = 0; } private void lazyComputeColoring() { if (vertexColoring != null) return; chi = neighbors.length + 1; partialColorAssignment = new int[neighbors.length]; completeColorAssignment = new int[neighbors.length]; partialColorAssignment[0] = 1; // Prefix color of first vertex. Optimization: Could prefix // all colors of largest clique colorCount = new int[neighbors.length]; colorCount[0] = 1; allowedColors = new BitSet[neighbors.length]; for (int i = 0; i < neighbors.length; i++) { allowedColors[i] = new BitSet(1); } recursiveColor(1); Map colorMap = new LinkedHashMap<>(); for (int i = 0; i < vertexList.size(); i++) colorMap.put(vertexList.get(i), completeColorAssignment[i]); vertexColoring = new ColoringImpl<>(colorMap, chi); } /** * Returns the chromatic number * of the input graph * * @return chromatic number of the graph */ public int getChromaticNumber() { lazyComputeColoring(); return vertexColoring.getNumberColors(); } @Override public Coloring getColoring() { lazyComputeColoring(); return vertexColoring; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/ChordalGraphColoring.java000066400000000000000000000163611402514743400326530ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.util.*; /** * Calculates a minimum vertex * coloring for a chordal graph. A * chordal graph is a simple graph in which all * cycles of four or more vertices have * a chord. A chord is an edge that is * not part of the cycle but connects two vertices of the cycle. * * To compute the vertex coloring, this implementation relies on the {@link ChordalityInspector} to * compute a * perfect elimination order. * * The vertex coloring for a chordal graph is computed in $\mathcal{O}(|V| + |E|)$ time. * * All the methods in this class are invoked in a lazy fashion, meaning that computations are only * started once the method gets invoked. * * @param the graph vertex type. * @param the graph edge type. * * @author Timofey Chudakov */ public class ChordalGraphColoring implements VertexColoringAlgorithm { private final Graph graph; private final ChordalityInspector chordalityInspector; private Coloring coloring; /** * Creates a new ChordalGraphColoring instance. The {@link ChordalityInspector} used in this * implementation uses the default {@link MaximumCardinalityIterator} iterator. * * @param graph graph */ public ChordalGraphColoring(Graph graph) { this(graph, ChordalityInspector.IterationOrder.MCS); } /** * Creates a new ChordalGraphColoring instance. The {@link ChordalityInspector} used in this * implementation uses either the {@link MaximumCardinalityIterator} iterator or the * {@link LexBreadthFirstIterator} iterator, depending on the parameter {@code iterationOrder}. * * @param graph graph * @param iterationOrder constant which defines iterator to be used by the * {@code ChordalityInspector} in this implementation. */ public ChordalGraphColoring( Graph graph, ChordalityInspector.IterationOrder iterationOrder) { this.graph = Objects.requireNonNull(graph); chordalityInspector = new ChordalityInspector<>(graph, iterationOrder); } /** * Lazily computes the coloring of the graph. */ private void lazyComputeColoring() { if (coloring == null && chordalityInspector.isChordal()) { List perfectEliminationOrder = chordalityInspector.getPerfectEliminationOrder(); Map vertexColoring = CollectionUtil.newHashMapWithExpectedSize(perfectEliminationOrder.size()); Map vertexInOrder = getVertexInOrder(perfectEliminationOrder); for (V vertex : perfectEliminationOrder) { Set predecessors = getPredecessors(vertexInOrder, vertex); Set predecessorColors = CollectionUtil.newHashSetWithExpectedSize(predecessors.size()); predecessors.forEach(v -> predecessorColors.add(vertexColoring.get(v))); // find the minimum unused color in the set of predecessors int minUnusedColor = 0; while (predecessorColors.contains(minUnusedColor)) { ++minUnusedColor; } vertexColoring.put(vertex, minUnusedColor); } int maxColor = (int) vertexColoring.values().stream().distinct().count(); coloring = new ColoringImpl<>(vertexColoring, maxColor); } } /** * Returns a map containing vertices from the {@code vertexOrder} mapped to their indices in * {@code vertexOrder}. * * @param vertexOrder a list with vertices. * @return a mapping of vertices from {@code vertexOrder} to their indices in * {@code vertexOrder}. */ private Map getVertexInOrder(List vertexOrder) { Map vertexInOrder = CollectionUtil.newHashMapWithExpectedSize(vertexOrder.size()); int i = 0; for (V vertex : vertexOrder) { vertexInOrder.put(vertex, i++); } return vertexInOrder; } /** * Returns the predecessors of {@code vertex} in the order defined by {@code map}. More * precisely, returns those of {@code vertex}, whose mapped index in {@code map} is less then * the index of {@code vertex}. * * @param vertexInOrder defines the mapping of vertices in {@code graph} to their indices in * order. * @param vertex the vertex whose predecessors in order are to be returned. * @return the predecessors of {@code vertex} in order defines by {@code map}. */ private Set getPredecessors(Map vertexInOrder, V vertex) { Set predecessors = new HashSet<>(); Integer vertexPosition = vertexInOrder.get(vertex); Set edges = graph.edgesOf(vertex); for (E edge : edges) { V oppositeVertex = Graphs.getOppositeVertex(graph, edge, vertex); Integer destPosition = vertexInOrder.get(oppositeVertex); if (destPosition < vertexPosition) predecessors.add(oppositeVertex); } return predecessors; } /** * Returns a minimum vertex * coloring of the inspected {@code graph}. If the graph isn't chordal, returns null. The * number of colors used in the coloring equals the chromatic number of the input graph. * * @return a coloring of the {@code graph} if it is chordal, null otherwise. */ @Override public Coloring getColoring() { lazyComputeColoring(); return coloring; } /** * Returns the * perfect elimination order used to create the coloring (if one exists). This method * returns null if the graph is not chordal. * * @return the perfect elimination order used to create the coloring, or null if graph is not * chordal. */ public List getPerfectEliminationOrder() { return chordalityInspector.getPerfectEliminationOrder(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/ColorRefinementAlgorithm.java000066400000000000000000000327671402514743400335720ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Christoph Grüne, Daniel Mock, Oliver Feith and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.util.*; import java.util.*; import java.util.stream.*; /** * Color refinement algorithm that finds the coarsest stable coloring of a graph based on a given * alpha coloring as described in the following * paper: C. Berkholz, P. Bonsma, and M. * Grohe. Tight lower and upper bounds for the complexity of canonical colour refinement. Theory of * Computing Systems, 60(4), p581--614, 2017. * *

* The complexity of this algorithm is $O((|V| + |E|)log |V|)$. * * @param the graph vertex type * @param the graph edge type * * @author Christoph Grüne * @author Daniel Mock * @author Oliver Feith */ public class ColorRefinementAlgorithm implements VertexColoringAlgorithm { private final Graph graph; private final Coloring alpha; /** * Construct a new coloring algorithm. * * @param graph the input graph * @param alpha the coloring on the graph to be refined */ public ColorRefinementAlgorithm(Graph graph, Coloring alpha) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); this.alpha = Objects.requireNonNull(alpha, "alpha cannot be null"); if (!isAlphaConsistent(alpha, graph)) { throw new IllegalArgumentException( "alpha is not a valid surjective l-coloring for the given graph."); } } /** * Construct a new coloring algorithm. * * @param graph the input graph */ public ColorRefinementAlgorithm(Graph graph) { this(graph, getDefaultAlpha(graph.vertexSet())); } /** * Calculates a canonical surjective k-coloring of the given graph such that the classes of the * coloring form the coarsest stable partition that refines alpha. * * @return the calculated coloring */ @Override public Coloring getColoring() { // initialize internal representation ColoringRepresentation rep = new ColoringRepresentation(graph, alpha); // get a sorted (ascending) stack of all colors that are predefined by alpha Deque refineStack = getSortedStack(alpha); // main iteration while (!refineStack.isEmpty()) { Integer currentColor = refineStack.pop(); Set adjacentColors = calculateColorDegrees(currentColor, rep); // split colors adjacentColors .stream().filter(c -> rep.minColorDegree[c] < rep.maxColorDegree[c]) .sorted(Comparator.comparingInt(o -> o)) // canonical order .forEach(color -> splitUpColor(color, refineStack, rep)); cleanupColorDegrees(adjacentColors, rep); } // return result return new ColoringImpl<>(rep.coloring, rep.coloring.size()); } /** * Helper method that calculates the color degree for every vertex and the maximum and minimum * color degree for every color. * * @param refiningColor color to refine * @param rep the coloring representation * @return the list of all colors that have at least one vertex with colorDegree >= 1 */ private Set calculateColorDegrees(int refiningColor, ColoringRepresentation rep) { int n = graph.vertexSet().size(); Set adjacentColors = CollectionUtil.newLinkedHashSetWithExpectedSize(n); // calculate color degree and update maxColorDegree for (V v : rep.colorClasses.get(refiningColor)) { Set inNeighborhood = graph .incomingEdgesOf(v).stream().map(e -> Graphs.getOppositeVertex(graph, e, v)) .collect(Collectors.toSet()); for (V w : inNeighborhood) { rep.colorDegree.put(w, rep.colorDegree.get(w) + 1); if (rep.colorDegree.get(w) == 1) { rep.positiveDegreeColorClasses.get(rep.coloring.get(w)).add(w); } adjacentColors.add(rep.coloring.get(w)); // update maxColorDegree for color(w) if maximum color degree has increased. if (rep.colorDegree.get(w) > rep.maxColorDegree[rep.coloring.get(w)]) { rep.maxColorDegree[rep.coloring.get(w)] = rep.colorDegree.get(w); } } } // update minColorDegree for (Integer c : adjacentColors) { // if there is a vertex with colorDegree(v) = 0 < 1, set minimum color degree to // 0 if (rep.colorClasses.get(c).size() != rep.positiveDegreeColorClasses.get(c).size()) { rep.minColorDegree[c] = 0; } else { rep.minColorDegree[c] = rep.maxColorDegree[c]; for (V v : rep.positiveDegreeColorClasses.get(c)) { if (rep.colorDegree.get(v) < rep.minColorDegree[c]) { rep.minColorDegree[c] = rep.colorDegree.get(v); } } } } return adjacentColors; } /** * Helper method that cleanups the internal representation of color degrees for a new iteration. * * @param adjacentColors the list of all colors that have at least one vertex with colorDegree * >= 1 * @param rep the coloring representation */ private void cleanupColorDegrees(Set adjacentColors, ColoringRepresentation rep) { for (int c : adjacentColors) { for (V v : rep.positiveDegreeColorClasses.get(c)) { rep.colorDegree.put(v, 0); } rep.maxColorDegree[c] = 0; rep.positiveDegreeColorClasses.set(c, new ArrayList<>()); } } /** * Helper method for splitting up a color. * * @param color the color to split the color class for * @param refineStack the stack containing all colors that have to be refined * @param rep the coloring representation */ private void splitUpColor(Integer color, Deque refineStack, ColoringRepresentation rep) { // Initialize and calculate numColorDegree (mapping from the color degree to the number of // vertices with that color degree). List positiveDegreeColorClasses = rep.positiveDegreeColorClasses.get(color); int maxColorDegree = rep.maxColorDegree[color]; int[] numColorDegree = new int[maxColorDegree + 1]; numColorDegree[0] = rep.colorClasses.get(color).size() - positiveDegreeColorClasses.size(); for (V v : positiveDegreeColorClasses) { int degree = rep.colorDegree.get(v); numColorDegree[degree] += 1; } // Helper variable storing the index with the maximum number of vertices with the // corresponding color degree int maxColorDegreeIndex = 0; for (int i = 1; i <= maxColorDegree; ++i) { if (numColorDegree[i] > numColorDegree[maxColorDegreeIndex]) { maxColorDegreeIndex = i; } } // Go through all indices (color degrees) of numColorDegree int[] newMapping = new int[maxColorDegree + 1]; boolean isCurrentColorInStack = refineStack.contains(color); for (int i = 0; i <= maxColorDegree; ++i) { if (numColorDegree[i] >= 1) { if (i == rep.minColorDegree[color]) { newMapping[i] = color; // keep current color // Push current color on the stack if it is not in the stack and i is not the // index with the maximum number of vertices with the corresponding color degree if (!isCurrentColorInStack && maxColorDegreeIndex != i) { refineStack.push(newMapping[i]); } } else { newMapping[i] = ++rep.lastUsedColor; // new color // Push current color on the stack if it is in the stack and i is not the index // with the maximum number of vertices with the corresponding color degree if (isCurrentColorInStack || i != maxColorDegreeIndex) { refineStack.push(newMapping[i]); } } } } // Update colors classes if some color has changed for (V v : positiveDegreeColorClasses) { int value = newMapping[rep.colorDegree.get(v)]; if (value != color.intValue()) { rep.colorClasses.get(color).remove(v); rep.colorClasses.get(value).add(v); rep.coloring.replace(v, value); } } } /** * Checks whether alpha is a valid surjective l-coloring for the given graph * * @param alpha the surjective l-coloring to be checked * @param graph the graph that is colored by alpha * @return whether alpha is a valid surjective l-coloring for the given graph */ private boolean isAlphaConsistent(Coloring alpha, Graph graph) { /* * Check if the coloring is restricted to the graph, i.e. there are exactly as many vertices * in the graph as in the coloring */ if (alpha.getColors().size() != graph.vertexSet().size()) { return false; } // check surjectivity, i.e. are the colors in the set {0, ..., maximumColor-1} // used? if (alpha.getColorClasses().size() != alpha.getNumberColors()) { return false; } for (V v : graph.vertexSet()) { // ensure that the key set of alpha and the vertex set of the graph actually // coincide if (!alpha.getColors().containsKey(v)) { return false; } // ensure the colors lie in in the set {0, ..., maximumColor-1} Integer currentColor = alpha.getColors().get(v); if (currentColor + 1 > alpha.getNumberColors() || currentColor < 0) { return false; } } return true; } /** * Returns a coloring such that all vertices have the same (zero) color. * * @param vertices the vertices that should be colored * @return the all-0 coloring */ private static Coloring getDefaultAlpha(Set vertices) { Map alpha = new HashMap<>(); for (V v : vertices) { alpha.put(v, 0); } return new ColoringImpl<>(alpha, 1); } /** * Returns a canonically sorted stack of all colors of alpha. It is important that alpha is * consistent. * * @param alpha the surjective l-coloring * @return a canonically sorted stack of all colors of alpha */ private Deque getSortedStack(Coloring alpha) { int numberColors = alpha.getNumberColors(); Deque stack = new ArrayDeque<>(graph.vertexSet().size()); for (int i = numberColors - 1; i >= 0; --i) { stack.push(i); } return stack; } private class ColoringRepresentation { /** * mapping from all colors to their classes */ List> colorClasses; /** * mapping from color to their classes, whereby every vertex in the classes has * colorDegree(v) >= 1 */ List> positiveDegreeColorClasses; /** * mapping from color to its maximum color degree */ int[] maxColorDegree; /** * mapping from color to its minimum color degree */ int[] minColorDegree; /** * mapping from vertex to the vertex color degree (number of neighbors with different * colors) */ Map colorDegree; /** * The actual coloring */ Map coloring; /** * Last used color */ int lastUsedColor; public ColoringRepresentation(Graph graph, Coloring alpha) { int n = graph.vertexSet().size(); this.colorClasses = new ArrayList<>(n); this.positiveDegreeColorClasses = new ArrayList<>(n); this.maxColorDegree = new int[n]; this.minColorDegree = new int[n]; this.colorDegree = new HashMap<>(); this.coloring = new HashMap<>(); for (int c = 0; c < n; ++c) { colorClasses.add(new ArrayList<>()); positiveDegreeColorClasses.add(new ArrayList<>()); } for (V v : graph.vertexSet()) { colorClasses.get(alpha.getColors().get(v)).add(v); colorDegree.put(v, 0); coloring.put(v, alpha.getColors().get(v)); } lastUsedColor = alpha.getNumberColors() - 1; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/GreedyColoring.java000066400000000000000000000055511402514743400315330ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * The greedy coloring algorithm. * *

* The algorithm iterates over all vertices and assigns the smallest possible color that is not used * by any neighbors. Subclasses may provide a different vertex ordering. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class GreedyColoring implements VertexColoringAlgorithm { /** * Error message if the input graph contains self-loops. */ protected static final String SELF_LOOPS_NOT_ALLOWED = "Self-loops not allowed"; /** * The input graph */ protected final Graph graph; /** * Construct a new coloring algorithm. * * @param graph the input graph */ public GreedyColoring(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); } /** * Get the ordering of the vertices used by the algorithm. * * @return the ordering of the vertices used by the algorithm */ protected Iterable getVertexOrdering() { return graph.vertexSet(); } /** * {@inheritDoc} */ @Override public Coloring getColoring() { int maxColor = -1; Map colors = new HashMap<>(); Set used = new HashSet<>(); for (V v : getVertexOrdering()) { // find used colors for (E e : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (v.equals(u)) { throw new IllegalArgumentException(SELF_LOOPS_NOT_ALLOWED); } if (colors.containsKey(u)) { used.add(colors.get(u)); } } // find first free int candidate = 0; while (used.contains(candidate)) { candidate++; } used.clear(); // set color colors.put(v, candidate); maxColor = Math.max(maxColor, candidate); } return new ColoringImpl<>(colors, maxColor + 1); } } LargestDegreeFirstColoring.java000066400000000000000000000060321402514743400337550ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/* * (C) Copyright 2017-2017 Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.util.*; import java.lang.reflect.*; import java.util.*; /** * The largest degree first greedy coloring algorithm. * *

* This is the greedy coloring algorithm which orders the vertices by non-increasing degree. See the * following paper for details. *

    *
  • D. J. A. Welsh and M. B. Powell. An upper bound for the chromatic number of a graph and its * application to timetabling problems. The Computer Journal, 10(1):85--86, 1967.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class LargestDegreeFirstColoring extends GreedyColoring { /** * Construct a new coloring algorithm. * * @param graph the input graph */ public LargestDegreeFirstColoring(Graph graph) { super(graph); } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") protected Iterable getVertexOrdering() { // compute degrees and maximum degree int n = graph.vertexSet().size(); int maxDegree = 0; Map degree = CollectionUtil.newHashMapWithExpectedSize(n); for (V v : graph.vertexSet()) { int d = graph.edgesOf(v).size(); degree.put(v, d); if (d > maxDegree) { maxDegree = d; } } if (maxDegree > 3 * n) { /* * Order vertices by degree by using a comparison based sort. */ List nodes = new ArrayList<>(graph.vertexSet()); nodes.sort((u, v) -> -1 * Integer.compare(degree.get(u), degree.get(v))); return nodes; } else { /* * Use bucket sort */ List nodes = new ArrayList<>(n); // create buckets final Set[] buckets = (Set[]) Array.newInstance(Set.class, maxDegree + 1); for (int i = 0; i <= maxDegree; i++) { buckets[i] = new HashSet<>(); } // fill buckets for (V v : graph.vertexSet()) { buckets[degree.get(v)].add(v); } // collect result for (int i = maxDegree; i >= 0; i--) { nodes.addAll(buckets[i]); } return nodes; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/RandomGreedyColoring.java000066400000000000000000000033721402514743400326730ustar00rootroot00000000000000/* * (C) Copyright 2017-2017 Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import java.util.*; /** * The greedy coloring algorithm with a random vertex ordering. * * @param the graph vertex type * @param the graph edge type */ public class RandomGreedyColoring extends GreedyColoring { /* * Random number generator */ private Random rng; /** * Construct a new coloring algorithm. * * @param graph the input graph */ public RandomGreedyColoring(Graph graph) { this(graph, new Random()); } /** * Construct a new coloring algorithm * * @param graph the input graph * @param rng the random number generator */ public RandomGreedyColoring(Graph graph, Random rng) { super(graph); this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); } /** * {@inheritDoc} */ @Override protected Iterable getVertexOrdering() { List order = new ArrayList(graph.vertexSet()); Collections.shuffle(order, rng); return order; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/SaturationDegreeColoring.java000066400000000000000000000230471402514743400335610ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.util.*; import java.lang.reflect.*; import java.util.*; /** * The Dsatur greedy coloring algorithm. * *

* This is the greedy coloring algorithm using saturation degree ordering. The saturation degree of * a vertex is defined as the number of different colors to which it is adjacent. The algorithm * selects always the vertex with the largest saturation degree. If multiple vertices have the same * maximum saturation degree, a vertex of maximum degree in the uncolored subgraph is selected. * *

* Note that the DSatur is not optimal in general, but is optimal for bipartite graphs. Compared to * other simpler greedy ordering heuristics, it is usually considered slower but more efficient * w.r.t. the number of used colors. See the following papers for details: *

    *
  • D. Brelaz. New methods to color the vertices of a graph. Communications of ACM, * 22(4):251–256, 1979.
  • *
  • The smallest hard-to-color graph for algorithm DSATUR. Discrete Mathematics, 236:151--165, * 2001.
  • *
* *

* This implementation requires $O(n^2)$ running time and space. The following paper discusses * possible improvements in the running time. *

    *
  • J. S. Turner. Almost all $k$-colorable graphs are easy to color. Journal of Algorithms. * 9(1):63--82, 1988.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class SaturationDegreeColoring implements VertexColoringAlgorithm { private final Graph graph; /** * Construct a new coloring algorithm. * * @param graph the input graph */ public SaturationDegreeColoring(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public Coloring getColoring() { /* * Initialize data structures */ int n = graph.vertexSet().size(); int maxColor = -1; Map colors = CollectionUtil.newHashMapWithExpectedSize(n); Map adjColors = CollectionUtil.newHashMapWithExpectedSize(n); Map saturation = CollectionUtil.newHashMapWithExpectedSize(n); /* * Compute degrees, available colors, and maximum degree. */ int maxDegree = 0; Map degree = CollectionUtil.newHashMapWithExpectedSize(n); for (V v : graph.vertexSet()) { int d = graph.edgesOf(v).size(); degree.put(v, d); maxDegree = Math.max(maxDegree, d); adjColors.put(v, new BitSet()); saturation.put(v, 0); } /* * Initialize heap */ Heap heap = new Heap(n, new DSaturComparator(saturation, degree)); Map handles = new HashMap<>(); for (V v : graph.vertexSet()) { handles.put(v, new HeapHandle(v)); } heap .bulkInsert( handles.values().toArray((HeapHandle[]) Array.newInstance(HeapHandle.class, 0))); /* * Color vertices */ while (heap.size() > 0) { V v = heap.deleteMin().vertex; // find first free color BitSet used = adjColors.get(v); int c = used.nextClearBit(0); maxColor = Math.max(maxColor, c); // color the vertex colors.put(v, c); // partial cleanup to save some space adjColors.remove(v); // update neighbors for (E e : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (!colors.containsKey(u)) { // update used colors int uSaturation = saturation.get(u); BitSet uAdjColors = adjColors.get(u); HeapHandle uHandle = handles.get(u); if (uAdjColors.get(c)) { // same saturation, degree decrease // remove and reinsert heap.delete(uHandle); degree.put(u, degree.get(u) - 1); heap.insert(uHandle); } else { // saturation increase, degree decrease uAdjColors.set(c); saturation.put(u, uSaturation + 1); degree.put(u, degree.get(u) - 1); // simple fix upwards inside heap since priority increased heap.fixup(uHandle); } } } } return new ColoringImpl<>(colors, maxColor + 1); } /* * Special case comparator for the DSatur algorithm. Compares first by saturation and then by * degree (maximum is better in both cases). */ private class DSaturComparator implements Comparator { private Map saturation; private Map degree; public DSaturComparator(Map saturation, Map degree) { this.saturation = saturation; this.degree = degree; } @Override public int compare(V o1, V o2) { int sat1 = saturation.get(o1); int sat2 = saturation.get(o2); if (sat1 > sat2) { return -1; } else if (sat1 < sat2) { return 1; } else { return -1 * Integer.compare(degree.get(o1), degree.get(o2)); } } } /* * An addressable heap handle. */ private class HeapHandle { int index; V vertex; public HeapHandle(V vertex) { this.vertex = vertex; this.index = -1; } } /* * An addressable binary heap. * * No checks are performed (on purpose) for invalid handle use, or capacity violations. */ private class Heap { private Comparator comparator; private int size; private HeapHandle[] array; @SuppressWarnings("unchecked") public Heap(int capacity, Comparator comparator) { this.comparator = comparator; this.size = 0; this.array = (HeapHandle[]) Array.newInstance(HeapHandle.class, capacity + 1); } private void fixdown(int k) { HeapHandle h = array[k]; while (2 * k <= size) { int j = 2 * k; if (j < size && comparator.compare(array[j].vertex, array[j + 1].vertex) > 0) { j++; } if (comparator.compare(h.vertex, array[j].vertex) <= 0) { break; } array[k] = array[j]; array[k].index = k; k = j; } array[k] = h; h.index = k; } private void fixup(int k) { HeapHandle h = array[k]; while (k > 1 && comparator.compare(array[k / 2].vertex, h.vertex) > 0) { array[k] = array[k / 2]; array[k].index = k; k /= 2; } array[k] = h; h.index = k; } private void forceFixup(int k) { HeapHandle h = array[k]; while (k > 1) { array[k] = array[k / 2]; array[k].index = k; k /= 2; } array[k] = h; h.index = k; } public HeapHandle deleteMin() { HeapHandle result = array[1]; if (size == 1) { array[1] = null; size = 0; } else { array[1] = array[size]; array[size] = null; size--; fixdown(1); } result.index = -1; return result; } public int size() { return size; } public void fixup(HeapHandle handle) { fixup(handle.index); } public void delete(HeapHandle handle) { forceFixup(handle.index); deleteMin(); } public void insert(HeapHandle handle) { size++; array[size] = handle; handle.index = size; fixup(size); } public void bulkInsert(HeapHandle[] handles) { for (int i = 0; i < handles.length; i++) { size++; array[size] = handles[i]; handles[i].index = size; } for (int i = size / 2; i > 0; i--) { fixdown(i); } } } } SmallestDegreeLastColoring.java000066400000000000000000000071451402514743400337620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/* * (C) Copyright 2010-2021, by Michael Behrisch, Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.util.*; import java.lang.reflect.*; import java.util.*; /** * The smallest degree last greedy coloring algorithm. * *

* This is the greedy coloring algorithm with the smallest-last ordering of the vertices. The basic * idea is as follows: Assuming that vertices $v_{k+1}, \dotso, v_n$ have been already selected, * choose $v_k$ so that the degree of $v_k$ in the subgraph induced by $V - $(v_{k+1}, \dotso, v_n)$ * is minimal. See the following paper for details. *

    *
  • D. Matula, G. Marble, and J. Isaacson. Graph coloring algorithms in Graph Theory and * Computing. Academic Press, 104--122, 1972.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Michael Behrisch * @author Dimitrios Michail */ public class SmallestDegreeLastColoring extends GreedyColoring { /** * Construct a new coloring algorithm. * * @param graph the input graph */ public SmallestDegreeLastColoring(Graph graph) { super(graph); } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") protected Iterable getVertexOrdering() { // compute degrees and maximum degree int n = graph.vertexSet().size(); int maxDegree = 0; Map degree = CollectionUtil.newHashMapWithExpectedSize(n); for (V v : graph.vertexSet()) { int d = graph.edgesOf(v).size(); degree.put(v, d); if (d > maxDegree) { maxDegree = d; } } // create buckets final Set[] buckets = (Set[]) Array.newInstance(Set.class, maxDegree + 1); for (int i = 0; i <= maxDegree; i++) { buckets[i] = new HashSet<>(); } // fill buckets for (V v : graph.vertexSet()) { buckets[degree.get(v)].add(v); } // create order Deque order = new ArrayDeque<>(); for (int i = 0; i <= maxDegree; i++) { while (buckets[i].size() > 0) { V v = buckets[i].iterator().next(); buckets[i].remove(v); order.addFirst(v); degree.remove(v); for (E e : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (v.equals(u)) { throw new IllegalArgumentException(SELF_LOOPS_NOT_ALLOWED); } Integer d = degree.get(u); if (d != null && d > 0) { buckets[d].remove(u); d--; degree.put(u, d); buckets[d].add(u); if (d < i) { i = d; } } } } } return order; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/color/package-info.java000066400000000000000000000001051402514743400311310ustar00rootroot00000000000000/** * Graph coloring algorithms. */ package org.jgrapht.alg.color; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/000077500000000000000000000000001402514743400273465ustar00rootroot00000000000000AbstractStrongConnectivityInspector.java000066400000000000000000000062021402514743400373600ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2005-2021, by Christian Soltenborn and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; /** * Base implementation of the strongly connected components algorithm. * * @param the graph vertex type * @param the graph edge type * * @author Christian Soltenborn * @author Christian Hammer * @author Dimitrios Michail */ abstract class AbstractStrongConnectivityInspector implements StrongConnectivityAlgorithm { protected final Graph graph; protected List> stronglyConnectedSets; protected List> stronglyConnectedSubgraphs; protected AbstractStrongConnectivityInspector(Graph graph) { this.graph = GraphTests.requireDirected(graph); } @Override public Graph getGraph() { return graph; } @Override public boolean isStronglyConnected() { return stronglyConnectedSets().size() == 1; } @Override public List> getStronglyConnectedComponents() { if (stronglyConnectedSubgraphs == null) { List> sets = stronglyConnectedSets(); stronglyConnectedSubgraphs = new ArrayList<>(sets.size()); for (Set set : sets) { stronglyConnectedSubgraphs.add(new AsSubgraph<>(graph, set, null)); } } return stronglyConnectedSubgraphs; } @Override public Graph, DefaultEdge> getCondensation() { List> sets = stronglyConnectedSets(); Graph, DefaultEdge> condensation = new SimpleDirectedGraph<>(DefaultEdge.class); Map> vertexToComponent = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); for (Set set : sets) { Graph component = new AsSubgraph<>(graph, set, null); condensation.addVertex(component); for (V v : set) { vertexToComponent.put(v, component); } } for (E e : graph.edgeSet()) { V s = graph.getEdgeSource(e); Graph sComponent = vertexToComponent.get(s); V t = graph.getEdgeTarget(e); Graph tComponent = vertexToComponent.get(t); if (sComponent != tComponent) { // reference equal on purpose condensation.addEdge(sComponent, tComponent); } } return condensation; } } BiconnectivityInspector.java000066400000000000000000000305341402514743400350170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; /** * Allows obtaining various connectivity aspects of a graph. The inspected graph is specified * at construction time and cannot be modified. No restrictions are imposed on the input graph. * Multigraphs and pseudographs are also supported. The inspector traverses connected components * (undirected graphs) or weakly connected components (directed graphs). To find strongly connected * components, use {@link KosarajuStrongConnectivityInspector} instead. This class offers an * alternative implementation of some of the functionality encountered in * {@link ConnectivityInspector}. It is likely to perform somewhat slower than * {@link ConnectivityInspector}, but offers more functionality in return. *

* The algorithm implemented in this class is Hopcroft and Tarjan's biconnected components * algorithm, described in: Hopcroft, J. Tarjan, R. Algorithm 447: efficient algorithms for graph * manipulation, 1973. Communications of the ACM. 16 (6): 372–378. This implementation runs in * linear time $O(|V|+|E|)$ and is based on a recursive depth-first search. More information about * this subject be be found in this wikipedia * article. * *

* The inspector methods work in a lazy fashion: no computations are performed unless immediately * necessary. Computation are done once and results are cached within this class for future need. * The core of this class is built around a recursive Depth-first search. * * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class BiconnectivityInspector { /** * Constructs a new BiconnectivityInspector * * @param graph the input graph */ public BiconnectivityInspector(Graph graph) { this.graph = Objects.requireNonNull(graph); if (graph.getType().isDirected()) this.graph = new AsUndirectedGraph<>(graph); } private Graph graph; private Set> blocks; private Set cutpoints; private Set bridges; /* Set which holds the vertices in the connected component which is being processed */ private Set connectedSet; /* Set which holds all connected components, expressed in vertex sets */ private Set> connectedSets; /* Set of connected components */ private Set> connectedComponents; /* Mapping of vertices to the blocks they are contained in */ private Map>> vertex2blocks; /* Mapping of vertices to the connected components they are contained in */ private Map> vertex2components; /* Discovery time of a vertex. */ private int time; /* Stack which keeps track of edges in biconnected components */ private Deque stack; /* Map which tracks when a vertex is discovered in the DFS search */ private Map discTime = new HashMap<>(); /** * Returns the cutpoints * (articulation points) of the graph. A vertex is a cutpoint if removal of that vertex (and all * edges incident to that vertex) would increase the number of (weakly) connected components in * the graph. * * @return the cutpoints of the graph */ public Set getCutpoints() { performLazyInspection(); return this.cutpoints; } /** * Returns the graph's bridges. An edge is a * bridge if removal of that edge * would increase the number of (weakly) connected components in the graph. Note that this * definition remains applicable in case of multigraphs or pseudographs. * * @return the graph's bridges */ public Set getBridges() { performLazyInspection(); return this.bridges; } /** * Returns a set of blocks (biconnected * components) containing the specified vertex. A block is a maximal biconnected subgraph. Each * non-cutpoint resides in at most one block. Each cutpoint resides in at least two blocks. * * @param vertex vertex in the initial graph. * @return the blocks containing the given vertex */ public Set> getBlocks(V vertex) { assert graph.containsVertex(vertex); if (vertex2blocks == null) { vertex2blocks = new HashMap<>(); for (V v : graph.vertexSet()) vertex2blocks.put(v, new LinkedHashSet<>()); for (Graph block : this.getBlocks()) { for (V v : block.vertexSet()) vertex2blocks.get(v).add(block); } } return this.vertex2blocks.get(vertex); } /** * Returns all blocks (biconnected * components) in the graph. A block is a maximal biconnected subgraph. * * @return all blocks (biconnected components) in the graph. */ public Set> getBlocks() { performLazyInspection(); return this.blocks; } /** * Returns all connected components in the graph. In case the graph is directed, this method * returns all weakly connected components. * * @return all connected components in the graph if the graph is undirected, or all weakly * connected components if the graph is directed. */ public Set> getConnectedComponents() { if (connectedComponents == null) { performLazyInspection(); connectedComponents = new LinkedHashSet<>(); for (Set vertexComponent : connectedSets) connectedComponents.add(new AsSubgraph<>(this.graph, vertexComponent)); } return connectedComponents; } /** * Returns the connected component containing the given vertex. If the underlying graph is * directed, this method returns a weakly connected component. * * @param vertex vertex * @return the connected component containing the given vertex, or a weakly connected component * if the underlying graph is directed. */ public Graph getConnectedComponent(V vertex) { assert this.graph.containsVertex(vertex); if (vertex2components == null) { vertex2components = new HashMap<>(); for (Graph component : this.getConnectedComponents()) for (V v : component.vertexSet()) vertex2components.put(v, component); } return vertex2components.get(vertex); } /** * Tests if the inspected graph is biconnected. A biconnected graph is a connected graph on two * or more vertices having no cutpoints. * * @return true if the graph is biconnected, false otherwise */ public boolean isBiconnected() { performLazyInspection(); return graph.vertexSet().size() >= 2 && blocks.size() == 1; } /** * Test if the inspected graph is connected. A graph is connected when, while ignoring edge * directionality, there exists a path between every pair of vertices. In a connected graph, * there are no unreachable vertices. When the inspected graph is a directed graph, this * method returns true if and only if the inspected graph is weakly connected. An empty * graph is not considered connected. * * @return true if and only if inspected graph is connected. */ public boolean isConnected() { performLazyInspection(); return connectedSets.size() == 1; } private void init() { blocks = new LinkedHashSet<>(); cutpoints = new LinkedHashSet<>(); bridges = new LinkedHashSet<>(); connectedSets = new LinkedHashSet<>(); stack = new ArrayDeque<>(graph.edgeSet().size()); for (V v : graph.vertexSet()) discTime.put(v, -1); } private void performLazyInspection() { if (blocks == null) { init(); // Iterate over all connected components for (V v : graph.vertexSet()) { if (discTime.get(v) == -1) { connectedSet = new HashSet<>(); dfs(v, null); // Stack can be non-empty when dfs finishes, for instance if the graph has no // cutpoints. // Construct the final component from the remaining edges. if (!stack.isEmpty()) buildBlock(0); connectedSets.add(connectedSet); } } if (this.graph.getType().isAllowingMultipleEdges()) { // check parallel edges: an edge is not a bridge when there are multiple edges // between the same pair of vertices for (Iterator it = bridges.iterator(); it.hasNext();) { E edge = it.next(); int nrParallelEdges = graph .getAllEdges(graph.getEdgeSource(edge), graph.getEdgeTarget(edge)).size(); if (nrParallelEdges > 1) it.remove(); } } } } /** * Each time a cutpoint is discovered, this method computes the biconnected component * * @param discTimeCutpoint discovery time of cutpoint */ private void buildBlock(int discTimeCutpoint) { Set vertexComponent = new HashSet<>(); while (!stack.isEmpty()) { E edge = stack.peek(); V source = graph.getEdgeSource(edge); V target = graph.getEdgeTarget(edge); if (discTime.get(source) < discTimeCutpoint && discTime.get(target) < discTimeCutpoint) break; stack.pop(); vertexComponent.add(source); vertexComponent.add(target); } blocks.add(new AsSubgraph<>(this.graph, vertexComponent)); } /** * Performs a depth-first search, starting from vertex v * * @param v vertex * @param parent parent of v * @return lowpoint of v */ private int dfs(V v, V parent) { int lowV = ++this.time; discTime.put(v, time); connectedSet.add(v); int children = 0; for (E edge : this.graph.edgesOf(v)) { V nv = Graphs.getOppositeVertex(this.graph, edge, v); if (discTime.get(nv) == -1) { // Node hasn't been discovered yet children++; this.stack.push(edge); int lowNV = dfs(nv, v); lowV = Math.min(lowNV, lowV); if (lowNV > discTime.get(v)) bridges.add(edge); // 1. nonroot vertex v is a cutpoint iff there is a child y of v such that // lowpoint(y) >= depth(v) // 2. root vertex v is a cutpoint if it has more than 1 child if ((parent != null && lowNV >= discTime.get(v)) || (parent == null && children > 1)) { this.cutpoints.add(v); // v is a cutpoint buildBlock(discTime.get(nv)); // construct biconnected component } } else if ((discTime.get(nv) < discTime.get(v)) && !nv.equals(parent)) { // found // backedge this.stack.push(edge); lowV = Math.min(discTime.get(nv), lowV); } } return lowV; } } BlockCutpointGraph.java000066400000000000000000000112271402514743400336770ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2007-2021, by France Telecom and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; /** * A Block-Cutpoint graph (also known as a block-cut tree). If $G$ is a graph, the block-cutpoint * graph of $G$, denoted $BC(G)$ is the simple bipartite graph with bipartition $(A, B)$ where $A$ * is the set of cut-vertices * (also known as articulation points) of $G$, and $B$ is the set of * blocks of $G$. $BC(G)$ contains an edge * $(a,b)$ for $a \in A$ and $b \in B$ if and only if block $b$ contains the cut-vertex $a$. A * vertex in $G$ is a cut-vertex if removal of the vertex from $G$ (and all edges incident to this * vertex) increases the number of connected components in the graph. A block of $G$ is a maximal * connected subgraph $H \subseteq G$ so that $H$ does not have a cut-vertex. Note that if $H$ is a * block, then either $H$ is 2-connected, or $|V(H)| \leq 2$. Each pair of blocks of $G$ share at * most one vertex, and that vertex is a cut-point in $G$. $BC(G)$ is a tree in which each leaf node * corresponds to a block of $G$. *

* Note: the block-cutpoint graph is not changed when the underlying graph is changed. * * * @param the graph vertex type * @param the graph edge type * * @author France Telecom S.A * @author Joris Kinable */ public class BlockCutpointGraph extends SimpleGraph, DefaultEdge> { private static final long serialVersionUID = -9101341117013163934L; /* Input graph */ private Graph graph; /* Set of cutpoints */ private Set cutpoints; /* Set of blocks */ private Set> blocks; /* Mapping of a vertex to the block it belongs to. */ private Map> vertex2block = new HashMap<>(); /** * Constructs a Block-Cutpoint graph * * @param graph the input graph */ public BlockCutpointGraph(Graph graph) { super(DefaultEdge.class); this.graph = graph; BiconnectivityInspector biconnectivityInspector = new BiconnectivityInspector<>(graph); // Construct the Block-cut point graph cutpoints = biconnectivityInspector.getCutpoints(); blocks = biconnectivityInspector.getBlocks(); for (Graph block : blocks) for (V v : block.vertexSet()) vertex2block.put(v, block); Graphs.addAllVertices(this, blocks); for (V cutpoint : this.cutpoints) { Graph subgraph = new AsSubgraph<>(graph, Collections.singleton(cutpoint)); this.vertex2block.put(cutpoint, subgraph); this.addVertex(subgraph); for (Graph block : biconnectivityInspector.getBlocks(cutpoint)) addEdge(subgraph, block); } } /** * Returns the vertex if vertex is a cutpoint, and otherwise returns the block (biconnected * component) containing the vertex. * * @param vertex vertex * @return the biconnected component containing the vertex */ public Graph getBlock(V vertex) { assert this.graph.containsVertex(vertex); return this.vertex2block.get(vertex); } /** * Returns all blocks (biconnected components) in the graph * * @return all blocks (biconnected components) in the graph. */ public Set> getBlocks() { return blocks; } /** * Returns the cutpoints of the initial graph. * * @return the cutpoints of the initial graph */ public Set getCutpoints() { return cutpoints; } /** * Returns true if the vertex is a cutpoint, false otherwise. * * @param vertex vertex in the initial graph. * @return true if the vertex is a cutpoint, false otherwise. */ public boolean isCutpoint(V vertex) { return cutpoints.contains(vertex); } } ConnectivityInspector.java000066400000000000000000000223321402514743400345010ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.event.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.util.*; /** * Allows obtaining various connectivity aspects of a graph. The inspected graph is specified * at construction time and cannot be modified. Currently, the inspector supports connected * components for an undirected graph and weakly connected components for a directed graph. To find * strongly connected components, use {@link KosarajuStrongConnectivityInspector} instead. * *

* The inspector methods work in a lazy fashion: no computation is performed unless immediately * necessary. Computation are done once and results and cached within this class for future need. *

* *

* The inspector is also a {@link org.jgrapht.event.GraphListener}. If added as a listener to the * inspected graph, the inspector will amend internal cached results instead of recomputing them. It * is efficient when a few modifications are applied to a large graph. If many modifications are * expected it will not be efficient due to added overhead on graph update operations. If inspector * is added as listener to a graph other than the one it inspects, results are undefined. *

* * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh * @author John V. Sichi */ public class ConnectivityInspector implements GraphListener { private List> connectedSets; private Map> vertexToConnectedSet; private Graph graph; /** * Creates a connectivity inspector for the specified graph. * * @param g the graph for which a connectivity inspector to be created. */ public ConnectivityInspector(Graph g) { init(); this.graph = Objects.requireNonNull(g); if (g.getType().isDirected()) this.graph = new AsUndirectedGraph<>(g); } /** * Test if the inspected graph is connected. A graph is connected when there is a path between * every pair of vertices. In a connected graph, there are no unreachable vertices. When the * inspected graph is a directed graph, this method returns true if and only if the * inspected graph is weakly connected. An empty graph is not considered * connected. * * @return true if and only if inspected graph is connected. */ public boolean isConnected() { return lazyFindConnectedSets().size() == 1; } /** * Returns a set of all vertices that are in the maximally connected component together with the * specified vertex. For more on maximally connected component, see * * http://www.nist.gov/dads/HTML/maximallyConnectedComponent.html. * * @param vertex the vertex for which the connected set to be returned. * * @return a set of all vertices that are in the maximally connected component together with the * specified vertex. */ public Set connectedSetOf(V vertex) { Set connectedSet = vertexToConnectedSet.get(vertex); if (connectedSet == null) { connectedSet = new HashSet<>(); BreadthFirstIterator i = new BreadthFirstIterator<>(graph, vertex); while (i.hasNext()) { connectedSet.add(i.next()); } vertexToConnectedSet.put(vertex, connectedSet); } return connectedSet; } /** * Returns a list of Set s, where each set contains all vertices that are in the * same maximally connected component. All graph vertices occur in exactly one set. For more on * maximally connected component, see * * http://www.nist.gov/dads/HTML/maximallyConnectedComponent.html. * * @return Returns a list of Set s, where each set contains all vertices that are * in the same maximally connected component. */ public List> connectedSets() { return lazyFindConnectedSets(); } /** * @see GraphListener#edgeAdded(GraphEdgeChangeEvent) */ @Override public void edgeAdded(GraphEdgeChangeEvent e) { V source = e.getEdgeSource(); V target = e.getEdgeTarget(); Set sourceSet = connectedSetOf(source); Set targetSet = connectedSetOf(target); // If source and target are in the same set, do nothing, otherwise, merge sets if (sourceSet != targetSet) { Set merge = CollectionUtil.newHashSetWithExpectedSize(sourceSet.size() + targetSet.size()); merge.addAll(sourceSet); merge.addAll(targetSet); connectedSets.remove(sourceSet); connectedSets.remove(targetSet); connectedSets.add(merge); for (V v : merge) vertexToConnectedSet.put(v, merge); } } /** * @see GraphListener#edgeRemoved(GraphEdgeChangeEvent) */ @Override public void edgeRemoved(GraphEdgeChangeEvent e) { init(); // for now invalidate cached results, in the future need to // amend them. If the edge is a bridge, 2 components need to be split. } /** * Tests whether two vertices lay respectively in the same connected component (undirected * graph), or in the same weakly connected component (directed graph). * * @param sourceVertex one end of the path. * @param targetVertex another end of the path. * * @return true if and only if the source and target vertex are in the same * connected component (undirected graph), or in the same weakly connected component * (directed graph). */ public boolean pathExists(V sourceVertex, V targetVertex) { return connectedSetOf(sourceVertex).contains(targetVertex); } /** * @see VertexSetListener#vertexAdded(GraphVertexChangeEvent) */ @Override public void vertexAdded(GraphVertexChangeEvent e) { Set component = new HashSet<>(); component.add(e.getVertex()); connectedSets.add(component); vertexToConnectedSet.put(e.getVertex(), component); } /** * @see VertexSetListener#vertexRemoved(GraphVertexChangeEvent) */ @Override public void vertexRemoved(GraphVertexChangeEvent e) { init(); // for now invalidate cached results, in the future need to // amend them. If the vertex is an articulation point, two // components need to be split } private void init() { connectedSets = null; vertexToConnectedSet = new HashMap<>(); } private List> lazyFindConnectedSets() { if (connectedSets == null) { connectedSets = new ArrayList<>(); Set vertexSet = graph.vertexSet(); if (!vertexSet.isEmpty()) { BreadthFirstIterator i = new BreadthFirstIterator<>(graph); i.addTraversalListener(new MyTraversalListener()); while (i.hasNext()) { i.next(); } } } return connectedSets; } /** * A traversal listener that groups all vertices according to to their containing connected set. * * @author Barak Naveh */ private class MyTraversalListener extends TraversalListenerAdapter { private Set currentConnectedSet; /** * @see TraversalListenerAdapter#connectedComponentFinished(ConnectedComponentTraversalEvent) */ @Override public void connectedComponentFinished(ConnectedComponentTraversalEvent e) { connectedSets.add(currentConnectedSet); } /** * @see TraversalListenerAdapter#connectedComponentStarted(ConnectedComponentTraversalEvent) */ @Override public void connectedComponentStarted(ConnectedComponentTraversalEvent e) { currentConnectedSet = new HashSet<>(); } /** * @see TraversalListenerAdapter#vertexTraversed(VertexTraversalEvent) */ @Override public void vertexTraversed(VertexTraversalEvent e) { V v = e.getVertex(); currentConnectedSet.add(v); vertexToConnectedSet.put(v, currentConnectedSet); } } } GabowStrongConnectivityInspector.java000066400000000000000000000117151402514743400366610ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2013-2021, by Sarah Komla-Ebri and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * Computes the strongly connected components of a directed graph. The implemented algorithm follows * Cheriyan-Mehlhorn/Gabow's algorithm presented in Path-based depth-first search for strong and * biconnected components by Gabow (2000). The running time is order of $O(|V|+|E|)$. * * @param the graph vertex type * @param the graph edge type * * @author Sarah Komla-Ebri * @author Hannes Wellmann */ public class GabowStrongConnectivityInspector extends AbstractStrongConnectivityInspector { // the sequence of (original) vertices encountered but not yet assigned to a component private Deque> stackS = new ArrayDeque<>(); // the boundaries between contracted vertices on the current path of the dfs-tree private Deque> stackB = new ArrayDeque<>(); // maps vertices to their VertexNumber object private Map> vertexToVertexNumber; // number of vertices private int c; /** * Constructor * * @param graph the graph to inspect * @throws NullPointerException in case the graph is null */ public GabowStrongConnectivityInspector(Graph graph) { super(graph); } @Override public List> stronglyConnectedSets() { if (stronglyConnectedSets == null) { stronglyConnectedSets = new ArrayList<>(); // create VertexData objects for all vertices, store them createVertexNumber(); // perform DFS for (VertexNumber data : vertexToVertexNumber.values()) { if (data.number == 0) { dfsVisit(data); } } vertexToVertexNumber = null; stackS = null; stackB = null; } return stronglyConnectedSets; } /* * Creates a VertexNumber object for every vertex in the graph and stores them in a HashMap. */ private void createVertexNumber() { c = graph.vertexSet().size(); vertexToVertexNumber = CollectionUtil.newHashMapWithExpectedSize(c); for (V vertex : graph.vertexSet()) { vertexToVertexNumber.put(vertex, new VertexNumber<>(vertex)); } stackS = new ArrayDeque<>(c); stackB = new ArrayDeque<>(c); } /* * The subroutine of DFS. */ private void dfsVisit(VertexNumber v) { stackS.push(v); v.number = stackS.size(); stackB.push(v); // follow all edges for (E edge : graph.outgoingEdgesOf(v.vertex)) { VertexNumber w = vertexToVertexNumber.get(graph.getEdgeTarget(edge)); if (w.number == 0) { dfsVisit(w); } else { /* contract if necessary */ while (w.number < stackB.peek().number) { stackB.pop(); } } } if (v == stackB.peek()) { // number vertices of the next strong component stackB.pop(); c++; Set sccVertices = createSCCVertexSetAndNumberVertices(v); stronglyConnectedSets.add(sccVertices); } } private Set createSCCVertexSetAndNumberVertices(VertexNumber v) { int sccSize = stackS.size() - v.number + 1; // All VertexNumber objects on S above and including v form the current SCC. // To collect them from S, elements have to be popped while the size of S is greater or // equal to v.number. This results in removals(pops) from S. Set scc; if (sccSize == 1) { VertexNumber r = stackS.pop(); scc = Collections.singleton(r.vertex); r.number = c; } else { scc = CollectionUtil.newHashSetWithExpectedSize(sccSize); for (int i = 0; i < sccSize; i++) { VertexNumber r = stackS.pop(); scc.add(r.vertex); r.number = c; } } return scc; } private static final class VertexNumber { private final V vertex; private int number = 0; private VertexNumber(V vertex) { this.vertex = vertex; } } } KosarajuStrongConnectivityInspector.java000066400000000000000000000170041402514743400373760ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2005-2021, by Christian Soltenborn and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; /** * Computes strongly connected components of a directed graph. The algorithm is implemented after * "Cormen et al: Introduction to algorithms", Chapter 22.5. It has a running time of $O(V + E)$. * *

* Unlike {@link ConnectivityInspector}, this class does not implement incremental inspection. The * full algorithm is executed at the first call of * {@link KosarajuStrongConnectivityInspector#stronglyConnectedSets()} or * {@link KosarajuStrongConnectivityInspector#isStronglyConnected()}. * * @param the graph vertex type * @param the graph edge type * * @author Christian Soltenborn * @author Christian Hammer */ public class KosarajuStrongConnectivityInspector extends AbstractStrongConnectivityInspector { // stores the vertices, ordered by their finishing time in first dfs private LinkedList> orderedVertices; // maps vertices to their VertexData object private Map> vertexToVertexData; /** * Constructor * * @param graph the input graph * @throws NullPointerException if the input graph is null */ public KosarajuStrongConnectivityInspector(Graph graph) { super(graph); } @Override public List> stronglyConnectedSets() { if (stronglyConnectedSets == null) { orderedVertices = new LinkedList<>(); stronglyConnectedSets = new ArrayList<>(); // create VertexData objects for all vertices, store them createVertexData(); // perform the first round of DFS, result is an ordering // of the vertices by decreasing finishing time for (VertexData data : vertexToVertexData.values()) { if (!data.isDiscovered()) { dfsVisit(graph, data, null); } } // 'create' inverse graph (i.e. every edge is reversed) Graph inverseGraph = new EdgeReversedGraph<>(graph); // get ready for next dfs round resetVertexData(); // second dfs round: vertices are considered in decreasing // finishing time order; every tree found is a strongly // connected set for (VertexData data : orderedVertices) { if (!data.isDiscovered()) { // new strongly connected set Set set = new HashSet<>(); stronglyConnectedSets.add(set); dfsVisit(inverseGraph, data, set); } } // clean up for garbage collection orderedVertices = null; vertexToVertexData = null; } return stronglyConnectedSets; } /* * Creates a VertexData object for every vertex in the graph and stores them in a HashMap. */ private void createVertexData() { vertexToVertexData = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); for (V vertex : graph.vertexSet()) { vertexToVertexData.put(vertex, new VertexData2<>(vertex, false, false)); } } /* * The subroutine of DFS. NOTE: the set is used to distinguish between 1st and 2nd round of DFS. * set == null: finished vertices are stored (1st round). set != null: all vertices found will * be saved in the set (2nd round) */ private void dfsVisit(Graph visitedGraph, VertexData vertexData, Set vertices) { Deque> stack = new ArrayDeque<>(); stack.add(vertexData); while (!stack.isEmpty()) { VertexData data = stack.removeLast(); if (!data.isDiscovered()) { data.setDiscovered(true); if (vertices != null) { vertices.add(data.getVertex()); } stack.add(new VertexData1<>(data, true, true)); // follow all edges for (E edge : visitedGraph.outgoingEdgesOf(data.getVertex())) { VertexData targetData = vertexToVertexData.get(visitedGraph.getEdgeTarget(edge)); if (!targetData.isDiscovered()) { // the "recursion" stack.add(targetData); } } } else if (data.isFinished() && vertices == null) { orderedVertices.addFirst(data.getFinishedData()); } } } /* * Resets all VertexData objects. */ private void resetVertexData() { for (VertexData data : vertexToVertexData.values()) { data.setDiscovered(false); data.setFinished(false); } } /* * Lightweight class storing some data for every vertex. */ private abstract static class VertexData { private byte bitfield; private VertexData(boolean discovered, boolean finished) { this.bitfield = 0; setDiscovered(discovered); setFinished(finished); } private boolean isDiscovered() { return (bitfield & 1) == 1; } private boolean isFinished() { return (bitfield & 2) == 2; } private void setDiscovered(boolean discovered) { if (discovered) { bitfield |= 1; } else { bitfield &= ~1; } } private void setFinished(boolean finished) { if (finished) { bitfield |= 2; } else { bitfield &= ~2; } } abstract VertexData getFinishedData(); abstract V getVertex(); } private static final class VertexData1 extends VertexData { private final VertexData finishedData; private VertexData1(VertexData finishedData, boolean discovered, boolean finished) { super(discovered, finished); this.finishedData = finishedData; } @Override VertexData getFinishedData() { return finishedData; } @Override V getVertex() { return null; } } private static final class VertexData2 extends VertexData { private final V vertex; private VertexData2(V vertex, boolean discovered, boolean finished) { super(discovered, finished); this.vertex = vertex; } @Override VertexData getFinishedData() { return null; } @Override V getVertex() { return vertex; } } } TreeDynamicConnectivity.java000066400000000000000000000524741402514743400347510ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.util.*; import java.util.*; import java.util.stream.*; import static org.jgrapht.util.AVLTree.TreeNode; import static org.jgrapht.util.DoublyLinkedList.ListNode; /** * Data structure for storing dynamic trees and querying node connectivity *

* This data structure supports the following operations: *

    *
  • Adding an element in $\mathcal{O}(\log 1)$
  • *
  • Checking if an element in present in $\mathcal{O}(1)$
  • *
  • Connecting two elements in $\mathcal{O}(\log n)$
  • *
  • Checking if two elements are connected in $\mathcal{O}(\log n)$
  • *
  • Removing connection between two nodes in $\mathcal{O}(\log n)$
  • *
  • Removing an element in $\mathcal{O}(deg(element)\cdot\log n + 1)$
  • *
*

* This data structure doesn't allow to store graphs with cycles. Also, the edges are considered to * be undirected. The memory complexity is linear in the number of inserted elements. The * implementation is based on the * Euler tour technique. *

* For the description of the Euler tour data structure, we refer to the Monika Rauch Henzinger, * Valerie King: Randomized dynamic graph algorithms with polylogarithmic time per operation. STOC * 1995: 519-527 * * @param element type * @author Timofey Chudakov */ public class TreeDynamicConnectivity { /** * Mapping from tree minimums to the trees they're stored in. This map contains one entry per * each tree, which has at least two nodes. */ private Map, AVLTree> minToTreeMap; /** * Mapping from the user specified values to the internal nodes they're represented by */ private Map nodeMap; /** * Mapping from zero-degree nodes to their trees. This map contains one entry for each * zero-degree node */ private Map> singletonNodes; /** * Constructs a new {@code TreeDynamicConnectivity} instance */ public TreeDynamicConnectivity() { minToTreeMap = new HashMap<>(); nodeMap = new HashMap<>(); singletonNodes = new HashMap<>(); } /** * Adds an {@code element} to this data structure. If the {@code element} has been added before, * this method returns {@code false} and has no effect. *

* This method has $\mathcal{O}(\log 1)$ running time complexity * * @param element an element to add * @return {@code true} upon successful modification, {@code false} otherwise */ public boolean add(T element) { if (contains(element)) { return false; } AVLTree newTree = new AVLTree<>(); Node node = new Node(element); nodeMap.put(element, node); singletonNodes.put(node, newTree); return true; } /** * Removes the {@code element} from this data structure. This method has no effect if the * {@code element} hasn't been added to this data structure *

* This method has $\mathcal{O}(deg(element)\cdot\log n + 1)$ running time complexity * * @param element an element to remove * @return {@code true} upon successful modification, {@code false} otherwise */ public boolean remove(T element) { if (!contains(element)) { return false; } Node node = getNode(element); while (!node.isSingleton()) { T targetValue = node.arcs.getLast().target.value; cut(element, targetValue); } nodeMap.remove(element); singletonNodes.remove(node); return true; } /** * Checks if this data structure contains the {@code element}. *

* This method has expected $\mathcal{O}(1)$ running time complexity * * @param element an element to check presence of * @return {@code true} if the {@code element} is stored in this data structure, {@code false} * otherwise */ public boolean contains(T element) { return nodeMap.containsKey(element); } /** * Adds an edge between the {@code first} and {@code second} elements. The method has no effect * if the elements are already connected by some path, i.e. belong to the same tree. In the case * some of the nodes haven't been added before, they're added to this data structure. *

* This method has $\mathcal{O}(\log n)$ running time complexity * * @param first an element * @param second an element * @return {@code true} upon successful modification, {@code false} otherwise */ public boolean link(T first, T second) { /* * Example: we have two trees [1 - 2] and [3 - 4 - 5] * * Euler tour of the first tree: [1 - 2] Euler tour of the second tree: [3 - 4 - 5 - 4] * * By invariant used in this implementation, we do not return to the start node * * Suppose, that we have a request: link(1, 5) */ addIfAbsent(first); addIfAbsent(second); if (connected(first, second)) { return false; } Node firstNode = getNode(first); Node secondNode = getNode(second); AVLTree firstTree = getTree(firstNode); AVLTree secondTree = getTree(secondNode); minToTreeMap.remove(firstTree.getMin()); minToTreeMap.remove(secondTree.getMin()); /* * First we make the nodes 1 and 5 the roots of the corresponding trees: * * [1 - 2] --> [1 - 2] [3 - 4 - 5 - 4] --> [5 - 4 - 3 - 4] */ makeRoot(firstTree, firstNode); makeRoot(secondTree, secondNode); /* * Add one more occurrence for the first element to the second tree: * * [5 - 4 - 3 - 4] --> [1 - 5 - 4 - 3 - 4] */ TreeNode newFirstOccurrence = secondTree.addMin(first); Arc newFirstArc = new Arc(secondNode, newFirstOccurrence); if (firstNode.isSingleton()) { // newFirstArc becomes the first arc of the first node singletonNodes.remove(firstNode); firstNode.addArcLast(newFirstArc); } else { /* * Since second element will be not the only element adjacent to the first element, we * are going to insert the arc to the second element into the circular list of arcs of * the first node * * Since first element is a root currently, we can find out the last outgoing arc by * simply checking the last element in its Euler tour * * In the example above, the last arc is (1, 2), so we're going to append a new arc (1, * 5) after it. * * By invariant we're maintaining, a subtree tour computed by following the arc is * placed after the arc tree node reference. * * For example, the first node will have 2 arcs: (1, 2) and (1, 5). If we follow the arc * (1, 2), a subtour will be just [2]. If we follow the arc (1, 5), the subtour will be * [5 - 4 - 3 - 4 - 5]. So, the arc will have the following tree node references * * (1, 2) [(1) - 2 - 1 - 5 - 4 - 3 - 4 - 5] | | ------------ (1, 5) [1 - 2 - (1) - 5 - 4 * - 3 - 4 - 5] | | -------------------- * * If we decide to make the arc (1, 5) the first arc, the method will just take the tree * node reference of the arc (1, 5) and will place it at the first place (1, 5) [(1) - 5 * - 4 - 3 - 4 - 5 - 1 - 2] | | ------------ (1, 2) [1 - 5 - 4 - 3 - 4 - 5 - (1) - 2] | * | ------------------------------------ */ T lastChild = firstTree.getMax().getValue(); Node lastChildNode = getNode(lastChild); Arc arcToLastChild = firstNode.getArcTo(lastChildNode); firstNode.addArcAfter(arcToLastChild, newFirstArc); } /* * Add one more occurrence for the second element to the second tree: * * [1 - 5 - 4 - 3 - 4] -> [1 - 5 - 4 - 3 - 4 - 5] * */ TreeNode newSecondOccurrence = secondTree.addMax(second); Arc newSecondArc = new Arc(firstNode, newSecondOccurrence); if (secondNode.isSingleton()) { // newSecondArc becomes the first arc of the second node singletonNodes.remove(secondNode); secondNode.addArcLast(newSecondArc); } else { /* * Similarly to the first case, we need to find out the last arc of the second node. At * this moment, the second tree looks like this: * * [1 - 5 - 4 - 3 - 4 - 5] * * The only arc of the node 5 is (5, 4). After the link operation, the node five will * have one more arc: (5, 1). The tree node references for the node 5 will look like * this: * * (5, 4) [1 - 2 - 1 - (5) - 4 - 3 - 4 - 5] | | -------------------------- (5, 1) [1 - 2 * - 1 - 5 - 4 - 3 - 4 - (5)] | | ------------------------------------------ * * Note that the invariant of the arc tree node references has a circular manner: the * subtree tour of the arc (5, 1) is [1 - 2 - 1], which is right after the tree node * reference of the arc (5, 1). */ T lastChild = secondTree.getMax().getPredecessor().getValue(); Node lastChildNode = getNode(lastChild); Arc arcToLastChild = secondNode.getArcTo(lastChildNode); secondNode.addArcAfter(arcToLastChild, newSecondArc); } /* * Merge the first and the second tree to obtain an Euler tour of the combined tree: * * [1 - 2] + [1 - 5 - 4 - 3 - 4 - 5] = [1 - 2 - 1 - 5 - 4 - 3 - 4 - 5] */ firstTree.mergeAfter(secondTree); minToTreeMap.put(firstTree.getMin(), firstTree); return true; } /** * Checks if the {@code first} and {@code second} belong to the same tree. The method will * return {@code false} if either of the elements hasn't been added to this data structure *

* This method has $\mathcal{O}(\log n)$ running time complexity * * @param first an element * @param second an element * @return {@code true} if the elements belong to the same tree, {@code false} otherwise */ public boolean connected(T first, T second) { if (!contains(first) || !contains(second)) { return false; } Node firstNode = getNode(first); if (firstNode.isSingleton()) { return false; } Node secondNode = getNode(second); if (secondNode.isSingleton()) { return false; } return getTree(firstNode) == getTree(secondNode); } /** * Removes an edge between the {@code first} and {@code second}. This method doesn't have any * effect if there's no edge between these elements *

* This method has $\mathcal{O}(\log n)$ running time complexity * * @param first an element * @param second an element * @return {@code true} upon successful modification, {@code false} otherwise */ public boolean cut(T first, T second) { if (!connected(first, second)) { return false; } /* * Suppose, we have a tree [2 - [1] - 5 - 4 - 3], which has the following Euler tour: * * [1 - 2 - 1 - 5 - 4 - 3 - 4 - 5] * * Let's assume that we received a query: cut(1, 2) */ Node firstNode = getNode(first); Node secondNode = getNode(second); AVLTree tree = getTree(firstNode); minToTreeMap.remove(tree.getMin()); /* * The arcToSecond is (1, 2). The operation of making the arc (1, 2) the last arc will * transform the tree as follows: * * (1, 2) [(1) - 2 - 1 - 5 - 4 - 3 - 4 - 5] --> [1 - 5 - 4 - 3 - 4 - 5 - (1) - 2] | | | * -------------------------------------------------------------------------- * * After this operation, a subtree of the arc (1, 2) is at the end of the Euler tour */ Arc arcToSecond = firstNode.getArcTo(secondNode); if (arcToSecond == null) { throw new IllegalArgumentException( String.format("Elements {%s} and {%s} are not connected", first, second)); } makeLastArc(tree, firstNode, arcToSecond); /* * Now we remove the subtree of the arc (1, 2) from the Euler tour: * * |-------> [1 - 5 - 4 - 3 - 4 - 5 - 1] (left part [1 - 5 - 4 - 3 - 4 - 5 - 1 - 2] -----| * |-------> [2] (right part) */ AVLTree right = tree.splitAfter(arcToSecond.arcTreeNode); /* * Removing the last occurrence of the element 1 from the Euler tour: * * [1 - 5 - 4 - 3 - 4 - 5 - 1] --> [1 - 5 - 4 - 3 - 4 - 5] * * Now the left part is a valid Euler tour */ tree.removeMax(); firstNode.removeArc(arcToSecond); if (!firstNode.isSingleton()) { minToTreeMap.put(tree.getMin(), tree); } else { singletonNodes.put(firstNode, tree); } /* * Removing the last occurrence of the element 2 from the right tree: * * [2] -> [] * * The element 2 becomes an element of zero degree (a singleton node). No arcs means an * empty tree * * That's why we place it to the map for zero degree nodes */ Arc secondToFirst = secondNode.getArcTo(firstNode); right.removeMax(); secondNode.removeArc(secondToFirst); if (!secondNode.isSingleton()) { minToTreeMap.put(right.getMin(), right); } else { singletonNodes.put(secondNode, right); } return true; } /** * Makes the {@code node} the root of the tree. In practice, this means that the value of the * {@code node} is the first in the Euler tour * * @param tree a tree the {@code node} is stored in * @param node a node to make a root */ private void makeRoot(AVLTree tree, Node node) { if (node.arcs.isEmpty()) { return; } makeFirstArc(tree, node.arcs.get(0)); } /** * Makes the {@code arc} the first arc traversed by the Euler tour * * @param tree corresponding binary tree the Euler tour is stored in * @param arc an arc to use for tree re-rooting */ private void makeFirstArc(AVLTree tree, Arc arc) { AVLTree right = tree.splitBefore(arc.arcTreeNode); tree.mergeBefore(right); } /** * Makes the {@code arc} the last arc of the {@code node} according to the Euler tour * * @param tree corresponding binary tree the Euler tour is stored in * @param node a new root node * @param arc an arc incident to the {@code node} */ private void makeLastArc(AVLTree tree, Node node, Arc arc) { if (node.arcs.size() == 1) { makeRoot(tree, node); } else { Arc nextArc = node.getNextArc(arc); makeFirstArc(tree, nextArc); } } /** * Returns an internal representation of the {@code element} * * @param element a user specified node element * @return an internal representation of the {@code element} */ private Node getNode(T element) { return nodeMap.get(element); } /** * Returns a binary tree, which contains an Euler tour of the tree the {@code node} belong to * * @param node a node * @return a corresponding binary tree an Euler tour is stored in */ private AVLTree getTree(Node node) { if (node.isSingleton()) { return singletonNodes.get(node); } return minToTreeMap.get(node.arcs.get(0).arcTreeNode.getTreeMin()); } /** * Adds the {@code element} to this data structure if it is not already present * * @param element a user specified element */ private void addIfAbsent(T element) { if (!contains(element)) { add(element); } } /** * An internal representation of the tree nodes. *

* Keeps track of the node values and outgoing arcs. The outgoing arcs are placed according to * the order they are traversed in the Euler tour */ private class Node { /** * Node value */ T value; /** * Arcs list */ DoublyLinkedList arcs; /** * Target node to arc mapping */ Map targetMap; /** * Constructs a new node * * @param value a user specified element to store in this node */ public Node(T value) { this.value = value; arcs = new DoublyLinkedList<>(); targetMap = new HashMap<>(); } /** * Removes the {@code arc} from the arc list * * @param arc an arc to remove */ void removeArc(Arc arc) { arcs.removeNode(arc.listNode); arc.listNode = null; targetMap.remove(arc.target); } /** * Append the {@code arc} to the arc list * * @param arc an arc to add */ void addArcLast(Arc arc) { arc.listNode = arcs.addElementLast(arc); targetMap.put(arc.target, arc); } /** * Inserts the {@code newArc} in the arc list after the {@code arc} * * @param arc an arc already stored in the arc list * @param newArc a new arc to add to the arc list */ void addArcAfter(Arc arc, Arc newArc) { newArc.listNode = arcs.addElementBeforeNode(arc.listNode.getNext(), newArc); targetMap.put(newArc.target, newArc); } /** * Returns an arc, which target is equal to the {@code node} * * @param node a target of the returned arc * @return an arc, which target is equal to the {@code node} */ Arc getArcTo(Node node) { return targetMap.get(node); } /** * Returns an arc which is stored right after the {@code arc}. The result may be equal to * the {@code arc} * * @param arc an arc stored in the arc list * @return an arc which is stored right after the {@code arc} */ Arc getNextArc(Arc arc) { return arc.listNode.getNext().getValue(); } /** * Checks if this node is a zero-degree node * * @return {@code true} if this node is a singleton node, {@code false otherwise} */ public boolean isSingleton() { return arcs.isEmpty(); } /** * {@inheritDoc} */ @Override public String toString() { return String .format( "{%s} -> [%s]", value, arcs .stream().map(a -> a.target.value.toString()) .collect(Collectors.joining(","))); } } /** * An internal representation of the tree edges. *

* Two arcs are created for every existing tree edge. This complies with the way an Euler tour * is constructed. */ private class Arc { /** * The target of this arc */ Node target; /** * A list node this arc is stored in. This is needed for constant time query time on the * doubly linked list. */ ListNode listNode; /** * The occurrence of the source node, which precedes the subtree Euler tour stored in the * binary tree */ TreeNode arcTreeNode; /** * Constructs a new arc with the target node {@code target} and the tree node reference * {@code arcTreeNode} * * @param target target node of this arc * @param arcTreeNode source tree node reference */ public Arc(Node target, TreeNode arcTreeNode) { this.target = target; this.arcTreeNode = arcTreeNode; } /** * {@inheritDoc} */ @Override public String toString() { return String.format("{%s} -> {%s}", arcTreeNode.getValue(), target.value); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/connectivity/package-info.java000066400000000000000000000014711402514743400325400ustar00rootroot00000000000000/** * Algorithms dealing with various connectivity aspects of a graph. * * A graph is connected when there is a path between every pair of vertices. In a connected graph, * there are no unreachable vertices. A graph that is not connected is disconnected. A connected * component is a maximal connected subgraph of $G$. Each vertex belongs to exactly one connected * component, as does each edge. *

* A directed graph is called weakly connected if replacing all of its directed edges with * undirected edges produces a connected (undirected) graph. It is strongly connected if it contains * a directed path from $u$ to $v$ and a directed path from $v$ to $u$ for every pair of vertices * $u$, $v$. The strong components are the maximal strongly connected subgraphs. * */ package org.jgrapht.alg.connectivity; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/000077500000000000000000000000001402514743400257275ustar00rootroot00000000000000AbstractFundamentalCycleBasis.java000066400000000000000000000131651402514743400344050ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; import java.util.stream.*; /** * A base implementation for the computation of a fundamental cycle basis of a graph. Subclasses * should only provide a method for constructing a spanning forest of the graph. A cycle basis is * fundamental if and only if each cycle in the basis contains at least one edge which is not * contained in any other cycle in the basis. * *

* For information on algorithms and heuristics for the computation of fundamental cycle bases see * the following paper: Narsingh Deo, G. Prabhu, and M. S. Krishnamoorthy. Algorithms for Generating * Fundamental Cycles in a Graph. ACM Trans. Math. Softw. 8, 1, 26-42, 1982. * *

* The implementation returns a fundamental cycle basis as an undirected cycle basis. For a * discussion of different kinds of cycle bases in graphs see the following paper: Christian * Liebchen, and Romeo Rizzi. Classes of Cycle Bases. Discrete Applied Mathematics, 155(3), 337-355, * 2007. * * @param the vertex type * @param the edge type * * @author Dimitrios Michail */ public abstract class AbstractFundamentalCycleBasis implements CycleBasisAlgorithm { protected Graph graph; /** * Constructor * * @param graph the input graph */ public AbstractFundamentalCycleBasis(Graph graph) { this.graph = GraphTests.requireDirectedOrUndirected(graph); } /** * {@inheritDoc} */ @Override public CycleBasis getCycleBasis() { // compute spanning forest Map spanningForest = computeSpanningForest(); // collect set with all tree edges Set treeEdges = spanningForest .entrySet().stream().map(Map.Entry::getValue).filter(Objects::nonNull) .collect(Collectors.toSet()); // build cycles for all non-tree edges Set> cycles = new LinkedHashSet<>(); int length = 0; double weight = 0d; for (E e : graph.edgeSet()) { if (!treeEdges.contains(e)) { Pair, Double> c = buildFundamentalCycle(e, spanningForest); cycles.add(c.getFirst()); length += c.getFirst().size(); weight += c.getSecond(); } } // return result return new CycleBasisImpl<>(graph, cycles, length, weight); } /** * Compute a spanning forest of the graph. * *

* The representation assumes that the map contains the predecessor edge of each vertex in the * forest. The predecessor edge is the forest edge that was used to discover the vertex. If no * such edge was used (the vertex is a leaf in the forest) then the corresponding entry must be * null. * * @return a map representation of a spanning forest. */ protected abstract Map computeSpanningForest(); /** * Given a non-tree edge and a spanning tree (forest) build a fundamental cycle. * * @param e a non-tree (forest) edge * @param spanningForest the spanning tree (forest) * @return a fundamental cycle */ private Pair, Double> buildFundamentalCycle(E e, Map spanningForest) { V source = graph.getEdgeSource(e); V target = graph.getEdgeTarget(e); // handle self-loops if (source.equals(target)) { return Pair.of(Collections.singletonList(e), graph.getEdgeWeight(e)); } /* * traverse half cycle */ Set path1 = new LinkedHashSet<>(); path1.add(e); V cur = source; while (!cur.equals(target)) { E edgeToParent = spanningForest.get(cur); if (edgeToParent == null) { break; } V parent = Graphs.getOppositeVertex(graph, edgeToParent, cur); path1.add(edgeToParent); cur = parent; } /* * traverse the other half cycle, while removing common edges */ double path2Weight = 0d; LinkedList path2 = new LinkedList<>(); if (!cur.equals(target)) { cur = target; while (true) { E edgeToParent = spanningForest.get(cur); if (edgeToParent == null) { break; } V parent = Graphs.getOppositeVertex(graph, edgeToParent, cur); if (path1.contains(edgeToParent)) { path1.remove(edgeToParent); } else { path2.add(edgeToParent); path2Weight += graph.getEdgeWeight(edgeToParent); } cur = parent; } } // now build cycle for (E a : path1) { path2Weight += graph.getEdgeWeight(a); path2.addFirst(a); } return Pair.of(path2, path2Weight); } } AhujaOrlinSharmaCyclicExchangeLocalAugmentation.java000066400000000000000000000427161402514743400400360ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/* * (C) Copyright 2018-2021, by Christoph Grüne and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; /** * Implementation of an algorithm for the local augmentation problem for the cyclic exchange * neighborhood, i.e. it finds subset-disjoint negative cycles in a graph, based on Ravindra K. * Ahuja, James B. Orlin, Dushyant Sharma, A composite very large-scale neighborhood structure for * the capacitated minimum spanning tree problem, Operations Research Letters, Volume 31, Issue 3, * 2003, Pages 185-194, ISSN 0167-6377, https://doi.org/10.1016/S0167-6377(02)00236-5. * (http://www.sciencedirect.com/science/article/pii/S0167637702002365) * * A subset-disjoint cycle is a cycle such that no two vertices in the cycle are in the same subset * of a given partition of the whole vertex set. * * This algorithm returns the first or the best found negative subset-disjoint cycle. In the case of * the first found cycle, the cycle has minimum number of vertices. It may enumerate all paths up to * the length given by the parameter lengthBound, i.e the algorithm runs in exponential * time. * * This algorithm is used to detect valid cyclic exchanges in a cyclic exchange neighborhood for the * Capacitated Minomum Spanning Tree problem * {@link org.jgrapht.alg.spanning.AhujaOrlinSharmaCapacitatedMinimumSpanningTree} * * @see org.jgrapht.alg.spanning.AhujaOrlinSharmaCapacitatedMinimumSpanningTree * * @param the vertex type the graph * @param the edge type of the graph * * @author Christoph Grüne * @since June 7, 2018 */ public class AhujaOrlinSharmaCyclicExchangeLocalAugmentation { /** * the input graph */ private Graph graph; /** * the map that maps each vertex to a subset (identified by labels) of the partition */ private Map labelMap; /** * bound on how long the cycle can get */ private int lengthBound; /** * contains whether the best or the first improvement is returned */ private boolean bestImprovement; /** * Constructs an algorithm with given inputs * * @param graph the directed graph on which to find the negative subset disjoint cycle. The * vertices of the graph are labeled according to labelMap. * @param lengthBound the (inclusive) upper bound for the length of cycles to detect * @param labelMap the labelMap of the vertices encoding the subsets of vertices * @param bestImprovement contains whether the best or the first improvement is returned: best * if true, first if false */ public AhujaOrlinSharmaCyclicExchangeLocalAugmentation( Graph graph, int lengthBound, Map labelMap, boolean bestImprovement) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); if (!graph.getType().isDirected()) { throw new IllegalArgumentException("The graph has to be directed."); } this.lengthBound = lengthBound; this.labelMap = Objects.requireNonNull(labelMap, "Labels cannot be null"); for (V vertex : graph.vertexSet()) { if (!labelMap.containsKey(vertex)) { throw new IllegalArgumentException( "Every vertex has to be labeled, that is, every vertex needs an entry in labelMap."); } } this.bestImprovement = bestImprovement; } /** * Calculates a valid subset-disjoint negative cycle. If there is no such cycle, it returns an * empty GraphWalk instance * * @return a valid subset-disjoint negative cycle encoded as GraphWalk */ public GraphWalk getLocalAugmentationCycle() { int k = 1; LabeledPath bestCycle = new LabeledPath<>(new ArrayList<>(lengthBound), Double.MAX_VALUE, new HashSet<>()); /* * Store the path in map with key PathSetKey>, since only paths with the * same head, same tail, and the subset of labels may be in domination relation. Thus the * algorithm runs faster. */ Map, LabeledPath> pathsLengthK = new LinkedHashMap<>(); Map, LabeledPath> pathsLengthKplus1 = new LinkedHashMap<>(); // initialize pathsLengthK for k = 1 for (E e : graph.edgeSet()) { if (graph.getEdgeWeight(e) < 0) { // initialize all paths of cost < 0 V sourceVertex = graph.getEdgeSource(e); V targetVertex = graph.getEdgeTarget(e); // catch self-loops directly if (sourceVertex == targetVertex) { ArrayList vertices = new ArrayList<>(); vertices.add(sourceVertex); vertices.add(targetVertex); double currentEdgeWeight = graph.getEdgeWeight(e); double oppositeEdgeWeight = graph.getEdgeWeight(graph.getEdge(targetVertex, sourceVertex)); if (bestImprovement) { if (bestCycle.cost > currentEdgeWeight + oppositeEdgeWeight) { HashSet labelSet = new HashSet<>(); labelSet.add(labelMap.get(sourceVertex)); bestCycle = new LabeledPath<>( vertices, currentEdgeWeight + oppositeEdgeWeight, labelSet); } } else { return new GraphWalk<>( graph, vertices, currentEdgeWeight + oppositeEdgeWeight); } } if (!labelMap.get(sourceVertex).equals(labelMap.get(targetVertex))) { ArrayList pathVertices = new ArrayList<>(lengthBound); HashSet pathLabels = new HashSet<>(); pathVertices.add(sourceVertex); pathVertices.add(targetVertex); pathLabels.add(labelMap.get(sourceVertex)); pathLabels.add(labelMap.get(targetVertex)); LabeledPath path = new LabeledPath<>(pathVertices, graph.getEdgeWeight(e), pathLabels); // add path to set of paths of length 1 updatePathIndex(pathsLengthK, path); } } } while (k < lengthBound) { // go through all valid paths of length k for (LabeledPath path : pathsLengthK.values()) { V head = path.getHead(); V tail = path.getTail(); E currentEdge = graph.getEdge(tail, head); if (currentEdge != null) { double currentCost = path.cost + graph.getEdgeWeight(currentEdge); if (currentCost < bestCycle.cost) { LabeledPath cycleResult = path.clone(); cycleResult .addVertex(head, graph.getEdgeWeight(currentEdge), labelMap.get(head)); /* * The path builds a valid negative cycle. Return the cycle if the first * improvement should be returned. */ if (!bestImprovement && currentCost < 0) { return new GraphWalk<>(graph, cycleResult.vertices, cycleResult.cost); } bestCycle = cycleResult; } } for (E e : graph.outgoingEdgesOf(tail)) { V currentVertex = graph.getEdgeTarget(e); // extend the path if the extension is still negative and correctly labeled double edgeWeight = graph.getEdgeWeight(e); int currentLabel = labelMap.get(currentVertex); if (!path.labels.contains(currentLabel) && path.cost + edgeWeight < 0) { LabeledPath newPath = path.clone(); newPath.addVertex(currentVertex, edgeWeight, currentLabel); /* * check if paths are dominated, i.e. if the path is definitely worse than * other paths and does not have to be considered in the future */ if (!checkDominatedPathsOfLengthKplus1(newPath, pathsLengthKplus1)) { if (!checkDominatedPathsOfLengthK(newPath, pathsLengthK)) { updatePathIndex(pathsLengthKplus1, newPath); } } } } } // update k and the corresponding sets k += 1; pathsLengthK = pathsLengthKplus1; pathsLengthKplus1 = new LinkedHashMap<>(); } return new GraphWalk<>(graph, bestCycle.vertices, bestCycle.cost); } /** * Checks whether path dominates the current minimal cost path with the same head, * tail and label set in the set of all paths of length k + 1. Thus, dominated paths are * eliminated. This is important out of efficiency reasons, otherwise many unnecessary paths may * be considered in further calculations. * * @param path the currently calculated path * @param pathsLengthKplus1 all before calculated paths of length k + 1 * * @return whether path dominates the current minimal cost path with the same head, * tail and label set. */ private boolean checkDominatedPathsOfLengthKplus1( LabeledPath path, Map, LabeledPath> pathsLengthKplus1) { // simulates domination test by using the index structure LabeledPath pathToCheck = pathsLengthKplus1.get(new PathSetKey<>(path.getHead(), path.getTail(), path.labels)); if (pathToCheck != null) { return pathToCheck.cost < path.cost; } return false; } /** * Checks whether path is dominated by some path in the previously calculated set * of paths of length k. This is important out of efficiency reasons, otherwise many unnecessary * paths may be considered in further calculations. * * @param path the currently calculated path * @param pathsLengthK all previously calculated paths of length k * * @return whether path is dominated by some path in pathsLengthK */ private boolean checkDominatedPathsOfLengthK( LabeledPath path, Map, LabeledPath> pathsLengthK) { Set modifiableLabelSet = new HashSet<>(path.labels); for (Integer label : path.labels) { modifiableLabelSet.remove(label); // simulates domination test by using the index structure LabeledPath pathToCheck = pathsLengthK .get(new PathSetKey<>(path.getHead(), path.getTail(), modifiableLabelSet)); if (pathToCheck != null) { if (pathToCheck.cost < path.cost) { return true; } } modifiableLabelSet.add(label); } return false; } /** * Adds a path and removes the path, which has the same tail, head and label set, to the data * structure paths, which contains all paths indexed by their head, tail and label * set. * * @param paths the map of paths, which are indexed by head, tail and label set, to add the path * to * @param path the path to add */ private void updatePathIndex(Map, LabeledPath> paths, LabeledPath path) { PathSetKey currentKey = new PathSetKey<>(path.getHead(), path.getTail(), path.labels); paths.put(currentKey, path); } /** * Implementation of a labeled path. It is used in * AhujaOrlinSharmaCyclicExchangeLocalAugmentation to efficiently maintain the paths in the * calculation. * * @param the vertex type * * @author Christoph Grüne * @since June 7, 2018 */ private class LabeledPath implements Cloneable { /** * the vertices in the path */ public ArrayList vertices; /** * the labels the path contains */ public HashSet labels; /** * the cost of the path */ public double cost; /** * Constructs a LabeledPath with the given inputs * * @param vertices the vertices of the path in order of the path * @param cost the cost of the edges connecting the vertices * @param labels the mapping of the vertices to labels (subsets) */ public LabeledPath(ArrayList vertices, double cost, HashSet labels) { this.vertices = vertices; this.cost = cost; this.labels = labels; } /** * Adds a vertex to the path * * @param v the vertex * @param edgeCost the cost of the edge connecting the last vertex of the path and the new * vertex * @param label the label of the new vertex */ public void addVertex(V v, double edgeCost, int label) { this.vertices.add(v); this.cost += edgeCost; this.labels.add(label); } /** * Returns the start vertex of the path * * @return the start vertex of the path */ public V getHead() { return vertices.get(0); } /** * Returns the end vertex of the path * * @return the end vertex of the path */ public V getTail() { return vertices.get(vertices.size() - 1); } /** * Returns whether the path is empty, i.e. has no vertices * * @return whether the path is empty */ public boolean isEmpty() { return vertices.isEmpty(); } /** * Returns a shallow copy of this labeled path instance. Vertices are not cloned. * * @return a shallow copy of this path. * * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ public LabeledPath clone() { try { LabeledPath newLabeledPath = TypeUtil.uncheckedCast(super.clone()); newLabeledPath.vertices = TypeUtil.uncheckedCast(this.vertices.clone()); newLabeledPath.labels = TypeUtil.uncheckedCast(this.labels.clone()); newLabeledPath.cost = this.cost; return newLabeledPath; } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new RuntimeException(); } } } /** * Implementation of a key for the path maps. It is used in * AhujaOrlinSharmaCyclicExchangeLocalAugmentation to efficiently maintain the path sets in the * calculation. * * @param the vertex type * * @author Christoph Grüne * @since June 7, 2018 */ private class PathSetKey { /** * the head of the paths indexed by this key */ private V head; /** * the tail of the paths indexed by this key */ private V tail; /** * the label set of the paths indexed by this key */ private Set labels; /** * Constructs a new PathSetKey object * * @param head the head of the paths indexed by this key * @param tail the tail of the paths indexed by this key * @param labels the label set of the paths indexed by this key */ private PathSetKey(V head, V tail, Set labels) { this.head = head; this.tail = tail; this.labels = labels; } @Override public int hashCode() { return Objects.hash(this.head, this.tail, this.labels); } @Override public boolean equals(Object o) { if (this == o) return true; else if (!(o instanceof PathSetKey)) return false; @SuppressWarnings("unchecked") PathSetKey other = (PathSetKey) o; return Objects.equals(head, other.head) && Objects.equals(tail, other.tail) && Objects.equals(labels, other.labels); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/BergeGraphInspector.java000066400000000000000000001604321402514743400324750ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Philipp S. Kaesgen and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import java.util.*; import java.util.stream.*; /** *

* Tests whether a graph is perfect. A * perfect graph, also known as a Berge graph, is a graph $G$ such that for every induced subgraph * of $G$, the clique number $\chi(G)$ equals the chromatic number $\omega(G)$, i.e., * $\omega(G)=\chi(G)$. Another characterization of perfect graphs is given by the Strong Perfect * Graph Theorem [M. Chudnovsky, N. Robertson, P. Seymour, R. Thomas. The strong perfect graph * theorem Annals of Mathematics, vol 164(1): pp. 51–230, 2006]: A graph $G$ is perfect if neither * $G$ nor its complement $\overline{G}$ have an odd hole. A hole in $G$ is an induced subgraph of * $G$ that is a cycle of length at least four, and it is odd or even if it has odd (or even, * respectively) length. *

* Some special classes of graphs are are * known to be perfect, e.g. Bipartite graphs and Chordal graphs. Testing whether a graph is resp. * Bipartite or Chordal can be done efficiently using {@link GraphTests#isBipartite} or * {@link org.jgrapht.alg.cycle.ChordalityInspector}. *

* The implementation of this class is based on the paper: M. Chudnovsky, G. Cornuejols, X. Liu, P. * Seymour, and K. Vuskovic. Recognizing Berge Graphs. Combinatorica 25(2): 143--186, 2003. *

* Special Thanks to Maria Chudnovsky for her kind help. * *

* The runtime complexity of this implementation is $O(|V|^9|)$. This implementation is far more * efficient than simplistically testing whether graph $G$ or its complement $\overline{G}$ have an * odd cycle, because testing whether one graph can be found as an induced subgraph of another is * known to be * NP-hard. * * @author Philipp S. Kaesgen (pkaesgen@freenet.de) * * @param the graph vertex type * @param the graph edge type */ public class BergeGraphInspector { private GraphPath certificate = null; private boolean certify = false; /** * Lists the vertices which are covered by two paths * * @param p1 A Path in g * @param p2 A Path in g * @return Set of vertices covered by both p1 and p2 */ private List intersectGraphPaths(GraphPath p1, GraphPath p2) { List res = new LinkedList<>(); res.addAll(p1.getVertexList()); res.retainAll(p2.getVertexList()); return res; } /** * Assembles a GraphPath of the Paths S and T. Required for the Pyramid Checker * * @param g A Graph * @param S A Path in g * @param T A Path in g * @param m A vertex * @param b1 A base vertex * @param b2 A base vertex * @param b3 A base vertex * @param s1 A vertex * @param s2 A vertex * @param s3 A vertex * @return The conjunct path of S and T */ private GraphPath P( Graph g, GraphPath S, GraphPath T, V m, V b1, V b2, V b3, V s1, V s2, V s3) { if (s1 == b1) { if (b1 == m) { List edgeList = new LinkedList<>(); return new GraphWalk<>(g, s1, b1, edgeList, 0); } else return null; } else { if (b1 == m) return null; if (g.containsEdge(m, b2) || g.containsEdge(m, b3) || g.containsEdge(m, s2) || g.containsEdge(m, s3) || S == null || T == null) return null; if (S .getVertexList().stream().anyMatch( t -> g.containsEdge(t, b2) || g.containsEdge(t, b3) || g.containsEdge(t, s2) || g.containsEdge(t, s3)) || T .getVertexList().stream().anyMatch( t -> t != b1 && (g.containsEdge(t, b2) || g.containsEdge(t, b3) || g.containsEdge(t, s2) || g.containsEdge(t, s3)))) return null; List intersection = intersectGraphPaths(S, T); if (intersection.size() != 1 || !intersection.contains(m)) return null; if (S .getVertexList().stream().anyMatch( s -> s != m && T .getVertexList().stream().anyMatch(t -> t != m && g.containsEdge(s, t)))) return null; List edgeList = new LinkedList<>(); edgeList.addAll(T.getEdgeList()); edgeList.addAll(S.getEdgeList()); double weight = edgeList.stream().mapToDouble(g::getEdgeWeight).sum(); return new GraphWalk<>(g, b1, s1, edgeList, weight); } } private void BFOddHoleCertificate(Graph g) { for (V start : g.vertexSet()) { if (g.degreeOf(start) < 2) continue; Set set = new HashSet<>(); set.addAll(g.vertexSet()); for (V neighborOfStart : g.vertexSet()) { if (neighborOfStart == start || !g.containsEdge(start, neighborOfStart) || g.degreeOf(neighborOfStart) != 2) continue; set.remove(neighborOfStart); Graph subg = new AsSubgraph<>(g, set); for (V neighborsNeighbor : g.vertexSet()) { if (neighborsNeighbor == start || neighborsNeighbor == neighborOfStart || !g.containsEdge(neighborsNeighbor, neighborOfStart) || g.containsEdge(neighborsNeighbor, start) || g.degreeOf(neighborsNeighbor) < 2) continue; GraphPath path = new DijkstraShortestPath<>(subg).getPath(start, neighborsNeighbor); if (path == null || path.getLength() < 3 || path.getLength() % 2 == 0) continue; List edgeList = new LinkedList<>(); edgeList.addAll(path.getEdgeList()); edgeList.add(g.getEdge(neighborsNeighbor, neighborOfStart)); edgeList.add(g.getEdge(neighborOfStart, start)); double weight = edgeList.stream().mapToDouble(g::getEdgeWeight).sum(); certificate = new GraphWalk<>(g, start, start, edgeList, weight); break; } if (certificate != null) break; } if (certificate != null) break; } } /** * Checks whether a graph contains a pyramid. Running time: O(|V(g)|^9) * * @param g Graph * @return Either it finds a pyramid (and hence an odd hole) in g, or it determines that g * contains no pyramid */ boolean containsPyramid(Graph g) { /* * A pyramid looks like this: * * b2-(T2)-m2-(S2)-s2 / | \ b1---(T1)-m1-(S1)-s1--a \ | / b3-(T3)-m3-(S3)-s3 * * Note that b1, b2, and b3 are connected and all names in parentheses are paths * */ Set> visitedTriangles = new HashSet<>(); for (V b1 : g.vertexSet()) { for (V b2 : g.vertexSet()) { if (b1 == b2 || !g.containsEdge(b1, b2)) continue; for (V b3 : g.vertexSet()) { if (b3 == b1 || b3 == b2 || !g.containsEdge(b2, b3) || !g.containsEdge(b1, b3)) continue; // Triangle detected for the pyramid base Set triangles = new HashSet<>(); triangles.add(b1); triangles.add(b2); triangles.add(b3); if (visitedTriangles.contains(triangles)) { continue; } visitedTriangles.add(triangles); for (V aCandidate : g.vertexSet()) { if (aCandidate == b1 || aCandidate == b2 || aCandidate == b3 || // a is adjacent to at most one of b1,b2,b3 g.containsEdge(aCandidate, b1) && g.containsEdge(aCandidate, b2) || g.containsEdge(aCandidate, b2) && g.containsEdge(aCandidate, b3) || g.containsEdge(aCandidate, b1) && g.containsEdge(aCandidate, b3)) { continue; } // aCandidate could now be the top of the pyramid for (V s1 : g.vertexSet()) { if (s1 == aCandidate || !g.containsEdge(s1, aCandidate) || s1 == b2 || s1 == b3 || s1 != b1 && (g.containsEdge(s1, b2) || g.containsEdge(s1, b3))) { continue; } for (V s2 : g.vertexSet()) { if (s2 == aCandidate || !g.containsEdge(s2, aCandidate) || g.containsEdge(s1, s2) || s1 == s2 || s2 == b1 || s2 == b3 || s2 != b2 && (g.containsEdge(s2, b1) || g.containsEdge(s2, b3))) { continue; } for (V s3 : g.vertexSet()) { if (s3 == aCandidate || !g.containsEdge(s3, aCandidate) || g.containsEdge(s3, s2) || s1 == s3 || s3 == s2 || g.containsEdge(s1, s3) || s3 == b1 || s3 == b2 || s3 != b3 && (g.containsEdge(s3, b1) || g.containsEdge(s3, b2))) { continue; } // s1, s2, s3 could now be the closest vertices to the top // vertex of the pyramid Set M = new HashSet<>(); M.addAll(g.vertexSet()); M.remove(b1); M.remove(b2); M.remove(b3); M.remove(s1); M.remove(s2); M.remove(s3); Map> S1 = new HashMap<>(), S2 = new HashMap<>(), S3 = new HashMap<>(), T1 = new HashMap<>(), T2 = new HashMap<>(), T3 = new HashMap<>(); // find paths which could be the edges of the pyramid for (V m1 : M) { Set validInterior = new HashSet<>(); validInterior.addAll(M); validInterior .removeIf( i -> g.containsEdge(i, b2) || g.containsEdge(i, s2) || g.containsEdge(i, b3) || g.containsEdge(i, s3)); validInterior.add(m1); validInterior.add(s1); Graph subg = new AsSubgraph<>(g, validInterior); S1 .put( m1, new DijkstraShortestPath<>(subg).getPath(m1, s1)); validInterior.remove(s1); validInterior.add(b1); subg = new AsSubgraph<>(g, validInterior); T1 .put( m1, new DijkstraShortestPath<>(subg).getPath(b1, m1)); } for (V m2 : M) { Set validInterior = new HashSet<>(); validInterior.addAll(M); validInterior .removeIf( i -> g.containsEdge(i, b1) || g.containsEdge(i, s1) || g.containsEdge(i, b3) || g.containsEdge(i, s3)); validInterior.add(m2); validInterior.add(s2); Graph subg = new AsSubgraph<>(g, validInterior); S2 .put( m2, new DijkstraShortestPath<>(subg).getPath(m2, s2)); validInterior.remove(s2); validInterior.add(b2); subg = new AsSubgraph<>(g, validInterior); T2 .put( m2, new DijkstraShortestPath<>(subg).getPath(b2, m2)); } for (V m3 : M) { Set validInterior = new HashSet<>(); validInterior.addAll(M); validInterior .removeIf( i -> g.containsEdge(i, b1) || g.containsEdge(i, s1) || g.containsEdge(i, b2) || g.containsEdge(i, s2)); validInterior.add(m3); validInterior.add(s3); Graph subg = new AsSubgraph<>(g, validInterior); S3 .put( m3, new DijkstraShortestPath<>(subg).getPath(m3, s3)); validInterior.remove(s3); validInterior.add(b3); subg = new AsSubgraph<>(g, validInterior, null); T3 .put( m3, new DijkstraShortestPath<>(subg).getPath(b3, m3)); } // Check if all edges of a pyramid are valid Set M1 = new HashSet<>(); M1.addAll(M); M1.add(b1); for (V m1 : M1) { GraphPath P1 = P( g, S1.get(m1), T1.get(m1), m1, b1, b2, b3, s1, s2, s3); if (P1 == null) continue; Set M2 = new HashSet<>(); M2.addAll(M); M2.add(b2); for (V m2 : M) { GraphPath P2 = P( g, S2.get(m2), T2.get(m2), m2, b2, b1, b3, s2, s1, s3); if (P2 == null) continue; Set M3 = new HashSet<>(); M3.addAll(M); M3.add(b3); for (V m3 : M3) { GraphPath P3 = P( g, S3.get(m3), T3.get(m3), m3, b3, b1, b2, s3, s1, s2); if (P3 == null) continue; if (certify) { if ((P1.getLength() + P2.getLength()) % 2 == 0) { Set set = new HashSet<>(); set.addAll(P1.getVertexList()); set.addAll(P2.getVertexList()); set.add(aCandidate); BFOddHoleCertificate( new AsSubgraph<>(g, set)); } else if ((P1.getLength() + P3.getLength()) % 2 == 0) { Set set = new HashSet<>(); set.addAll(P1.getVertexList()); set.addAll(P3.getVertexList()); set.add(aCandidate); BFOddHoleCertificate( new AsSubgraph<>(g, set)); } else { Set set = new HashSet<>(); set.addAll(P3.getVertexList()); set.addAll(P2.getVertexList()); set.add(aCandidate); BFOddHoleCertificate( new AsSubgraph<>(g, set)); } } return true; } } } } } } } } } } return false; } /** * Finds all Components of a set F contained in V(g) * * @param g A graph * @param F A vertex subset of g * @return Components of F in g */ private List> findAllComponents(Graph g, Set F) { return new ConnectivityInspector<>(new AsSubgraph<>(g, F)).connectedSets(); } /** * Checks whether a graph contains a Jewel. Running time: O(|V(g)|^6) * * @param g Graph * @return Decides whether there is a jewel in g */ boolean containsJewel(Graph g) { for (V v2 : g.vertexSet()) { for (V v3 : g.vertexSet()) { if (v2 == v3 || !g.containsEdge(v2, v3)) continue; for (V v5 : g.vertexSet()) { if (v2 == v5 || v3 == v5) continue; Set F = new HashSet<>(); for (V f : g.vertexSet()) { if (f == v2 || f == v3 || f == v5 || g.containsEdge(f, v2) || g.containsEdge(f, v3) || g.containsEdge(f, v5)) continue; F.add(f); } List> componentsOfF = findAllComponents(g, F); Set X1 = new HashSet<>(); for (V x1 : g.vertexSet()) { if (x1 == v2 || x1 == v3 || x1 == v5 || !g.containsEdge(x1, v2) || !g.containsEdge(x1, v5) || g.containsEdge(x1, v3)) continue; X1.add(x1); } Set X2 = new HashSet<>(); for (V x2 : g.vertexSet()) { if (x2 == v2 || x2 == v3 || x2 == v5 || g.containsEdge(x2, v2) || !g.containsEdge(x2, v5) || !g.containsEdge(x2, v3)) continue; X2.add(x2); } for (V v1 : X1) { if (g.containsEdge(v1, v3)) continue; for (V v4 : X2) { if (v1 == v4 || g.containsEdge(v1, v4) || g.containsEdge(v2, v4)) continue; for (Set FPrime : componentsOfF) { if (hasANeighbour(g, FPrime, v1) && hasANeighbour(g, FPrime, v4)) { if (certify) { Set validSet = new HashSet<>(); validSet.addAll(FPrime); validSet.add(v1); validSet.add(v4); GraphPath p = new DijkstraShortestPath<>( new AsSubgraph<>(g, validSet)).getPath(v1, v4); List edgeList = new LinkedList<>(); edgeList.addAll(p.getEdgeList()); if (p.getLength() % 2 == 1) { edgeList.add(g.getEdge(v4, v5)); edgeList.add(g.getEdge(v5, v1)); } else { edgeList.add(g.getEdge(v4, v3)); edgeList.add(g.getEdge(v3, v2)); edgeList.add(g.getEdge(v2, v1)); } double weight = edgeList.stream().mapToDouble(g::getEdgeWeight).sum(); certificate = new GraphWalk<>(g, v1, v1, edgeList, weight); } return true; } } } } } } } return false; } /** * Checks whether a graph contains a clean shortest odd hole. Running time: O(|V(g)|^4) * * @param g Graph containing no pyramid or jewel * @return Decides whether g contains a clean shortest odd hole */ boolean containsCleanShortestOddHole(Graph g) { /* * Find 3 Paths which are an uneven odd hole when conjunct */ for (V u : g.vertexSet()) { for (V v : g.vertexSet()) { if (u == v || g.containsEdge(u, v)) continue; GraphPath puv = new DijkstraShortestPath<>(g).getPath(u, v); if (puv == null) continue; for (V w : g.vertexSet()) { if (w == u || w == v || g.containsEdge(w, u) || g.containsEdge(w, v)) continue; GraphPath pvw = new DijkstraShortestPath<>(g).getPath(v, w); if (pvw == null) continue; GraphPath pwu = new DijkstraShortestPath<>(g).getPath(w, u); if (pwu == null) continue; Set set = new HashSet<>(); set.addAll(puv.getVertexList()); set.addAll(pvw.getVertexList()); set.addAll(pwu.getVertexList()); Graph subg = new AsSubgraph<>(g, set); // Look for holes with more than 6 edges and uneven length if (set.size() < 7 || subg.vertexSet().size() != set.size() || subg.edgeSet().size() != subg.vertexSet().size() || subg.vertexSet().size() % 2 == 0 || subg.vertexSet().stream().anyMatch(t -> subg.degreeOf(t) != 2)) continue; if (certify) { List edgeList = new LinkedList<>(); edgeList.addAll(puv.getEdgeList()); edgeList.addAll(pvw.getEdgeList()); edgeList.addAll(pwu.getEdgeList()); double weight = edgeList.stream().mapToDouble(g::getEdgeWeight).sum(); certificate = new GraphWalk<>(g, u, u, edgeList, weight); } return true; } } } return false; } /** * Returns a path in g from start to end avoiding the vertices in X * * @param g A Graph * @param start start vertex * @param end end vertex * @param X set of vertices which should not be in the graph * @return A Path in G\X */ private GraphPath getPathAvoidingX(Graph g, V start, V end, Set X) { Set vertexSet = new HashSet<>(); vertexSet.addAll(g.vertexSet()); vertexSet.removeAll(X); vertexSet.add(start); vertexSet.add(end); Graph subg = new AsSubgraph<>(g, vertexSet, null); return new DijkstraShortestPath<>(subg).getPath(start, end); } /** * Checks whether the vertex set of a graph without a vertex set X contains a shortest odd hole. * Running time: O(|V(g)|^4) * * @param g Graph containing neither pyramid nor jewel * @param X Subset of V(g) and a possible Cleaner for an odd hole * @return Determines whether g has an odd hole such that X is a near-cleaner for it */ private boolean containsShortestOddHole(Graph g, Set X) { for (V y1 : g.vertexSet()) { if (X.contains(y1)) continue; for (V x1 : g.vertexSet()) { if (x1 == y1) continue; GraphPath rx1y1 = getPathAvoidingX(g, x1, y1, X); for (V x3 : g.vertexSet()) { if (x3 == x1 || x3 == y1 || !g.containsEdge(x1, x3)) continue; for (V x2 : g.vertexSet()) { if (x2 == x3 || x2 == x1 || x2 == y1 || g.containsEdge(x2, x1) || !g.containsEdge(x3, x2)) continue; GraphPath rx2y1 = getPathAvoidingX(g, x2, y1, X); double n; if (rx1y1 == null || rx2y1 == null) continue; V y2 = null; for (V y2Candidate : rx2y1.getVertexList()) { if (g.containsEdge(y1, y2Candidate) && y2Candidate != x1 && y2Candidate != x2 && y2Candidate != x3 && y2Candidate != y1) { y2 = y2Candidate; break; } } if (y2 == null) continue; GraphPath rx3y1 = getPathAvoidingX(g, x3, y1, X); GraphPath rx3y2 = getPathAvoidingX(g, x3, y2, X); GraphPath rx1y2 = getPathAvoidingX(g, x1, y2, X); if (rx3y1 != null && rx3y2 != null && rx1y2 != null && rx2y1.getLength() == (n = rx1y1.getLength() + 1) && n == rx1y2.getLength() && rx3y1.getLength() >= n && rx3y2.getLength() >= n) { if (certify) { List edgeList = new LinkedList<>(); edgeList.addAll(rx1y1.getEdgeList()); for (int i = rx2y1.getLength() - 1; i >= 0; i--) edgeList.add(rx2y1.getEdgeList().get(i)); edgeList.add(g.getEdge(x2, x3)); edgeList.add(g.getEdge(x3, x1)); double weight = edgeList.stream().mapToDouble(g::getEdgeWeight).sum(); certificate = new GraphWalk<>(g, x1, x1, edgeList, weight); } return true; } } } } } return false; } /** * Checks whether a clean shortest odd hole is in g or whether X is a cleaner for an amenable * shortest odd hole * * @param g A graph, containing no pyramid or jewel * @param X A subset X of V(g) and a possible Cleaner for an odd hole * @return Returns whether g has an odd hole or there is no shortest odd hole in C such that X * is a near-cleaner for C. */ private boolean routine1(Graph g, Set X) { return containsCleanShortestOddHole(g) || containsShortestOddHole(g, X); } /** * Checks whether a graph has a configuration of type T1. A configuration of type T1 in g is a * hole of length 5 * * @param g A Graph * @return whether g contains a configuration of Type T1 (5-cycle) */ private boolean hasConfigurationType1(Graph g) { for (V v1 : g.vertexSet()) { Set temp = new ConnectivityInspector<>(g).connectedSetOf(v1); for (V v2 : temp) { if (v1 == v2 || !g.containsEdge(v1, v2)) continue; for (V v3 : temp) { if (v3 == v1 || v3 == v2 || !g.containsEdge(v2, v3) || g.containsEdge(v1, v3)) continue; for (V v4 : temp) { if (v4 == v1 || v4 == v2 || v4 == v3 || g.containsEdge(v1, v4) || g.containsEdge(v2, v4) || !g.containsEdge(v3, v4)) continue; for (V v5 : temp) { if (v5 == v1 || v5 == v2 || v5 == v3 || v5 == v4 || g.containsEdge(v2, v5) || g.containsEdge(v3, v5) || !g.containsEdge(v1, v5) || !g.containsEdge(v4, v5)) continue; if (certify) { List edgeList = new LinkedList<>(); edgeList.add(g.getEdge(v1, v2)); edgeList.add(g.getEdge(v2, v3)); edgeList.add(g.getEdge(v3, v4)); edgeList.add(g.getEdge(v4, v5)); edgeList.add(g.getEdge(v5, v1)); double weight = edgeList.stream().mapToDouble(g::getEdgeWeight).sum(); certificate = new GraphWalk<>(g, v1, v1, edgeList, weight); } return true; } } } } } return false; } /** * A vertex y is X-complete if y contained in V(g)\X is adjacent to every vertex in X. * * @param g A Graph * @param y Vertex whose X-completeness is to assess * @param X Set of vertices * @return whether y is X-complete */ boolean isYXComplete(Graph g, V y, Set X) { return X.stream().allMatch(t -> g.containsEdge(t, y)); } /** * Returns all anticomponents of a graph and a vertex set. * * @param g A Graph * @param Y A set of vertices * @return List of anticomponents of Y in g */ private List> findAllAnticomponentsOfY(Graph g, Set Y) { Graph target; if (g.getType().isSimple()) target = new SimpleGraph<>( g.getVertexSupplier(), g.getEdgeSupplier(), g.getType().isWeighted()); else target = new Multigraph<>( g.getVertexSupplier(), g.getEdgeSupplier(), g.getType().isWeighted()); new ComplementGraphGenerator<>(g).generateGraph(target); return findAllComponents(target, Y); } /** *

* Checks whether a graph is of configuration type T2. A configuration of type T2 in g is a * sequence v1,v2,v3,v4,P,X such that: *

*
    *
  • v1-v2-v3-v4 is a path of g
  • *
  • X is an anticomponent of the set of all {v1,v2,v4}-complete vertices
  • *
  • P is a path in G\(X+{v2,v3}) between v1,v4, and no vertex in P*, i.e. P's interior, is * X-complete or adjacent to v2 or adjacent to v3
  • *
* An example is the complement graph of a cycle-7-graph * * @param g A Graph * @return whether g contains a configuration of Type T2 */ boolean hasConfigurationType2(Graph g) { for (V v1 : g.vertexSet()) { for (V v2 : g.vertexSet()) { if (v1 == v2 || !g.containsEdge(v1, v2)) continue; for (V v3 : g.vertexSet()) { if (v3 == v2 || v1 == v3 || g.containsEdge(v1, v3) || !g.containsEdge(v2, v3)) continue; for (V v4 : g.vertexSet()) { if (v4 == v1 || v4 == v2 || v4 == v3 || g.containsEdge(v4, v2) || g.containsEdge(v4, v1) || !g.containsEdge(v3, v4)) continue; Set temp = new HashSet<>(); temp.add(v1); temp.add(v2); temp.add(v4); Set Y = new HashSet<>(); for (V y : g.vertexSet()) { if (isYXComplete(g, y, temp)) { Y.add(y); } } List> anticomponentsOfY = findAllAnticomponentsOfY(g, Y); for (Set X : anticomponentsOfY) { Set v2v3 = new HashSet<>(); v2v3.addAll(g.vertexSet()); v2v3.remove(v2); v2v3.remove(v3); v2v3.removeAll(X); if (!v2v3.contains(v1) || !v2v3.contains(v4)) continue; GraphPath Path = new DijkstraShortestPath<>(new AsSubgraph<>(g, v2v3)) .getPath(v1, v4); if (Path == null) continue; List P = Path.getVertexList(); if (!P.contains(v1) || !P.contains(v4)) continue; boolean cont = true; for (V p : P) { if (p != v1 && p != v4 && (g.containsEdge(p, v2) || g.containsEdge(p, v3) || isYXComplete(g, p, X))) { cont = false; break; } } if (cont) { if (certify) { List edgeList = new LinkedList<>(); if (Path.getLength() % 2 == 0) { edgeList.add(g.getEdge(v1, v2)); edgeList.add(g.getEdge(v2, v3)); edgeList.add(g.getEdge(v3, v4)); edgeList.addAll(Path.getEdgeList()); } else { edgeList.addAll(Path.getEdgeList()); V x = X.iterator().next(); edgeList.add(g.getEdge(v4, x)); edgeList.add(g.getEdge(x, v1)); } double weight = edgeList.stream().mapToDouble(g::getEdgeWeight).sum(); certificate = new GraphWalk<>(g, v1, v1, edgeList, weight); } return true; } } } } } } return false; } /** * Reports whether v has at least one neighbour in set * * @param g A Graph * @param set A set of vertices * @param v A vertex * @return whether v has at least one neighbour in set */ private boolean hasANeighbour(Graph g, Set set, V v) { return set.stream().anyMatch(s -> g.containsEdge(s, v)); } /** * For each anticomponent X, find the maximal connected subset F' containing v5 with the * properties that v1,v2 have no neighbours in F' and no vertex of F'\v5 is X-complete * * @param g A Graph * @param X A set of vertices * @param v1 A vertex * @param v2 A vertex * @param v5 A Vertex * @return The maximal connected vertex subset containing v5, no neighbours of v1 and v2, and no * X-complete vertex except v5 */ private Set findMaximalConnectedSubset(Graph g, Set X, V v1, V v2, V v5) { Set FPrime = new ConnectivityInspector<>(g).connectedSetOf(v5); FPrime .removeIf( t -> t != v5 && isYXComplete(g, t, X) || v1 == t || v2 == t || g.containsEdge(v1, t) || g.containsEdge(v2, t)); return FPrime; } /** * Reports whether a vertex has at least one nonneighbour in X * * @param g A Graph * @param v A Vertex * @param X A set of vertices * @return whether v has a nonneighbour in X */ private boolean hasANonneighbourInX(Graph g, V v, Set X) { return X.stream().anyMatch(x -> !g.containsEdge(v, x)); } /** *

* Checks whether a graph is of configuration type T3. A configuration of type T3 in g is a * sequence v1,...,v6,P,X such that *

*
    *
  • v1,...,v6 are distinct vertices of g
  • *
  • v1v2,v3v4,v2v3,v3v5,v4v6 are edges, and v1v3,v2v4,v1v5,v2v5,v1v6,v2v6,v4v5 are * non-edges
  • *
  • X is an anticomponent of the set of all {v1,v2,v5}-complete vertices, and v3,v4 are not * X-complete
  • *
  • P is a path of g\(X+{v1,v2,v3,v4}) between v5,v6, and no vertex in P* is X-complete or * adjacent to v1 or adjacent to v2
  • *
  • if v5v6 is an edge then v6 is not X-complete
  • *
* * @param g A Graph * @return whether g contains a configuration of Type T3 */ boolean hasConfigurationType3(Graph g) { for (V v1 : g.vertexSet()) { for (V v2 : g.vertexSet()) { if (v1 == v2 || !g.containsEdge(v1, v2)) continue; for (V v5 : g.vertexSet()) { if (v1 == v5 || v2 == v5 || g.containsEdge(v1, v5) || g.containsEdge(v2, v5)) continue; Set triple = new HashSet<>(); triple.add(v1); triple.add(v2); triple.add(v5); Set Y = new HashSet<>(); for (V y : g.vertexSet()) { if (isYXComplete(g, y, triple)) { Y.add(y); } } List> anticomponents = findAllAnticomponentsOfY(g, Y); for (Set X : anticomponents) { Set FPrime = findMaximalConnectedSubset(g, X, v1, v2, v5); Set F = new HashSet<>(); F.addAll(FPrime); for (V x : X) { if (!g.containsEdge(x, v1) && !g.containsEdge(x, v2) && !g.containsEdge(x, v5) && hasANeighbour(g, FPrime, x)) F.add(x); } for (V v4 : g.vertexSet()) { if (v4 == v1 || v4 == v2 || v4 == v5 || g.containsEdge(v2, v4) || g.containsEdge(v5, v4) || !g.containsEdge(v1, v4) || !hasANeighbour(g, F, v4) || !hasANonneighbourInX(g, v4, X) || isYXComplete(g, v4, X)) continue; for (V v3 : g.vertexSet()) { if (v3 == v1 || v3 == v2 || v3 == v4 || v3 == v5 || !g.containsEdge(v2, v3) || !g.containsEdge(v3, v4) || !g.containsEdge(v5, v3) || g.containsEdge(v1, v3) || !hasANonneighbourInX(g, v3, X) || isYXComplete(g, v3, X)) continue; for (V v6 : F) { if (v6 == v1 || v6 == v2 || v6 == v3 || v6 == v4 || v6 == v5 || !g.containsEdge(v4, v6) || g.containsEdge(v1, v6) || g.containsEdge(v2, v6) || g.containsEdge(v5, v6) && !isYXComplete(g, v6, X)) continue; Set verticesForPv5v6 = new HashSet<>(); verticesForPv5v6.addAll(FPrime); verticesForPv5v6.add(v5); verticesForPv5v6.add(v6); verticesForPv5v6.remove(v1); verticesForPv5v6.remove(v2); verticesForPv5v6.remove(v3); verticesForPv5v6.remove(v4); if (new ConnectivityInspector<>( new AsSubgraph<>(g, verticesForPv5v6)).pathExists(v6, v5)) { if (certify) { List edgeList = new LinkedList<>(); edgeList.add(g.getEdge(v1, v4)); edgeList.add(g.getEdge(v4, v6)); GraphPath P = new DijkstraShortestPath<>(g).getPath(v6, v5); edgeList.addAll(P.getEdgeList()); if (P.getLength() % 2 == 1) { V x = X.iterator().next(); edgeList.add(g.getEdge(v5, x)); edgeList.add(g.getEdge(x, v1)); } else { edgeList.add(g.getEdge(v5, v3)); edgeList.add(g.getEdge(v3, v4)); edgeList.add(g.getEdge(v4, v1)); } double weight = edgeList .stream().mapToDouble(g::getEdgeWeight).sum(); certificate = new GraphWalk<>(g, v1, v1, edgeList, weight); } return true; } } } } } } } } return false; } /** * If true, the graph is not Berge. Checks whether g contains a Pyramid, Jewel, configuration * type 1, 2 or 3. * * @param g A Graph * @return whether g contains a pyramid, a jewel, a T1, a T2, or a T3 */ private boolean routine2(Graph g) { return containsPyramid(g) || containsJewel(g) || hasConfigurationType1(g) || hasConfigurationType2(g) || hasConfigurationType3(g); } /** * N(a,b) is the set of all {a,b}-complete vertices * * @param g A Graph * @param a A Vertex * @param b A Vertex * @return The set of all {a,b}-complete vertices */ private Set N(Graph g, V a, V b) { return g .vertexSet().stream().filter(t -> g.containsEdge(t, a) && g.containsEdge(t, b)) .collect(Collectors.toSet()); } /** * r(a,b,c) is the cardinality of the largest anticomponent of N(a,b) that contains a * nonneighbour of c (or 0, if c is N(a,b)-complete) * * @param g a Graph * @param Nab The set of all {a,b}-complete vertices * @param c A vertex * @return The cardinality of the largest anticomponent of N(a,b) that contains a nonneighbour * of c (or 0, if c is N(a,b)-complete) */ private int r(Graph g, Set Nab, V c) { if (isYXComplete(g, c, Nab)) return 0; List> anticomponents = findAllAnticomponentsOfY(g, Nab); return anticomponents.stream().mapToInt(Set::size).max().getAsInt(); } /** * Y(a,b,c) is the union of all anticomponents of N(a,b) that have cardinality strictly greater * than r(a,b,c) * * @param g A graph * @param Nab The set of all {a,b}-complete vertices * @param c A vertex * @return A Set of vertices with cardinality greater r(a,b,c) */ private Set Y(Graph g, Set Nab, V c) { int cutoff = r(g, Nab, c); List> anticomponents = findAllAnticomponentsOfY(g, Nab); Set res = new HashSet<>(); for (Set anticomponent : anticomponents) { if (anticomponent.size() > cutoff) { res.addAll(anticomponent); } } return res; } /** * W(a,b,c) is the anticomponent of N(a,b)+{c} that contains c * * @param g A graph * @param Nab The set of all {a,b}-complete vertices * @param c A vertex * @return The anticomponent of N(a,b)+{c} containing c */ private Set W(Graph g, Set Nab, V c) { Set temp = new HashSet(); temp.addAll(Nab); temp.add(c); List> anticomponents = findAllAnticomponentsOfY(g, temp); for (Set anticomponent : anticomponents) if (anticomponent.contains(c)) return anticomponent; return null; } /** * Z(a,b,c) is the set of all (Y(a,b,c)+W(a,b,c))-complete vertices * * @param g A graph * @param Nab The set of all {a,b}-complete vertices * @param c A vertex * @return A set of vertices */ private Set Z(Graph g, Set Nab, V c) { Set temp = new HashSet<>(); temp.addAll(Y(g, Nab, c)); temp.addAll(W(g, Nab, c)); Set res = new HashSet<>(); for (V it : g.vertexSet()) { if (isYXComplete(g, it, temp)) res.add(it); } return res; } /** * X(a,b,c)=Y(a,b,c)+Z(a,b,c) * * @param g A graph * @param Nab The set of all {a,b}-complete vertices * @param c A vertex * @return The union of Y(a,b,c) and Z(a,b,c) */ private Set X(Graph g, Set Nab, V c) { Set res = new HashSet<>(); res.addAll(Y(g, Nab, c)); res.addAll(Z(g, Nab, c)); return res; } /** * A triple (a,b,c) of vertices is relevant if a,b are distinct and nonadjacent, and c is not * contained in N(a,b) (possibly c is contained in {a,b}). * * @param g A graph * @param a A vertex * @param b A vertex * @param c A vertex * @return Assessement whether a,b,c is a relevant triple */ private boolean isTripleRelevant(Graph g, V a, V b, V c) { return a != b && !g.containsEdge(a, b) && !N(g, a, b).contains(c); } /** * Returns a set of vertex sets that may be near-cleaners for an amenable hole in g. * * @param g A graph * @return possible near-cleaners */ Set> routine3(Graph g) { Set> NuvList = new HashSet<>(); for (V u : g.vertexSet()) { for (V v : g.vertexSet()) { if (u == v || !g.containsEdge(u, v)) continue; NuvList.add(N(g, u, v)); } } Set> tripleList = new HashSet<>(); for (V a : g.vertexSet()) { for (V b : g.vertexSet()) { if (a == b || g.containsEdge(a, b)) continue; Set Nab = N(g, a, b); for (V c : g.vertexSet()) { if (isTripleRelevant(g, a, b, c)) { tripleList.add(X(g, Nab, c)); } } } } Set> res = new HashSet<>(); for (Set Nuv : NuvList) { for (Set triple : tripleList) { Set temp = new HashSet<>(); temp.addAll(Nuv); temp.addAll(triple); res.add(temp); } } return res; } /** * Performs the Berge Recognition Algorithm. *

* First this algorithm is used to test whether $G$ or its complement contain a jewel, a pyramid * or a configuration of type 1, 2 or 3. If so, it is output that $G$ is not Berge. If not, then * every shortest odd hole in $G$ is amenable. This asserted, the near-cleaner subsets of $V(G)$ * are determined. For each of them in turn it is checked, if this subset is a near-cleaner and, * thus, if there is an odd hole. If an odd hole is found, this checker will output that $G$ is * not Berge. If no odd hole is found, all near-cleaners for the complement graph are determined * and it will be proceeded as before. If again no odd hole is detected, $G$ is Berge. * *

* A certificate can be obtained through the {@link BergeGraphInspector#getCertificate} method, * if computeCertificate is true. *

* Running this method takes $O(|V|^9)$, and computing the certificate takes $O(|V|^5)$. * * @param g A graph * @param computeCertificate toggles certificate computation * @return whether g is Berge and, thus, perfect */ public boolean isBerge(Graph g, boolean computeCertificate) { GraphTests.requireDirectedOrUndirected(g); Graph complementGraph; if (g.getType().isSimple()) complementGraph = new SimpleGraph<>( g.getVertexSupplier(), g.getEdgeSupplier(), g.getType().isWeighted()); else complementGraph = new Multigraph<>( g.getVertexSupplier(), g.getEdgeSupplier(), g.getType().isWeighted()); new ComplementGraphGenerator<>(g).generateGraph(complementGraph); certify = computeCertificate; if (routine2(g) || routine2(complementGraph)) { certify = false; return false; } for (Set it : routine3(g)) { if (routine1(g, it)) { certify = false; return false; } } for (Set it : routine3(complementGraph)) { if (routine1(complementGraph, it)) { certify = false; return false; } } certify = false; return true; } /** * Performs the Berge Recognition Algorithm. *

* First this algorithm is used to test whether $G$ or its complement contain a jewel, a pyramid * or a configuration of type 1, 2 or 3. If so, it is output that $G$ is not Berge. If not, then * every shortest odd hole in $G$ is amenable. This asserted, the near-cleaner subsets of $V(G)$ * are determined. For each of them in turn it is checked, if this subset is a near-cleaner and, * thus, if there is an odd hole. If an odd hole is found, this checker will output that $G$ is * not Berge. If no odd hole is found, all near-cleaners for the complement graph are determined * and it will be proceeded as before. If again no odd hole is detected, $G$ is Berge. * *

* This method by default does not compute a certificate. For obtaining a certificate, call * {@link BergeGraphInspector#isBerge} with computeCertificate=true. *

* Running this method takes $O(|V|^9)$. * * @param g A graph * @return whether g is Berge and, thus, perfect */ public boolean isBerge(Graph g) { return this.isBerge(g, false); } /** * Returns the certificate in the form of a hole or anti-hole in the inspected graph, when the * {@link BergeGraphInspector#isBerge} method is previously called with * computeCertificate=true. Returns null if the inspected graph is perfect. * * @return a hole or * anti-hole in the * inspected graph, null if the graph is perfect */ public GraphPath getCertificate() { return certificate; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChinesePostman.java000066400000000000000000000334021402514743400315140ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.matching.*; import org.jgrapht.alg.matching.blossom.v5.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.util.*; import java.util.stream.*; /** * This class solves the Chinese Postman Problem (CPP), also known as the Route Inspection Problem. * The CPP asks to find a closed walk of minimum length that visits every edge of the graph * at least once. In weighted graphs, the length of the closed walk is defined as the sum of * its edge weights; in unweighted graphs, a closed walk with the least number of edges is returned * (the same result can be obtained for weighted graphs with uniform edge weights). *

* The algorithm works with directed and undirected graphs which may contain loops and/or multiple * edges. The runtime complexity is O(N^3) where N is the number of vertices in the graph. Mixed * graphs are currently not supported, as solving the CPP for mixed graphs is NP-hard. The graph on * which this algorithm is invoked must be strongly connected; invoking this algorithm on a graph * which is not strongly connected may result in undefined behavior. In case of weighted graphs, all * edge weights must be positive. * * If the input graph is Eulerian (see {@link GraphTests#isEulerian(Graph)} for details) use * {@link HierholzerEulerianCycle} instead. *

* The implementation is based on the following paper:
* Edmonds, J., Johnson, E.L. Matching, Euler tours and the Chinese postman, Mathematical * Programming (1973) 5: 88. doi:10.1007/BF01580113
* * More concise descriptions of the algorithms can be found here: *

* * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class ChinesePostman { /** * Solves the Chinese Postman Problem on the given graph. For Undirected graph, this * implementation uses the @{@link KolmogorovWeightedPerfectMatching} matching algorithm; for * directed graphs, @{@link KuhnMunkresMinimalWeightBipartitePerfectMatching} is used instead. * The input graph must be strongly connected. Otherwise the behavior of this class is * undefined. * * @param graph the input graph (must be a strongly connected graph) * @return Eulerian circuit of minimum weight. */ public GraphPath getCPPSolution(Graph graph) { // Mixed graphs are currently not supported. Solving the CPP for mixed graphs is NP-Hard GraphTests.requireDirectedOrUndirected(graph); // If graph has no vertices, or no edges, instantly return. if (graph.vertexSet().isEmpty() || graph.edgeSet().isEmpty()) return new HierholzerEulerianCycle().getEulerianCycle(graph); assert GraphTests.isStronglyConnected(graph); if (graph.getType().isUndirected()) return solveCPPUndirected(graph); else return solveCPPDirected(graph); } /** * Solves the CPP for undirected graphs * * @param graph input graph * @return CPP solution (closed walk) */ private GraphPath solveCPPUndirected(Graph graph) { // 1. Find all odd degree vertices (there should be an even number of those) List oddDegreeVertices = graph .vertexSet().stream().filter(v -> graph.degreeOf(v) % 2 == 1) .collect(Collectors.toList()); // 2. Compute all pairwise shortest paths for the oddDegreeVertices Map, GraphPath> shortestPaths = new HashMap<>(); ShortestPathAlgorithm sp = new DijkstraShortestPath<>(graph); for (int i = 0; i < oddDegreeVertices.size() - 1; i++) { V u = oddDegreeVertices.get(i); ShortestPathAlgorithm.SingleSourcePaths paths = sp.getPaths(u); for (int j = i + 1; j < oddDegreeVertices.size(); j++) { V v = oddDegreeVertices.get(j); shortestPaths.put(new UnorderedPair<>(u, v), paths.getPath(v)); } } // 3. Solve a matching problem. For that we create an auxiliary graph. Graph auxGraph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(auxGraph, oddDegreeVertices); for (V u : oddDegreeVertices) { for (V v : oddDegreeVertices) { if (u == v) continue; Graphs .addEdge( auxGraph, u, v, shortestPaths.get(new UnorderedPair<>(u, v)).getWeight()); } } MatchingAlgorithm.Matching matching = new KolmogorovWeightedPerfectMatching<>(auxGraph).getMatching(); // 4. On the original graph, add shortcuts between the odd vertices. These shortcuts have // been // identified by the matching algorithm. A shortcut from u to v // indirectly implies duplicating all edges on the shortest path from u to v Graph eulerGraph = new Pseudograph<>( graph.getVertexSupplier(), graph.getEdgeSupplier(), graph.getType().isWeighted()); Graphs.addGraph(eulerGraph, graph); Map> shortcutEdges = new HashMap<>(); for (DefaultWeightedEdge e : matching.getEdges()) { V u = auxGraph.getEdgeSource(e); V v = auxGraph.getEdgeTarget(e); E shortcutEdge = eulerGraph.addEdge(u, v); shortcutEdges.put(shortcutEdge, shortestPaths.get(new UnorderedPair<>(u, v))); } EulerianCycleAlgorithm eulerianCycleAlgorithm = new HierholzerEulerianCycle<>(); GraphPath pathWithShortcuts = eulerianCycleAlgorithm.getEulerianCycle(eulerGraph); return replaceShortcutEdges(graph, pathWithShortcuts, shortcutEdges); } /** * Solves the CPP for directed graphs * * @param graph input graph * @return CPP solution (closed walk) */ private GraphPath solveCPPDirected(Graph graph) { // 1. Find all imbalanced vertices Map imbalancedVertices = new LinkedHashMap<>(); Set negImbalancedVertices = new HashSet<>(); Set postImbalancedVertices = new HashSet<>(); for (V v : graph.vertexSet()) { int imbalance = graph.outDegreeOf(v) - graph.inDegreeOf(v); if (imbalance == 0) continue; imbalancedVertices.put(v, Math.abs(imbalance)); if (imbalance < 0) negImbalancedVertices.add(v); else postImbalancedVertices.add(v); } // 2. Compute all pairwise shortest paths from the negative imbalanced vertices to the // positive imbalanced vertices Map, GraphPath> shortestPaths = new HashMap<>(); ShortestPathAlgorithm sp = new DijkstraShortestPath<>(graph); for (V u : negImbalancedVertices) { ShortestPathAlgorithm.SingleSourcePaths paths = sp.getPaths(u); for (V v : postImbalancedVertices) { shortestPaths.put(new Pair<>(u, v), paths.getPath(v)); } } // 3. Solve a matching problem. For that we create an auxiliary bipartite graph. Partition1 // contains all nodes with negative imbalance, // Partition2 contains all nodes with positive imbalance. Each imbalanced node is duplicated // a number of times. The number of duplicates of a // node equals its imbalance. Graph auxGraph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); List duplicateMap = new ArrayList<>(); Set negImbalancedPartition = new HashSet<>(); Set postImbalancedPartition = new HashSet<>(); Integer vertex = 0; for (V v : negImbalancedVertices) { for (int i = 0; i < imbalancedVertices.get(v); i++) { auxGraph.addVertex(vertex); duplicateMap.add(v); negImbalancedPartition.add(vertex); vertex++; } } for (V v : postImbalancedVertices) { for (int i = 0; i < imbalancedVertices.get(v); i++) { auxGraph.addVertex(vertex); duplicateMap.add(v); postImbalancedPartition.add(vertex); vertex++; } } for (Integer i : negImbalancedPartition) { for (Integer j : postImbalancedPartition) { V u = duplicateMap.get(i); V v = duplicateMap.get(j); Graphs.addEdge(auxGraph, i, j, shortestPaths.get(new Pair<>(u, v)).getWeight()); } } MatchingAlgorithm.Matching matching = new KuhnMunkresMinimalWeightBipartitePerfectMatching<>( auxGraph, negImbalancedPartition, postImbalancedPartition).getMatching(); // 4. On the original graph, add shortcuts between the imbalanced vertices. These shortcuts // have // been identified by the matching algorithm. A shortcut from u to v // indirectly implies duplicating all edges on the shortest path from u to v Graph eulerGraph = new DirectedPseudograph<>( graph.getVertexSupplier(), graph.getEdgeSupplier(), graph.getType().isWeighted()); Graphs.addGraph(eulerGraph, graph); Map> shortcutEdges = new HashMap<>(); for (DefaultWeightedEdge e : matching.getEdges()) { int i = auxGraph.getEdgeSource(e); int j = auxGraph.getEdgeTarget(e); V u = duplicateMap.get(i); V v = duplicateMap.get(j); E shortcutEdge = eulerGraph.addEdge(u, v); shortcutEdges.put(shortcutEdge, shortestPaths.get(new Pair<>(u, v))); } EulerianCycleAlgorithm eulerianCycleAlgorithm = new HierholzerEulerianCycle<>(); GraphPath pathWithShortcuts = eulerianCycleAlgorithm.getEulerianCycle(eulerGraph); return replaceShortcutEdges(graph, pathWithShortcuts, shortcutEdges); } private GraphPath replaceShortcutEdges( Graph inputGraph, GraphPath pathWithShortcuts, Map> shortcutEdges) { V startVertex = pathWithShortcuts.getStartVertex(); V endVertex = pathWithShortcuts.getEndVertex(); List vertexList = new ArrayList<>(); List edgeList = new ArrayList<>(); List verticesInPathWithShortcuts = pathWithShortcuts.getVertexList(); // should contain // at least 2 // vertices List edgesInPathWithShortcuts = pathWithShortcuts.getEdgeList(); // cannot be empty for (int i = 0; i < verticesInPathWithShortcuts.size() - 1; i++) { vertexList.add(verticesInPathWithShortcuts.get(i)); E edge = edgesInPathWithShortcuts.get(i); if (shortcutEdges.containsKey(edge)) { // shortcut edge // replace shortcut edge by its implied path GraphPath shortcut = shortcutEdges.get(edge); if (vertexList.get(vertexList.size() - 1).equals(shortcut.getStartVertex())) { // check // direction // of // path vertexList .addAll( shortcut .getVertexList().subList(1, shortcut.getVertexList().size() - 1)); edgeList.addAll(shortcut.getEdgeList()); } else { List reverseVertices = new ArrayList<>( shortcut.getVertexList().subList(1, shortcut.getVertexList().size() - 1)); Collections.reverse(reverseVertices); List reverseEdges = new ArrayList<>(shortcut.getEdgeList()); Collections.reverse(reverseEdges); vertexList.addAll(reverseVertices); edgeList.addAll(reverseEdges); } } else { // original edge edgeList.add(edge); } } vertexList.add(endVertex); double pathWeight = edgeList.stream().mapToDouble(inputGraph::getEdgeWeight).sum(); return new GraphWalk<>( inputGraph, startVertex, endVertex, vertexList, edgeList, pathWeight); } } ChordalGraphMinimalVertexSeparatorFinder.java000066400000000000000000000206011402514743400365660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * Allows obtaining a mapping of all * minimal vertex * separators of a graph to their multiplicities *

* In the context of this implementation following definitions are used: *

    *
  • A set of vertices $S$ of a graph $G=(V, E)$ is called a u-v separator, if vertices $u$ * and $v$ in the induced graph on vertices $V(G) - S$ are in different connected components.
  • *
  • A set $S$ is called a minimal u-v separator if it is a u-v separator and no proper * subset of $S$ is a u-v separator.
  • *
  • A set $S$ is called a minimal vertex separator if it is minimal u-v separator for some * vertices $u$ and $v$ of the graph $G$.
  • *
  • A set of vertices $S$ is called a minimal separator if no proper subset of $S$ is a * separator of the graph $G$.
  • *
*

* Let $\sigma = (v_1, v_2, \dots, v_n)$ be some perfect elimination order (peo) of the graph $G = * (V, E)$. The induced graph on vertices $(v_1, v_2, \dots, v_i)$ with respect to peo $\sigma$ is * denoted as $G_i$. The predecessors set of vertex $v$ with respect to peo $\sigma$ is denoted as * $N(v, \sigma)$. A set $B$ is called a base set with respect to $\sigma$, is there exist * some vertex $v$ with $t = \sigma(v)$ such that $N(v, \sigma) = B$ and B is not a maximal clique * in $G_{t-1}$. The vertices which satisfy conditions described above are called dependent * vertices with respect to $\sigma$. The cardinality of the set of dependent vertices is called * a multiplicity of the base set $B$. The multiplicity of a minimal vertex separator indicates the * number of different pairs of vertices separated by it.The definitions of a base set and a minimal * vertex separator in the context of chordal graphs are equivalent. *

* For more information on the topic see: Kumar, P. Sreenivasa & Madhavan, C. E. Veni. (1998). * Minimal * vertex separators of chordal graphs. Discrete Applied Mathematics. 89. 155-168. * 10.1016/S0166-218X(98)00123-1. *

* The running time of the algorithm is $\mathcal{O}(\omega(G)(|V| + |E|))$, where $\omega(G)$ is * the size of a maximum clique of the graph $G$. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see ChordalityInspector */ public class ChordalGraphMinimalVertexSeparatorFinder { /** * The graph in which minimal vertex separators to searched in */ private final Graph graph; /** * {@link ChordalityInspector} for testing chordality of the {@code graph} */ private final ChordalityInspector chordalityInspector; /** * A mapping of minimal separators to their multiplicities */ private Map, Integer> minimalSeparatorsWithMultiplicities; /** * Creates new {@code ChordalGraphMinimalVertexSeparatorFinder} instance. The * {@link ChordalityInspector} used in this implementation uses the * {@link org.jgrapht.traverse.MaximumCardinalityIterator} iterator * * @param graph the graph minimal separators to search in */ public ChordalGraphMinimalVertexSeparatorFinder(Graph graph) { this.graph = Objects.requireNonNull(graph); chordalityInspector = new ChordalityInspector<>(graph, ChordalityInspector.IterationOrder.MCS); } /** * Computes a set of all minimal separators of the {@code graph} and returns it. Returns null if * the {@code graph} isn't chordal. * * @return computed set of all minimal separators, or null if the {@code graph} isn't chordal */ public Set> getMinimalSeparators() { lazyComputeMinimalSeparatorsWithMultiplicities(); return minimalSeparatorsWithMultiplicities == null ? null : minimalSeparatorsWithMultiplicities.keySet(); } /** * Computes a mapping of all minimal vertex separators of the {@code graph} and returns it. * Returns null if the {@code graph} isn't chordal. * * @return computed mapping of all minimal separators to their multiplicities, or null if the * {@code graph} isn't chordal */ public Map, Integer> getMinimalSeparatorsWithMultiplicities() { lazyComputeMinimalSeparatorsWithMultiplicities(); return minimalSeparatorsWithMultiplicities; } /** * Lazy computes a set of all minimal separators and a mapping of all minimal vertex separators * to their multiplicities */ private void lazyComputeMinimalSeparatorsWithMultiplicities() { if (minimalSeparatorsWithMultiplicities == null && chordalityInspector.isChordal()) { minimalSeparatorsWithMultiplicities = new HashMap<>(); List perfectEliminationOrder = chordalityInspector.getPerfectEliminationOrder(); Map vertexInOrder = getVertexInOrder(perfectEliminationOrder); Set previous; Set current = new HashSet<>(); for (int i = 1; i < perfectEliminationOrder.size(); i++) { previous = current; current = getPredecessors(vertexInOrder, perfectEliminationOrder.get(i)); if (current.size() <= previous.size()) { // current set is a minimal separator if (minimalSeparatorsWithMultiplicities.containsKey(current)) { // found another vertex dependent on current set minimalSeparatorsWithMultiplicities .put(current, minimalSeparatorsWithMultiplicities.get(current) + 1); } else { // vertex at position i is the first vertex dependent on current set minimalSeparatorsWithMultiplicities.put(current, 1); } } } } } /** * Returns a map containing vertices from the {@code vertexOrder} mapped to their indices in * {@code vertexOrder}. * * @param vertexOrder a list with vertices. * @return a mapping of vertices from {@code vertexOrder} to their indices in * {@code vertexOrder}. */ private Map getVertexInOrder(List vertexOrder) { Map vertexInOrder = CollectionUtil.newHashMapWithExpectedSize(vertexOrder.size()); int i = 0; for (V vertex : vertexOrder) { vertexInOrder.put(vertex, i++); } return vertexInOrder; } /** * Returns the predecessors of {@code vertex} in the order defined by {@code map}. More * precisely, returns those of {@code vertex}, whose mapped index in {@code map} is less then * the index of {@code vertex}. * * @param vertexInOrder defines the mapping of vertices in {@code graph} to their indices in * order. * @param vertex the vertex whose predecessors in order are to be returned. * @return the predecessors of {@code vertex} in order defines by {@code map}. */ private Set getPredecessors(Map vertexInOrder, V vertex) { Set predecessors = new HashSet<>(); Integer vertexPosition = vertexInOrder.get(vertex); Set edges = graph.edgesOf(vertex); for (E edge : edges) { V oppositeVertex = Graphs.getOppositeVertex(graph, edge, vertex); Integer destPosition = vertexInOrder.get(oppositeVertex); if (destPosition < vertexPosition) predecessors.add(oppositeVertex); } return predecessors; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/ChordalityInspector.java000066400000000000000000000376211402514743400325740ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.util.*; /** * Tests whether a graph is chordal. A * chordal graph is a simple graph in which all * cycles of four or more vertices have * a chord. A chord is an edge that is * not part of the cycle but connects two vertices of the cycle. A graph is chordal if and only if * it has a * perfect elimination order. A perfect elimination order in a graph is an ordering of the * vertices of the graph such that, for each vertex $v$, $v$ and the neighbors of $v$ that occur * after $v$ in the order form a clique. This implementation uses either * {@link MaximumCardinalityIterator} or {@link LexBreadthFirstIterator} to compute a perfect * elimination order. The desired method is specified during construction time. *

* Chordal graphs are a subset of the * perfect graphs. They may be recognized in polynomial time, and several problems that are hard * on other classes of graphs such as minimum vertex coloring or determining maximum cardinality * cliques and independent set can be performed in polynomial time when the input is chordal. *

* All methods in this class run in $\mathcal{O}(|V| + |E|)$ time. Determining whether a graph is * chordal, as well as computing a perfect elimination order takes $\mathcal{O}(|V| + |E|)$ time, * independent of the algorithm ({@link MaximumCardinalityIterator} or * {@link LexBreadthFirstIterator}) used to compute the perfect elimination order. *

* All the methods in this class are invoked in a lazy fashion, meaning that computations are only * started once the method gets invoked. * * @param the graph vertex type. * @param the graph edge type. * * @author Timofey Chudakov */ public class ChordalityInspector { /** * Stores the type of iterator used by this {@code ChordalityInspector}. */ private final IterationOrder iterationOrder; /** * Iterator used for producing perfect elimination order. */ private final GraphIterator orderIterator; /** * The inspected graph. */ private final Graph graph; /** * Contains true if the graph is chordal, otherwise false. */ private boolean chordal = false; /** * Order produced by {@code orderIterator}. */ private List order; /** * A hole contained in the inspected {@code graph}. */ private GraphPath hole; /** * Creates a chordality inspector for {@code graph}, which uses * {@link MaximumCardinalityIterator} as a default iterator. * * @param graph the graph for which a chordality inspector to be created. */ public ChordalityInspector(Graph graph) { this(graph, IterationOrder.MCS); } /** * Creates a chordality inspector for {@code graph}, which uses an iterator defined by the * second parameter as an internal iterator. * * @param graph the graph for which a chordality inspector is to be created. * @param iterationOrder the constant, which defines iterator to be used by this * {@code ChordalityInspector}. */ public ChordalityInspector(Graph graph, IterationOrder iterationOrder) { Objects.requireNonNull(graph); if (graph.getType().isDirected()) { this.graph = new AsUndirectedGraph<>(graph); } else { this.graph = graph; } this.iterationOrder = iterationOrder; this.hole = null; if (iterationOrder == IterationOrder.MCS) { this.orderIterator = new MaximumCardinalityIterator<>(graph); } else { this.orderIterator = new LexBreadthFirstIterator<>(graph); } } /** * Checks whether the inspected graph is chordal. * * @return true if this graph is chordal, otherwise false. */ public boolean isChordal() { if (order == null) { order = Collections.unmodifiableList(lazyComputeOrder()); chordal = isPerfectEliminationOrder(order, true); } return chordal; } /** * Returns a * perfect elimination order if one exists. The existence of a perfect elimination order * certifies that the graph is chordal. This method returns null if the graph is not chordal. * * @return a perfect elimination order of a graph or null if graph is not chordal. */ public List getPerfectEliminationOrder() { isChordal(); if (chordal) { return order; } return null; } /** * A graph which is not chordal, must contain a * hole (chordless cycle on 4 or more * vertices). The existence of a hole certifies that the graph is not chordal. This method * returns a chordless cycle if the graph is not chordal, or null if the graph is chordal. * * @return a hole if the {@code graph} is not chordal, or null if the graph is chordal. */ public GraphPath getHole() { isChordal(); return hole; } /** * Checks whether the vertices in the {@code vertexOrder} form a * perfect elimination order with respect to the inspected graph. Returns false otherwise. * * @param vertexOrder the sequence of vertices of the {@code graph}. * @return true if the {@code graph} is chordal and the vertices in {@code vertexOrder} are in * perfect elimination order, otherwise false. */ public boolean isPerfectEliminationOrder(List vertexOrder) { return isPerfectEliminationOrder(vertexOrder, false); } /** * Computes vertex order via {@code orderIterator}. * * @return computed order. */ private List lazyComputeOrder() { if (order == null) { int vertexNum = graph.vertexSet().size(); order = new ArrayList<>(vertexNum); for (int i = 0; i < vertexNum; i++) { order.add(orderIterator.next()); } } return order; } /** * Checks whether the vertices in the {@code vertexOrder} form a * perfect elimination order with respect to the inspected graph. Returns false otherwise. * Computes a hole if the {@code computeHole} is true. * * @param vertexOrder the sequence of vertices of {@code graph}. * @param computeHole tells whether to compute the hole if the graph isn't chordal. * @return true if the {@code graph} is chordal and the vertices in {@code vertexOrder} are in * perfect elimination order. */ private boolean isPerfectEliminationOrder(List vertexOrder, boolean computeHole) { Set graphVertices = graph.vertexSet(); if (graphVertices.size() == vertexOrder.size() && graphVertices.containsAll(vertexOrder)) { Map vertexInOrder = getVertexInOrder(vertexOrder); for (V vertex : vertexOrder) { Set predecessors = getPredecessors(vertexInOrder, vertex); if (predecessors.size() > 0) { V maxPredecessor = Collections.max(predecessors, Comparator.comparingInt(vertexInOrder::get)); for (V predecessor : predecessors) { if (!predecessor.equals(maxPredecessor) && !graph.containsEdge(predecessor, maxPredecessor)) { if (computeHole) { // predecessor, vertex and maxPredecessor are vertices, which lie // consecutively on // some chordless cycle in the graph findHole(predecessor, vertex, maxPredecessor); } return false; } } } } return true; } else { return false; } } /** * Returns a map containing vertices from the {@code vertexOrder} mapped to their indices in * {@code vertexOrder}. * * @param vertexOrder a list with vertices. * @return a mapping of vertices from {@code vertexOrder} to their indices in * {@code vertexOrder}. */ private Map getVertexInOrder(List vertexOrder) { Map vertexInOrder = CollectionUtil.newHashMapWithExpectedSize(vertexOrder.size()); int i = 0; for (V vertex : vertexOrder) { vertexInOrder.put(vertex, i++); } return vertexInOrder; } /** * Computes a hole from the vertices of {@code subgraph} of the inspected {@code graph} with * vertices {@code a}, {@code b} and {@code c} on this cycle (there must be no edge between * {@code a} and {@code c}. * * @param a vertex that belongs to the cycle * @param b vertex that belongs to the cycle * @param c vertex that belongs to the cycle */ private void findHole(V a, V b, V c) { // b is the first vertex in the order produced by the iterator whose predecessors don't form // a clique. // a and c are a pair of vertices, which are predecessors of b and are not adjacent. These // three vertices // belong to some chordless cycle in the G[S] where G[S] is a subgraph of G on vertices in // S = {u : index_in_order(u) <= index_in_order(v)}. // this method uses dfs to find any cycle in G, in which every vertex isn't adjacent to b, // except for a and b. // then it finds a chordless subcycle in linear time and returns it. List cycle = new ArrayList<>(Arrays.asList(a, b, c)); Map visited = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); for (V vertex : graph.vertexSet()) { visited.put(vertex, false); } visited.put(a, true); visited.put(b, true); dfsVisit(cycle, visited, a, b, c); cycle = minimizeCycle(cycle); hole = new GraphWalk<>(graph, cycle, 0); } /** * Computes some cycle in the graph on the vertices from the domain of the map {@code visited}. * More precisely, finds some path from {@code middle} to {@code finish}. The vertex * {@code middle} isn't the endpoint of any chord in this cycle. * * @param cycle already computed part of the cycle * @param visited the map that defines which vertex has been visited by this method * @param finish the last vertex in the cycle. * @param middle the vertex, which must be adjacent onl * @param current currently examined vertex. */ private void dfsVisit(List cycle, Map visited, V finish, V middle, V current) { visited.put(current, true); for (E edge : graph.edgesOf(current)) { V opposite = Graphs.getOppositeVertex(graph, edge, current); if ((!visited.get(opposite) && !graph.containsEdge(opposite, middle)) || opposite.equals(finish)) { cycle.add(opposite); if (opposite.equals(finish)) { return; } dfsVisit(cycle, visited, finish, middle, opposite); if (cycle.get(cycle.size() - 1).equals(finish)) { return; } else { cycle.remove(cycle.size() - 1); } } } } /** * Minimizes the cycle represented by the list {@code cycle}. More precisely it retains first 2 * vertices and finds a chordless cycle starting from the third vertex. * * @param cycle vertices of the graph that represent the cycle. * @return a chordless cycle */ private List minimizeCycle(List cycle) { Set cycleVertices = new HashSet<>(cycle); cycleVertices.remove(cycle.get(1)); List minimized = new ArrayList<>(); minimized.add(cycle.get(0)); minimized.add(cycle.get(1)); for (int i = 2; i < cycle.size() - 1;) { V vertex = cycle.get(i); minimized.add(vertex); cycleVertices.remove(vertex); Set forward = new HashSet<>(); // compute vertices with the higher index in the cycle for (E edge : graph.edgesOf(vertex)) { V opposite = Graphs.getOppositeVertex(graph, edge, vertex); if (cycleVertices.contains(opposite)) { forward.add(opposite); } } // jump to the vertex with the highest index with respect to the current vertex for (V forwardVertex : forward) { if (cycleVertices.contains(forwardVertex)) { do { cycleVertices.remove(cycle.get(i)); i++; } while (i < cycle.size() && !cycle.get(i).equals(forwardVertex)); } } } minimized.add(cycle.get(cycle.size() - 1)); return minimized; } /** * Returns the predecessors of {@code vertex} in the order defined by {@code map}. More * precisely, returns those of {@code vertex}, whose mapped index in {@code map} is less then * the index of {@code vertex}. * * @param vertexInOrder defines the mapping of vertices in {@code graph} to their indices in * order. * @param vertex the vertex whose predecessors in order are to be returned. * @return the predecessors of {@code vertex} in order defines by {@code map}. */ private Set getPredecessors(Map vertexInOrder, V vertex) { Set predecessors = new HashSet<>(); Integer vertexPosition = vertexInOrder.get(vertex); Set edges = graph.edgesOf(vertex); for (E edge : edges) { V oppositeVertex = Graphs.getOppositeVertex(graph, edge, vertex); Integer destPosition = vertexInOrder.get(oppositeVertex); if (destPosition < vertexPosition) { predecessors.add(oppositeVertex); } } return predecessors; } /** * Returns the type of iterator used in this {@code ChordalityInspector} * * @return the type of iterator used in this {@code ChordalityInspector} */ public IterationOrder getIterationOrder() { return iterationOrder; } /** * Specifies internal iterator type. */ public enum IterationOrder { MCS, LEX_BFS, } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/CycleDetector.java000066400000000000000000000152611402514743400313300ustar00rootroot00000000000000/* * (C) Copyright 2004-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.traverse.*; import java.util.*; /** * Performs cycle detection on a graph. The inspected graph is specified at construction time * and cannot be modified. Currently, the detector supports only directed graphs. * * @param the graph vertex type * @param the graph edge type * * @author John V. Sichi */ public class CycleDetector { /** * Graph on which cycle detection is being performed. */ private Graph graph; /** * Creates a cycle detector for the specified graph. Currently only directed graphs are * supported. * * @param graph the directed graph in which to detect cycles */ public CycleDetector(Graph graph) { this.graph = GraphTests.requireDirected(graph); } /** * Performs yes/no cycle detection on the entire graph. * * @return true iff the graph contains at least one cycle */ public boolean detectCycles() { try { execute(null, null); } catch (CycleDetectedException ex) { return true; } return false; } /** * Performs yes/no cycle detection on an individual vertex. * * @param v the vertex to test * * @return true if v is on at least one cycle */ public boolean detectCyclesContainingVertex(V v) { try { execute(null, v); } catch (CycleDetectedException ex) { return true; } return false; } /** * Finds the vertex set for the subgraph of all cycles. * * @return set of all vertices which participate in at least one cycle in this graph */ public Set findCycles() { // ProbeIterator can't be used to handle this case, // so use StrongConnectivityAlgorithm instead. StrongConnectivityAlgorithm inspector = new KosarajuStrongConnectivityInspector<>(graph); List> components = inspector.stronglyConnectedSets(); // A vertex participates in a cycle if either of the following is // true: (a) it is in a component whose size is greater than 1 // or (b) it is a self-loop Set set = new LinkedHashSet<>(); for (Set component : components) { if (component.size() > 1) { // cycle set.addAll(component); } else { V v = component.iterator().next(); if (graph.containsEdge(v, v)) { // self-loop set.add(v); } } } return set; } /** * Finds the vertex set for the subgraph of all cycles which contain a particular vertex. * *

* REVIEW jvs 25-Aug-2006: This implementation is not guaranteed to cover all cases. If you want * to be absolutely certain that you report vertices from all cycles containing v, it's safer * (but less efficient) to use StrongConnectivityAlgorithm instead and return the strongly * connected component containing v. * * @param v the vertex to test * * @return set of all vertices reachable from v via at least one cycle */ public Set findCyclesContainingVertex(V v) { Set set = new LinkedHashSet<>(); execute(set, v); return set; } private void execute(Set s, V v) { ProbeIterator iter = new ProbeIterator<>(graph, s, v); while (iter.hasNext()) { iter.next(); } } /** * Exception thrown internally when a cycle is detected during a yes/no cycle test. Must be * caught by top-level detection method. */ private static class CycleDetectedException extends RuntimeException { private static final long serialVersionUID = 3834305137802950712L; } /** * Version of DFS which maintains a backtracking path used to probe for cycles. */ private static class ProbeIterator extends DepthFirstIterator { private List path; private Set cycleSet; private V root; ProbeIterator(Graph graph, Set cycleSet, V startVertex) { super(graph, startVertex); this.path = new ArrayList<>(); this.cycleSet = cycleSet; this.root = startVertex; } /** * {@inheritDoc} */ @Override protected void encounterVertexAgain(V vertex, E edge) { super.encounterVertexAgain(vertex, edge); int i; if (root != null) { // For rooted detection, the path must either // double back to the root, or to a node of a cycle // which has already been detected. if (vertex.equals(root)) { i = 0; } else if ((cycleSet != null) && cycleSet.contains(vertex)) { i = 0; } else { return; } } else { i = path.indexOf(vertex); } if (i > -1) { if (cycleSet == null) { // we're doing yes/no cycle detection throw new CycleDetectedException(); } else { for (; i < path.size(); ++i) { cycleSet.add(path.get(i)); } } } } /** * {@inheritDoc} */ @Override protected V provideNextVertex() { V v = super.provideNextVertex(); // backtrack for (int i = path.size() - 1; i >= 0; --i) { if (graph.containsEdge(path.get(i), v)) { break; } path.remove(i); } path.add(v); return v; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/Cycles.java000066400000000000000000000071701402514743400300210ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; /** * Collection of helper methods related to cycles. * * @author Dimitrios Michail */ public abstract class Cycles { /** * Transform a simple cycle from an edge set representation to a graph path. A simple cycle * contains vertices with degrees either zero or two. This method treats directed graphs as * undirected. * * @param graph the graph * @param cycle the simple cycle * @return the cycle as a graph path * @param graph vertex type * @param graph edge type * @throws IllegalArgumentException if the provided edge set is not a simple cycle (circuit) */ public static GraphPath simpleCycleToGraphPath(Graph graph, List cycle) { Objects.requireNonNull(graph, "Graph cannot be null"); Objects.requireNonNull(cycle, "Cycle cannot be null"); if (cycle.isEmpty()) { return null; } // index Map firstEdge = new HashMap<>(); Map secondEdge = new HashMap<>(); for (E e : cycle) { V s = graph.getEdgeSource(e); if (!firstEdge.containsKey(s)) { firstEdge.put(s, e); } else { if (!secondEdge.containsKey(s)) { secondEdge.put(s, e); } else { throw new IllegalArgumentException("Not a simple cycle"); } } V t = graph.getEdgeTarget(e); if (!firstEdge.containsKey(t)) { firstEdge.put(t, e); } else { if (!secondEdge.containsKey(t)) { secondEdge.put(t, e); } else { throw new IllegalArgumentException("Not a simple cycle"); } } } // traverse List edges = new ArrayList<>(); double weight = 0d; E e = cycle.stream().findAny().get(); edges.add(e); weight += graph.getEdgeWeight(e); V start = graph.getEdgeSource(e); V cur = Graphs.getOppositeVertex(graph, e, start); while (!cur.equals(start)) { E fe = firstEdge.get(cur); if (fe == null) { throw new IllegalArgumentException("Not a simple cycle"); } E se = secondEdge.get(cur); if (se == null) { throw new IllegalArgumentException("Not a simple cycle"); } if (fe.equals(e)) { e = se; } else if (se.equals(e)) { e = fe; } else { throw new IllegalArgumentException("Not a simple cycle"); } edges.add(e); weight += graph.getEdgeWeight(e); cur = Graphs.getOppositeVertex(graph, e, cur); } // return result return new GraphWalk<>(graph, start, start, edges, weight); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/DirectedSimpleCycles.java000066400000000000000000000022101402514743400326250ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import java.util.*; /** * A common interface for classes implementing algorithms for enumeration of the simple cycles of a * directed graph. * * @param the vertex type. * @param the edge type. * * @author Nikolay Ognyanov */ public interface DirectedSimpleCycles { /** * Find the simple cycles of the graph. * * @return The list of all simple cycles. Possibly empty but never null. */ List> findSimpleCycles(); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HawickJamesSimpleCycles.java000066400000000000000000000172701402514743400333040ustar00rootroot00000000000000/* * (C) Copyright 2014-2021, by Luiz Kill and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import java.util.*; import static java.util.Collections.singletonList; import static java.util.stream.Collectors.toList; /** * Find all simple cycles of a directed graph using the algorithm described by Hawick and James. * *

* See:
* K. A. Hawick, H. A. James. Enumerating Circuits and Loops in Graphs with Self-Arcs and * Multiple-Arcs. Computational Science Technical Note CSTN-013, 2008 * * @param the vertex type. * @param the edge type. * * @author Luiz Kill */ public class HawickJamesSimpleCycles implements DirectedSimpleCycles { private Graph graph; private int nVertices = 0; private long nCycles = 0; private List> cycles = null; // The main state of the algorithm private Integer start = 0; private List[] Ak = null; private List[] B = null; private boolean[] blocked = null; private ArrayDeque stack = null; // Indexing the vertices private V[] iToV = null; private Map vToI = null; private int pathLimit = 0; private boolean hasLimit = false; private Runnable operation; /** * Create a simple cycle finder with an unspecified graph. */ public HawickJamesSimpleCycles() { } /** * Create a simple cycle finder for the specified graph. * * @param graph the DirectedGraph in which to find cycles. * * @throws IllegalArgumentException if the graph argument is * null. */ public HawickJamesSimpleCycles(Graph graph) throws IllegalArgumentException { this.graph = GraphTests.requireDirected(graph, "Graph must be directed"); } @SuppressWarnings("unchecked") private void initState() { nCycles = 0; nVertices = graph.vertexSet().size(); blocked = new boolean[nVertices]; stack = new ArrayDeque<>(nVertices); B = new ArrayList[nVertices]; for (int i = 0; i < nVertices; i++) { B[i] = new ArrayList<>(); } iToV = (V[]) graph.vertexSet().toArray(); vToI = new HashMap<>(); for (int i = 0; i < iToV.length; i++) { vToI.put(iToV[i], i); } Ak = buildAdjacencyList(); stack.clear(); } @SuppressWarnings("unchecked") private List[] buildAdjacencyList() { @SuppressWarnings("rawtypes") List[] Ak = new ArrayList[nVertices]; for (int j = 0; j < nVertices; j++) { V v = iToV[j]; List s = Graphs.successorListOf(graph, v); Ak[j] = new ArrayList(s.size()); for (V value : s) { Ak[j].add(vToI.get(value)); } } return Ak; } private void clearState() { Ak = null; nVertices = 0; blocked = null; stack = null; iToV = null; vToI = null; B = null; operation = () -> { }; } private boolean circuit(Integer v, int steps) { boolean f = false; stack.push(v); blocked[v] = true; for (Integer w : Ak[v]) { if (w < start) { continue; } if (Objects.equals(w, start)) { operation.run(); f = true; } else if (!blocked[w]) { if (limitReached(steps) || circuit(w, steps + 1)) { f = true; } } } if (f) { unblock(v); } else { for (Integer w : Ak[v]) { if (w < start) { continue; } if (!B[w].contains(v)) { B[w].add(v); } } } stack.pop(); return f; } private void unblock(Integer u) { blocked[u] = false; for (int wPos = 0; wPos < B[u].size(); wPos++) { Integer w = B[u].get(wPos); int sizeBeforeRemove = B[u].size(); B[u].removeAll(singletonList(w)); wPos -= (sizeBeforeRemove - B[u].size()); if (blocked[w]) { unblock(w); } } } /** * Get the graph * * @return graph */ public Graph getGraph() { return graph; } /** * Set the graph * * @param graph graph */ public void setGraph(Graph graph) { this.graph = GraphTests.requireDirected(graph, "Graph must be directed"); } /** * {@inheritDoc} */ @Override public List> findSimpleCycles() throws IllegalArgumentException { if (graph == null) { throw new IllegalArgumentException("Null graph."); } initState(); cycles = new ArrayList<>(); operation = () -> { List cycle = stack.stream().map(v -> iToV[v]).collect(toList()); Collections.reverse(cycle); cycles.add(cycle); }; analyzeCircuits(); List> result = cycles; clearState(); return result; } /** * Print to the standard output all simple cycles without building a list to keep them, thus * avoiding high memory consumption when investigating large and much connected graphs. */ public void printSimpleCycles() { if (graph == null) { throw new IllegalArgumentException("Null graph."); } initState(); operation = () -> { stack.stream().map(i -> iToV[i].toString() + " ").forEach(System.out::print); System.out.println(); }; analyzeCircuits(); clearState(); } /** * Count the number of simple cycles. It can count up to Long.MAX cycles in a graph. * * @return the number of simple cycles */ public long countSimpleCycles() { if (graph == null) { throw new IllegalArgumentException("Null graph."); } initState(); nCycles = 0; operation = () -> nCycles++; analyzeCircuits(); clearState(); return nCycles; } private void analyzeCircuits() { for (int i = 0; i < nVertices; i++) { for (int j = 0; j < nVertices; j++) { blocked[j] = false; B[j].clear(); } start = vToI.get(iToV[i]); circuit(start, 0); } } /** * Limits the maximum number of edges in a cycle. * * @param pathLimit maximum paths. */ public void setPathLimit(int pathLimit) { this.pathLimit = pathLimit - 1; this.hasLimit = true; } /** * This is the default behaviour of the algorithm. It will keep looking as long as there are * paths available. */ public void clearPathLimit() { this.hasLimit = false; } private boolean limitReached(int steps) { return hasLimit && steps >= pathLimit; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HierholzerEulerianCycle.java000066400000000000000000000433561402514743400333650ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; /** * An implementation of Hierholzer's algorithm for finding an Eulerian cycle in Eulerian graphs. The * algorithm works with directed and undirected graphs which may contain loops and/or multiple * (parallel) edges. The running time is linear, i.e. $O(|E|)$ where $|E|$ is the cardinality of the * edge set of the graph. * *

* See the Wikipedia article for details * and references about Eulerian cycles and a short description of Hierholzer's algorithm for the * construction of an Eulerian cycle. The original presentation of the algorithm dates back to 1873 * and the following paper: Carl Hierholzer: Über die Möglichkeit, einen Linienzug ohne * Wiederholung und ohne Unterbrechung zu umfahren. Mathematische Annalen 6(1), 30–32, 1873. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class HierholzerEulerianCycle implements EulerianCycleAlgorithm { /* * The input graph. */ protected Graph g; /* * Whether the graph is directed or not. */ protected boolean isDirected; /* * Non-zero degree vertices list head. */ protected VertexNode verticesHead; /* * Result edge list head. */ protected EdgeNode eulerianHead; /* * Result first vertex in the tour. */ protected V startVertex; /** * Test whether a graph is Eulerian. An * Eulerian graph is a graph * containing an Eulerian cycle. * * @param graph the input graph * @return true if the graph is Eulerian, false otherwise */ public boolean isEulerian(Graph graph) { GraphTests.requireDirectedOrUndirected(graph); if (graph.vertexSet().isEmpty()) { // null-graph return false return false; } else if (graph.edgeSet().isEmpty()) { // empty-graph with vertices return true; } else if (graph.getType().isUndirected()) { // check odd degrees for (V v : graph.vertexSet()) { if (graph.degreeOf(v) % 2 == 1) { return false; } } // check that at most one connected component contains edges boolean foundComponentWithEdges = false; for (Set component : new ConnectivityInspector<>(graph).connectedSets()) { for (V v : component) { if (graph.degreeOf(v) > 0) { if (foundComponentWithEdges) { return false; } foundComponentWithEdges = true; break; } } } return true; } else { // check same in and out degrees for (V v : graph.vertexSet()) { if (graph.inDegreeOf(v) != graph.outDegreeOf(v)) { return false; } } // check that at most one strongly connected component contains // edges boolean foundComponentWithEdges = false; for (Set component : new KosarajuStrongConnectivityInspector<>(graph) .stronglyConnectedSets()) { for (V v : component) { if (graph.inDegreeOf(v) > 0 || graph.outDegreeOf(v) > 0) { if (foundComponentWithEdges) { return false; } foundComponentWithEdges = true; break; } } } return true; } } /** * Compute an Eulerian cycle of a graph. * * @param g the input graph * @return an Eulerian cycle * @throws IllegalArgumentException in case the graph is not Eulerian */ public GraphPath getEulerianCycle(Graph g) { if (!isEulerian(g)) { throw new IllegalArgumentException("Graph is not Eulerian"); } else if (g.vertexSet().isEmpty()) { throw new IllegalArgumentException("Null graph not permitted"); } else if (GraphTests.isEmpty(g)) { return GraphWalk.emptyWalk(g); } /* * Create doubly-linked lists */ initialize(g); /* * Main loop */ while (verticesHead != null) { /* * Record where to insert next partial cycle. */ EdgeNode whereToInsert = verticesHead.insertLocation; /* * Find partial cycle, while removing used edges. */ Pair partialCycle = computePartialCycle(); /* * Iterate over partial cycle to remove vertices with zero degrees and compute new * insert locations for vertices with non-zero degrees. It is important to move vertices * with new insert locations to the front of the vertex list, in order to make sure that * we always start partial cycles from already visited vertices. */ updateGraphAndInsertLocations(partialCycle, verticesHead); /* * Insert partial cycle into Eulerian cycle */ if (whereToInsert == null) { eulerianHead = partialCycle.getFirst(); } else { partialCycle.getSecond().next = whereToInsert.next; whereToInsert.next = partialCycle.getFirst(); } } // build final result GraphWalk walk = buildWalk(); // cleanup cleanup(); return walk; } /** * Index the graph and create a double-linked list representation suitable for vertex and edge * removals in constant time. Ignore any vertices with zero degrees. * * @param g the graph to index */ protected void initialize(Graph g) { this.g = g; this.isDirected = g.getType().isDirected(); this.verticesHead = null; this.eulerianHead = null; this.startVertex = null; Map vertices = new HashMap<>(); for (V v : g.vertexSet()) { if (g.outDegreeOf(v) > 0) { VertexNode n = new VertexNode(null, v, verticesHead); if (verticesHead != null) { verticesHead.prev = n; } verticesHead = n; vertices.put(v, n); } } for (E e : g.edgeSet()) { VertexNode sNode = vertices.get(g.getEdgeSource(e)); VertexNode tNode = vertices.get(g.getEdgeTarget(e)); addEdge(sNode, tNode, e); } } /** * Release any memory held. */ protected void cleanup() { this.g = null; this.verticesHead = null; this.eulerianHead = null; this.startVertex = null; } /** * Computes a partial cycle assuming that all vertices have an even degree. The partial cycle * always begin from the first graph vertex in the vertex list. * * @return the partial's cycle head and tail nodes as a pair */ protected Pair computePartialCycle() { if (startVertex == null) { // record global start vertex startVertex = verticesHead.v; } EdgeNode partialHead = null; EdgeNode partialTail = null; VertexNode v = verticesHead; do { EdgeNode e = v.adjEdgesHead; v = getOppositeVertex(v, e); unlink(e); if (partialTail == null) { partialTail = e; partialHead = partialTail; } else { partialTail.next = e; partialTail = partialTail.next; } } while (!v.equals(verticesHead)); return Pair.of(partialHead, partialTail); } /** * Iterate over the partial cycle to remove vertices with zero degrees and compute new insert * locations for vertices with non-zero degrees. It is important to move vertices with new * insert locations to the front of the vertex list, in order to make sure that we always start * partial cycles from already visited vertices. * * @param partialCycle the partial cycle * @param partialCycleSourceVertex the source vertex of the first edge in the partial cycle */ protected void updateGraphAndInsertLocations( Pair partialCycle, VertexNode partialCycleSourceVertex) { EdgeNode e = partialCycle.getFirst(); assert e != null : "Graph is not Eulerian"; VertexNode v = getOppositeVertex(partialCycleSourceVertex, e); while (true) { if (v.adjEdgesHead != null) { v.insertLocation = e; moveToFront(v); } else { unlink(v); } e = e.next; if (e == null) { break; } v = getOppositeVertex(v, e); } } /** * Build final walk * * @return the final walk */ protected GraphWalk buildWalk() { double totalWeight = 0d; List result = new ArrayList<>(); EdgeNode it = eulerianHead; while (it != null) { result.add(it.e); totalWeight += g.getEdgeWeight(it.e); it = it.next; } return new GraphWalk<>(g, startVertex, startVertex, result, totalWeight); } /** * Add an edge to the index. * * @param sNode source vertex * @param tNode target vertex * @param e original (wrapped) edge */ protected void addEdge(VertexNode sNode, VertexNode tNode, E e) { EdgeNode sHead = sNode.adjEdgesHead; if (sHead == null) { sHead = new EdgeNode(sNode, tNode, null, e, null, null); } else { EdgeNode n = new EdgeNode(sNode, tNode, null, e, null, sHead); sHead.prev = n; sHead = n; } sNode.adjEdgesHead = sHead; // if undirected and not a self-loop, add edge to target if (!isDirected && !sNode.equals(tNode)) { EdgeNode tHead = tNode.adjEdgesHead; if (tHead == null) { tHead = new EdgeNode(tNode, sNode, null, e, sHead, null); } else { EdgeNode n = new EdgeNode(tNode, sNode, null, e, sHead, tHead); tHead.prev = n; tHead = n; } sHead.reverse = tHead; tNode.adjEdgesHead = tHead; } } /** * Unlink a vertex from the vertex list. * * @param vNode vertex to unlink */ protected void unlink(VertexNode vNode) { if (verticesHead == null) { return; } else if (!verticesHead.equals(vNode) && vNode.prev == null && vNode.next == null) { // does not belong to list return; } else if (vNode.prev != null) { vNode.prev.next = vNode.next; if (vNode.next != null) { vNode.next.prev = vNode.prev; } } else { verticesHead = vNode.next; if (verticesHead != null) { verticesHead.prev = null; } } vNode.next = null; vNode.prev = null; } /** * Move a vertex as first in the vertex list. * * @param vNode vertex to move to front */ protected void moveToFront(VertexNode vNode) { if (vNode.prev != null) { vNode.prev.next = vNode.next; if (vNode.next != null) { vNode.next.prev = vNode.prev; } verticesHead.prev = vNode; vNode.next = verticesHead; vNode.prev = null; verticesHead = vNode; } } /** * Unlink an edge from the adjacency lists of its end-points. * * @param eNode edge to unlink */ protected void unlink(EdgeNode eNode) { VertexNode vNode = eNode.sourceNode; if (eNode.prev != null) { eNode.prev.next = eNode.next; if (eNode.next != null) { eNode.next.prev = eNode.prev; } } else { if (eNode.next != null) { eNode.next.prev = null; } vNode.adjEdgesHead = eNode.next; } // remove reverse if (!isDirected && eNode.reverse != null) { EdgeNode revNode = eNode.reverse; VertexNode uNode = revNode.sourceNode; if (revNode.prev != null) { revNode.prev.next = revNode.next; if (revNode.next != null) { revNode.next.prev = revNode.prev; } } else { if (revNode.next != null) { revNode.next.prev = null; } uNode.adjEdgesHead = revNode.next; } } eNode.next = null; eNode.prev = null; eNode.reverse = null; } /** * Compute the opposite end-point of an end-point of an edge. * * @param v vertex that is part of edge * @param e edge used to find opposite vertex * @return opposite vertex in edge */ protected VertexNode getOppositeVertex(VertexNode v, EdgeNode e) { return v.equals(e.sourceNode) ? e.targetNode : e.sourceNode; } /* * A list node for the vertices */ protected class VertexNode { // actual vertex public V v; // list public VertexNode prev; public VertexNode next; // insert location in global Eulerian list public EdgeNode insertLocation; // adjacent edges public EdgeNode adjEdgesHead; /** * Create VertexNode * * @param prev previous vertex * @param v original (wrapped) vertex * @param next next vertex */ public VertexNode(VertexNode prev, V v, VertexNode next) { this.prev = prev; this.v = v; this.next = next; this.adjEdgesHead = null; this.insertLocation = null; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((v == null) ? 0 : v.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; VertexNode other = TypeUtil.uncheckedCast(obj); return Objects.equals(this.v, other.v); } @Override public String toString() { return v.toString(); } } /* * A list node for the edges */ protected class EdgeNode { // the edge public E e; // list public EdgeNode next; public EdgeNode prev; // reverse if undirected and not a self-loop public EdgeNode reverse; // source and target public VertexNode sourceNode; public VertexNode targetNode; /** * Create EdgeNode * * @param sourceNode source vertex * @param targetNode target vertex * @param prev previous edge * @param e wrapped (original) edge * @param reverse reverse edge * @param next next edge */ public EdgeNode( VertexNode sourceNode, VertexNode targetNode, EdgeNode prev, E e, EdgeNode reverse, EdgeNode next) { this.sourceNode = sourceNode; this.targetNode = targetNode; this.prev = prev; this.e = e; this.reverse = reverse; this.next = next; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((e == null) ? 0 : e.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; EdgeNode other = TypeUtil.uncheckedCast(obj); return Objects.equals(this.e, other.e); } @Override public String toString() { return e.toString(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/HowardMinimumMeanCycle.java000066400000000000000000000364431402514743400331450ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.Graph; import org.jgrapht.GraphPath; import org.jgrapht.Graphs; import org.jgrapht.alg.connectivity.GabowStrongConnectivityInspector; import org.jgrapht.alg.interfaces.MinimumCycleMeanAlgorithm; import org.jgrapht.alg.interfaces.StrongConnectivityAlgorithm; import org.jgrapht.alg.util.ToleranceDoubleComparator; import org.jgrapht.graph.GraphWalk; import org.jgrapht.util.CollectionUtil; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Objects; /** * Implementation of Howard`s algorithm for finding minimum cycle mean in a graph. * *

* The algorithm is described in the article: Ali Dasdan, Sandy S. Irani, and Rajesh K. Gupta. 1999. * Efficient algorithms for optimum cycle mean and optimum cost to time ratio problems. In * Proceedings of the 36th annual ACM/IEEE Design Automation Conference (DAC ’99). Association for * Computing Machinery, New York, NY, USA, 37–42. DOI:https://doi.org/10.1145/309847.309862 * *

* Firstly, the graph is divided into strongly connected components. The minimum cycle mean is then * computed as the globally minimum cycle mean over all components. In the process the necessary * information is recorded to be able to reconstruct the cycle with minimum mean. * *

* The computations are divided into iterations. In each iteration the algorithm tries to update * current minimum cycle mean value. There is a possibility to limit the total number of iteration * via a constructor parameter. * * @param graph vertex type * @param graph edge type * @author Semen Chudakov */ public class HowardMinimumMeanCycle implements MinimumCycleMeanAlgorithm { /** * The underlying graph. */ private final Graph graph; /** * Algorithm for computing strongly connected components in the {@code graph}. */ private final StrongConnectivityAlgorithm strongConnectivityAlgorithm; /** * Maximum number of iterations performed during the computation. If not provided via * constructor the value if defaulted to {@link Integer#MAX_VALUE}. */ private final int maximumIterations; /** * Used to compare floating point numbers. */ private final Comparator comparator; /** * Determines if a cycle is found on current iteration. */ private boolean isCurrentCycleFound; /** * Total weight of a cycle found on current iteration. */ private double currentCycleWeight; /** * Length of a cycle found on current iteration. */ private int currentCycleLength; /** * Vertex which is used to reconstruct the cycle found on current iteration. */ private V currentCycleVertex; /** * For each vertex contains an edge, which together for the policy graph on current iteration. */ private Map policyGraph; /** * For each vertex indicates, if it has been reached by a search during computing vertices * distance in the policy graph. */ private Map reachedVertices; /** * For each vertex stores its level which is used to find a cycle in the policy graph. */ private Map vertexLevel; /** * For each vertex stores its distance in the policy graph. */ private Map vertexDistance; /** * Constructs an instance of the algorithm for the given {@code graph}. * * @param graph graph */ public HowardMinimumMeanCycle(Graph graph) { this(graph, Integer.MAX_VALUE); } /** * Constructs an instance of the algorithm for the given {@code graph} and * {@code maximumIterations}. * * @param graph graph * @param maximumIterations maximum number of iterations */ public HowardMinimumMeanCycle(Graph graph, int maximumIterations) { this(graph, maximumIterations, new GabowStrongConnectivityInspector<>(graph), 1e-9); } /** * Constructs an instance of the algorithm for the given {@code graph}, * {@code maximumIterations}, {@code strongConnectivityAlgorithm} and {@code toleranceEpsilon}. * * @param graph graph * @param maximumIterations maximum number of iterations * @param strongConnectivityAlgorithm algorithm to compute strongly connected components * @param toleranceEpsilon tolerance to compare floating point numbers */ public HowardMinimumMeanCycle( Graph graph, int maximumIterations, StrongConnectivityAlgorithm strongConnectivityAlgorithm, double toleranceEpsilon) { this.graph = Objects.requireNonNull(graph, "graph should not be null!"); this.strongConnectivityAlgorithm = Objects .requireNonNull( strongConnectivityAlgorithm, "strongConnectivityAlgorithm should not be null!"); if (maximumIterations < 0) { throw new IllegalArgumentException("maximumIterations should be non-negative"); } this.maximumIterations = maximumIterations; this.comparator = new ToleranceDoubleComparator(toleranceEpsilon); this.policyGraph = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); this.reachedVertices = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); this.vertexLevel = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); this.vertexDistance = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); } /** * {@inheritDoc} */ @Override public double getCycleMean() { GraphPath cycle = getCycle(); if (cycle == null) { return Double.POSITIVE_INFINITY; } return cycle.getWeight() / cycle.getLength(); } /** * {@inheritDoc} */ @Override public GraphPath getCycle() { // best cycle information boolean isBestCycleFound = false; double bestCycleWeight = 0.0; int bestCycleLength = 1; V bestCycleVertex = null; // search for best cycle over strongly connected components separately int numberOfIterations = 0; for (Graph component : strongConnectivityAlgorithm.getStronglyConnectedComponents()) { // special case: connected component is empty // or contains one vertex with no incoming edges boolean skip = component.vertexSet().size() == 0; skip |= component.vertexSet().size() == 1 && component.incomingEdgesOf(component.vertexSet().iterator().next()).size() == 0; if (skip) { continue; } constructPolicyGraph(component); // try to improve currently best cycle boolean improved = true; while (numberOfIterations < maximumIterations && improved) { constructCycle(component); improved = computeVertexDistance(component); ++numberOfIterations; } // update best cycle information if necessary if (isCurrentCycleFound && (!isBestCycleFound || currentCycleWeight * bestCycleLength < bestCycleWeight * currentCycleLength)) { isBestCycleFound = true; bestCycleWeight = currentCycleWeight; bestCycleLength = currentCycleLength; bestCycleVertex = currentCycleVertex; } // iterations limit reached if (numberOfIterations == maximumIterations) { break; } } if (isBestCycleFound) { return buildPath(bestCycleVertex, bestCycleLength, bestCycleWeight); } // no cycle found in the graph return null; } /** * Computes policy graph for {@code component} and stores result in {@code policyGraph} and * {@code vertexDistance}. For every vertex in the policy graph an edge with the minimum weight * is retained in the policy graph. * * @param component connected component */ private void constructPolicyGraph(Graph component) { for (V v : component.vertexSet()) { vertexDistance.put(v, Double.POSITIVE_INFINITY); } for (V u : component.vertexSet()) { for (E e : component.incomingEdgesOf(u)) { V v = Graphs.getOppositeVertex(component, e, u); double eWeight = component.getEdgeWeight(e); if (eWeight < vertexDistance.get(v)) { vertexDistance.put(v, eWeight); policyGraph.put(v, e); } } } } /** * Finds cycle in the {@code policyGraph} and computes computes its mean. The found cycle is * identified by a vertex {@code currentCycleVertex}. The cycle returned by this method does not * necessarily has the smalles mean over all cycles in the policy graph. * *

* To find cycles this methods assigns a level to each vertex. Initially every vertex has a * level equal to $-1$ which means that the vertex has not been visited. During the computations * this method starts DFS from every not visited vertex and assigns a unique positive level $l$ * to every traversed vertex. If DFS comes across a vertex with level $l$ this indicates that a * cycle has been detected. * * @param component connected component */ private void constructCycle(Graph component) { for (V v : component.vertexSet()) { vertexLevel.put(v, -1); } isCurrentCycleFound = false; int currentCycleLevel = 0; double currentWeight; int currentSize; for (V u : component.vertexSet()) { if (vertexLevel.get(u) >= 0) { // vertex is already belongs to a cycle continue; } // run DFS while (vertexLevel.get(u) < 0) { vertexLevel.put(u, currentCycleLevel); u = Graphs.getOppositeVertex(component, policyGraph.get(u), u); } // check if a cycle has been found if (vertexLevel.get(u) == currentCycleLevel) { currentWeight = component.getEdgeWeight(policyGraph.get(u)); currentSize = 1; // compute weight and length of the found cycle V v = Graphs.getOppositeVertex(component, policyGraph.get(u), u); while (!v.equals(u)) { currentWeight += component.getEdgeWeight(policyGraph.get(v)); ++currentSize; v = Graphs.getOppositeVertex(component, policyGraph.get(v), v); } // update minimum mean value if (!isCurrentCycleFound || (currentWeight * currentCycleLength < currentCycleWeight * currentSize)) { isCurrentCycleFound = true; currentCycleWeight = currentWeight; currentCycleLength = currentSize; currentCycleVertex = u; } } ++currentCycleLevel; } } /** * This method runs the reverted BFS starting from {@code currentCycleVertex} to update data in * {@code policyGraph} and {@code vertexDistance}. This step is needed to identify if current * value of minimum mean is optimal for the {@code graph}. This method also uses * {@code comparator} to find out if update value of minium mean is sufficiently smaller than * the previous one. * * @param component connected component * @return if the currently best mean has been improved */ private boolean computeVertexDistance(Graph component) { // BFS queue Deque queue = new ArrayDeque<>(); for (V v : component.vertexSet()) { reachedVertices.put(v, false); } queue.addLast(currentCycleVertex); reachedVertices.put(currentCycleVertex, true); double currentMean = currentCycleWeight / currentCycleLength; // run reversed BFS while (!queue.isEmpty()) { V u = queue.removeFirst(); for (E e : component.incomingEdgesOf(u)) { V v = Graphs.getOppositeVertex(component, e, u); if (policyGraph.get(v).equals(e) && !reachedVertices.get(v)) { reachedVertices.put(v, true); double updatedDistance = vertexDistance.get(u) + component.getEdgeWeight(e) - currentMean; vertexDistance.put(v, updatedDistance); queue.addLast(v); } } } // identify if the current value of minimum mean // is optimal for the graph boolean improved = false; for (V u : component.vertexSet()) { for (E e : component.incomingEdgesOf(u)) { V v = Graphs.getOppositeVertex(component, e, u); double oldDistance = vertexDistance.get(v); double updatedDistance = vertexDistance.get(u) + component.getEdgeWeight(e) - currentMean; if (oldDistance > updatedDistance) { // check if the value of minimum mean // has been sufficiently improved if (comparator.compare(oldDistance, updatedDistance) > 0) { improved = true; } vertexDistance.put(v, updatedDistance); policyGraph.put(v, e); } } } return improved; } /** * Constructs cycle with minimum mean using information in {@code policyGraph}. * * @param bestCycleVertex cycle vertex * @param bestCycleLength cycle length * @param bestCycleWeight cycle weight * @return constructed minimum mean cycle */ private GraphPath buildPath( V bestCycleVertex, int bestCycleLength, double bestCycleWeight) { List pathEdges = new ArrayList<>(bestCycleLength); List pathVertices = new ArrayList<>(bestCycleLength + 1); V v = bestCycleVertex; pathVertices.add(bestCycleVertex); do { E e = policyGraph.get(v); v = Graphs.getOppositeVertex(graph, e, v); pathEdges.add(e); pathVertices.add(v); } while (!v.equals(bestCycleVertex)); return new GraphWalk<>( graph, bestCycleVertex, bestCycleVertex, pathVertices, pathEdges, bestCycleWeight); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/JohnsonSimpleCycles.java000066400000000000000000000241521402514743400325310ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.builder.*; import java.util.*; /** * Find all simple cycles of a directed graph using the Johnson's algorithm. * *

* See:
* D.B.Johnson, Finding all the elementary circuits of a directed graph, SIAM J. Comput., 4 (1975), * pp. 77-84. * * @param the vertex type. * @param the edge type. * * @author Nikolay Ognyanov */ public class JohnsonSimpleCycles implements DirectedSimpleCycles { // The graph. private Graph graph; // The main state of the algorithm. private List> cycles = null; private V[] iToV = null; private Map vToI = null; private Set blocked = null; private Map> bSets = null; private ArrayDeque stack = null; // The state of the embedded Tarjan SCC algorithm. private List> SCCs = null; private int index = 0; private Map vIndex = null; private Map vLowlink = null; private ArrayDeque path = null; private Set pathSet = null; /** * Create a simple cycle finder for the specified graph. * * @param graph - the DirectedGraph in which to find cycles. * * @throws IllegalArgumentException if the graph argument is * null. */ public JohnsonSimpleCycles(Graph graph) { this.graph = GraphTests.requireDirected(graph, "Graph must be directed"); if (GraphTests.hasMultipleEdges(graph)) { throw new IllegalArgumentException("Graph should not have multiple (parallel) edges"); } } /** * {@inheritDoc} */ @Override public List> findSimpleCycles() { if (graph == null) { throw new IllegalArgumentException("Null graph."); } initState(); int startIndex = 0; int size = graph.vertexSet().size(); while (startIndex < size) { Pair, Integer> minSCCGResult = findMinSCSG(startIndex); if (minSCCGResult != null) { startIndex = minSCCGResult.getSecond(); Graph scg = minSCCGResult.getFirst(); V startV = toV(startIndex); for (E e : scg.outgoingEdgesOf(startV)) { V v = graph.getEdgeTarget(e); blocked.remove(v); getBSet(v).clear(); } findCyclesInSCG(startIndex, startIndex, scg); startIndex++; } else { break; } } List> result = cycles; clearState(); return result; } private Pair, Integer> findMinSCSG(int startIndex) { /* * Per Johnson : "adjacency structure of strong component $K$ with least vertex in subgraph * of $G$ induced by $(s, s + 1, n)$". Or in contemporary terms: the strongly connected * component of the subgraph induced by $(v_1, \dotso ,v_n)$ which contains the minimum * (among those SCCs) vertex index. We return that index together with the graph. */ initMinSCGState(); List> SCCs = findSCCS(startIndex); // find the SCC with the minimum index int minIndexFound = Integer.MAX_VALUE; Set minSCC = null; for (Set scc : SCCs) { for (V v : scc) { int t = toI(v); if (t < minIndexFound) { minIndexFound = t; minSCC = scc; } } } if (minSCC == null) { return null; } // build a graph for the SCC found Graph resultGraph = GraphTypeBuilder . directed().edgeSupplier(graph.getEdgeSupplier()) .vertexSupplier(graph.getVertexSupplier()).allowingMultipleEdges(false) .allowingSelfLoops(true).buildGraph(); for (V v : minSCC) { resultGraph.addVertex(v); } for (V v : minSCC) { for (V w : minSCC) { E edge = graph.getEdge(v, w); if (edge != null) { resultGraph.addEdge(v, w, edge); } } } Pair, Integer> result = Pair.of(resultGraph, minIndexFound); clearMinSCCState(); return result; } private List> findSCCS(int startIndex) { // Find SCCs in the subgraph induced // by vertices startIndex and beyond. // A call to StrongConnectivityAlgorithm // would be too expensive because of the // need to materialize the subgraph. // So - do a local search by the Tarjan's // algorithm and pretend that vertices // with an index smaller than startIndex // do not exist. for (V v : graph.vertexSet()) { int vI = toI(v); if (vI < startIndex) { continue; } if (!vIndex.containsKey(v)) { getSCCs(startIndex, vI); } } List> result = SCCs; SCCs = null; return result; } private void getSCCs(int startIndex, int vertexIndex) { V vertex = toV(vertexIndex); vIndex.put(vertex, index); vLowlink.put(vertex, index); index++; path.push(vertex); pathSet.add(vertex); Set edges = graph.outgoingEdgesOf(vertex); for (E e : edges) { V successor = graph.getEdgeTarget(e); int successorIndex = toI(successor); if (successorIndex < startIndex) { continue; } if (!vIndex.containsKey(successor)) { getSCCs(startIndex, successorIndex); vLowlink.put(vertex, Math.min(vLowlink.get(vertex), vLowlink.get(successor))); } else if (pathSet.contains(successor)) { vLowlink.put(vertex, Math.min(vLowlink.get(vertex), vIndex.get(successor))); } } if (vLowlink.get(vertex).equals(vIndex.get(vertex))) { Set result = new HashSet<>(); V temp; do { temp = path.pop(); pathSet.remove(temp); result.add(temp); } while (!vertex.equals(temp)); if (result.size() == 1) { V v = result.iterator().next(); if (graph.containsEdge(vertex, v)) { SCCs.add(result); } } else { SCCs.add(result); } } } private boolean findCyclesInSCG(int startIndex, int vertexIndex, Graph scg) { /* * Find cycles in a strongly connected graph per Johnson. */ boolean foundCycle = false; V vertex = toV(vertexIndex); stack.push(vertex); blocked.add(vertex); for (E e : scg.outgoingEdgesOf(vertex)) { V successor = scg.getEdgeTarget(e); int successorIndex = toI(successor); if (successorIndex == startIndex) { List cycle = new ArrayList<>(stack.size()); stack.descendingIterator().forEachRemaining(cycle::add); cycles.add(cycle); foundCycle = true; } else if (!blocked.contains(successor)) { boolean gotCycle = findCyclesInSCG(startIndex, successorIndex, scg); foundCycle = foundCycle || gotCycle; } } if (foundCycle) { unblock(vertex); } else { for (E ew : scg.outgoingEdgesOf(vertex)) { V w = scg.getEdgeTarget(ew); Set bSet = getBSet(w); bSet.add(vertex); } } stack.pop(); return foundCycle; } private void unblock(V vertex) { blocked.remove(vertex); Set bSet = getBSet(vertex); while (bSet.size() > 0) { V w = bSet.iterator().next(); bSet.remove(w); if (blocked.contains(w)) { unblock(w); } } } @SuppressWarnings("unchecked") private void initState() { cycles = new LinkedList<>(); iToV = (V[]) graph.vertexSet().toArray(); vToI = new HashMap<>(); blocked = new HashSet<>(); bSets = new HashMap<>(); stack = new ArrayDeque<>(); for (int i = 0; i < iToV.length; i++) { vToI.put(iToV[i], i); } } private void clearState() { cycles = null; iToV = null; vToI = null; blocked = null; bSets = null; stack = null; } private void initMinSCGState() { index = 0; SCCs = new ArrayList<>(); vIndex = new HashMap<>(); vLowlink = new HashMap<>(); path = new ArrayDeque<>(); pathSet = new HashSet<>(); } private void clearMinSCCState() { index = 0; SCCs = null; vIndex = null; vLowlink = null; path = null; pathSet = null; } private Integer toI(V vertex) { return vToI.get(vertex); } private V toV(Integer i) { return iToV[i]; } private Set getBSet(V v) { // B sets typically not all needed, // so instantiate lazily. return bSets.computeIfAbsent(v, k -> new HashSet<>()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/PatonCycleBase.java000066400000000000000000000134641402514743400314360ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Find a cycle basis of an undirected graph using a variant of Paton's algorithm. * *

* See:
* K. Paton, An algorithm for finding a fundamental set of cycles for an undirected linear graph, * Comm. ACM 12 (1969), pp. 514-518. * *

* Note that Paton's algorithm produces a fundamental cycle basis while this implementation produces * a weakly * fundamental cycle basis. A cycle basis is called weakly fundamental if there exists a linear * ordering of the cycles in a cycle basis such that each cycle includes at least one edge that is * not part of any previous cycle. Every fundamental cycle basis is weakly fundamental (for all * linear orderings) but not necessarily vice versa. * * @param the graph vertex type * @param the graph edge type * * @author Nikolay Ognyanov */ public class PatonCycleBase implements CycleBasisAlgorithm { private Graph graph; /** * Create a cycle base finder for the specified graph. * * @param graph the input graph * @throws IllegalArgumentException if the graph argument is null or the graph is * not undirected */ public PatonCycleBase(Graph graph) { this.graph = GraphTests.requireUndirected(graph); } /** * Return an undirected cycle basis of a graph. Works only for undirected graphs which do not * have multiple (parallel) edges. * * @return an undirected cycle basis * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if the graph contains multiple edges between two vertices */ @Override public CycleBasis getCycleBasis() { GraphTests.requireUndirected(graph); if (GraphTests.hasMultipleEdges(graph)) { throw new IllegalArgumentException("Graphs with multiple edges not supported"); } Map> used = new HashMap<>(); Map parent = new HashMap<>(); ArrayDeque stack = new ArrayDeque<>(); Set> cycles = new LinkedHashSet<>(); int totalLength = 0; double totalWeight = 0d; for (V root : graph.vertexSet()) { // Loop over the connected // components of the graph. if (parent.containsKey(root)) { continue; } // Free some memory in case of // multiple connected components. used.clear(); // Prepare to walk the spanning tree. parent.put(root, null); used.put(root, new HashMap<>()); stack.push(root); // Do the walk. It is a BFS with // a LIFO instead of the usual // FIFO. Thus it is easier to // find the cycles in the tree. while (!stack.isEmpty()) { V current = stack.pop(); Map currentUsed = used.get(current); for (E e : graph.edgesOf(current)) { V neighbor = Graphs.getOppositeVertex(graph, e, current); if (!used.containsKey(neighbor)) { // found a new node parent.put(neighbor, e); Map neighbourUsed = new HashMap<>(); neighbourUsed.put(current, e); used.put(neighbor, neighbourUsed); stack.push(neighbor); } else if (neighbor.equals(current)) { // found a self loop List cycle = new ArrayList<>(); cycle.add(e); totalWeight += graph.getEdgeWeight(e); totalLength += 1; cycles.add(cycle); } else if (!currentUsed.containsKey(neighbor)) { // found a cycle Map neighbourUsed = used.get(neighbor); double weight = 0d; List cycle = new ArrayList<>(); cycle.add(e); weight += graph.getEdgeWeight(e); V v = current; while (!neighbourUsed.containsKey(v)) { E p = parent.get(v); cycle.add(p); weight += graph.getEdgeWeight(p); v = Graphs.getOppositeVertex(graph, p, v); } E a = neighbourUsed.get(v); cycle.add(a); weight += graph.getEdgeWeight(a); neighbourUsed.put(current, e); cycles.add(cycle); totalLength += cycle.size(); totalWeight += weight; } } } } return new CycleBasisImpl(graph, cycles, totalLength, totalWeight); } } QueueBFSFundamentalCycleBasis.java000066400000000000000000000062261402514743400342610ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import java.util.*; /** * Generate a set of fundamental cycles by building a spanning tree (forest) using a straightforward * implementation of BFS using a FIFO queue. The implementation first constructs the spanning forest * and then builds the fundamental-cycles set. It supports graphs with self-loops and/or graphs with * multiple (parallel) edges. * *

* For information on algorithms computing fundamental cycle bases see the following paper: Narsingh * Deo, G. Prabhu, and M. S. Krishnamoorthy. Algorithms for Generating Fundamental Cycles in a * Graph. ACM Trans. Math. Softw. 8, 1, 26-42, 1982. *

* *

* The total length of the fundamental-cycle set can be as large as $O(n^3)$ where $n$ is the number * of vertices of the graph. *

* * @param the vertex type * @param the edge type * * @author Dimitrios Michail */ public class QueueBFSFundamentalCycleBasis extends AbstractFundamentalCycleBasis { /** * Constructor * * @param graph the input graph */ public QueueBFSFundamentalCycleBasis(Graph graph) { super(graph); } /** * Compute a spanning forest of the graph using a straightforward BFS implementation. * *

* The representation assumes that the map contains the predecessor edge of each vertex in the * forest. The predecessor edge is the forest edge that was used to discover the vertex. If no * such edge was used (the vertex is a leaf in the forest) then the corresponding entry must be * null. * * @return a map representation of a spanning forest. */ @Override protected Map computeSpanningForest() { Map pred = new HashMap<>(); ArrayDeque queue = new ArrayDeque<>(); for (V s : graph.vertexSet()) { // loop over connected-components if (pred.containsKey(s)) { continue; } // add s in queue pred.put(s, null); queue.addLast(s); // start traversal while (!queue.isEmpty()) { V v = queue.removeFirst(); for (E e : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (!pred.containsKey(u)) { pred.put(u, e); queue.addLast(u); } } } } return pred; } } StackBFSFundamentalCycleBasis.java000066400000000000000000000061401402514743400342350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import java.util.*; /** * Generate a set of fundamental cycles by building a spanning tree (forest) using an implementation * of BFS using a LIFO Stack. The implementation first constructs the spanning forest and then * builds the fundamental-cycles set. It supports graphs with self-loops and/or graphs with multiple * (parallel) edges. * *

* The algorithm constructs the same fundamental cycle basis as the algorithm in the following * paper: K. Paton, An algorithm for finding a fundamental set of cycles for an undirected linear * graph, Comm. ACM 12 (1969), pp. 514-518. * *

* The total length of the fundamental-cycle set can be as large as $O(n^3)$ where $n$ is the number * of vertices of the graph. * * @param the vertex type * @param the edge type * * @author Dimitrios Michail */ public class StackBFSFundamentalCycleBasis extends AbstractFundamentalCycleBasis { /** * Constructor * * @param graph the input graph */ public StackBFSFundamentalCycleBasis(Graph graph) { super(graph); } /** * Compute a spanning forest of the graph using a stack (LIFO) based BFS implementation. * *

* The representation assumes that the map contains the predecessor edge of each vertex in the * forest. The predecessor edge is the forest edge that was used to discover the vertex. If no * such edge was used (the vertex is a leaf in the forest) then the corresponding entry must be * null. * * @return a map representation of a spanning forest. */ @Override protected Map computeSpanningForest() { Map pred = new HashMap<>(); ArrayDeque stack = new ArrayDeque<>(); for (V s : graph.vertexSet()) { // loop over connected-components if (pred.containsKey(s)) { continue; } // add s in stack pred.put(s, null); stack.push(s); // start traversal while (!stack.isEmpty()) { V v = stack.pop(); for (E e : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (!pred.containsKey(u)) { pred.put(u, e); stack.push(u); } } } } return pred; } } SzwarcfiterLauerSimpleCycles.java000066400000000000000000000163611402514743400343330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/* * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import java.util.*; /** * Find all simple cycles of a directed graph using the Schwarcfiter and Lauer's algorithm. * *

* See:
* J.L.Szwarcfiter and P.E.Lauer, Finding the elementary cycles of a directed graph in $O(n + m)$ * per cycle, Technical Report Series, #60, May 1974, Univ. of Newcastle upon Tyne, Newcastle upon * Tyne, England. * * @param the vertex type. * @param the edge type. * * @author Nikolay Ognyanov */ public class SzwarcfiterLauerSimpleCycles implements DirectedSimpleCycles { // The graph. private Graph graph; // The state of the algorithm. private List> cycles = null; private V[] iToV = null; private Map vToI = null; private Map> bSets = null; private ArrayDeque stack = null; private Set marked = null; private Map> removed = null; private int[] position = null; private boolean[] reach = null; private List startVertices = null; /** * Create a simple cycle finder with an unspecified graph. */ public SzwarcfiterLauerSimpleCycles() { } /** * Create a simple cycle finder for the specified graph. * * @param graph - the DirectedGraph in which to find cycles. * * @throws IllegalArgumentException if the graph argument is * null. */ public SzwarcfiterLauerSimpleCycles(Graph graph) { this.graph = GraphTests.requireDirected(graph, "Graph must be directed"); } /** * Get the graph * * @return graph */ public Graph getGraph() { return graph; } /** * Set the graph * * @param graph graph */ public void setGraph(Graph graph) { this.graph = GraphTests.requireDirected(graph, "Graph must be directed"); } /** * {@inheritDoc} */ @Override public List> findSimpleCycles() { // Just a straightforward implementation of // the algorithm. if (graph == null) { throw new IllegalArgumentException("Null graph."); } initState(); KosarajuStrongConnectivityInspector inspector = new KosarajuStrongConnectivityInspector<>(graph); List> sccs = inspector.stronglyConnectedSets(); for (Set scc : sccs) { int maxInDegree = -1; V startVertex = null; for (V v : scc) { int inDegree = graph.inDegreeOf(v); if (inDegree > maxInDegree) { maxInDegree = inDegree; startVertex = v; } } startVertices.add(startVertex); } for (V vertex : startVertices) { cycle(toI(vertex), 0); } List> result = cycles; clearState(); return result; } private boolean cycle(int v, int q) { boolean foundCycle = false; V vV = toV(v); marked.add(vV); stack.push(vV); int t = stack.size(); position[v] = t; if (!reach[v]) { q = t; } Set avRemoved = getRemoved(vV); Set edgeSet = graph.outgoingEdgesOf(vV); for (E e : edgeSet) { V wV = graph.getEdgeTarget(e); if (avRemoved.contains(wV)) { continue; } int w = toI(wV); if (!marked.contains(wV)) { boolean gotCycle = cycle(w, q); if (gotCycle) { foundCycle = true; } else { noCycle(v, w); } } else if (position[w] <= q) { foundCycle = true; List cycle = new ArrayList<>(); Iterator it = stack.descendingIterator(); V current; while (it.hasNext()) { current = it.next(); if (wV.equals(current)) { break; } } cycle.add(wV); while (it.hasNext()) { current = it.next(); cycle.add(current); if (current.equals(vV)) { break; } } cycles.add(cycle); } else { noCycle(v, w); } } stack.pop(); if (foundCycle) { unmark(v); } reach[v] = true; position[v] = graph.vertexSet().size(); return foundCycle; } private void noCycle(int x, int y) { V xV = toV(x); V yV = toV(y); Set by = getBSet(yV); Set axRemoved = getRemoved(xV); by.add(xV); axRemoved.add(yV); } private void unmark(int x) { V xV = toV(x); marked.remove(xV); Set bx = getBSet(xV); for (V yV : bx) { Set ayRemoved = getRemoved(yV); ayRemoved.remove(xV); if (marked.contains(yV)) { unmark(toI(yV)); } } bx.clear(); } @SuppressWarnings("unchecked") private void initState() { cycles = new ArrayList<>(); iToV = (V[]) graph.vertexSet().toArray(); vToI = new HashMap<>(); bSets = new HashMap<>(); stack = new ArrayDeque<>(); marked = new HashSet<>(); removed = new HashMap<>(); int size = graph.vertexSet().size(); position = new int[size]; reach = new boolean[size]; startVertices = new ArrayList<>(); for (int i = 0; i < iToV.length; i++) { vToI.put(iToV[i], i); } } private void clearState() { cycles = null; iToV = null; vToI = null; bSets = null; stack = null; marked = null; removed = null; position = null; reach = null; startVertices = null; } private Integer toI(V v) { return vToI.get(v); } private V toV(int i) { return iToV[i]; } private Set getBSet(V v) { // B sets are typically not all // needed, so instantiate lazily. return bSets.computeIfAbsent(v, k -> new HashSet<>()); } private Set getRemoved(V v) { // Removed sets typically not all // needed, so instantiate lazily. return removed.computeIfAbsent(v, k -> new HashSet<>()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/TarjanSimpleCycles.java000066400000000000000000000120451402514743400323300ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import java.util.*; /** * Find all simple cycles of a directed graph using the Tarjan's algorithm. * *

* See:
* R. Tarjan, Enumeration of the elementary circuits of a directed graph, SIAM J. Comput., 2 (1973), * pp. 211-216. * * @param the vertex type. * @param the edge type. * * @author Nikolay Ognyanov */ public class TarjanSimpleCycles implements DirectedSimpleCycles { private Graph graph; private List> cycles; private Set marked; private ArrayDeque markedStack; private ArrayDeque pointStack; private Map vToI; private Map> removed; /** * Create a simple cycle finder with an unspecified graph. */ public TarjanSimpleCycles() { } /** * Create a simple cycle finder for the specified graph. * * @param graph - the DirectedGraph in which to find cycles. * * @throws IllegalArgumentException if the graph argument is * null. */ public TarjanSimpleCycles(Graph graph) { this.graph = GraphTests.requireDirected(graph, "Graph must be directed"); } /** * Get the graph * * @return graph */ public Graph getGraph() { return graph; } /** * Set the graph * * @param graph graph */ public void setGraph(Graph graph) { this.graph = GraphTests.requireDirected(graph, "Graph must be directed"); } /** * {@inheritDoc} */ @Override public List> findSimpleCycles() { if (graph == null) { throw new IllegalArgumentException("Null graph."); } initState(); for (V start : graph.vertexSet()) { backtrack(start, start); while (!markedStack.isEmpty()) { marked.remove(markedStack.pop()); } } List> result = cycles; clearState(); return result; } private boolean backtrack(V start, V vertex) { boolean foundCycle = false; pointStack.push(vertex); marked.add(vertex); markedStack.push(vertex); for (E currentEdge : graph.outgoingEdgesOf(vertex)) { V currentVertex = graph.getEdgeTarget(currentEdge); if (getRemoved(vertex).contains(currentVertex)) { continue; } int comparison = toI(currentVertex).compareTo(toI(start)); if (comparison < 0) { getRemoved(vertex).add(currentVertex); } else if (comparison == 0) { foundCycle = true; List cycle = new ArrayList<>(); Iterator it = pointStack.descendingIterator(); V v; while (it.hasNext()) { v = it.next(); if (start.equals(v)) { break; } } cycle.add(start); while (it.hasNext()) { cycle.add(it.next()); } cycles.add(cycle); } else if (!marked.contains(currentVertex)) { boolean gotCycle = backtrack(start, currentVertex); foundCycle = foundCycle || gotCycle; } } if (foundCycle) { while (!markedStack.peek().equals(vertex)) { marked.remove(markedStack.pop()); } marked.remove(markedStack.pop()); } pointStack.pop(); return foundCycle; } private void initState() { cycles = new ArrayList<>(); marked = new HashSet<>(); markedStack = new ArrayDeque<>(); pointStack = new ArrayDeque<>(); vToI = new HashMap<>(); removed = new HashMap<>(); int index = 0; for (V v : graph.vertexSet()) { vToI.put(v, index++); } } private void clearState() { cycles = null; marked = null; markedStack = null; pointStack = null; vToI = null; } private Integer toI(V v) { return vToI.get(v); } private Set getRemoved(V v) { // Removed sets typically not all // needed, so instantiate lazily. return removed.computeIfAbsent(v, k -> new HashSet<>()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/TiernanSimpleCycles.java000066400000000000000000000117151402514743400325140ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import java.util.*; /** * Find all simple cycles of a directed graph using the Tiernan's algorithm. * *

* See:
* J.C.Tiernan An Efficient Search Algorithm Find the Elementary Circuits of a Graph., * Communications of the ACM, vol.13, 12, (1970), pp. 722 - 726. * * @param the vertex type. * @param the edge type. * * @author Nikolay Ognyanov */ public class TiernanSimpleCycles implements DirectedSimpleCycles { private Graph graph; /** * Create a simple cycle finder with an unspecified graph. */ public TiernanSimpleCycles() { } /** * Create a simple cycle finder for the specified graph. * * @param graph - the DirectedGraph in which to find cycles. * * @throws IllegalArgumentException if the graph argument is * null. */ public TiernanSimpleCycles(Graph graph) { this.graph = GraphTests.requireDirected(graph, "Graph must be directed"); } /** * Get the graph * * @return graph */ public Graph getGraph() { return graph; } /** * Set the graph * * @param graph graph */ public void setGraph(Graph graph) { this.graph = GraphTests.requireDirected(graph, "Graph must be directed"); } /** * {@inheritDoc} */ @Override public List> findSimpleCycles() { if (graph == null) { throw new IllegalArgumentException("Null graph."); } Map indices = new HashMap<>(); List path = new ArrayList<>(); Set pathSet = new HashSet<>(); Map> blocked = new HashMap<>(); List> cycles = new LinkedList<>(); int index = 0; for (V v : graph.vertexSet()) { blocked.put(v, new HashSet<>()); indices.put(v, index++); } Iterator vertexIterator = graph.vertexSet().iterator(); if (!vertexIterator.hasNext()) { return cycles; } V startOfPath; V endOfPath; V temp; int endIndex; boolean extensionFound; endOfPath = vertexIterator.next(); path.add(endOfPath); pathSet.add(endOfPath); // A mostly straightforward implementation // of the algorithm. Except that there is // no real need for the state machine from // the original paper. while (true) { // path extension do { extensionFound = false; for (E e : graph.outgoingEdgesOf(endOfPath)) { V n = graph.getEdgeTarget(e); int cmp = indices.get(n).compareTo(indices.get(path.get(0))); if ((cmp > 0) && !pathSet.contains(n) && !blocked.get(endOfPath).contains(n)) { path.add(n); pathSet.add(n); endOfPath = n; extensionFound = true; break; } } } while (extensionFound); // circuit confirmation startOfPath = path.get(0); if (graph.containsEdge(endOfPath, startOfPath)) { List cycle = new ArrayList<>(path); cycles.add(cycle); } // vertex closure if (path.size() > 1) { blocked.get(endOfPath).clear(); endIndex = path.size() - 1; path.remove(endIndex); pathSet.remove(endOfPath); --endIndex; temp = endOfPath; endOfPath = path.get(endIndex); blocked.get(endOfPath).add(temp); continue; } // advance initial index if (vertexIterator.hasNext()) { path.clear(); pathSet.clear(); endOfPath = vertexIterator.next(); path.add(endOfPath); pathSet.add(endOfPath); for (V vt : blocked.keySet()) { blocked.get(vt).clear(); } continue; } // terminate break; } return cycles; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/WeakChordalityInspector.java000066400000000000000000001040311402514743400333720ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; /** * Tests whether a graph is weakly * chordal. Weakly chordal graphs are also known as weakly triangulated graphs. Triangulated in * the context of chordality has a different meaning than triangulated in the context of planarity, * where it refers to a maximal planar graph, see: * * http://mathworld.wolfram.com/TriangulatedGraph.html *

* The following definitions of are equivalent: *

    *
  1. A graph is weakly chordal (weakly triangulated) if neither it nor its complement contains a * chordless cycles with five or more * vertices.
  2. *
  3. A 2-pair in a graph is a pair of non-adjacent vertices $x$, $y$ such that every chordless * path has exactly two edges. A graph is weakly chordal if every connected * induced subgraph $H$ that is not a * complete graph, contains a 2-pair.
  4. *
* Chordal and weakly chordal graphs are * perfect.
* For more details, refer to: Hayward, R.B. Weakly triangulated graphs, Journal of Combinatorial * Theory, Series B, vol 39, Issue 3, pp 200-208, 1985. *

* The implementation in this class is based on: Lars Severin Skeide (2002) * Recognizing weakly chordal graphs. * Candidate Scientist Thesis in Informatics. Department of Informatics, University of Bergen, * Norway. The terminology used in this implementation is consistent with the one used in this * thesis. *

* Both the runtime complexity and space complexity of the algorithm implemented in this class is * $\mathcal{O}(|E|^2)$.
* The inspected {@code graph} is specified at the construction time and cannot be modified. When * the graph is modified externally, the behavior of the {@code WeakChordalityInspector} is * undefined. *

* In the case the inspected graph in not weakly chordal, this inspector provides a certificate in * the form of some hole or * anti-hole. The running time of * finding a hole is $\mathcal{O}(|V| + |E|)$ and of finding an anti-hole - $\mathcal{O}(|E|^2)$ in * the worst case. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov */ public class WeakChordalityInspector { /** * Vertex number */ private final int n; /** * Edge number */ private final int m; /** * The inspected graph */ private Graph graph; /** * Bijective mapping of vertices onto $\left[0,n-1\right]$ */ private Map vertices; /** * Inverse of the bijective mapping of vertices onto $\left[0,n-1\right]$ */ private List indices; /** * Contains true if the graph is weakly chordal, otherwise false. Is null before the first call * to the {@link WeakChordalityInspector#isWeaklyChordal()}. */ private Boolean weaklyChordal = null; /** * Contains a hole or an anti-hole of the graph, if it isn't weakly chordal */ private GraphPath certificate; /** * Creates a weak chordality inspector for the {@code graph} * * @param graph the inspected {@code graph} */ public WeakChordalityInspector(Graph graph) { this.graph = Objects.requireNonNull(graph); if (graph.getType().isDirected()) { this.graph = new AsUndirectedGraph<>(graph); } n = graph.vertexSet().size(); m = graph.edgeSet().size(); initMappings(); } /** * Initializes the mappings of the vertices */ private void initMappings() { VertexToIntegerMapping mapping = new VertexToIntegerMapping<>(graph.vertexSet()); vertices = mapping.getVertexMap(); indices = mapping.getIndexList(); } /** * Check whether the inspected {@code graph} is weakly chordal. Note: this value is computed * lazily. * * @return true, if the inspected {@code graph} is weakly chordal, otherwise false. */ public boolean isWeaklyChordal() { return lazyComputeWeakChordality(); } /** * Computes and returns the certificate in the form of a hole or anti-hole in the inspected * {@code graph}. Returns null if the inspected graph is weakly chordal. Note: certificate is * computed lazily. * * @return a hole or * anti-hole in the * inspected {@code graph}, null if the {@code graph} is weakly chordal */ public GraphPath getCertificate() { lazyComputeWeakChordality(); return certificate; } /** * Lazily tests the weak chordality of the {@code graph} and returns the computed value. * * @return true, if the inspected {@code graph} is weakly chordal, otherwise false. */ private boolean lazyComputeWeakChordality() { if (weaklyChordal == null) { List>, E>> globalSeparatorList = computeGlobalSeparatorList(); if (globalSeparatorList.size() > 0) { Pair pair; sortSeparatorsList(globalSeparatorList); // Iterating over separators. Computing coconnected components only for distinct // separators int separatorsNum = 1; List> original = globalSeparatorList.get(0).getFirst(); List> coConnectedComponents = computeCoConnectedComponents(graph, original); for (Pair>, E> separator : globalSeparatorList) { if (unequalSeparators(original, separator.getFirst())) { original = separator.getFirst(); ++separatorsNum; if (n + m < separatorsNum) { return weaklyChordal = false; } else { coConnectedComponents = computeCoConnectedComponents(graph, original); } } if ((pair = checkLabels(coConnectedComponents, separator.getFirst())) != null) { // Found a pair of vertices which has labels 1 and 2. This means the graph // isn't weakly chordal. Start detecting a hole E holeFormer = separator.getSecond(); V source = graph.getEdgeSource(holeFormer); V target = graph.getEdgeTarget(holeFormer); V sourceInSeparator = indices.get(pair.getFirst()); V targetInSeparator = indices.get(pair.getSecond()); if (!graph.containsEdge(source, sourceInSeparator)) { V t = sourceInSeparator; sourceInSeparator = targetInSeparator; targetInSeparator = t; } if (graph.containsEdge(sourceInSeparator, targetInSeparator)) { findAntiHole(source, targetInSeparator); } else { findHole(sourceInSeparator, source, target, targetInSeparator); } return weaklyChordal = false; } } return weaklyChordal = true; } else { return weaklyChordal = true; } } return weaklyChordal; } /** * Computes the global separator list of the {@code graph}. More precisely, for every edge $e$ * in the $G = (V, E)$ computes list of minimal separators $S_e$ in the neighborhood of $e$ and * then concatenates these lists. Note: the result may contain duplicates * * @return the list of minimal separators of every edge $e$ in the inspected graph */ private List>, E>> computeGlobalSeparatorList() { List>, E>> globalSeparatorList = new ArrayList<>(); for (E edge : graph.edgeSet()) { V source = graph.getEdgeSource(edge); V target = graph.getEdgeTarget(edge); if (source != target) { List> edgeSeparators = findSeparators(graph, edge); globalSeparatorList.addAll(reformatSeparatorList(edgeSeparators, edge)); } } return globalSeparatorList; } /** * Reformats the list o {@code separators} so that is can be conveniently used by this * inspector. More precisely, in every separator from the list of minimal separators in the * neighborhood of the {@code edge} substitutes all vertices for their indices in the numeration * defined by {@code vertices}. Pairs every separator with the {@code edge}. * * @param separators the list of minimal separators in the neighborhood of the {@code edge} * @param edge the edge, which neighborhood contains minimal separators from {@code separators} * @return the reformatted list of minimal separators */ private List>, E>> reformatSeparatorList( List> separators, E edge) { List labeling = getLabeling(edge); List>, E>> reformattedSeparators = new ArrayList<>(); List>>> vInSeparator = new ArrayList<>(n); for (int i = 0; i < n; i++) { vInSeparator.add(new ArrayList<>()); } for (Set computedSeparator : separators) { List> reformattedSeparator = new ArrayList<>(computedSeparator.size()); reformattedSeparators.add(new Pair<>(reformattedSeparator, edge)); for (V vertex : computedSeparator) { int vertexIndex = vertices.get(vertex); vInSeparator.get(vertexIndex).add(reformattedSeparator); } } for (int vertex = 0; vertex < n; vertex++) { List>> listOfSeparators = vInSeparator.get(vertex); for (List> separator : listOfSeparators) { separator.add(new Pair<>(vertex, labeling.get(vertex))); } } return reformattedSeparators; } /** * Computes the labeling of the neighborhood of {@code edge} on the vertices {@code source} and * {@code target}. Vertex from the neighborhood is labeled with "1" if it sees only * {@code source}, "2" is it sees only {@code target}, and "3" if it sees both vertices. * * @param edge the edge, whose neighborhood is to be labeled * @return the computed labeling with the respect to the rule described above */ private List getLabeling(E edge) { V source = graph.getEdgeSource(edge); V target = graph.getEdgeTarget(edge); List labeling = new ArrayList<>(Collections.nCopies(n, null)); for (E sourceEdge : graph.edgesOf(source)) { labeling.set(vertices.get(Graphs.getOppositeVertex(graph, sourceEdge, source)), 1); } for (E targetEdge : graph.edgesOf(target)) { Integer oppositeIndex = vertices.get(Graphs.getOppositeVertex(graph, targetEdge, target)); if (labeling.get(oppositeIndex) != null) { labeling.set(oppositeIndex, 3); } else { labeling.set(oppositeIndex, 2); } } return labeling; } /** * Sorts the {@code separators} using bucket sort * * @param separators the list of separators to be sorted */ private void sortSeparatorsList(List>, E>> separators) { Queue>, E>> mainQueue = new ArrayDeque<>(); int maxSeparatorLength = 0; for (Pair>, E> separator : separators) { if (separator.getFirst().size() > maxSeparatorLength) { maxSeparatorLength = separator.getFirst().size(); } mainQueue.add(separator); } separators.clear(); List>, E>>> queues = new ArrayList<>(n); for (int i = 0; i < n; i++) { queues.add(new LinkedList<>()); } for (int i = 0; i < maxSeparatorLength; i++) { while (!mainQueue.isEmpty()) { Pair>, E> separator = mainQueue.remove(); if (i >= separator.getFirst().size()) { separators.add(separator); } else { queues .get( separator .getFirst().get(separator.getFirst().size() - i - 1).getFirst()) .add(separator); } } for (Queue>, E>> queue : queues) { mainQueue.addAll(queue); queue.clear(); } } separators.addAll(mainQueue); } /** * Compares two separators for inequality. Labeling of the vertices in the separators isn't * considered * * @param sep1 first separator * @param sep2 second separator * @return true, if the separators are unequal, false otherwise */ private boolean unequalSeparators( List> sep1, List> sep2) { if (sep1.size() == sep2.size()) { for (int i = 0; i < sep1.size(); i++) { if (!sep2.get(i).getFirst().equals(sep1.get(i).getFirst())) { return true; } } return false; } else { return true; } } /** * Computes the connected components of the complement of the graph induces by the vertices of * the {@code separator}. They are also called "coconnected components". The running time is * $\mathcal{O}(|V| + |E|)$. * * @param separator the separators, whose coconnected components are computed * @return the coconected of the {@code separator} */ private List> computeCoConnectedComponents( Graph graph, List> separator) { List> coConnectedComponents = new ArrayList<>(); // Initializing buckets, labels and set of unvisited vertices. Every vertex in separator is // put // to bucket with label 0 List> bucketsByLabel = new ArrayList<>(separator.size()); for (int i = 0; i < separator.size(); i++) { bucketsByLabel.add(new HashSet<>()); } List labels = new ArrayList<>(Collections.nCopies(n, -1)); Set unvisited = CollectionUtil.newHashSetWithExpectedSize(separator.size()); separator.forEach(pair -> { unvisited.add(pair.getFirst()); labels.set(pair.getFirst(), 0); }); bucketsByLabel.set(0, unvisited); int minLabel = 0; while (unvisited.size() > 0) { List coConnectedComponent = new ArrayList<>(); do { // When minLabel = coConnectedComponent.size(), we've visited all vertices in some // coconnected component in the separator. If there exist unvisited vertices, we // start again while (!bucketsByLabel.get(minLabel).isEmpty()) { Integer vertex = bucketsByLabel.get(minLabel).iterator().next(); bucketsByLabel.get(minLabel).remove(vertex); coConnectedComponent.add(vertex); labels.set(vertex, -1); for (E edge : graph.edgesOf(indices.get(vertex))) { Integer opposite = vertices .get(Graphs.getOppositeVertex(graph, edge, indices.get(vertex))); Integer oppositeLabel = labels.get(opposite); if (oppositeLabel != -1) { putToNextBucket(opposite, oppositeLabel, bucketsByLabel, labels); } } } ++minLabel; } while (minLabel != coConnectedComponent.size()); reload(bucketsByLabel, labels, minLabel); coConnectedComponents.add(coConnectedComponent); minLabel = 0; } return coConnectedComponents; } /** * Moves the {@code vertex} to the next bucket. * * @param vertex the vertex to be moved * @param vertexLabel the label of the {@code vertex} * @param bucketsByLabel the buckets, in which vertices are stored * @param labels the labels of the vertices */ private void putToNextBucket( Integer vertex, Integer vertexLabel, List> bucketsByLabel, List labels) { bucketsByLabel.get(vertexLabel).remove(vertex); bucketsByLabel.get(vertexLabel + 1).add(vertex); labels.set(vertex, vertexLabel + 1); } /** * Moves all vertices from the bucket with label {@code minLabel} to the bucket with label 0. * Clears the bucket with label {@code minLabel}. Updates the labeling accordingly. * * @param bucketsByLabel the buckets vertices are stored in * @param labels the labels of the vertices * @param minLabel the minimum value of the non-empty bucket */ private void reload(List> bucketsByLabel, List labels, int minLabel) { if (minLabel != 0 && minLabel < bucketsByLabel.size()) { Set bucket = bucketsByLabel.get(minLabel); for (Integer vertex : bucket) { labels.set(vertex, 0); bucketsByLabel.get(0).add(vertex); } bucket.clear(); } } /** * For a given coconnected component of the {@code separator} checks whether every vertex in it * is seen by al least one vertex of the edge that is separated by the {@code separator} * * @param coConnectedComponents the set of the coconected components of the {@code separator} * @param separator minimal separator of some edge in the {@code graph} * @return true if the condition described above holds, false otherwise */ private Pair checkLabels( List> coConnectedComponents, List> separator) { List vertexLabels = new ArrayList<>(Collections.nCopies(n, null)); for (Pair vertexAndLabel : separator) { vertexLabels.set(vertexAndLabel.getFirst(), vertexAndLabel.getSecond()); } for (List coConnectedComponent : coConnectedComponents) { int label = 0; Integer labelVertex = null; for (Integer vertex : coConnectedComponent) { if (vertexLabels.get(vertex) != 3) { if (label != 0) { if (label != vertexLabels.get(vertex)) { return new Pair<>(labelVertex, vertex); } } else { label = vertexLabels.get(vertex); labelVertex = vertex; } } } } return null; } /** * Finds a hole in the inspected {@code graph}. Vertices {@code sourceInSeparator}, * {@code source}, {@code target} and {@code targetInSeparator} belong to the computes hole. * They are used to correctly find a hole in the inspected graph. * * @param sourceInSeparator vertex on the hole * @param source vertex on the hole * @param target vertex on the hole * @param targetInSeparator vertex on the hole */ private void findHole(V sourceInSeparator, V source, V target, V targetInSeparator) { this.certificate = findHole(graph, sourceInSeparator, source, target, targetInSeparator); } /** * Finds an anti-hole in the inspected {@code graph}. Vertices {@code source} and * {@code targetInSeparator} specify an edge, which belongs to the anti-hole in the complement * of the {@code graph}. Then the hole in the complement of the graph is computed in the graph's * complement in the same way a hole is computed in the {@code graph}. * * @param source endpoint of the edge that belongs to the anti-hole * @param targetInSeparator endpoint of the edge that belongs to the anti-hole */ private void findAntiHole(V source, V targetInSeparator) { // Generating the complement of the inspected graph ComplementGraphGenerator generator = new ComplementGraphGenerator<>(graph, false); Graph complement = Pseudograph. createBuilder(graph.getEdgeSupplier()).build(); generator.generateGraph(complement); E cycleFormer = complement.getEdge(source, targetInSeparator); V cycleSource = graph.getEdgeSource(cycleFormer); V cycleTarget = graph.getEdgeTarget(cycleFormer); // For edge cycleFormer we need to find the separator, which contains vertices with labels 1 // and 2 // After that the procedure of detecting a hole in the complement of the graph is identical // to finding a hole in the graph itself List> separators = findSeparators(complement, cycleFormer); List>, E>> reformatted = reformatSeparatorList(separators, cycleFormer); sortSeparatorsList(reformatted); List> original = reformatted.get(0).getFirst(); List> coConnectedComponents = computeCoConnectedComponents(complement, original); Pair pair; for (Pair>, E> separator : reformatted) { if (unequalSeparators(separator.getFirst(), original)) { original = separator.getFirst(); coConnectedComponents = computeCoConnectedComponents(complement, separator.getFirst()); } if ((pair = checkLabels(coConnectedComponents, separator.getFirst())) != null) { // Found a pair of vertices with labels 1 and 2 V cycleSourceInSeparator = indices.get(pair.getFirst()); V cycleTargetInSeparator = indices.get(pair.getSecond()); if (!complement.containsEdge(cycleSourceInSeparator, cycleSource)) { V t = cycleSourceInSeparator; cycleSourceInSeparator = cycleTargetInSeparator; cycleTargetInSeparator = t; } this.certificate = findHole( complement, cycleSourceInSeparator, cycleSource, cycleTarget, cycleTargetInSeparator); return; } } } /** * Finds a hole in the specified {@code graph}. Vertices {@code sourceInSeparator}, * {@code source}, {@code target} and {@code targetInSeparator} belong to the computes hole. * They are used to correctly find a hole in the specified {@code graph}. * * @param sourceInSeparator vertex on the hole * @param source vertex on the hole * @param target vertex on the hole * @param targetInSeparator vertex on the hole * @return the computed hole on the {@code graph} */ private GraphPath findHole( Graph graph, V sourceInSeparator, V source, V target, V targetInSeparator) { Set visited = CollectionUtil.newHashSetWithExpectedSize(graph.vertexSet().size()); visited.add(target); visited.add(source); // Obtaining some cycle, which can be minimized to a hole List cycle = findCycle(visited, graph, targetInSeparator, target, source, sourceInSeparator); cycle = minimizeCycle(graph, cycle, target, targetInSeparator, source, sourceInSeparator); return new GraphWalk<>(graph, cycle, 0); } /** * Starts the iterative depth-first traversal from {@code sourInSep} vertex. Tries to build a * cycle with the vertices, which aren't adjacent to the {@code tar} and {@code sour}. This * condition is used in order to ensure that the cycle contains a hole, to which it is later * minimized. * * @param visited defines which vertices have been visited already * @param graph the graph the search is performed on * @param tarInSep the end point of the cycle * @param tar the vertex, which can't be adjacent to the vertices in the cycle * @param sour the vertex, which can't be adjacent to the vertices in the cycle * @param sourInSep the vertex the search is started from * @return the computed cycle, which contains a hole */ private List findCycle( Set visited, Graph graph, V tarInSep, V tar, V sour, V sourInSep) { List cycle = new ArrayList<>(Arrays.asList(tarInSep, tar, sour)); Deque stack = new ArrayDeque<>(); stack.add(sourInSep); while (!stack.isEmpty()) { V currentVertex = stack.removeLast(); if (visited.add(currentVertex)) { // trying to advance cycle from current vertex // removing all vertices from the head of the cycle, which aren't adjacent to the // current vertex while (!graph.containsEdge(cycle.get(cycle.size() - 1), currentVertex)) { cycle.remove(cycle.size() - 1); } cycle.add(currentVertex); if (tarInSep.equals(currentVertex)) { // the cycle is complete break; } else { for (V neighbor : Graphs.neighborListOf(graph, currentVertex)) { // add a vertex to the stack if it hasn't been visited yet and it isn't // adjacent to the // source vertex and (it isn't adjacent to the target vertex or it is // targetInSeparator (the end of the cycle)) if (!visited.contains(neighbor) && !graph.containsEdge(sour, neighbor) && (!graph.containsEdge(tar, neighbor) || neighbor.equals(tarInSep))) { stack.add(neighbor); } } } } } return cycle; } /** * Minimizes the {@code cycle} so that it contains a hole in the {@code graph}. Vertices * {@code tar}, {@code tarInSep}, {@code sour} and {@code sourInSep} belong to the final result. * * @param graph the graph, which contains vertices from {@code cycle} * @param cycle the cycle to minimize * @param tar vertex, which should belong to the final result * @param tarInSep vertex, which should belong to the final result * @param sour vertex, which should belong to the final result * @param sourInSep vertex, which should belong to the final result * @return a list of vertices, which defines a hole in the {@code graph} */ private List minimizeCycle( Graph graph, List cycle, V tar, V tarInSep, V sour, V sourInSep) { List minimizedCycle = new ArrayList<>(Arrays.asList(tarInSep, tar, sour)); Set forwardVertices = new HashSet<>(cycle); forwardVertices.remove(tar); forwardVertices.remove(sour); forwardVertices.remove(sourInSep); for (int i = 3; i < cycle.size() - 1;) { V current = cycle.get(i); minimizedCycle.add(current); forwardVertices.remove(current); // Computing a set of vertices, which are adjacent to current and have greater index // in the cycle than current Set currentForward = new HashSet<>(); for (V neighbor : Graphs.neighborListOf(graph, current)) { if (forwardVertices.contains(neighbor)) { currentForward.add(neighbor); } } // Jump to the forward vertex with the greatest index. Therefore we ensure, that // the resulting cycle doesn't contain chords for (V forwardVertex : currentForward) { if (forwardVertices.contains(forwardVertex)) { do { forwardVertices.remove(cycle.get(i)); i++; } while (i < cycle.size() && !cycle.get(i).equals(forwardVertex)); } } } minimizedCycle.add(tarInSep); return minimizedCycle; } /** * Computes and returns all minimal separators in the neighborhood of the {@code edge} in the * {@code graph}. The result may contain duplicate separators. * * @param graph the graph to search minimal separators in * @param edge the edge, whose neighborhood is being explored * @return the list of all minimal separators in the neighborhood of the {@code edge}. The * resulted list may contain duplicates. */ private List> findSeparators(Graph graph, E edge) { List> separators = new ArrayList<>(); V source = graph.getEdgeSource(edge); V target = graph.getEdgeTarget(edge); Set neighborhood = neighborhoodSetOf(graph, edge); Map dfsMap = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); // 0 - unvisited (white), 1 - neighbor of the edge (red), 2 - visited (black) for (V vertex : graph.vertexSet()) { if (neighborhood.contains(vertex)) { dfsMap.put(vertex, (byte) 1); } else { dfsMap.put(vertex, (byte) 0); } } dfsMap.put(source, (byte) 2); dfsMap.put(target, (byte) 2); for (V vertex : graph.vertexSet()) { if (dfsMap.get(vertex) == 0) { // possible to find one more separator Set separator = getSeparator(graph, vertex, dfsMap); if (!separator.isEmpty()) { separators.add(separator); } } } return separators; } /** * Performs iterative depth-first search starting from the {@code startVertex} in the * {@code graph}. Adds every encountered red vertex to the resulting separator. Doesn't process * red vertices. Marks all white vertices with black color. * * @param graph the graph dfs is performed on * @param startVertex the vertex to start depth-first traversal from * @param dfsMap the depth-first vertex labeling * @return the computed separator, which consists of all encountered red vertices */ private Set getSeparator(Graph graph, V startVertex, Map dfsMap) { Deque stack = new ArrayDeque<>(); Set separator = new HashSet<>(); stack.add(startVertex); while (!stack.isEmpty()) { V currentVertex = stack.removeLast(); if (dfsMap.get(currentVertex) == 0) { dfsMap.put(currentVertex, (byte) 2); for (E edge : graph.edgesOf(currentVertex)) { V opposite = Graphs.getOppositeVertex(graph, edge, currentVertex); if (dfsMap.get(opposite) == 0) { stack.add(opposite); } else if (dfsMap.get(opposite) == 1) { separator.add(opposite); // found red vertex, which belongs to the separator } } } } return separator; } /** * Returns a set of vertices that are neighbors of the source of the specified edge or of the * target of specified edge. The endpoints of the specified edge aren't included in the result. * * @param g the graph to look for neighbors in * @param edge the edge to get the neighbors of * @return a set of vertices that are neighbors of at least one endpoint of the specified edge. * The endpoints of the specified edge aren't included in the result */ private Set neighborhoodSetOf(Graph g, E edge) { Set neighborhood = new HashSet<>(); V source = g.getEdgeSource(edge); V target = g.getEdgeTarget(edge); for (E e : g.edgesOf(source)) { neighborhood.add(Graphs.getOppositeVertex(g, e, source)); } for (E e : g.edgesOf(target)) { neighborhood.add(Graphs.getOppositeVertex(g, e, target)); } neighborhood.remove(source); neighborhood.remove(target); return neighborhood; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/cycle/package-info.java000066400000000000000000000072141402514743400311220ustar00rootroot00000000000000/** * Algorithms related to graph cycles. * *

Algorithms for enumeration of simple cycles in graphs

* * Contains four different algorithms for the enumeration of simple cycles in directed graphs. The * worst case time complexity of the algorithms is: *
    *
  1. Szwarcfiter and Lauer - $O(V + EC)$
  2. *
  3. Tarjan - $O(VEC)$
  4. *
  5. Johnson - $O(((V+E)C)$
  6. *
  7. Tiernan - $O(V.const^V)$
  8. *
* where $V$ is the number of vertices, $E$ is the number of edges and $C$ is the number of the * simple cycles in the graph. All the above implementations work correctly with loops but not with * multiple edges. Space complexity for all cases is $O(V+E)$. * *

* The worst case performance is achieved for graphs with special structure, so on practical * workloads an algorithm with higher worst case complexity may outperform an algorithm with lower * worst case complexity. Note also that "administrative costs" of algorithms with better worst case * performance are higher. Also higher is their memory cost. * *

* See the following papers for details of the above algorithms: *

    *
  1. J.C.Tiernan An Efficient Search Algorithm Find the Elementary Circuits of a Graph., * Communications of the ACM, V13, 12, (1970), pp. 722 - 726.
  2. *
  3. R.Tarjan, Depth-first search and linear graph algorithms., SIAM J. Comput. 1 (1972), pp. * 146-160.
  4. *
  5. R. Tarjan, Enumeration of the elementary circuits of a directed graph, SIAM J. Comput., 2 * (1973), pp. 211-216.
  6. *
  7. D. B. Johnson, Finding all the elementary circuits of a directed graph, SIAM J. Comput., 4 * (1975), pp. 77-84.
  8. *
  9. J. L. Szwarcfiter and P. E. Lauer, Finding the elementary cycles of a directed graph in O(n + * m) per cycle, Technical Report Series, #60, May 1974, Univ. of Newcastle upon Tyne, Newcastle * upon Tyne, England.
  10. *
  11. L. G. Bezem and J. van Leeuwen, Enumeration in graphs., Technical report RUU-CS-87-7, * University of Utrecht, The Netherlands, 1987.
  12. *
* *

Algorithms for the computation of undirected cycle basis

* *
    *
  1. A variant of Paton's algorithm {@link org.jgrapht.alg.cycle.PatonCycleBase}, performing a BFS * using a stack which returns a weakly fundamental cycle basis. Supports graphs with self-loops but * not multiple (parallel) edges.
  2. *
  3. A variant of Paton's algorithm {@link org.jgrapht.alg.cycle.StackBFSFundamentalCycleBasis}, * which returns a fundamental cycle basis. This is a more generic implementation which supports * self-loops and multiple (parallel) edges.
  4. *
  5. An algorithm {@link org.jgrapht.alg.cycle.QueueBFSFundamentalCycleBasis} which constructs a * fundamental cycle basis using a straightforward implementation of BFS using a queue. The * implementation supports graphs with self-loops and multiple (parallel) edges.
  6. *
* * The worst case time complexity of all above algorithms is $O(|V|^3)$ since the length of the * cycle basis can be that large. * *

* See the following papers for details of the above algorithms: *

    *
  1. K. Paton, An algorithm for finding a fundamental set of cycles for an undirected linear * graph, Comm. ACM 12 (1969), pp. 514-518.
  2. *
  3. Narsingh Deo, G. Prabhu, and M. S. Krishnamoorthy. Algorithms for Generating Fundamental * Cycles in a Graph. ACM Trans. Math. Softw. 8, 1, 26-42, 1982.
  4. *
* *

Algorithms for the computation of Eulerian cycles

* *
    *
  1. An implementation of {@link org.jgrapht.alg.cycle.HierholzerEulerianCycle Hierholzer}'s * algorithm for finding an Eulerian cycle in Eulerian graphs.
  2. *
*/ package org.jgrapht.alg.cycle; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/000077500000000000000000000000001402514743400275045ustar00rootroot00000000000000DulmageMendelsohnDecomposition.java000066400000000000000000000274341402514743400364320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/* * (C) Copyright 2018-2021, by CAE Tech Limited and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.decomposition; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.MatchingAlgorithm.*; import org.jgrapht.alg.matching.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.traverse.*; import java.util.*; /** *

* This class computes a Dulmage-Mendelsohn Decomposition of a bipartite graph. A Dulmage–Mendelsohn * decomposition is a partition of the vertices of a bipartite graph into subsets, with the property * that two adjacent vertices belong to the same subset if and only if they are paired with each * other in a perfect matching of the graph. This particular implementation is capable of computing * both a coarse and a fine Dulmage-Mendelsohn Decomposition. *

* *

* The Dulmage-Mendelsohn Decomposition is based on a maximum-matching of the graph $G$. This * implementation uses the Hopcroft-Karp maximum matching algorithm by default. *

* *

* A coarse Dulmage-Mendelsohn Decomposition is a partitioning into three subsets. Where $D$ is the * set of vertices in G that are not matched in the maximum matching of $G$, these subsets are: *

*
    *
  • The vertices in $D \cap U$ and their neighbors
  • *
  • The vertices in $D \cap V$ and their neighbors
  • *
  • The remaining vertices
  • *
* *

* A fine Dulmage-Mendelsohn Decomposition further partitions the remaining vertices into * strongly-connected sets. This implementation uses Kosaraju's algorithm for the * strong-connectivity analysis. *

* *

* The Dulmage-Mendelsohn Decomposition was introduced in:
* Dulmage, A.L., Mendelsohn, N.S. Coverings of bipartitegraphs, Canadian J. Math., 10, 517-534, * 1958. *

* *

* The implementation of this class is based on:
* Bunus P., Fritzson P., Methods for Structural Analysis and Debugging of Modelica Models, 2nd * International Modelica Conference 2002 *

* *

* The runtime complexity of this class is $O(V + E)$. *

* * @author Peter Harman * @param Vertex type * @param Edge type */ public class DulmageMendelsohnDecomposition { private final Graph graph; private final Set partition1; private final Set partition2; /** * Construct the algorithm for a given bipartite graph $G=(V_1,V_2,E)$ and it's partitions $V_1$ * and $V_2$, where $V_1\cap V_2=\emptyset$. * * @param graph bipartite graph * @param partition1 the first partition, $V_1$, of vertices in the bipartite graph * @param partition2 the second partition, $V_2$, of vertices in the bipartite graph */ public DulmageMendelsohnDecomposition(Graph graph, Set partition1, Set partition2) { this.graph = Objects.requireNonNull(graph); this.partition1 = partition1; this.partition2 = partition2; assert GraphTests.isBipartite(graph); } /** * Perform the decomposition, using the Hopcroft-Karp maximum-cardinality matching algorithm to * perform the matching. * * @param fine true if the fine decomposition is required, false if the coarse decomposition is * required * @return the {@link Decomposition} */ public Decomposition getDecomposition(boolean fine) { // Get a maximum matching to the bipartite problem HopcroftKarpMaximumCardinalityBipartiteMatching hopkarp = new HopcroftKarpMaximumCardinalityBipartiteMatching<>(graph, partition1, partition2); Matching matching = hopkarp.getMatching(); return decompose(matching, fine); } /** * Perform the decomposition, using a pre-calculated bipartite matching * * @param matching the matching from a {@link MatchingAlgorithm} * @param fine true if the fine decomposition is required * @return the {@link Decomposition} */ public Decomposition decompose(Matching matching, boolean fine) { // Determine the unmatched vertices from both partitions Set unmatched1 = new HashSet<>(); Set unmatched2 = new HashSet<>(); getUnmatched(matching, unmatched1, unmatched2); // Assemble a directed graph Graph dg = asDirectedGraph(matching); // Find the non-square subgraph dominated by partition1 Set subset1 = new HashSet<>(); unmatched1.stream().map((v) -> { subset1.add(v); return v; }).map((v) -> new DepthFirstIterator<>(dg, v)).forEachOrdered((it) -> { while (it.hasNext()) { subset1.add(it.next()); } }); // Find the non-square subgraph dominated by partition2 Graph gd = new EdgeReversedGraph<>(dg); Set subset2 = new HashSet<>(); unmatched2.stream().map((v) -> { subset2.add(v); return v; }).map((v) -> new DepthFirstIterator<>(gd, v)).forEachOrdered((it) -> { while (it.hasNext()) { subset2.add(it.next()); } }); // Find the square subgraph Set subset3 = new HashSet<>(); subset3.addAll(partition1); subset3.addAll(partition2); subset3.removeAll(subset1); subset3.removeAll(subset2); if (fine) { List> out = new ArrayList<>(); // Build a directed graph between edges of the matching in subset3 Graph graphH = asDirectedEdgeGraph(matching, subset3); // Perform strongly-connected-components on the graph StrongConnectivityAlgorithm sci = new KosarajuStrongConnectivityInspector<>(graphH); // Divide into sets of vertices for (Set edgeSet : sci.stronglyConnectedSets()) { Set vertexSet = new HashSet<>(); edgeSet.stream().map((edge) -> { vertexSet.add(graph.getEdgeSource(edge)); return edge; }).forEachOrdered((edge) -> { vertexSet.add(graph.getEdgeTarget(edge)); }); out.add(vertexSet); } return new Decomposition<>(subset1, subset2, out); } else { return new Decomposition<>(subset1, subset2, Collections.singletonList(subset3)); } } /** * The output of a decomposition operation * * @param vertex type * @param edge type */ public static class Decomposition { private final Set subset1; private final Set subset2; private final List> subset3; Decomposition(Set subset1, Set subset2, List> subset3) { this.subset1 = subset1; this.subset2 = subset2; this.subset3 = subset3; } /** * Gets the subset dominated by partition1. Where $D$ is the set of vertices in $G$ that are * not matched in the maximum matching of $G$, this set contains members of the first * partition and vertices from the second partition that neighbour them. * * @return The vertices in $D \cap V_1$ and their neighbours */ public Set getPartition1DominatedSet() { return subset1; } /** * Gets the subset dominated by partition2. Where $D$ is the set of vertices in $G$ that are * not matched in the maximum matching of $G$, this set contains members of the second * partition and vertices from the first partition that neighbour them. * * @return The vertices in $D \cap V_2$ and their neighbours */ public Set getPartition2DominatedSet() { return subset2; } /** * Gets the remaining subset, or subsets in the fine decomposition. This set contains * vertices that are matched in the maximum matching of the graph $G$. If the fine * decomposition was used, this will be multiple sets, each a strongly-connected-component * of the matched subset of $G$. * * @return List of Sets of vertices in the subsets */ public List> getPerfectMatchedSets() { return subset3; } } private void getUnmatched(Matching matching, Set unmatched1, Set unmatched2) { unmatched1.addAll(partition1); unmatched2.addAll(partition2); matching.forEach((e) -> { V source = graph.getEdgeSource(e); V target = graph.getEdgeTarget(e); if (partition1.contains(source)) { unmatched1.remove(source); unmatched2.remove(target); } else { unmatched2.remove(source); unmatched1.remove(target); } }); } private Graph asDirectedGraph(Matching matching) { GraphBuilder> builder = DefaultDirectedGraph.createBuilder(DefaultEdge.class); graph.vertexSet().forEach((v) -> { builder.addVertex(v); }); graph.edgeSet().forEach((e) -> { V v1 = graph.getEdgeSource(e); V v2 = graph.getEdgeTarget(e); if (partition1.contains(v1)) { builder.addEdge(v1, v2); if (matching.getEdges().contains(e)) { builder.addEdge(v2, v1); } } else { builder.addEdge(v2, v1); if (matching.getEdges().contains(e)) { builder.addEdge(v1, v2); } } }); return builder.build(); } private Graph asDirectedEdgeGraph(Matching matching, Set subset) { GraphBuilder> graphHBuilder = DefaultDirectedGraph.createBuilder(DefaultEdge.class); for (E e : graph.edgeSet()) { V v1 = graph.getEdgeSource(e); V v2 = graph.getEdgeTarget(e); if (subset.contains(v1) && subset.contains(v2)) { if (matching.getEdges().contains(e)) { graphHBuilder.addVertex(e); } else { E e1 = null; E e2 = null; for (E other : graph.edgesOf(v1)) { if (matching.getEdges().contains(other)) { e1 = other; graphHBuilder.addVertex(e1); break; } } for (E other : graph.edgesOf(v2)) { if (matching.getEdges().contains(other)) { e2 = other; graphHBuilder.addVertex(e2); break; } } graphHBuilder.addEdge(e1, e2); } } } return graphHBuilder.build(); } } HeavyPathDecomposition.java000066400000000000000000000432511402514743400347230ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.decomposition; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.util.*; import java.util.*; /** * Algorithm for computing the heavy path decomposition of a rooted tree/forest. * *

* Heavy path decomposition is a technique for decomposing a rooted tree/forest into a set of * disjoint paths. * *

* The techniques was first introduced in Sleator, D. D.; Tarjan, R. E. (1983). "A Data Structure * for Dynamic Trees". Proceedings of the thirteenth annual ACM symposium on Theory of computing - * STOC '81 doi:10.1145/800076.802464 *

* *

* In a heavy path decomposition, the edges set is partitioned into two sets, a set of heavy edges * and a set of light ones according to the relative number of nodes in the vertex's subtree. * * We define the size of a vertex v in the forest, denoted by size(v), to be the number of * descendants of v, including v itself. We define a tree edge (v,parent(v)) to be heavy if * $2*size(v)$ > $size(parent(v))$ and light, otherwise. * * The set of heavy edges form the edges of the decomposition. * *

* A benefit of this decomposition is that on any root-to-leaf path of a tree with n nodes, there * can be at most $log_2(n)$ light edges. * *

* This implementation runs in $O(|V|)$ time and requires $O(|V|)$ extra memory, where $|V|$ is the * number of vertices in the tree/forest. * *

* Note: If an edge is not reachable from any of the roots provided, then that edge is neither light * nor heavy. *

* * @author Alexandru Valeanu * * @param the graph vertex type * @param the graph edge type */ public class HeavyPathDecomposition implements TreeToPathDecompositionAlgorithm { private final Graph graph; private final Set roots; private Map vertexMap; private List indexList; private int[] sizeSubtree, parent, depth, component; private int[] path, lengthPath, positionInPath, firstNodeInPath; private int numberOfPaths; private List> paths; private Set heavyEdges; private Set lightEdges; /** * Create an instance with a reference to the tree that we will decompose and to the root of the * tree. * * Note: The constructor will NOT check if the input graph is a valid tree. * * @param tree the input tree * @param root the root of the tree */ public HeavyPathDecomposition(Graph tree, V root) { this(tree, Collections.singleton(Objects.requireNonNull(root, "root cannot be null"))); } /** * Create an instance with a reference to the forest that we will decompose and to the sets of * roots of the forest (one root per tree). * * Note: If two roots appear in the same tree, an error will be thrown. Note: The constructor * will NOT check if the input graph is a valid forest. * * @param forest the input forest * @param roots the set of roots of the graph */ public HeavyPathDecomposition(Graph forest, Set roots) { this.graph = Objects.requireNonNull(forest, "input tree/forrest cannot be null"); this.roots = Objects.requireNonNull(roots, "set of roots cannot be null"); decompose(); } private void allocateArrays() { final int n = graph.vertexSet().size(); sizeSubtree = new int[n]; parent = new int[n]; depth = new int[n]; component = new int[n]; path = new int[n]; lengthPath = new int[n]; positionInPath = new int[n]; heavyEdges = new HashSet<>(); lightEdges = new HashSet<>(); } private void normalizeGraph() { /* * Normalize the graph by mapping each vertex to an integer. */ VertexToIntegerMapping vertexToIntegerMapping = Graphs.getVertexToIntegerMapping(graph); vertexMap = vertexToIntegerMapping.getVertexMap(); indexList = vertexToIntegerMapping.getIndexList(); } /** * An iterative dfs implementation for computing the paths. * * For each node u we have to execute two sequences of operations: 1: before the 'recursive' * call (the then part of the if-statement) 2: after the 'recursive' call (the else part of the * if-statement) * * @param u the (normalized) vertex * @param c the component number to be used for u's tree */ private void dfsIterative(int u, int c) { // Set of vertices for which the the part of the if has been performed // (In other words: u ∈ explored iff dfs(u, c') has been called as some point) Set explored = new HashSet<>(); ArrayDeque stack = new ArrayDeque<>(); stack.push(u); while (!stack.isEmpty()) { u = stack.poll(); if (!explored.contains(u)) { explored.add(u); // simulate the return from recursion (the else part for u) stack.push(u); component[u] = c; sizeSubtree[u] = 1; V vertexU = indexList.get(u); for (E edge : graph.edgesOf(vertexU)) { int child = vertexMap.get(Graphs.getOppositeVertex(graph, edge, vertexU)); // Check if child has not been explored (i.e. dfs(child, c) has not been called) if (!explored.contains(child)) { parent[child] = u; depth[child] = depth[u] + 1; stack.push(child); } } } else { // For u compute pathChild. If it exists then u becomes part of pathChild's path. // If not then start a new path with u. // // pathChild = v ∈ children(u) such that sizeSubtree(v) = max{sizeSubtree(v') | v' ∈ // children(u)} int pathChild = -1; E pathEdge = null; V vertexU = indexList.get(u); for (E edge : graph.edgesOf(vertexU)) { int child = vertexMap.get(Graphs.getOppositeVertex(graph, edge, vertexU)); // Check if child is a descendant of u and not its parent if (child != parent[u]) { sizeSubtree[u] += sizeSubtree[child]; if (pathChild == -1 || sizeSubtree[pathChild] < sizeSubtree[child]) { pathChild = child; pathEdge = edge; } // assume all edges are light lightEdges.add(edge); } } if (pathChild == -1) path[u] = numberOfPaths++; else { path[u] = path[pathChild]; // Is pathEdge=(pathChild, u) a heavy edge? if (2 * sizeSubtree[pathChild] > sizeSubtree[u]) { heavyEdges.add(pathEdge); // assumption was wrong => remove pathEdge from light-edges set lightEdges.remove(pathEdge); } } // Compute the positions in reverse order: the first node in the path is the first // one that was // added (the order will be reversed in decompose). positionInPath[u] = lengthPath[path[u]]++; } } } private void decompose() { // If we already have a decomposition stop. if (path != null) return; normalizeGraph(); allocateArrays(); Arrays.fill(parent, -1); Arrays.fill(path, -1); Arrays.fill(depth, -1); Arrays.fill(component, -1); Arrays.fill(positionInPath, -1); // Iterate through all roots and compute the paths for each tree individually int numberComponent = 0; for (V root : roots) { Integer u = vertexMap.get(root); if (u == null) { throw new IllegalArgumentException("root: " + root + " not contained in graph"); } if (component[u] == -1) { dfsIterative(u, numberComponent++); } else { throw new IllegalArgumentException("multiple roots in the same tree"); } } firstNodeInPath = new int[numberOfPaths]; // Reverse the position of all vertices that are present in some path. // After this the positionInPath[u] = 0 if u is the first node in the path (i.e. the node // closest to the root) // // Also compute firstNodeInPath[i] = u such that path[u] = i and positionInPath[u] = 0 for (int i = 0; i < graph.vertexSet().size(); i++) { if (path[i] != -1) { positionInPath[i] = lengthPath[path[i]] - positionInPath[i] - 1; if (positionInPath[i] == 0) firstNodeInPath[path[i]] = i; } } // Compute the paths as unmodifiable data structures (list) List> paths = new ArrayList<>(numberOfPaths); for (int i = 0; i < numberOfPaths; i++) { List path = new ArrayList<>(lengthPath[i]); for (int j = 0; j < lengthPath[i]; j++) { path.add(null); } paths.add(path); } for (int i = 0; i < graph.vertexSet().size(); i++) { if (path[i] != -1) { paths.get(path[i]).set(positionInPath[i], indexList.get(i)); } } for (int i = 0; i < numberOfPaths; i++) { paths.set(i, Collections.unmodifiableList(paths.get(i))); } this.paths = Collections.unmodifiableList(paths); this.heavyEdges = Collections.unmodifiableSet(this.heavyEdges); } /** * Set of heavy edges. * * @return (immutable) set of heavy edges */ public Set getHeavyEdges() { return this.heavyEdges; } /** * Set of light edges. * * @return (immutable) set of light edges */ public Set getLightEdges() { return this.lightEdges; } /** * {@inheritDoc} */ @Override public PathDecomposition getPathDecomposition() { return new PathDecompositionImpl<>(graph, getHeavyEdges(), this.paths); } /** * Return the internal representation of the data. * * Note: this data representation is intended only for use by other components within JGraphT * * @return the internal state representation */ public InternalState getInternalState() { return new InternalState(); } /** * Internal representation of the data */ public class InternalState { /** * Returns the parent of vertex $v$ in the internal DFS tree/forest. If the vertex $v$ has * not been explored or it is the root of its tree, $null$ will be returned. * * @param v vertex * @return parent of vertex $v$ in the DFS tree/forest */ public V getParent(V v) { int index = vertexMap.getOrDefault(v, -1); if (index == -1 || parent[index] == -1) return null; else return indexList.get(parent[index]); } /** * Returns the depth of vertex $v$ in the internal DFS tree/forest. * *

* The depth of a vertex $v$ is defined as the number of edges traversed on the path from * the root of the DFS tree to vertex $v$. The root of each DFS tree has depth 0. * *

* If the vertex $v$ has not been explored, $-1$ will be returned. * * @param v vertex * @return depth of vertex $v$ in the DFS tree/forest */ public int getDepth(V v) { int index = vertexMap.getOrDefault(v, -1); if (index == -1) return -1; else return depth[index]; } /** * Returns the size of vertex $v$'s subtree in the internal DFS tree/forest. * *

* The size of a vertex $v$'s subtree is defined as the number of vertices in the subtree * rooted at $v$ (including $v). * *

* If the vertex $v$ has not been explored, $0$ will be returned. * * @param v vertex * @return size of vertex $v$'s subtree in the DFS tree/forest */ public int getSizeSubtree(V v) { int index = vertexMap.getOrDefault(v, -1); if (index == -1) return 0; else return sizeSubtree[index]; } /** * Returns the component id of vertex $v$ in the internal DFS tree/forest. For two vertices * $u$ and $v$, $component[u] = component[v]$ iff $u$ and $v$ are in the same tree. * *

* The component ids are numbers between $0$ and $numberOfTrees - 1$. * *

* If the vertex $v$ has not been explored, $-1$ will be returned. * * @param v vertex * @return component id of vertex $v$ in the DFS tree/forest */ public int getComponent(V v) { int index = vertexMap.getOrDefault(v, -1); if (index == -1) return -1; else return component[index]; } /** * Return the vertex map, a mapping from vertices to unique integers. * * For each vertex $v \in V$, let $vertexMap(v) = x$ such that no two vertices share the * same x and all x's are integers between $0$ and $|V| - 1$. Let $indexList(x) = v$ be the * reverse mapping from integers to vertices. * * Note: The structure returned is immutable. * * @return the vertexMap */ public Map getVertexMap() { return Collections.unmodifiableMap(vertexMap); } /** * Return the index list, a mapping from unique integers to vertices. * * For each vertex $v \in V$, let $vertexMap(v) = x$ such that no two vertices share the * same x and all x's are integers between $0$ and $|V| - 1$. Let $indexList(x) = v$ be the * reverse mapping from integers to vertices. * * Note: The structure returned is immutable. * * @return the indexList */ public List getIndexList() { return Collections.unmodifiableList(indexList); } /** * Return the internal depth array. For each vertex $v \in V$, * $depthArray[normalizeVertex(v)] = getDepth(v)$ * * @return internal depth array */ public int[] getDepthArray() { return depth; } /** * Return the internal sizeSubtree array. For each vertex $v$, * $sizeSubtreeArray[normalizeVertex(v)] = getSizeSubtree(v)$ * * @return internal sizeSubtree array */ public int[] getSizeSubtreeArray() { return sizeSubtree; } /** * Return the internal component array. For each vertex $v$, * $componentArray[normalizeVertex(v)] = getComponent(v)$ * * @return internal component array */ public int[] getComponentArray() { return component; } /** * Return the internal path array. For each vertex $v$, $pathArray[normalizeVertex(v)] = i$ * iff $v$ appears on path $i$ or $-1$ if $v$ doesn't belong to any path. * * @return internal path array */ public int[] getPathArray() { return path; } /** * Return the internal positionInPath array. For each vertex $v$, * $positionInPathArray[normalizeVertex(v)] = k$ iff $v$ appears as the $k-th$ vertex on its * path (0-indexed) or $-1$ if $v$ doesn't belong to any path. * * @return internal positionInPath array */ public int[] getPositionInPathArray() { return positionInPath; } /** * Return the internal firstNodeInPath array. For each path $i$, $firstNodeInPath[i] = * normalizeVertex(v)$ iff $v$ appears as the first vertex on the path. * * @return internal firstNodeInPath array */ public int[] getFirstNodeInPathArray() { return firstNodeInPath; } /** * Return the internal parent array. For each vertex $v \in V$, * $parentArray[normalizeVertex(v)] = normalizeVertex(u)$ if $getParent(v) = u$ or $-1$ if * $getParent(v) = null$. * * @return internal parent array */ public int[] getParentArray() { return parent; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/decomposition/package-info.java000066400000000000000000000001331402514743400326700ustar00rootroot00000000000000/** * Algorithms for computing decompositions. */ package org.jgrapht.alg.decomposition; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/000077500000000000000000000000001402514743400274625ustar00rootroot00000000000000GoldbergMaximumDensitySubgraphAlgorithm.java000066400000000000000000000110371402514743400402360ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/* * (C) Copyright 2018-2021, by Andre Immig and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.densesubgraph; import org.jgrapht.*; import org.jgrapht.alg.flow.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.function.*; /** * This class computes a maximum density subgraph based on the algorithm described by Andrew * Vladislav Goldberg in * Finding Maximum Density Subgraphs, 1984, University of Berkley.
* The basic concept is to construct a network that can be used to compute the maximum density * subgraph using a binary search approach. See {@link GoldbergMaximumDensitySubgraphAlgorithmBase} * for further details *

* This variant of the algorithm assumes the density of a positive real-weighted graph G=(V,E) to be * defined as \[\frac{\sum\limits_{e \in E} w(e)}{\left|{V}\right|}\] and sets the weights of the * network from {@link GoldbergMaximumDensitySubgraphAlgorithmBase} as proposed in the above paper. * For this case the weights of the network must be chosen to be: \[c_{ij}=w(ij)\,\forall \{i,j\}\in * E\] \[c_{it}=m+2g-d_i\,\forall i \in V\] \[c_{si}=m\,\forall i \in V\] where $m=\left|{E}\right|$ * and $d_i$ is the degree of vertex $i$.
* All the math to prove the correctness of these weights is the same as in * {@link GoldbergMaximumDensitySubgraphAlgorithmBase}.
*

* Because the density is per definition guaranteed to be rational, the distance of 2 possible * solutions for the maximum density can't be smaller than $\frac{1}{W(W-1)}$. This means shrinking * the binary search interval to this size, the correct solution is found. The runtime can in this * case be given by $O(M(n,n+m)\log{W})$, where $M(n,m)$ is the runtime of the internally used * {@link MinimumSTCutAlgorithm} and $W$ is the sum of all weights from $G$. *

* * @param Type of vertices * @param Type of edges * * @author Andre Immig */ public class GoldbergMaximumDensitySubgraphAlgorithm extends GoldbergMaximumDensitySubgraphAlgorithmBase { /** * Constructor * * @param algFactory factory to construct the algorithm to use * @param graph input for computation * @param s additional source vertex * @param t additional target vertex * @param epsilon to use for internal computation */ public GoldbergMaximumDensitySubgraphAlgorithm( Graph graph, V s, V t, double epsilon, Function, MinimumSTCutAlgorithm> algFactory) { super(graph, s, t, false, epsilon, algFactory); } /** * Convenience constructor that uses PushRelabel as default MinimumSTCutAlgorithm * * @param graph input for computation * @param s additional source vertex * @param t additional target vertex * @param epsilon to use for internal computation */ public GoldbergMaximumDensitySubgraphAlgorithm(Graph graph, V s, V t, double epsilon) { this(graph, s, t, epsilon, PushRelabelMFImpl::new); } /** * Getter for network weights of edges su for u in V * * @param v of V * @return weight of the edge */ protected double getEdgeWeightFromSourceToVertex(V v) { return this.graph.edgeSet().size(); } /** * Getter for network weights of edges ut for u in V * * @param v of V * @return weight of the edge */ protected double getEdgeWeightFromVertexToSink(V v) { return this.graph.edgeSet().size() + 2 * guess - this.graph.outgoingEdgesOf(v).stream().mapToDouble(this.graph::getEdgeWeight).sum(); } @Override protected double computeDensityNumerator(Graph g) { return g.edgeSet().stream().mapToDouble(g::getEdgeWeight).sum(); } @Override protected double computeDensityDenominator(Graph g) { return g.vertexSet().size(); } } GoldbergMaximumDensitySubgraphAlgorithmBase.java000066400000000000000000000321571402514743400410370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/* * (C) Copyright 2018-2021, by Andre Immig and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.densesubgraph; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * This abstract base class computes a maximum density subgraph based on the algorithm described by * Andrew Vladislav Goldberg in * Finding Maximum * Density Subgraphs, 1984, University of Berkley. Each subclass decides which concrete * definition of density is used by implementing {@link #getEdgeWeightFromSourceToVertex(Object)} * and {@link #getEdgeWeightFromVertexToSink(Object)} as proposed in the paper. After the * computation the density is computed using {@link MaximumDensitySubgraphAlgorithm#getDensity()}. *
* The basic concept is to construct a network that can be used to compute the maximum density * subgraph using a binary search approach. *

* In the simplest case of an unweighted graph $G=(V,E)$ the density of $G$ can be defined to be * \[\frac{\left|{E}\right|}{\left|{V}\right|}\], where a directed graph can be considered as * undirected. Therefore it is in this case equal to half the average vertex degree. This variant is * implemented in {@link GoldbergMaximumDensitySubgraphAlgorithm}; because the following math * translates directly to other variants the full math is only fully explained once. *

* The idea of the algorithm is to construct a network based on the input graph $G=(V,E)$ and some * guess $g$ for the density. This network $N=(V_N, E_N)$ is constructed as follows: \[V_N=V\cup * {s,t}\] \[E_N=\{(i,j)| \{i,j\} \in E\} \cup \{(s,i)| i\in V\} \cup \{(i,t)| i \in V\}\]
* Additionally one defines the following weights for the network: \[c_{ij}=1 \forall \{i,j\}\in E\] * \[c_{si}=m \forall i \in V\] \[c_{it}=m+2g-d_i \forall i \in V\] where $m=\left|{E}\right|$ and * $d_i$ is the degree of vertex $i$.
* As seen later these weights depend on the definition of the density. Therefore these weights and * the following applies to the definition of density from above. Definitions suitable for other * cases in can be found in the corresponding subclasses. *

* Using this network one can show some important properties, that are essential for the algorithm * to work. The capacity of a s-t of N is given by: \[C(S,T) = m\left|{V}\right| + * 2\left|{V_1}\right|\left(g - D_1\right)\] where $V_1 \dot{\cup} V_2=V$ and $V_1 = S\setminus * \{s\}, V_2= T\setminus \{t\}$ and $D_1$ shall be the density of the induced subgraph of $V_1$ * regarding $G$. *

*

* Especially important is the capacity of minimum s-t Cut. Using the above equation, one can derive * that given a minimum s-t Cut of $N$ and the maximum density of $G$ to be $D$, then $g\geq D$ if * $V_1=\emptyset$,otherwise $g\leq D$. Moreover the induced subgraph of $V_1$ regarding G is * guaranteed to have density greater $g$, otherwise it can be used to proof that there can't exist * any subgraph of $G$ greater $g$. Based on this property one can use a binary search approach to * shrink the possible interval which contains the solution. *

*

* Because the density is per definition guaranteed to be rational, the distance of 2 possible * solutions for the maximum density can't be smaller than $\frac{1}{n(n-1)}$. This means shrinking * the binary search interval to this size, the correct solution is found. The runtime can in this * case be given by $O(M(n,n+m)\log{n}$, where $M(n,m)$ is the runtime of the internally used * {@link MinimumSTCutAlgorithm}. Especially for large networks it is advised to use a * {@link MinimumSTCutAlgorithm} whose runtime doesn't depend on the number of edges, because the * network $N$ has $O(n+m)$ edges. Preferably one should use * {@link org.jgrapht.alg.flow.PushRelabelMFImpl}, leading to a runtime of $O(n^{3}\log{n})$. *

*

* Similar to the above explanation the same argument can be applied for other definitions of * density by adapting the definitions and the network accordingly. Some generalizations can be * found in the paper. As these more general variants including edge weights are only guaranteed to * terminate for integer edge weights, instead of using the natural termination property, the * algorithm needs to be called with $\varepsilon$ in the constructor. The computation then ensures, * that the returned maximum density only differs at most $\varepsilon$ from the correct solution. * This is why subclasses of this class might have a little different runtime analysis regarding the * $\log{n}$ part. *

* * @param Type of vertices * @param Type of edges * * @author Andre Immig */ public abstract class GoldbergMaximumDensitySubgraphAlgorithmBase implements MaximumDensitySubgraphAlgorithm { private double lower, upper, epsilon; protected double guess; protected final Graph graph; private Graph densestSubgraph; private Graph currentNetwork; private Set currentVertices; private V s, t; private MinimumSTCutAlgorithm minSTCutAlg; private boolean checkWeights; /** * Constructor * * @param graph input for computation * @param s additional source vertex * @param t additional target vertex * @param checkWeights if true implementation will enforce all internal weights to be positive * @param epsilon to use for internal computation * @param algFactory function to construct the subalgorithm */ public GoldbergMaximumDensitySubgraphAlgorithmBase( Graph graph, V s, V t, boolean checkWeights, double epsilon, Function, MinimumSTCutAlgorithm> algFactory) { if (graph.containsVertex(s) || graph.containsVertex(t)) { throw new IllegalArgumentException("Source or sink vertex already in graph"); } this.s = Objects.requireNonNull(s, "Source vertex is null"); this.t = Objects.requireNonNull(t, "Sink vertex is null"); this.graph = Objects.requireNonNull(graph, "Graph is null"); this.epsilon = epsilon; this.guess = 0; this.lower = 0; this.upper = this.computeDensityNumerator(this.graph); this.checkWeights = checkWeights; this.currentNetwork = this.buildNetwork(); this.currentVertices = new HashSet<>(); this.initializeNetwork(); this.checkForEmptySolution(); this.minSTCutAlg = algFactory.apply(currentNetwork); } /** * Helper method for constructing the internally used network */ private Graph buildNetwork() { return GraphTypeBuilder . directed().allowingMultipleEdges(true).allowingSelfLoops(true) .weighted(true).edgeSupplier(DefaultWeightedEdge::new).buildGraph(); } /** * Updates network for next computation, e.g edges from v to t and from s to v Enforces * positivity on network weights if specified by adding subtracting the lowest weights to all * edges $(v,t)$ and $(v,s)$. **/ private void updateNetwork() { for (V v : this.graph.vertexSet()) { currentNetwork .setEdgeWeight(currentNetwork.getEdge(v, t), getEdgeWeightFromVertexToSink(v)); currentNetwork .setEdgeWeight(currentNetwork.getEdge(s, v), getEdgeWeightFromSourceToVertex(v)); } if (this.checkWeights) { double minCapacity = getMinimalCapacity(); if (minCapacity < 0) { DefaultWeightedEdge e; for (V v : this.graph.vertexSet()) { e = currentNetwork.getEdge(v, t); currentNetwork.setEdgeWeight(e, currentNetwork.getEdgeWeight(e) - minCapacity); e = currentNetwork.getEdge(s, v); currentNetwork.setEdgeWeight(e, currentNetwork.getEdgeWeight(e) - minCapacity); } } } } /** * @return the minimal capacity of all edges vt and sv */ private double getMinimalCapacity() { DoubleStream sourceWeights = this.graph .vertexSet().stream() .mapToDouble(v -> currentNetwork.getEdgeWeight(currentNetwork.getEdge(v, t))); DoubleStream sinkWeights = this.graph .vertexSet().stream() .mapToDouble(v -> currentNetwork.getEdgeWeight(currentNetwork.getEdge(s, v))); OptionalDouble min = DoubleStream.concat(sourceWeights, sinkWeights).min(); return min.isPresent() ? min.getAsDouble() : 0; } /** * Initializes network (only once) for Min-Cut computation Adds s,t to vertex set Adds every v * in V to vertex set Adds edge sv and vt for each v in V to edge set Adds every edge uv and vu * from E to edge set Sets edge weights for all edges from E */ private void initializeNetwork() { currentNetwork.addVertex(s); currentNetwork.addVertex(t); for (V v : this.graph.vertexSet()) { currentNetwork.addVertex(v); currentNetwork.addEdge(s, v); currentNetwork.addEdge(v, t); } for (E e : this.graph.edgeSet()) { DefaultWeightedEdge e1 = currentNetwork.addEdge(this.graph.getEdgeSource(e), this.graph.getEdgeTarget(e)); DefaultWeightedEdge e2 = currentNetwork.addEdge(this.graph.getEdgeTarget(e), this.graph.getEdgeSource(e)); double weight = this.graph.getEdgeWeight(e); currentNetwork.setEdgeWeight(e1, weight); currentNetwork.setEdgeWeight(e2, weight); } } /** * Algorithm to compute max density subgraph Performs binary search on the initial interval * lower-upper until interval is smaller than epsilon In case no solution is found because * epsilon is too big, the computation continues until a (first) solution is found, thereby * avoiding to return an empty graph. * * @return max density subgraph of the graph */ public Graph calculateDensest() { if (this.densestSubgraph != null) { return this.densestSubgraph; } Set sourcePartition; while (Double.compare(upper - lower, this.epsilon) >= 0) { guess = lower + ((upper - lower)) / 2; updateNetwork(); minSTCutAlg.calculateMinCut(s, t); sourcePartition = minSTCutAlg.getSourcePartition(); sourcePartition.remove(s); if (sourcePartition.isEmpty()) { upper = guess; } else { lower = guess; currentVertices = new HashSet<>(sourcePartition); } } this.densestSubgraph = new AsSubgraph<>(graph, currentVertices); return this.densestSubgraph; } /** * Computes density of a maximum density subgraph. * * @return the actual density of the maximum density subgraph */ public double getDensity() { if (this.densestSubgraph == null) { this.calculateDensest(); } double denominator = computeDensityDenominator(this.densestSubgraph); if (denominator != 0) { return computeDensityNumerator(this.densestSubgraph) / denominator; } return 0; } /** * Getter for network weights of edges su for u in V * * @param vertex of V * @return weight of the edge (s,v) */ protected abstract double getEdgeWeightFromSourceToVertex(V vertex); /** * Getter for network weights of edges ut for u in V * * @param vertex of V * @return weight of the edge (v,t) */ protected abstract double getEdgeWeightFromVertexToSink(V vertex); /** * @param g the graph to compute the numerator density from * @return numerator part of the density */ protected abstract double computeDensityNumerator(Graph g); /** * @param g the graph to compute the denominator density from * @return numerator part of the density */ protected abstract double computeDensityDenominator(Graph g); /** * Check if denominator will be empty to avoid dividing by 0. */ private void checkForEmptySolution() { if (Double.compare(computeDensityDenominator(this.graph), 0) == 0) { this.densestSubgraph = new AsSubgraph<>(this.graph, null); } } } GoldbergMaximumDensitySubgraphAlgorithmNodeWeightPerEdgeWeight.java000066400000000000000000000107101402514743400446150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/* * (C) Copyright 2018-2021, by Andre Immig and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.densesubgraph; import org.jgrapht.*; import org.jgrapht.alg.flow.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.util.function.*; /** * This class computes a maximum density subgraph based on the algorithm described by Andrew * Vladislav Goldberg in * Finding Maximum Density Subgraphs, 1984, University of Berkley.
* The basic concept is to construct a network that can be used to compute the maximum density * subgraph using a binary search approach. *

* This variant of the algorithm assumes the density of a positive real edge and vertex weighted * graph G=(V,E) to be defined as \[\frac{\sum\limits_{e \in E} w(e)}{\sum\limits_{v \in V} w(v)}\] * and sets the weights of the network from {@link GoldbergMaximumDensitySubgraphAlgorithmBase} as * proposed in the above paper. For this case the weights of the network must be chosen to be: * \[c_{ij}=w(ij)\,\forall \{i,j\}\in E\] \[c_{it}=m'+2gw(i)-d_i\,\forall i \in V\] * \[c_{si}=m'\,\forall i \in V\] where $m'$ is such, that all weights are positive and $d_i$ is the * degree of vertex $i$ and $w(v)$ is the weight of vertex $v$.
* All the math to prove the correctness of these weights is the same as in * {@link GoldbergMaximumDensitySubgraphAlgorithmBase}.
*

* Because the density is per definition guaranteed to be rational, the distance of 2 possible * solutions for the maximum density can't be smaller than $\frac{1}{W(W-1)}$. This means shrinking * the binary search interval to this size, the correct solution is found. The runtime can in this * case be given by $O(M(n,n+m)\log{W})$, where $M(n,m)$ is the runtime of the internally used * {@link MinimumSTCutAlgorithm} and $W$ is the sum of all edge weights from $G$. *

* * @param Type of vertices * @param Type of edges * * @author Andre Immig */ public class GoldbergMaximumDensitySubgraphAlgorithmNodeWeightPerEdgeWeight< V extends Pair, E> extends GoldbergMaximumDensitySubgraphAlgorithmBase { /** * Constructor * * @param graph input for computation * @param s additional source vertex * @param t additional target vertex * @param epsilon to use for internal computation * @param algFactory function to construct the subalgorithm */ public GoldbergMaximumDensitySubgraphAlgorithmNodeWeightPerEdgeWeight( Graph graph, V s, V t, double epsilon, Function, MinimumSTCutAlgorithm> algFactory) { super(graph, s, t, true, epsilon, algFactory); } /** * Convenience constructor that uses PushRelabel as default MinimumSTCutAlgorithm * * @param graph input for computation * @param s additional source vertex * @param t additional target vertex * @param epsilon to use for internal computation */ public GoldbergMaximumDensitySubgraphAlgorithmNodeWeightPerEdgeWeight( Graph graph, V s, V t, double epsilon) { this(graph, s, t, epsilon, PushRelabelMFImpl::new); } @Override protected double computeDensityNumerator(Graph g) { return g.edgeSet().stream().mapToDouble(g::getEdgeWeight).sum(); } @Override protected double computeDensityDenominator(Graph g) { return g.vertexSet().stream().mapToDouble(v -> v.getSecond()).sum(); } @Override protected double getEdgeWeightFromSourceToVertex(V v) { return 0; } @Override protected double getEdgeWeightFromVertexToSink(V v) { return 2 * guess * v.getSecond() - this.graph.outgoingEdgesOf(v).stream().mapToDouble(this.graph::getEdgeWeight).sum(); } } GoldbergMaximumDensitySubgraphAlgorithmNodeWeights.java000066400000000000000000000110331402514743400423730ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/* * (C) Copyright 2018-2021, by Andre Immig and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.densesubgraph; import org.jgrapht.*; import org.jgrapht.alg.flow.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.util.function.*; /** * This class computes a maximum density subgraph based on the algorithm described by Andrew * Vladislav Goldberg in * Finding Maximum Density Subgraphs, 1984, University of Berkley.
* The basic concept is to construct a network that can be used to compute the maximum density * subgraph using a binary search approach. *

* This variant of the algorithm assumes the density of a positive real edge and vertex weighted * graph G=(V,E) to be defined as \[\frac{\sum\limits_{e \in E} w(e) + \sum\limits_{v \in V} * w(v)}{\left|{V}\right|}\] and sets the weights of the network from * {@link GoldbergMaximumDensitySubgraphAlgorithmBase} as proposed in the above paper. For this case * the weights of the network must be chosen to be: \[c_{ij}=w(ij)\,\forall \{i,j\}\in E\] * \[c_{it}=m'+2g-d_i-2w(i)\,\forall i \in V\] \[c_{si}=m'\,\forall i \in V\] where $m'$ is such * that all weights are positive and $d_i$ is the degree of vertex $i$ and $w(v)$ is the weight of * vertex $v$.
* For details see {@link GoldbergMaximumDensitySubgraphAlgorithmBase}. All the math to prove the * correctness of these weights is the same.
*

* Because the density is per definition guaranteed to be rational, the distance of 2 possible * solutions for the maximum density can't be smaller than $\frac{1}{W(W-1)}$. This means shrinking * the binary search interval to this size, the correct solution is found. The runtime can in this * case be given by $O(M(n,n+m)\log{W})$, where $M(n,m)$ is the runtime of the internally used * {@link MinimumSTCutAlgorithm} and $W$ is the sum all edge and vertex weights from $G$. *

* * @param Type of vertices * @param Type of edges * * @author Andre Immig */ public class GoldbergMaximumDensitySubgraphAlgorithmNodeWeights, E> extends GoldbergMaximumDensitySubgraphAlgorithmBase { /** * Constructor * * @param graph input for computation * @param s additional source vertex * @param t additional target vertex * @param epsilon to use for internal computation * @param algFactory function to construct the subalgorithm */ public GoldbergMaximumDensitySubgraphAlgorithmNodeWeights( Graph graph, V s, V t, double epsilon, Function, MinimumSTCutAlgorithm> algFactory) { super(graph, s, t, true, epsilon, algFactory); } /** * Convenience constructor that uses PushRelabel as default MinimumSTCutAlgorithm * * @param graph input for computation * @param s additional source vertex * @param t additional target vertex * @param epsilon to use for internal computation */ public GoldbergMaximumDensitySubgraphAlgorithmNodeWeights( Graph graph, V s, V t, double epsilon) { this(graph, s, t, epsilon, PushRelabelMFImpl::new); } @Override protected double computeDensityNumerator(Graph g) { double sum = g.edgeSet().stream().mapToDouble(g::getEdgeWeight).sum(); for (V v : g.vertexSet()) { sum += v.getSecond(); } return sum; } @Override protected double computeDensityDenominator(Graph g) { return g.vertexSet().size(); } @Override protected double getEdgeWeightFromSourceToVertex(V v) { return 0; } @Override protected double getEdgeWeightFromVertexToSink(V v) { return 2 * guess - this.graph.outgoingEdgesOf(v).stream().mapToDouble(this.graph::getEdgeWeight).sum() - 2 * v.getSecond(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/densesubgraph/package-info.java000066400000000000000000000001461402514743400326520ustar00rootroot00000000000000/** * Algorithms for computing maximum density subgraphs. */ package org.jgrapht.alg.densesubgraph; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/000077500000000000000000000000001402514743400262635ustar00rootroot00000000000000BarycenterGreedyTwoLayeredBipartiteLayout2D.java000066400000000000000000000112631402514743400375400ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.drawing.model.LayoutModel2D; import org.jgrapht.alg.drawing.model.Point2D; import org.jgrapht.alg.util.Pair; /** * The barycenter heuristic greedy algorithm for edge crossing minimization in two layered bipartite * layouts. * * The algorithm draws a bipartite graph using straight edges. Vertices are arranged along two * vertical or horizontal lines, trying to minimize crossings. This algorithm targets the one-sided * problem where one of the two layers is considered fixed and the algorithm is allowed to adjust * the positions of vertices in the other layer. * * The algorithm is described in the following paper: K. Sugiyama, S. Tagawa, and M. Toda. Methods * for visual understanding of hierarchical system structures. IEEE Transaction on Systems, Man, and * Cybernetics, 11(2):109–125, 1981. * * The problem of minimizing edge crossings when drawing bipartite graphs as two layered graphs is * NP-complete. If the coordinates of the nodes in the fixed layer are allowed to vary wildly, then * the barycenter heuristic can perform badly. If the coordinates of the nodes in the fixed layer * are $1, 2, 3, \ldots, ...$ then it is an $\mathcal{O}(\sqrt{n})$-approximation algorithm. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class BarycenterGreedyTwoLayeredBipartiteLayout2D extends TwoLayeredBipartiteLayout2D { /** * Create a new layout */ public BarycenterGreedyTwoLayeredBipartiteLayout2D() { super(); } /** * Create a new layout * * @param partition one of the two partitions, can be null * @param vertexComparator vertex order, can be null * @param vertical draw on two vertical or horizontal lines */ public BarycenterGreedyTwoLayeredBipartiteLayout2D( Set partition, Comparator vertexComparator, boolean vertical) { super(partition, vertexComparator, vertical); } @Override protected void drawSecondPartition(Graph graph, List partition, LayoutModel2D model) { if (partition.isEmpty()) { throw new IllegalArgumentException("Partition cannot be empty"); } // compute new order final Map> order = new HashMap<>(); int i = 0; for (V v : partition) { int degree = graph.degreeOf(v); if (degree == 0) { // singleton order.put(v, Pair.of(-Double.MAX_VALUE, i)); } else { double barycenter = 0d; for (E e : graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); Point2D p2d = model.get(u); double coord = vertical ? p2d.getX() : p2d.getY(); barycenter += coord; } barycenter /= degree; order.put(v, Pair.of(barycenter, i)); } i++; } // create comparator for new order Comparator newOrderComparator = (v, u) -> { Pair pv = order.get(v); Pair pu = order.get(u); int d = Double.compare(pv.getFirst(), pu.getFirst()); if (d != 0) { return d; } int degreeV = graph.degreeOf(v); int degreeU = graph.degreeOf(u); if (degreeV % 2 == 1 && degreeU % 2 == 0) { return -1; } if (degreeV % 2 == 0 && degreeU % 2 == 1) { return 1; } return Integer.compare(pv.getSecond(), pu.getSecond()); }; // sort with new order and delegate partition.sort(newOrderComparator); super.drawSecondPartition(graph, partition, model); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/BaseLayoutAlgorithm2D.java000066400000000000000000000045061402514743400332400ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.model.*; import java.util.function.*; /** * A base class for a 2d layout algorithm. * * @author Dimitrios Michail * * @param the vertex type * @param the edge type */ abstract class BaseLayoutAlgorithm2D implements LayoutAlgorithm2D { /** * A model initializer */ protected Function initializer; /** * Create a new layout algorithm */ public BaseLayoutAlgorithm2D() { this(null); } /** * Create a new layout algorithm with an initializer. * * @param initializer the initializer */ public BaseLayoutAlgorithm2D(Function initializer) { this.initializer = initializer; } /** * Get the initializer * * @return the initializer */ public Function getInitializer() { return initializer; } /** * Set the initializer * * @param initializer the initializer */ public void setInitializer(Function initializer) { this.initializer = initializer; } /** * Initialize a model using the initializer. * * @param graph the graph * @param model the model */ protected void init(Graph graph, LayoutModel2D model) { Function initializer = getInitializer(); if (initializer != null) { for (V v : graph.vertexSet()) { Point2D value = initializer.apply(v); if (value != null) { model.put(v, value); } } } } } CircularLayoutAlgorithm2D.java000066400000000000000000000072151402514743400340530ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.model.*; import org.jgrapht.alg.util.*; import java.util.*; import java.util.stream.*; /** * Circular layout. * *

* The algorithm places the graph vertices on a circle evenly spaced. The vertices are iterated * based on the iteration order of the vertex set of the graph. The order can be adjusted by * providing an external comparator. * * @author Dimitrios Michail * * @param the vertex type * @param the edge type */ public class CircularLayoutAlgorithm2D extends BaseLayoutAlgorithm2D { protected double radius; protected Comparator comparator; protected Comparator vertexComparator; /** * Create a new layout algorithm */ public CircularLayoutAlgorithm2D() { this(0.5d); } /** * Create a new layout algorithm * * @param radius the circle radius */ public CircularLayoutAlgorithm2D(double radius) { this(radius, null); } /** * Create a new layout algorithm. The algorithm will iterate over the vertices of the graph * using the provided ordering. * * @param radius the circle radius * @param vertexComparator the vertex comparator. Can be null. */ public CircularLayoutAlgorithm2D(double radius, Comparator vertexComparator) { this.comparator = new ToleranceDoubleComparator(); this.radius = radius; if (comparator.compare(radius, 0d) <= 0) { throw new IllegalArgumentException("Radius must be positive"); } this.vertexComparator = vertexComparator; } @Override public void layout(Graph graph, LayoutModel2D model) { super.init(graph, model); Box2D drawableArea = model.getDrawableArea(); double width = drawableArea.getWidth(); if (comparator.compare(2d * radius, width) > 0) { throw new IllegalArgumentException("Circle does not fit into drawable area width"); } double height = drawableArea.getHeight(); if (comparator.compare(2d * radius, height) > 0) { throw new IllegalArgumentException("Circle does not fit into drawable area height"); } double minX = drawableArea.getMinX(); double minY = drawableArea.getMinY(); int n = graph.vertexSet().size(); double angleStep = 2 * Math.PI / n; Stream vertexStream; if (vertexComparator != null) { vertexStream = graph.vertexSet().stream().sorted(vertexComparator); } else { vertexStream = graph.vertexSet().stream(); } Iterator it = vertexStream.iterator(); int i = 0; while (it.hasNext()) { double x = radius * Math.cos(angleStep * i) + width / 2; double y = radius * Math.sin(angleStep * i) + height / 2; V v = it.next(); model.put(v, Point2D.of(minX + x, minY + y)); i++; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/FRLayoutAlgorithm2D.java000066400000000000000000000321741402514743400326770ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.model.*; import org.jgrapht.alg.util.ToleranceDoubleComparator; import java.util.*; import java.util.function.*; /** * Fruchterman and Reingold Force-Directed Placement Algorithm. * * The algorithm belongs in the broad category of * force directed graph * drawing algorithms and is described in the paper: * *

    *
  • Thomas M. J. Fruchterman and Edward M. Reingold. Graph drawing by force-directed placement. * Software: Practice and experience, 21(11):1129--1164, 1991.
  • *
* * @author Dimitrios Michail * * @param the vertex type * @param the edge type */ public class FRLayoutAlgorithm2D extends BaseLayoutAlgorithm2D { /** * Default number of iterations */ public static final int DEFAULT_ITERATIONS = 100; /** * Default normalization factor when calculating optimal distance */ public static final double DEFAULT_NORMALIZATION_FACTOR = 0.5; protected Random rng; protected double optimalDistance; protected double normalizationFactor; protected int iterations; protected BiFunction, Integer, TemperatureModel> temperatureModelSupplier; protected final ToleranceDoubleComparator comparator; /** * Create a new layout algorithm */ public FRLayoutAlgorithm2D() { this(DEFAULT_ITERATIONS, DEFAULT_NORMALIZATION_FACTOR, new Random()); } /** * Create a new layout algorithm * * @param iterations number of iterations */ public FRLayoutAlgorithm2D(int iterations) { this(iterations, DEFAULT_NORMALIZATION_FACTOR, new Random()); } /** * Create a new layout algorithm * * @param iterations number of iterations * @param normalizationFactor normalization factor for the optimal distance */ public FRLayoutAlgorithm2D(int iterations, double normalizationFactor) { this(iterations, normalizationFactor, new Random()); } /** * Create a new layout algorithm * * @param iterations number of iterations * @param normalizationFactor normalization factor for the optimal distance * @param rng the random number generator */ public FRLayoutAlgorithm2D(int iterations, double normalizationFactor, Random rng) { this(iterations, normalizationFactor, rng, ToleranceDoubleComparator.DEFAULT_EPSILON); } /** * Create a new layout algorithm * * @param iterations number of iterations * @param normalizationFactor normalization factor for the optimal distance * @param rng the random number generator * @param tolerance tolerance used when comparing floating point values */ public FRLayoutAlgorithm2D( int iterations, double normalizationFactor, Random rng, double tolerance) { this.rng = Objects.requireNonNull(rng); this.iterations = iterations; this.normalizationFactor = normalizationFactor; this.temperatureModelSupplier = (model, totalIterations) -> { double dimension = Math.min(model.getDrawableArea().getWidth(), model.getDrawableArea().getHeight()); return new InverseLinearTemperatureModel( -1d * dimension / (10d * totalIterations), dimension / 10d); }; this.comparator = new ToleranceDoubleComparator(tolerance); } /** * Create a new layout algorithm * * @param iterations number of iterations * @param normalizationFactor normalization factor for the optimal distance * @param temperatureModelSupplier a simulated annealing temperature model supplier * @param rng the random number generator */ public FRLayoutAlgorithm2D( int iterations, double normalizationFactor, BiFunction, Integer, TemperatureModel> temperatureModelSupplier, Random rng) { this( iterations, normalizationFactor, temperatureModelSupplier, rng, ToleranceDoubleComparator.DEFAULT_EPSILON); } /** * Create a new layout algorithm * * @param iterations number of iterations * @param normalizationFactor normalization factor for the optimal distance * @param temperatureModelSupplier a simulated annealing temperature model supplier * @param rng the random number generator * @param tolerance tolerance used when comparing floating point values */ public FRLayoutAlgorithm2D( int iterations, double normalizationFactor, BiFunction, Integer, TemperatureModel> temperatureModelSupplier, Random rng, double tolerance) { this.rng = Objects.requireNonNull(rng); this.iterations = iterations; this.normalizationFactor = normalizationFactor; this.temperatureModelSupplier = Objects.requireNonNull(temperatureModelSupplier); this.comparator = new ToleranceDoubleComparator(tolerance); } @Override public void layout(Graph graph, LayoutModel2D model) { // read area Box2D drawableArea = model.getDrawableArea(); double minX = drawableArea.getMinX(); double minY = drawableArea.getMinY(); if (getInitializer() != null) { // respect user initializer init(graph, model); // make sure all vertices have coordinates for (V v : graph.vertexSet()) { Point2D vPos = model.get(v); if (vPos == null) { model.put(v, Point2D.of(minX, minY)); } } } else { // assign random initial positions MapLayoutModel2D randomModel = new MapLayoutModel2D<>(drawableArea); new RandomLayoutAlgorithm2D(rng).layout(graph, randomModel); for (V v : graph.vertexSet()) { model.put(v, randomModel.get(v)); } } // calculate optimal distance between vertices double width = drawableArea.getWidth(); double height = drawableArea.getHeight(); double area = width * height; int n = graph.vertexSet().size(); if (n == 0) { return; } optimalDistance = normalizationFactor * Math.sqrt(area / n); // create temperature model TemperatureModel temperatureModel = temperatureModelSupplier.apply(model, iterations); // start main iterations for (int i = 0; i < iterations; i++) { // repulsive forces Map repulsiveDisp = calculateRepulsiveForces(graph, model); // attractive forces Map attractiveDisp = calculateAttractiveForces(graph, model); // calculate current temperature double temp = temperatureModel.temperature(i, iterations); // limit maximum displacement by the temperature // and prevent from being displaced outside frame for (V v : graph.vertexSet()) { // limit by temperature Point2D vDisp = Points .add(repulsiveDisp.get(v), attractiveDisp.getOrDefault(v, Point2D.of(0d, 0d))); double vDispLen = Points.length(vDisp); Point2D vPos = Points .add( model.get(v), Points.scalarMultiply(vDisp, Math.min(vDispLen, temp) / vDispLen)); // limit by frame vPos = Point2D .of( Math.min(minX + width, Math.max(minX, vPos.getX())), Math.min(minY + height, Math.max(minY, vPos.getY()))); // store result model.put(v, vPos); } } } /** * Calculate the attractive force. * * @param distance the distance * @return the force */ protected double attractiveForce(double distance) { return distance * distance / optimalDistance; } /** * Calculate the repulsive force. * * @param distance the distance * @return the force */ protected double repulsiveForce(double distance) { return optimalDistance * optimalDistance / distance; } /** * Calculate the repulsive forces between vertices * * @param graph the graph * @param model the model * @return the displacement per vertex due to the repulsive forces */ protected Map calculateRepulsiveForces(Graph graph, LayoutModel2D model) { Point2D origin = Point2D.of(model.getDrawableArea().getMinX(), model.getDrawableArea().getMinY()); Map disp = new HashMap<>(); for (V v : graph.vertexSet()) { Point2D vPos = Points.subtract(model.get(v), origin); Point2D vDisp = Point2D.of(0d, 0d); for (V u : graph.vertexSet()) { if (v == u) { continue; } Point2D uPos = Points.subtract(model.get(u), origin); if (comparator.compare(vPos.getX(), uPos.getX()) != 0 || comparator.compare(vPos.getY(), uPos.getY()) != 0) { Point2D delta = Points.subtract(vPos, uPos); double deltaLen = Points.length(delta); Point2D dispContribution = Points.scalarMultiply(delta, repulsiveForce(deltaLen) / deltaLen); vDisp = Points.add(vDisp, dispContribution); } } disp.put(v, vDisp); } return disp; } /** * Calculate the repulsive forces between vertices connected with edges. * * @param graph the graph * @param model the model * @return the displacement per vertex due to the attractive forces */ protected Map calculateAttractiveForces(Graph graph, LayoutModel2D model) { Point2D origin = Point2D.of(model.getDrawableArea().getMinX(), model.getDrawableArea().getMinY()); Map disp = new HashMap<>(); for (E e : graph.edgeSet()) { V v = graph.getEdgeSource(e); V u = graph.getEdgeTarget(e); Point2D vPos = Points.subtract(model.get(v), origin); Point2D uPos = Points.subtract(model.get(u), origin); if (comparator.compare(vPos.getX(), uPos.getX()) != 0 || comparator.compare(vPos.getY(), uPos.getY()) != 0) { Point2D delta = Points.subtract(vPos, uPos); double deltaLen = Points.length(delta); Point2D dispContribution = Points.scalarMultiply(delta, attractiveForce(deltaLen) / deltaLen); disp .put( v, Points .add( disp.getOrDefault(v, Point2D.of(0d, 0d)), Points.negate(dispContribution))); disp.put(u, Points.add(disp.getOrDefault(u, Point2D.of(0d, 0d)), dispContribution)); } } return disp; } /** * A general interface for a temperature model. * *

* The temperature should start from a high enough value and gradually become zero. */ public interface TemperatureModel { /** * Return the temperature for the new iteration * * @param iteration the next iteration * @param maxIterations total number of iterations * @return the temperature for the next iteration */ double temperature(int iteration, int maxIterations); } /** * An inverse linear temperature model. */ protected class InverseLinearTemperatureModel implements TemperatureModel { private double a; private double b; /** * Create a new inverse linear temperature model. * * @param a a * @param b b */ public InverseLinearTemperatureModel(double a, double b) { this.a = a; this.b = b; } @Override public double temperature(int iteration, int maxIterations) { if (iteration >= maxIterations - 1) { return 0.0; } return a * iteration + b; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/FRQuadTree.java000066400000000000000000000200711402514743400310700ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.alg.drawing.model.*; import org.jgrapht.alg.util.*; import java.util.*; /** * A simple QuadTree for indexing during force * calculations in the Fruchterman and Reingold Force-Directed Placement Algorithm. * *

* See the following paper for the definition of a QuadTree. *

    *
  • Raphael Finkel and J.L. Bentley. Quad Trees: A Data Structure for Retrieval on Composite * Keys. Acta Informatica, 4(1):1--9, 1974.
  • *
* *

* The tree supports adding points one by one and maintains the centroid and total number of points * on each tree node. * * @author Dimitrios Michail */ class FRQuadTree { private static final int NW = 0; private static final int NE = 1; private static final int SW = 2; private static final int SE = 3; private Node root; /** * Create a new tree for a certain area. * * @param box the area */ public FRQuadTree(Box2D box) { this.root = new Node(box); } /** * Insert a new point. * * @param p the new point */ public void insert(Point2D p) { Node cur = root; while (true) { if (cur.isLeaf()) { if (cur.points.size() == 0) { cur.points.add(p); return; } // split Box2D rect = cur.getBox(); Pair xsplit = Boxes.splitAlongXAxis(rect); Pair west = Boxes.splitAlongYAxis(xsplit.getFirst()); Pair east = Boxes.splitAlongYAxis(xsplit.getSecond()); // create 4 children cur.children = new Node[4]; cur.children[NW] = new Node(west.getSecond()); cur.children[NE] = new Node(east.getSecond()); cur.children[SW] = new Node(west.getFirst()); cur.children[SE] = new Node(east.getFirst()); // distribute old points and compute centroid double centroidX = 0, centroidY = 0; for (Point2D point : cur.points) { if (Boxes.containsPoint(cur.children[NW].getBox(), point)) { cur.children[NW].points.add(point); } else if (Boxes.containsPoint(cur.children[NE].getBox(), point)) { cur.children[NE].points.add(point); } else if (Boxes.containsPoint(cur.children[SW].getBox(), point)) { cur.children[SW].points.add(point); } else if (Boxes.containsPoint(cur.children[SE].getBox(), point)) { cur.children[SE].points.add(point); } centroidX += point.getX(); centroidY += point.getY(); } cur.totalPoints = cur.points.size(); cur.centroid = Point2D.of(centroidX / cur.totalPoints, centroidY / cur.totalPoints); // change from leaf to internal node cur.points = null; } // here we are not a leaf // count new point and update centroid cur.totalPoints++; cur.centroid = Point2D .of( (cur.centroid.getX() * (cur.totalPoints - 1) + p.getX()) / cur.totalPoints, (cur.centroid.getY() * (cur.totalPoints - 1) + p.getY()) / cur.totalPoints); // non-leaf if (Boxes.containsPoint(cur.children[NW].getBox(), p)) { cur = cur.children[NW]; } else if (Boxes.containsPoint(cur.children[NE].getBox(), p)) { cur = cur.children[NE]; } else if (Boxes.containsPoint(cur.children[SW].getBox(), p)) { cur = cur.children[SW]; } else if (Boxes.containsPoint(cur.children[SE].getBox(), p)) { cur = cur.children[SE]; } else { throw new IllegalArgumentException(); } } } /** * Get the root node of the tree. * * @return the root */ public Node getRoot() { return root; } /** * The Quad-Tree node. * * @author Dimitrios Michail */ public class Node { // node region Box2D box; // internal node int totalPoints; Point2D centroid; Node[] children; // leaf node List points; /** * Create a new node for a given area * * @param box the area */ public Node(Box2D box) { this.box = Objects.requireNonNull(box); this.points = new ArrayList<>(); } /** * Check if a node is a leaf. * * @return true if leaf, false otherwise */ public boolean isLeaf() { return points != null; } /** * Get a list of all points contained in this node. * * @return a list of points */ public List getPoints() { if (points != null) { return points; } else { List result = new ArrayList<>(); getChildren().forEach(node -> { result.addAll(node.getPoints()); }); return result; } } /** * Check if the node contains any points. * * @return true if the node contains points, false otherwise */ public boolean hasPoints() { if (points != null) { return points.size() != 0; } else { return totalPoints != 0; } } /** * Get the area represented by this node. * * @return the area of the node */ public Box2D getBox() { return box; } /** * Get the total number of points under this node. * * @return the total number of points */ public int getNumberOfPoints() { if (points != null) { return points.size(); } else { return totalPoints; } } /** * Get the centroid of all points contained in this node. * * @return the centroid of all points contained in this node */ public Point2D getCentroid() { if (points != null) { int numPoints = points.size(); if (numPoints == 0) { throw new IllegalArgumentException("No points"); } double x = 0, y = 0; for (Point2D p : points) { x += p.getX(); y += p.getY(); } return Point2D.of(x / numPoints, y / numPoints); } else { return centroid; } } /** * Get the children of this node as a list. * * @return a list containing the children of this node */ public List getChildren() { if (children == null) { return Collections.emptyList(); } return Arrays.asList(children); } } } IndexedFRLayoutAlgorithm2D.java000066400000000000000000000156271402514743400341250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.FRQuadTree.*; import org.jgrapht.alg.drawing.model.*; import org.jgrapht.alg.util.*; import java.util.*; /** * Fruchterman and Reingold Force-Directed Placement Algorithm using the * Barnes-Hut indexing * technique with a QuadTree. * * The Barnes-Hut indexing technique is described in the following paper: *

    *
  • J. Barnes and P. Hut. A hierarchical O(N log N) force-calculation algorithm. Nature. * 324(4):446--449, 1986.
  • *
* * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class IndexedFRLayoutAlgorithm2D extends FRLayoutAlgorithm2D { /** * Default $\theta$ value for approximation using the Barnes-Hut technique */ public static final double DEFAULT_THETA_FACTOR = 0.5; protected double theta; protected long savedComparisons; /** * Create a new layout algorithm */ public IndexedFRLayoutAlgorithm2D() { this(DEFAULT_ITERATIONS, DEFAULT_THETA_FACTOR, DEFAULT_NORMALIZATION_FACTOR); } /** * Create a new layout algorithm * * @param iterations number of iterations * @param theta parameter for approximation using the Barnes-Hut technique */ public IndexedFRLayoutAlgorithm2D(int iterations, double theta) { this(iterations, theta, DEFAULT_NORMALIZATION_FACTOR); } /** * Create a new layout algorithm * * @param iterations number of iterations * @param theta parameter for approximation using the Barnes-Hut technique * @param normalizationFactor normalization factor for the optimal distance */ public IndexedFRLayoutAlgorithm2D(int iterations, double theta, double normalizationFactor) { this(iterations, theta, normalizationFactor, new Random()); } /** * Create a new layout algorithm * * @param iterations number of iterations * @param theta theta parameter for the Barnes-Hut approximation * @param normalizationFactor normalization factor for the optimal distance * @param rng the random number generator */ public IndexedFRLayoutAlgorithm2D( int iterations, double theta, double normalizationFactor, Random rng) { this( iterations, theta, normalizationFactor, rng, ToleranceDoubleComparator.DEFAULT_EPSILON); } /** * Create a new layout algorithm * * @param iterations number of iterations * @param theta theta parameter for the Barnes-Hut approximation * @param normalizationFactor normalization factor for the optimal distance * @param rng the random number generator * @param tolerance tolerance used when comparing floating point values */ public IndexedFRLayoutAlgorithm2D( int iterations, double theta, double normalizationFactor, Random rng, double tolerance) { super(iterations, normalizationFactor, rng, tolerance); this.theta = theta; if (theta < 0d || theta > 1d) { throw new IllegalArgumentException("Illegal theta value"); } this.savedComparisons = 0; } @Override public void layout(Graph graph, LayoutModel2D model) { this.savedComparisons = 0; super.layout(graph, model); } @Override protected Map calculateRepulsiveForces(Graph graph, LayoutModel2D model) { // index all points FRQuadTree quadTree = new FRQuadTree(model.getDrawableArea()); for (V v : graph.vertexSet()) { quadTree.insert(model.get(v)); } Point2D origin = Point2D.of(model.getDrawableArea().getMinX(), model.getDrawableArea().getMinY()); // compute displacement with index Map disp = new HashMap<>(); for (V v : graph.vertexSet()) { Point2D vPos = Points.subtract(model.get(v), origin); Point2D vDisp = Point2D.of(0d, 0d); Deque queue = new ArrayDeque<>(); queue.add(quadTree.getRoot()); while (!queue.isEmpty()) { Node node = queue.removeFirst(); Box2D box = node.getBox(); double boxWidth = box.getWidth(); Point2D uPos = null; if (node.isLeaf()) { if (!node.hasPoints()) { continue; } uPos = Points.subtract(node.getPoints().iterator().next(), origin); } else { double distanceToCentroid = Points.length(Points.subtract(vPos, node.getCentroid())); if (comparator.compare(distanceToCentroid, 0d) == 0) { savedComparisons += node.getNumberOfPoints() - 1; continue; } else if (comparator.compare(boxWidth / distanceToCentroid, theta) < 0) { uPos = Points.subtract(node.getCentroid(), origin); savedComparisons += node.getNumberOfPoints() - 1; } else { for (Node child : node.getChildren()) { queue.add(child); } continue; } } if (comparator.compare(vPos.getX(), uPos.getX()) != 0 || comparator.compare(vPos.getY(), uPos.getY()) != 0) { Point2D delta = Points.subtract(vPos, uPos); double deltaLen = Points.length(delta); Point2D dispContribution = Points.scalarMultiply(delta, repulsiveForce(deltaLen) / deltaLen); vDisp = Points.add(vDisp, dispContribution); } } disp.put(v, vDisp); } return disp; } /** * Get the total number of saved comparisons due to the Barnes-Hut technique. * * @return the total number of saved comparisons */ public long getSavedComparisons() { return savedComparisons; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/LayoutAlgorithm2D.java000066400000000000000000000025631402514743400324460ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.model.*; /** * A general interface for a layout 2D algorithm. * * A layout algorithm takes as input a graph and computes point coordinates for each of the graph * vertices. Details such as the dimensions of the drawable area, the storage of the vertices' * coordinates, etc. are provided using a {@link LayoutModel2D}. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public interface LayoutAlgorithm2D { /** * Layout a graph. * * @param graph the graph * @param model the layout model to use */ void layout(Graph graph, LayoutModel2D model); } MedianGreedyTwoLayeredBipartiteLayout2D.java000066400000000000000000000106421402514743400366370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.drawing.model.LayoutModel2D; import org.jgrapht.alg.drawing.model.Point2D; import org.jgrapht.alg.util.Pair; /** * The median heuristic greedy algorithm for edge crossing minimization in two layered bipartite * layouts. * * The algorithm draws a bipartite graph using straight edges. Vertices are arranged along two * vertical or horizontal lines, trying to minimize crossings. This algorithm targets the one-sided * problem where one of the two layers is considered fixed and the algorithm is allowed to adjust * the positions of vertices in the other layer. * * The algorithm is described in the following paper: Eades, Peter, and Nicholas C. Wormald. "Edge * crossings in drawings of bipartite graphs." Algorithmica 11.4 (1994): 379-403. * * The problem of minimizing edge crossings when drawing bipartite graphs as two layered graphs is * NP-complete and the median heuristic is a 3-approximation algorithm. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class MedianGreedyTwoLayeredBipartiteLayout2D extends TwoLayeredBipartiteLayout2D { /** * Create a new layout */ public MedianGreedyTwoLayeredBipartiteLayout2D() { super(); } /** * Create a new layout * * @param partition one of the two partitions, can be null * @param vertexComparator vertex order, can be null * @param vertical draw on two vertical or horizontal lines */ public MedianGreedyTwoLayeredBipartiteLayout2D( Set partition, Comparator vertexComparator, boolean vertical) { super(partition, vertexComparator, vertical); } @Override protected void drawSecondPartition(Graph graph, List partition, LayoutModel2D model) { if (partition.isEmpty()) { throw new IllegalArgumentException("Partition cannot be empty"); } // compute new order final Map> order = new HashMap<>(); int i = 0; for (V v : partition) { ArrayList other = new ArrayList<>(); for (E e : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); Point2D p2d = model.get(u); double coord = vertical ? p2d.getX() : p2d.getY(); other.add(coord); } other.sort(null); if (other.isEmpty()) { // singleton order.put(v, Pair.of(-Double.MAX_VALUE, i)); } else { double median = other.get(other.size() / 2); order.put(v, Pair.of(median, i)); } i++; } // create comparator for new order Comparator newOrderComparator = (v, u) -> { Pair pv = order.get(v); Pair pu = order.get(u); int d = Double.compare(pv.getFirst(), pu.getFirst()); if (d != 0) { return d; } int degreeV = graph.degreeOf(v); int degreeU = graph.degreeOf(u); if (degreeV % 2 == 1 && degreeU % 2 == 0) { return -1; } if (degreeV % 2 == 0 && degreeU % 2 == 1) { return 1; } return Integer.compare(pv.getSecond(), pu.getSecond()); }; // sort with new order and delegate partition.sort(newOrderComparator); super.drawSecondPartition(graph, partition, model); } } RandomLayoutAlgorithm2D.java000066400000000000000000000042761402514743400335330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.model.*; import java.util.*; /** * Random layout. The algorithm assigns vertex coordinates uniformly at random. * * @author Dimitrios Michail * * @param the vertex type * @param the edge type */ public class RandomLayoutAlgorithm2D extends BaseLayoutAlgorithm2D { private Random rng; /** * Create a new layout algorithm */ public RandomLayoutAlgorithm2D() { this(new Random()); } /** * Create a new layout algorithm * * @param seed seed for the random number generator */ public RandomLayoutAlgorithm2D(long seed) { this(new Random(seed)); } /** * Create a new layout algorithm * * @param rng the random number generator */ public RandomLayoutAlgorithm2D(Random rng) { this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); } @Override public void layout(Graph graph, LayoutModel2D model) { super.init(graph, model); Box2D drawableArea = model.getDrawableArea(); double minX = drawableArea.getMinX(); double minY = drawableArea.getMinX(); double width = drawableArea.getWidth(); double height = drawableArea.getHeight(); for (V v : graph.vertexSet()) { double x = rng.nextDouble() * width; double y = rng.nextDouble() * height; model.put(v, Point2D.of(minX + x, minY + y)); } } } RescaleLayoutAlgorithm2D.java000066400000000000000000000076451402514743400336740ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import java.util.OptionalDouble; import java.util.stream.StreamSupport; import org.jgrapht.Graph; import org.jgrapht.alg.drawing.model.Box2D; import org.jgrapht.alg.drawing.model.LayoutModel2D; import org.jgrapht.alg.drawing.model.Point2D; /** * A layout algorithm which re-scales vertex positions to (center-scale,center+scale) in all * dimensions. * * The algorithm first subtracts the mean on each axis separately, then all values are adjusted so * that the maximum magnitude becomes scale. The result is finally translated back to the old * center. This procedure preserves the aspect ratio. * * @author Dimitrios Michail * * @param the vertex type * @param the edge type */ public class RescaleLayoutAlgorithm2D extends BaseLayoutAlgorithm2D { private double scale; /** * Create a new layout algorithm * * @param scale the scale parameter */ public RescaleLayoutAlgorithm2D(double scale) { if (scale <= 0d) { throw new IllegalArgumentException("Scale must be positive"); } this.scale = scale; } @Override public void layout(Graph graph, LayoutModel2D model) { Box2D oldArea = model.getDrawableArea(); double oldCenterX = oldArea.getMinX() + oldArea.getWidth() / 2.0; double oldCenterY = oldArea.getMinY() + oldArea.getHeight() / 2.0; double maxX = 0d, maxY = 0d; OptionalDouble optMeanX = StreamSupport .stream(model.spliterator(), false).mapToDouble(e -> e.getValue().getX()).average(); if (optMeanX.isPresent()) { double meanX = optMeanX.getAsDouble(); for (V v : graph.vertexSet()) { Point2D p = model.get(v); double newX = p.getX() - meanX; Point2D newP = Point2D.of(newX, p.getY()); model.put(v, newP); maxX = Math.max(Math.abs(newX), maxX); } } OptionalDouble optMeanY = StreamSupport .stream(model.spliterator(), false).mapToDouble(e -> e.getValue().getY()).average(); if (optMeanY.isPresent()) { double meanY = optMeanY.getAsDouble(); for (V v : graph.vertexSet()) { Point2D p = model.get(v); double newY = p.getY() - meanY; Point2D newP = Point2D.of(p.getX(), newY); model.put(v, newP); maxY = Math.max(Math.abs(newY), maxY); } } double allMax = Math.max(maxX, maxY); if (allMax > 0d) { for (V v : graph.vertexSet()) { Point2D p = model.get(v); double newX = oldCenterX + p.getX() * scale / allMax; Point2D newP = Point2D.of(newX, p.getY()); model.put(v, newP); } } if (allMax > 0d) { for (V v : graph.vertexSet()) { Point2D p = model.get(v); double newY = oldCenterY + p.getY() * scale / allMax; Point2D newP = Point2D.of(p.getX(), newY); model.put(v, newP); } } model .setDrawableArea( Box2D.of(oldCenterX - scale, oldCenterY - scale, 2.0 * scale, 2.0 * scale)); } } TwoLayeredBipartiteLayout2D.java000066400000000000000000000163731402514743400343700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.alg.drawing.model.Box2D; import org.jgrapht.alg.drawing.model.LayoutModel2D; import org.jgrapht.alg.drawing.model.Point2D; import org.jgrapht.alg.interfaces.PartitioningAlgorithm.Partitioning; import org.jgrapht.alg.partition.BipartitePartitioning; import org.jgrapht.alg.util.Pair; /** * A two layered bipartite layout. * * The algorithm draws a bipartite graph using straight edges. Vertices are arranged along two * vertical or horizontal lines. No attempt is made to minimize edge crossings. * * The order of the vertices can be adjusted by providing a vertex comparator. Similarly the user * can also determine the two partitions or can let the algorithm compute them. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class TwoLayeredBipartiteLayout2D implements LayoutAlgorithm2D { protected Comparator vertexComparator; protected boolean vertical; protected Set partition; /** * Create a new layout */ public TwoLayeredBipartiteLayout2D() { this(null, null, true); } /** * Create a new layout * * @param partition one of the two partitions, can be null * @param vertexComparator vertex order, can be null * @param vertical draw on two vertical or horizontal lines */ public TwoLayeredBipartiteLayout2D( Set partition, Comparator vertexComparator, boolean vertical) { this.partition = partition; this.vertexComparator = vertexComparator; this.vertical = vertical; } /** * Adjust the vertex comparator which specifies the vertex order. * * @param vertexComparator the vertex comparator, or null in order to use the graph ordering * @return the layout algorithm instance */ public TwoLayeredBipartiteLayout2D withVertexComparator(Comparator vertexComparator) { this.vertexComparator = vertexComparator; return this; } /** * Adjust whether the layout will be vertical or horizontal. * * @param vertical if true vertical, otherwize horizontal * @return the layout algorithm instance */ public TwoLayeredBipartiteLayout2D withVertical(boolean vertical) { this.vertical = vertical; return this; } /** * Specify the first of the two bipartite partitions. If not provided, the algorithm computes a * partitioning. * * @param partition the partition * @return the layout algorithm instance */ public TwoLayeredBipartiteLayout2D withFirstPartition(Set partition) { this.partition = partition; return this; } @Override public void layout(Graph graph, LayoutModel2D model) { Pair, List> partitions = computePartitions(graph); drawFirstPartition(graph, partitions.getFirst(), model); drawSecondPartition(graph, partitions.getSecond(), model); } protected void drawFirstPartition(Graph graph, List partition, LayoutModel2D model) { if (partition.isEmpty()) { throw new IllegalArgumentException("Partition cannot be empty"); } Box2D drawableArea = model.getDrawableArea(); double height = drawableArea.getHeight(); double width = drawableArea.getWidth(); double minX = drawableArea.getMinX(); double minY = drawableArea.getMinY(); int n = partition.size(); double step = 0d; if (n > 1) { step = (vertical ? height : width) / (n - 1); } if (vertical) { double y = minY; for (V v : partition) { model.put(v, Point2D.of(minX, y)); y += step; } } else { double x = minX; for (V v : partition) { model.put(v, Point2D.of(x, minY)); x += step; } } } protected void drawSecondPartition(Graph graph, List partition, LayoutModel2D model) { if (partition.isEmpty()) { throw new IllegalArgumentException("Partition cannot be empty"); } Box2D drawableArea = model.getDrawableArea(); double height = drawableArea.getHeight(); double width = drawableArea.getWidth(); double minX = drawableArea.getMinX(); double minY = drawableArea.getMinY(); int n = partition.size(); double step = 0d; if (n > 1) { step = (vertical ? height : width) / (n - 1); } if (vertical) { double y = minY; for (V v : partition) { model.put(v, Point2D.of(minX + width, y)); y += step; } } else { double x = minX; for (V v : partition) { model.put(v, Point2D.of(x, minY + height)); x += step; } } } /** * Compute the vertex partitions. * * @param graph the input graph * @return a pair of two vertex lists */ protected Pair, List> computePartitions(Graph graph) { List left = new ArrayList<>(); List right = new ArrayList<>(); if (partition != null) { // partition is given for (V v : graph.vertexSet()) { if (partition.contains(v)) { left.add(v); } else { right.add(v); } } for (E e : graph.edgeSet()) { V v = graph.getEdgeSource(e); V u = graph.getEdgeTarget(e); if (!(partition.contains(v) ^ partition.contains(u))) { throw new IllegalArgumentException("Invalid provided bipartite partition."); } } } else { // compute partition Partitioning partitioning = new BipartitePartitioning(graph).getPartitioning(); if (partitioning == null) { throw new IllegalArgumentException("Graph is not bipartite."); } left.addAll(partitioning.getPartition(0)); right.addAll(partitioning.getPartition(1)); } // sort by comparator if (vertexComparator != null) { left.sort(vertexComparator); right.sort(vertexComparator); } return Pair.of(left, right); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/000077500000000000000000000000001402514743400273635ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Box2D.java000066400000000000000000000115201402514743400311430ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import java.io.*; import java.util.*; /** * A 2-dimensional box (rectangle). * * @author Dimitrios Michail * */ public class Box2D implements Serializable { private static final long serialVersionUID = -1855277817131669241L; /** * The coordinates of the lower corner */ protected double[] coordinates; /** * The side lengths */ protected double[] sides; /** * Create a new box * * @param width the width * @param height the height */ public Box2D(double width, double height) { this(0d, 0d, width, height); } /** * Create a new box * * @param x the x coordinate of the lower-left corner * @param y the y coordinate of the lower-left corner * @param width the width * @param height the height */ public Box2D(double x, double y, double width, double height) { this(new double[2], new double[2]); assert width >= 0d && height >= 0d; coordinates[0] = x; coordinates[1] = y; sides[0] = width; sides[1] = height; } /** * Create a new box * * @param coordinates the lower left corner coordinates * @param sides width and height */ public Box2D(double[] coordinates, double[] sides) { assert coordinates.length == 2; assert sides.length == 2; this.coordinates = Objects.requireNonNull(coordinates); this.sides = Objects.requireNonNull(sides); if (coordinates.length != sides.length) { throw new IllegalArgumentException("Box dimensions do not match"); } } /** * Get the minimum x coordinate * * @return the minimum x coordinate */ public double getMinX() { return coordinates[0]; } /** * Get the minimum y coordinate * * @return the minimum y coordinate */ public double getMinY() { return coordinates[1]; } /** * Get the width * * @return the width */ public double getWidth() { return sides[0]; } /** * Get the height * * @return the height */ public double getHeight() { return sides[1]; } /** * Get the maximum x coordinate * * @return the maximum x coordinate */ public double getMaxX() { return coordinates[0] + sides[0]; } /** * Get the maximum y coordinate * * @return the maximum y coordinate */ public double getMaxY() { return coordinates[1] + sides[1]; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + Arrays.hashCode(coordinates); result = prime * result + Arrays.hashCode(sides); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Box2D other = (Box2D) obj; if (!Arrays.equals(coordinates, other.coordinates)) return false; if (!Arrays.equals(sides, other.sides)) return false; return true; } @Override public String toString() { return "Box2D [minX=" + coordinates[0] + ", minY=" + coordinates[1] + ", width=" + sides[0] + ", height=" + sides[1] + "]"; } /** * Create a new box * * @param width the width * @param height the height * @return the box */ public static Box2D of(double width, double height) { return new Box2D(new double[] { 0.0, 0.0 }, new double[] { width, height }); } /** * Create a new box * * @param x the x coordinate of the lower-left corner * @param y the y coordinate of the lower-left corner * @param width the width * @param height the height * @return the box */ public static Box2D of(double x, double y, double width, double height) { return new Box2D(new double[] { x, y }, new double[] { width, height }); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Boxes.java000066400000000000000000000066331402514743400313160ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import org.jgrapht.alg.util.*; import java.util.*; /** * A collection of utilities to assist with boxes manipulation. * * @author Dimitrios Michail */ public abstract class Boxes { /** * Test whether a box contains a point. * * @param box the box * @param p the point * @return true if the point is contained inside the box, false otherwise */ public static boolean containsPoint(Box2D box, Point2D p) { double maxX = box.getMinX() + box.getWidth(); if (p.getX() > maxX) { return false; } if (p.getX() < box.getMinX()) { return false; } double maxY = box.getMinY() + box.getHeight(); if (p.getY() > maxY) { return false; } if (p.getY() < box.getMinY()) { return false; } return true; } /** * Split a box along the x axis into two equal boxes. * * @param box the box to split * @return a pair with the two resulting boxes */ public static Pair splitAlongXAxis(Box2D box) { double newWidth = box.getWidth() / 2d; double height = box.getHeight(); return Pair .of( Box2D.of(box.getMinX(), box.getMinY(), newWidth, height), Box2D.of(box.getMinX() + newWidth, box.getMinY(), newWidth, height)); } /** * Split a box along the y axis into two equal boxes. * * @param box the box to split * @return a pair with the two resulting boxes */ public static Pair splitAlongYAxis(Box2D box) { double width = box.getWidth(); double newHeight = box.getHeight() / 2d; return Pair .of( Box2D.of(box.getMinX(), box.getMinY(), width, newHeight), Box2D.of(box.getMinX(), box.getMinY() + newHeight, width, newHeight)); } /** * Test whether a box contains a point. * * @param box the box * @param p the point * @param comparator the comparator to use * @return true if the point is contained inside the box, false otherwise */ public static boolean containsPoint(Box2D box, Point2D p, Comparator comparator) { double maxX = box.getMinX() + box.getWidth(); if (comparator.compare(p.getX(), maxX) > 0) { return false; } if (comparator.compare(p.getX(), box.getMinX()) < 0) { return false; } double maxY = box.getMinY() + box.getHeight(); if (comparator.compare(p.getY(), maxY) > 0) { return false; } if (comparator.compare(p.getY(), box.getMinY()) < 0) { return false; } return true; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/LayoutModel2D.java000066400000000000000000000072451402514743400326620ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import org.jgrapht.alg.drawing.*; import java.util.*; /** * A general interface for the 2D layout model. * * The layout model provides the necessary components to a {@link LayoutAlgorithm2D} in order to * draw a graph. Its responsibility is to provide the available drawable area, to be able to store * and answer queries about vertex coordinates, and to allow someone to fix (make permanent) a * vertex location. * * @author Dimitrios Michail * * @param the vertex type */ public interface LayoutModel2D extends Iterable> { /** * Get the drawable area of the model. * * @return the drawable area of the model */ Box2D getDrawableArea(); /** * Set the drawable area of the model. * * @param drawableArea the drawable area to use */ void setDrawableArea(Box2D drawableArea); /** * Get the last location of a particular vertex in the model. May return null if the vertex has * not been assigned a location or if the particular implementation does not store the * coordinates. * * @param vertex the graph vertex * @return the last location of the vertex */ Point2D get(V vertex); /** * Set the location of a vertex. * * @param vertex the graph vertex * @param point the location * @return the previous location or null if the vertex did not have a previous location or if * the model does not store locations */ Point2D put(V vertex, Point2D point); /** * Set a point as being a "fixed-point" or not. * * It is the model's responsibility to make sure that changing the coordinates of a fixed point * by calling {@link #put(Object, Point2D)} has no effect. * * @param vertex a vertex * @param fixed whether it is a fixed point or not. */ void setFixed(V vertex, boolean fixed); /** * Check whether a vertex is a fixed point. * * It is the model's responsibility to make sure that changing the coordinates of a fixed point * by calling {@link #put(Object, Point2D)} has no effect. * * @param vertex the vertex * @return true if a fixed point, false otherwise */ boolean isFixed(V vertex); /** * Collect a map of all vertices locations. May return null if the model does not store * locations. * * @return a map with all the locations */ default Map collect() { Map map = new LinkedHashMap<>(); for (Map.Entry p : this) { map.put(p.getKey(), p.getValue()); } return map; } /** * Get an iterator with all vertices' locations. May return an empty iterator if the model does * not store locations. * * @return an iterator which returns all vertices with their locations. May return an empty * iterator if the model does not store locations. */ Iterator> iterator(); } ListenableLayoutModel2D.java000066400000000000000000000061521402514743400346020ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import java.util.*; import java.util.Map.*; import java.util.function.*; /** * A layout model wrapper which adds support for listeners. * * @author Dimitrios Michail * * @param the vertex type */ public class ListenableLayoutModel2D implements LayoutModel2D { protected LayoutModel2D model; protected List> listeners; /** * Create a new model * * @param model the underlying layout model */ public ListenableLayoutModel2D(LayoutModel2D model) { this.model = Objects.requireNonNull(model); this.listeners = new ArrayList<>(); } @Override public Box2D getDrawableArea() { return model.getDrawableArea(); } @Override public void setDrawableArea(Box2D drawableArea) { model.setDrawableArea(drawableArea); } @Override public Iterator> iterator() { return model.iterator(); } @Override public Point2D get(V vertex) { return model.get(vertex); } @Override public Point2D put(V vertex, Point2D point) { if (!model.isFixed(vertex)) { Point2D oldValue = model.put(vertex, point); notifyListeners(vertex, point); return oldValue; } else { return model.get(vertex); } } @Override public void setFixed(V vertex, boolean fixed) { model.setFixed(vertex, fixed); } @Override public boolean isFixed(V vertex) { return model.isFixed(vertex); } /** * Add a new listener. * * @param listener the listener to add * @return the newly added listener */ public BiConsumer addListener(BiConsumer listener) { listeners.add(listener); return listener; } /** * Remove a listener. * * @param listener the listener to remove * @return true if the listener was removed, false otherwise */ public boolean removeListener(BiConsumer listener) { return listeners.remove(listener); } /** * Notify all registered listeners. * * @param vertex the vertex * @param point the vertex location */ protected void notifyListeners(V vertex, Point2D point) { for (BiConsumer listener : listeners) { listener.accept(vertex, point); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/MapLayoutModel2D.java000066400000000000000000000044211402514743400333110ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import java.util.*; import java.util.Map.*; /** * A layout model which uses a hashtable to store the vertices' locations. * * @author Dimitrios Michail * * @param the vertex type */ public class MapLayoutModel2D implements LayoutModel2D { protected Box2D drawableArea; protected Map points; protected Set fixed; /** * Create a new model. * * @param drawableArea the drawable area */ public MapLayoutModel2D(Box2D drawableArea) { this.drawableArea = drawableArea; this.points = new LinkedHashMap<>(); this.fixed = new HashSet<>(); } @Override public Box2D getDrawableArea() { return drawableArea; } @Override public void setDrawableArea(Box2D drawableArea) { this.drawableArea = drawableArea; } @Override public Iterator> iterator() { return points.entrySet().iterator(); } @Override public Point2D get(V vertex) { return points.get(vertex); } @Override public Point2D put(V vertex, Point2D point) { boolean isFixed = fixed.contains(vertex); if (!isFixed) { return points.put(vertex, point); } return points.putIfAbsent(vertex, point); } @Override public void setFixed(V vertex, boolean fixed) { if (fixed) { this.fixed.add(vertex); } else { this.fixed.remove(vertex); } } @Override public boolean isFixed(V vertex) { return fixed.contains(vertex); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Point2D.java000066400000000000000000000054051402514743400315110ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import java.io.*; /** * A 2-dimensional point in Euclidean space. * * @author Dimitrios Michail */ public class Point2D implements Serializable { private static final long serialVersionUID = -5410937389829502498L; protected double x, y; /** * Create a new point * * @param x the x coordinate * @param y the y coordinate */ public Point2D(double x, double y) { this.x = x; this.y = y; } /** * Get the number of dimensions of the point * * @return the number of dimensions of the point */ public int getNumDimensions() { return 2; } /** * Get the x coordinate * * @return the x coordinate */ public double getX() { return x; } /** * Get the y coordinate * * @return the y coordinate */ public double getY() { return y; } @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(x); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(y); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Point2D other = (Point2D) obj; if (Double.doubleToLongBits(x) != Double.doubleToLongBits(other.x)) return false; if (Double.doubleToLongBits(y) != Double.doubleToLongBits(other.y)) return false; return true; } /** * Create a new point * * @param x the x coordinate * @param y the y coordinate * @return the point */ public static Point2D of(double x, double y) { return new Point2D(x, y); } @Override public String toString() { return "(" + x + ", " + y + ")"; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/Points.java000066400000000000000000000065341402514743400315120ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import org.jgrapht.alg.util.*; import java.util.function.*; /** * A collection of utilities to assist with point manipulation. * * @author Dimitrios Michail */ public abstract class Points { private static final ToleranceDoubleComparator TOLERANCE_DOUBLE_COMPARATOR = new ToleranceDoubleComparator(1e-9); /** * Compute the length of a vector. The length of vector $(x,y)$ is given by $\sqrt{x^2+y^2}$. * * @param v the vector * @return the length of a vector */ public static double length(Point2D v) { return Math.sqrt(v.getX() * v.getX() + v.getY() * v.getY()); } /** * Add 2-dimensional vectors * * @param a the first vector * @param b the second vector * @return the vector $a+b$ */ public static Point2D add(Point2D a, Point2D b) { return Point2D.of(a.getX() + b.getX(), a.getY() + b.getY()); } /** * Subtract 2-dimensional vectors * * @param a the first vector * @param b the second vector * @return the vector $a-b$ */ public static Point2D subtract(Point2D a, Point2D b) { return Point2D.of(a.getX() - b.getX(), a.getY() - b.getY()); } /** * Given a vector $a$ compute $-a$. * * @param a the vector * @return the vector $-a$ */ public static Point2D negate(Point2D a) { return scalarMultiply(a, -1.0); } /** * Multiply a vector with a scalar. * * @param a the vector * @param scalar the scalar * @return the result of scalar multiplication */ public static Point2D scalarMultiply(Point2D a, double scalar) { return scalarMultiply(a, scalar, (x, s) -> x * s); } /** * Multiply a vector with a scalar. * * @param a the vector * @param scalar the scalar * @param mult the multiplication operator * @return the result of scalar multiplication * * @param the scalar type */ public static < S> Point2D scalarMultiply(Point2D a, S scalar, BiFunction mult) { return Point2D.of(mult.apply(a.getX(), scalar), mult.apply(a.getY(), scalar)); } /** * Compare two points for equality using tolerance 1e-9. * * @param p1 the first point * @param p2 the second point * @return whether the two points are equal or not */ public static boolean equals(Point2D p1, Point2D p2) { int xEquals = TOLERANCE_DOUBLE_COMPARATOR.compare(p1.getX(), p2.getX()); if (xEquals != 0) { return false; } return TOLERANCE_DOUBLE_COMPARATOR.compare(p1.getY(), p2.getY()) == 0; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/model/package-info.java000066400000000000000000000001301402514743400325440ustar00rootroot00000000000000/** * Graph Drawing Basic Types and Models. */ package org.jgrapht.alg.drawing.model; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/drawing/package-info.java000066400000000000000000000000731402514743400314520ustar00rootroot00000000000000/** * Graph Drawing. */ package org.jgrapht.alg.drawing; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/000077500000000000000000000000001402514743400255775ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/BoykovKolmogorovMFImpl.java000066400000000000000000000777101402514743400330530ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.Graph; import org.jgrapht.alg.util.extension.ExtensionFactory; import java.util.*; /** * This is an implementation of the * * Boykov-Kolmogorov maximum flow algorithm. This algorithm is a special-purpose approach to * solving computer vision related maximum flow problems. The algorithm was initially described in: * Y. Boykov and V. Kolmogorov, "An experimental comparison of min-cut/max-flow algorithms for * energy minimization in vision," in IEEE Transactions on Pattern Analysis and Machine * Intelligence, vol. 26, no. 9, pp. 1124-1137, Sept. 2004, doi: 10.1109/TPAMI.2004.60.. An * extended description is given in: Vladimir Kolmogorov. 2004. Graph based algorithms for scene * reconstruction from two or more views. Ph.D. Dissertation. Cornell University, USA. Advisor(s) * Ramin Zabih. Order Number: AAI3114475.. *

* This implementation uses 2 heuristics described in Vladimir Kolmogorov's original PhD thesis: *

    *
  • Timestamp heuristic.
  • *
  • Distance heuristic;
  • *
*

* The worse-case running time of this algorithm on a network $G = (V, E)$ with a capacity function * $c: E \rightArrow R^{+}$ is $\mathcal{O}(E\times f)$, where $f$ is the maximum flow value. The * reason for this is that the algorithm doesn't necessarily augments shortest $s-t$ paths in a * residual network. That's why the argument about the running time complexity is the same as with * the Ford-Fulkerson algorithm. *

* This algorithm doesn't have the best performance on all types of networks. It's recommended to * check if this algorithm gives substantial performance improvement before using it in a particular * application. A good general-purpose alternative which works fast in all scenarios is the * {@link PushRelabelMFImpl}. *

* This algorithm works with both directed and undirected networks. The algorithm doesn't have * internal synchronization, thus any concurrent network modification has undefined behaviour. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov */ public class BoykovKolmogorovMFImpl extends MaximumFlowAlgorithmBase { /** * Whether to print debug related messages. */ private static final boolean DEBUG = false; /** * The timestamp used for free nodes. This value is the smallest among all node timestamps and * is assigned only to free vertices. */ private static final long FREE_NODE_TIMESTAMP = 0; /** * A timestamp for the first algorithm loop iteration. */ private static final long INITIAL_TIMESTAMP = 1; /** * The value of the current iteration timestamp. After each iteration, the current timestamp is * incremented. */ private long currentTimestamp; /** * Vertex extension factory used during initialization. */ private final ExtensionFactory vertexExtensionsFactory; /** * Edge extension factory used during initialization. */ private final ExtensionFactory edgeExtensionsFactory; /** * The network source of the current algorithm invocation. */ private VertexExtension currentSource; /** * The network sink of the current algorithm invocation. */ private VertexExtension currentSink; /** * The queue of active vertices. An active vertex is a network vertex which: (a) belongs to * source or sink flow tree. (b) has an outgoing edge with positive capacity, which target is a * free vertex. The active vertices are processed according to the FIFO principle. */ private final Deque activeVertices; /** * A list of orphans emerged after an s-t path augmentation. An orphan is a network node which * parent edge in the residual network flow tree became saturated. */ private final List orphans; /** * A queue of child orphans. A child orphan is a descendant of an orphan, which didn't get a new * parent in corresponding flow free. These child orphans have precedence over regular orphans * and are processed according to the FIFO principle. */ private final Deque childOrphans; /** * Creates a new algorithm instance with the specified {@code network}. The created algorithm * uses default epsilon. * * @param network flow network. */ public BoykovKolmogorovMFImpl(Graph network) { this(network, DEFAULT_EPSILON); } /** * Construct a new algorithm instance with the specifies {@code network} and {@code epsilon}. * * @param network flow network * @param epsilon tolerance for the comparison of floating point values */ public BoykovKolmogorovMFImpl(Graph network, double epsilon) { super(Objects.requireNonNull(network, "Network must be not null!"), epsilon); vertexExtensionsFactory = VertexExtension::new; edgeExtensionsFactory = AnnotatedFlowEdge::new; activeVertices = new ArrayDeque<>(); orphans = new ArrayList<>(); childOrphans = new ArrayDeque<>(); } /** * {@inheritDoc} */ @Override public MaximumFlow getMaximumFlow(V source, V sink) { this.calculateMaximumFlow(source, sink); maxFlow = composeFlow(); return new MaximumFlowImpl<>(maxFlowValue, maxFlow); } /** * Computes the maximum flow value. *

* This is the main algorithm loop. First, an algorithm initialization is performed. The * initialization includes augmenting all source-sink and source-node-sink paths. After that, * the algorithm finds the rest of the augmenting path by iteratively: *

* - growing the source and sink flow trees using active vertices - augmenting s-t paths using * bounding edges between source and sink flow trees. - adopting orphan nodes emerged after s-t * path augmentation. * * @param source network source * @param sink network sink. */ private void calculateMaximumFlow(V source, V sink) { super.init(source, sink, vertexExtensionsFactory, edgeExtensionsFactory); if (!network.containsVertex(source)) { throw new IllegalArgumentException("invalid source (null or not from this network)"); } if (!network.containsVertex(sink)) { throw new IllegalArgumentException("invalid sink (null or not from this network)"); } if (source.equals(sink)) { throw new IllegalArgumentException("source is equal to sink"); } currentSource = getVertexExtension(source); currentSink = getVertexExtension(sink); currentTimestamp = INITIAL_TIMESTAMP; augmentShortPaths(currentSource, currentSink); currentSource.treeStatus = VertexTreeStatus.SOURCE_TREE_VERTEX; currentSink.treeStatus = VertexTreeStatus.SINK_TREE_VERTEX; makeActive(currentSource); makeActive(currentSink); for (;;) { AnnotatedFlowEdge boundingEdge = grow(); if (boundingEdge == null) { break; } augment(boundingEdge); nextIteration(); adopt(); } } /** * Augments all source-sink and source-node-sink paths. This improved performance on the * computer vision maximum flow networks. * * @param source network source. * @param sink network sink. */ private void augmentShortPaths(VertexExtension source, VertexExtension sink) { for (AnnotatedFlowEdge sourceEdge : source.getOutgoing()) { VertexExtension mediumVertex = sourceEdge.getTarget(); if (mediumVertex == sink) { double flow = sourceEdge.getResidualCapacity(); pushFlowThrough(sourceEdge, flow); maxFlowValue += flow; } else { for (AnnotatedFlowEdge sinkEdge : mediumVertex.getOutgoing()) { VertexExtension targetVertex = sinkEdge.getTarget(); if (targetVertex == sink) { double flow = Math .min(sourceEdge.getResidualCapacity(), sinkEdge.getResidualCapacity()); pushFlowThrough(sourceEdge, flow); pushFlowThrough(sinkEdge, flow); maxFlowValue += flow; } // if all the capacity of the source edge was used, // it doesn't make sense to continue searching for s-t path if (!sourceEdge.hasCapacity()) { break; } } } } } /** * Performs an algorithm grow phase. *

* During the grow phase, the network active vertices are iteratively processed. The goal of * this processing is to find an (outgoing for source tree / incoming for sink tree) edge with * positive capacity which opposite node is either a free node or belongs to the other tree. In * the first case, the tree gets one more node, in the second case, a bounding edge is found and * the algorithm can proceed to the augment phase. *

* Since processing logic is different for source and sink trees, the code handles there cases * separately. This method returns either a bounding edge or {@code null}. The {@code null} * value can be returned only after all of the active vertices are processed and no bounding * edge is found. This means that the residual network is disconnected and the algorithm can * terminate. * * @return a bounding edge or {@code null} if no bounding edge exists. */ private AnnotatedFlowEdge grow() { for (VertexExtension activeVertex = nextActiveVertex(); activeVertex != null; activeVertex = nextActiveVertex()) { if (activeVertex.isSourceTreeVertex()) { // processing source tree vertex for (AnnotatedFlowEdge edge : activeVertex.getOutgoing()) { if (edge.hasCapacity()) { VertexExtension target = edge.getTarget(); if (target.isSinkTreeVertex()) { // found a bounding edge if (DEBUG) { System.out.printf("Bounding edge = %s\n\n", edge); } return edge; } else if (target.isFreeVertex()) { // found a node which can be added to the source tree if (DEBUG) { System.out .printf( "Growing source tree: %s -> %s\n\n", edge, target.prototype); } target.parentEdge = edge; target.treeStatus = VertexTreeStatus.SOURCE_TREE_VERTEX; target.distance = activeVertex.distance + 1; target.timestamp = activeVertex.timestamp; makeActive(target); } else { /* * The target node belongs to the source tree the distance heuristic can * be applied to possibly build a tree with smaller height. */ assert target.isSourceTreeVertex(); if (isCloserToTerminal(activeVertex, target)) { target.parentEdge = edge; target.distance = activeVertex.distance + 1; target.timestamp = activeVertex.timestamp; } } } } } else { assert activeVertex.isSinkTreeVertex(); // the logic for processing sink tree vertices is symmetrical for (AnnotatedFlowEdge edge : activeVertex.getOutgoing()) { if (edge.hasCapacity()) { VertexExtension source = edge.getSource(); if (source.isSourceTreeVertex()) { if (DEBUG) { System.out.printf("Bounding edge = %s\n\n", edge); } return edge; } else if (source.isFreeVertex()) { if (DEBUG) { System.out .printf( "Growing sink tree: %s -> %s\n\n", source.prototype, edge); } source.parentEdge = edge; source.treeStatus = VertexTreeStatus.SINK_TREE_VERTEX; source.distance = activeVertex.distance + 1; source.timestamp = activeVertex.timestamp; makeActive(source); } else { assert source.isSinkTreeVertex(); if (isCloserToTerminal(activeVertex, source)) { source.parentEdge = edge; source.distance = activeVertex.distance + 1; source.timestamp = activeVertex.timestamp; } } } } } // remove the vertex from the active vertex list finishVertex(activeVertex); } return null; } /** * Augments an s-t path specified using the {@code boundingEdge} and computes the set of tree * orphans emerged after augmentation. *

* First, the path flow bottleneck is found. Then the bottleneck flow value is pushed through * every path edge. If some path edge gets saturated, the corresponding tree node is added to * the orphan set. In the case the saturated edge connects source tree vertices, the edge target * becomes an orphan, otherwise if the saturated edge connects sink tree vertices, that the edge * source becomes an orphan. * * @param boundingEdge s-t path bounding edge between source and sink trees. */ private void augment(AnnotatedFlowEdge boundingEdge) { double bottleneck = findBottleneck(boundingEdge); if (DEBUG) { Deque pathEdges = new ArrayDeque<>(); pathEdges.addFirst(boundingEdge); VertexExtension debugSource = boundingEdge.getSource(); while (debugSource != currentSource) { pathEdges.addFirst(debugSource.parentEdge); debugSource = debugSource.parentEdge.getSource(); } VertexExtension debugTarget = boundingEdge.getTarget(); while (debugTarget != currentSink) { pathEdges.addLast(debugTarget.parentEdge); debugTarget = debugTarget.parentEdge.getTarget(); } System.out.printf("Pushing %.0f flow through path:\n", bottleneck); for (AnnotatedFlowEdge edge : pathEdges) { System.out .printf("(%s, %s) - ", edge.getSource().prototype, edge.getTarget().prototype); } System.out.println("\n"); } pushFlowThrough(boundingEdge, bottleneck); // pushing flow through source tree part of the path VertexExtension source = boundingEdge.getSource(); while (source != currentSource) { AnnotatedFlowEdge parentEdge = source.parentEdge; pushFlowThrough(parentEdge, bottleneck); if (!parentEdge.hasCapacity()) { source.makeOrphan(); orphans.add(source); } source = parentEdge.getSource(); } // pushing flow through sink tree part of the path VertexExtension target = boundingEdge.getTarget(); while (target != currentSink) { AnnotatedFlowEdge parentEdge = target.parentEdge; pushFlowThrough(target.parentEdge, bottleneck); if (!parentEdge.hasCapacity()) { target.makeOrphan(); orphans.add(target); } target = parentEdge.getTarget(); } maxFlowValue += bottleneck; } /** * Finds augmenting path bottleneck by traversing the path edges. * * @param boundingEdge s-t path bounding edge. * @return the computed bottleneck. */ private double findBottleneck(AnnotatedFlowEdge boundingEdge) { double bottleneck = boundingEdge.getResidualCapacity(); VertexExtension source = boundingEdge.getSource(); while (source != currentSource) { bottleneck = Math.min(bottleneck, source.parentEdge.getResidualCapacity()); source = source.parentEdge.getSource(); } VertexExtension target = boundingEdge.getTarget(); while (target != currentSink) { bottleneck = Math.min(bottleneck, target.parentEdge.getResidualCapacity()); target = target.parentEdge.getTarget(); } return bottleneck; } /** * Adopts all orphans. *

* Processing every orphan, the goal of this procedure is to either find a parent node within * the same tree, or identify that no such parent can be found, make the orphan a free vertex * and process all descendants of this node the same way. If multiple parents exist, the closest * to terminal is selected using distance and timestamp heuristic. */ private void adopt() { while (!orphans.isEmpty() || !childOrphans.isEmpty()) { VertexExtension currentVertex; // child orphans take precedence if (childOrphans.isEmpty()) { currentVertex = orphans.get(orphans.size() - 1); orphans.remove(orphans.size() - 1); } else { currentVertex = childOrphans.removeLast(); } if (currentVertex.isSourceTreeVertex()) { AnnotatedFlowEdge newParentEdge = null; int minDistance = Integer.MAX_VALUE; // find a parent edge which source has the smaller distance // to a terminal vertex according the distance heuristic for (AnnotatedFlowEdge edge : currentVertex.getOutgoing()) { if (edge.getInverse().hasCapacity()) { VertexExtension targetNode = edge.getTarget(); if (targetNode.isSourceTreeVertex() && hasConnectionToTerminal(targetNode)) { if (targetNode.distance < minDistance) { minDistance = targetNode.distance; newParentEdge = edge.getInverse(); } } } } if (newParentEdge == null) { if (DEBUG) { System.out.printf("Vertex %s becomes free\n\n", currentVertex.prototype); } // can't adopt this vertex currentVertex.timestamp = FREE_NODE_TIMESTAMP; currentVertex.treeStatus = VertexTreeStatus.FREE_VERTEX; for (AnnotatedFlowEdge edge : currentVertex.getOutgoing()) { VertexExtension targetVertex = edge.getTarget(); if (targetVertex.isSourceTreeVertex()) { if (edge.getInverse().hasCapacity()) { makeActive(targetVertex); } if (targetVertex.parentEdge == edge) { // target vertex is a child of the current vertex targetVertex.makeOrphan(); childOrphans.addFirst(targetVertex); } } } } else { if (DEBUG) { System.out .printf( "Vertex %s get's adopted via %s\n\n", currentVertex.prototype, newParentEdge); } // adopt this vertex makeCheckedInThisIteration(currentVertex); currentVertex.parentEdge = newParentEdge; currentVertex.distance = minDistance + 1; } } else { // current node is from sink tree // the processing logic is symmetrical assert currentVertex.isSinkTreeVertex(); AnnotatedFlowEdge newParentEdge = null; int minDistance = Integer.MAX_VALUE; for (AnnotatedFlowEdge edge : currentVertex.getOutgoing()) { if (edge.hasCapacity()) { VertexExtension targetNode = edge.getTarget(); if (targetNode.isSinkTreeVertex() && hasConnectionToTerminal(targetNode)) { if (targetNode.distance < minDistance) { minDistance = targetNode.distance; newParentEdge = edge; } } } } if (newParentEdge == null) { if (DEBUG) { System.out.printf("Vertex %s becomes free\n\n", currentVertex.prototype); } // can't adopt this vertex currentVertex.timestamp = FREE_NODE_TIMESTAMP; currentVertex.treeStatus = VertexTreeStatus.FREE_VERTEX; for (AnnotatedFlowEdge edge : currentVertex.getOutgoing()) { VertexExtension targetVertex = edge.getTarget(); if (targetVertex.isSinkTreeVertex()) { if (edge.hasCapacity()) { makeActive(targetVertex); } if (targetVertex.parentEdge == edge.getInverse()) { // target vertex is a child of the current vertex targetVertex.makeOrphan(); childOrphans.addFirst(targetVertex); } } } } else { if (DEBUG) { System.out .printf( "Vertex %s get's adopted via %s\n\n", currentVertex.prototype, newParentEdge); } // adopt this vertex makeCheckedInThisIteration(currentVertex); currentVertex.parentEdge = newParentEdge; currentVertex.distance = minDistance + 1; } } } } /** * Initializes a new algorithm iteration. */ private void nextIteration() { currentTimestamp++; makeCheckedInThisIteration(currentSource); makeCheckedInThisIteration(currentSink); } /** * Makes the {@code vertex} an active vertex. * * @param vertex network vertex. */ private void makeActive(VertexExtension vertex) { if (!vertex.active) { vertex.active = true; activeVertices.addFirst(vertex); } } /** * Returns the next active vertex to be processed. * * @return the next active vertex to be processed. */ private VertexExtension nextActiveVertex() { while (!activeVertices.isEmpty()) { VertexExtension nextActive = activeVertices.getLast(); assert nextActive.active; if (!nextActive.isFreeVertex()) { return nextActive; } else { activeVertices.removeLast(); nextActive.active = false; } } return null; } /** * Makes the {@code vertex} inactive. * * @param vertex network vertex. */ private void finishVertex(VertexExtension vertex) { assert activeVertices.getLast() == vertex; activeVertices.pollLast(); vertex.active = false; } /** * Sets the timestamp of the {@code vertex} equal to the {@code currentTimestamp}. * * @param vertex network vertex. */ private void makeCheckedInThisIteration(VertexExtension vertex) { vertex.timestamp = currentTimestamp; } /** * Checks if the distance of the {@code vertex} was updated during this iteration. * * @param vertex network vertex. * @return {@code true} if the distance of the {@code vertex} was updated in this iteration, * {@code false} otherwise. */ private boolean wasCheckedInThisIteration(VertexExtension vertex) { return vertex.timestamp == currentTimestamp; } /** * Checks if the {@code vertex} is connected to a terminal vertex (source or sink). * * @param vertex network vertex. * @return {@code true} if the {@code vertex} is connected to a terminal vertex, {@code false} * otherwise. */ private boolean hasConnectionToTerminal(VertexExtension vertex) { int distance = 0; for (VertexExtension currentVertex = vertex; currentVertex != currentSource && currentVertex != currentSink; currentVertex = currentVertex.getParent()) { if (currentVertex.parentEdge == null) { return false; } else if (wasCheckedInThisIteration(vertex)) { distance += currentVertex.distance; break; } distance++; } // update distance and timestamp values for every path vertex for (VertexExtension currentVertex = vertex; !wasCheckedInThisIteration(currentVertex); currentVertex = currentVertex.getParent()) { currentVertex.distance = distance; distance--; makeCheckedInThisIteration(currentVertex); } return true; } /** * Checks if the vertex {@code p} is closer to terminal than the vertex {@code t} using the * distance heuristic. * * @param p network vertex. * @param t network vertex. * @return {@code true} is {@code p} is closer to terminal than {@code t}, {@code false} * otherwise. */ private boolean isCloserToTerminal(VertexExtension p, VertexExtension t) { return p.timestamp >= t.timestamp && p.distance + 1 < t.distance; } /** * Returns a vertex extension which corresponds to the network {@code vertex}. * * @param vertex network vertex. * @return a vertex extension which corresponds to the network {@code vertex}. */ private VertexExtension getVertexExtension(V vertex) { return (VertexExtension) vertexExtensionManager.getExtension(vertex); } /** * Enum specifying vertex tree status */ private enum VertexTreeStatus { SOURCE_TREE_VERTEX { @Override public String toString() { return "SOURCE_TREE_VERTEX"; } }, SINK_TREE_VERTEX { @Override public String toString() { return "SINK_TREE_VERTEX"; } }, FREE_VERTEX { @Override public String toString() { return "FREE_VERTEX"; } }; public abstract String toString(); } /** * Network vertex extension used to store auxiliary vertex information. */ private class VertexExtension extends VertexExtensionBase { /** * This vertex timestamp. The timestamp is the last iteration in which the distance to * terminal of this vertex was updated. If this value isn't equal to the most recent * iteration index, the distance value may be outdated. */ long timestamp; /** * The distance of this vertex to a terminal vertex (network source or sink). This value may * not represent the actual distance as it's not updated every iteration. */ int distance; /** * If this vertex is in the active vertex list. */ boolean active; /** * Edge to the tree parent. */ AnnotatedFlowEdge parentEdge; /** * Tree status of this vertex. */ VertexTreeStatus treeStatus; /** * Creates a new free vertex. */ VertexExtension() { parentEdge = null; treeStatus = VertexTreeStatus.FREE_VERTEX; } /** * Checks if this vertex belongs to the source tree. * * @return {@code true} if this vertex belongs to the source tree, {@code false} otherwise. */ boolean isSourceTreeVertex() { return treeStatus == VertexTreeStatus.SOURCE_TREE_VERTEX; } /** * Checks if this vertex belongs to the sink tree. * * @return {@code true} if this vertex belongs to the sink tree, {@code false} otherwise. */ boolean isSinkTreeVertex() { return treeStatus == VertexTreeStatus.SINK_TREE_VERTEX; } /** * Checks if this vertex belongs to no tree, i.e. is a free vertex. * * @return {@code true} if this vertex is free, {@code false} otherwise. */ boolean isFreeVertex() { return treeStatus == VertexTreeStatus.FREE_VERTEX; } /** * Disconnects this vertex from its parent. */ void makeOrphan() { parentEdge = null; } /** * Returns the parent of this vertex. * * @return the parent of this vertex. */ VertexExtension getParent() { assert parentEdge != null; return this == parentEdge.getSource() ? parentEdge.getTarget() : parentEdge.getSource(); } /** * {@inheritDoc} */ @Override public String toString() { return String .format( "{%s}: parent_edge = %s, tree_status = %s, distance = %d, timestamp = %d", prototype, parentEdge == null ? "null" : String .format( "(%s, %s)", parentEdge.getSource().prototype, parentEdge.getTarget().prototype), treeStatus, distance, timestamp); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/DinicMFImpl.java000066400000000000000000000200771402514743400305430ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Kirill Vishnyakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.util.extension.*; import java.util.*; /** * Implementation of {@literal }Dinic * algorithm{@literal } with scaling for * {@literal }. * * The running time of the algorithm is $O(n^2m)$. * * Dinic algorithm firstly was mentioned in {@literal }DINIC, E. A. 1970. Algorithm for Solution * of a Problem of Maximum Flow in Networks With Power Estimation. Soviet Math. Dokl. 11, * 1277-1280.{@literal } * * Scheme of the algorithm: * * 1). Create a level graph. If we can't reach the sink return flow value. * * 2). Find a blocking flow $f'$ in the level graph. * * 3). Add $f'$ to the flow $f$. Move to the step $1$. * * @param the graph vertex type. * @param the graph edge type. * * @author Kirill Vishnyakov */ public class DinicMFImpl extends MaximumFlowAlgorithmBase { /** * Current source vertex. */ private VertexExtension currentSource; /** * Current sink vertex. */ private VertexExtension currentSink; private final ExtensionFactory vertexExtensionsFactory; private final ExtensionFactory edgeExtensionsFactory; /** * Constructor. Constructs a new network on which we will calculate the maximum flow, using * Dinic algorithm. * * @param network the network on which we calculate the maximum flow. * @param epsilon the tolerance for the comparison of floating point values. */ public DinicMFImpl(Graph network, double epsilon) { super(network, epsilon); this.vertexExtensionsFactory = VertexExtension::new; this.edgeExtensionsFactory = AnnotatedFlowEdge::new; if (epsilon <= 0) { throw new IllegalArgumentException("Epsilon must be positive!"); } for (E e : network.edgeSet()) { if (network.getEdgeWeight(e) < -epsilon) { throw new IllegalArgumentException("Capacity must be non-negative!"); } } } /** * Constructor. Constructs a new network on which we will calculate the maximum flow. * * @param network the network on which we calculate the maximum flow. */ public DinicMFImpl(Graph network) { this(network, DEFAULT_EPSILON); } @Override public MaximumFlow getMaximumFlow(V source, V sink) { this.calculateMaxFlow(source, sink); maxFlow = composeFlow(); return new MaximumFlowImpl<>(maxFlowValue, maxFlow); } /** * Assigns source to currentSource and sink to currentSink. Afterwards invokes dinic() method to * calculate the maximum flow in the network using Dinic algorithm with scaling. * * @param source source vertex. * @param sink sink vertex. * @return the value of the maximum flow in the network. */ private double calculateMaxFlow(V source, V sink) { super.init(source, sink, vertexExtensionsFactory, edgeExtensionsFactory); if (!network.containsVertex(source)) { throw new IllegalArgumentException("Network does not contain source!"); } if (!network.containsVertex(sink)) { throw new IllegalArgumentException("Network does not contain sink!"); } if (source.equals(sink)) { throw new IllegalArgumentException("Source is equal to sink!"); } currentSource = getVertexExtension(source); currentSink = getVertexExtension(sink); dinic(); return maxFlowValue; } /** * Creates a level graph. We can split all vertices of the graph in disjoint sets. In the same * set will lie vertices with equal distance from the source. It's obvious that level network * cannot contain edges $i \to j$, where $i$ and $j$ are two vertices for which holds: $|i.level * - j.level| > 1$. It follows from a property of the shortest paths. Level graph contains only * edges that lead from level $i$ to the level $i + 1$. Thus level graph does not contain * backward edges or edges that lead from $i$-th level to $i$-th. * * @return true, if level graph has been constructed(i.e we reached the sink), otherwise false. */ private boolean bfs() { for (V v : network.vertexSet()) { getVertexExtension(v).level = -1; } Queue queue = new ArrayDeque<>(); queue.offer(currentSource); currentSource.level = 0; while (!queue.isEmpty() && currentSink.level == -1) { VertexExtension v = queue.poll(); for (AnnotatedFlowEdge edge : v.getOutgoing()) { VertexExtension u = edge.getTarget(); if (comparator.compare(edge.flow, edge.capacity) < 0 && u.level == -1) { u.level = v.level + 1; queue.offer(u); } } } return currentSink.level != -1; } /** * Finds a blocking flow in the network. For each vertex we have a pointer on the first edge * which we can use to reach the sink. If we can't reach the sink using current edge, we * increment the pointer. So on each iteration we either saturate at least one edge or we * increment pointer. * * @param v current vertex. * @param flow we can push through. * @return value of the flow we can push. */ public double dfs(VertexExtension v, double flow) { if (comparator.compare(0.0, flow) == 0) { return flow; } if (v.equals(currentSink)) { return flow; } double pushed; while (v.index < v.getOutgoing().size()) { AnnotatedFlowEdge edge = v.getOutgoing().get(v.index); VertexExtension u = edge.getTarget(); if (comparator.compare(edge.flow, edge.capacity) < 0 && u.level == v.level + 1) { pushed = dfs(u, Math.min(flow, edge.capacity - edge.flow)); if (comparator.compare(pushed, 0.0) != 0) { pushFlowThrough(edge, pushed); return pushed; } } v.index++; } return 0; } /** * Runs Dinic algorithm with scaling. Construct a level graph, then find blocking flow and * finally increase the flow. */ public void dinic() { for (;;) { if (!bfs()) { break; } for (V v : network.vertexSet()) { getVertexExtension(v).index = 0; } while (true) { double pushed = dfs(currentSource, Double.POSITIVE_INFINITY); if (pushed == 0.0) { break; } maxFlowValue += pushed; } } } private VertexExtension getVertexExtension(V v) { return (VertexExtension) vertexExtensionManager.getExtension(v); } /** * Extension for vertex class. */ class VertexExtension extends VertexExtensionBase { /** * Stores index of the first unexplored edge from current vertex. */ int index; /** * Level of vertex in the level graph. */ int level; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/EdmondsKarpMFImpl.java000066400000000000000000000253431402514743400317250ustar00rootroot00000000000000/* * (C) Copyright 2008-2021, by Ilya Razenshteyn and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.util.extension.*; import java.util.*; /** * This class computes a maximum flow in a * flow network using * Edmonds-Karp algorithm. Given * is a weighted directed or undirected graph $G(V,E)$ with vertex set $V$ and edge set $E$. Each * edge $e\in E$ has an associated non-negative capacity $u_e$. The maximum flow problem involves * finding a feasible flow from a source vertex $s$ to a sink vertex $t$ which is maximum. The * amount of flow $f_e$ through any edge $e$ cannot exceed capacity $u_e$. Moreover, flow * conservation must hold: the sum of flows entering a node must equal the sum of flows exiting that * node, except for the source and the sink nodes. *

* Mathematically, the maximum flow problem is stated as follows: \[ \begin{align} \max~& * \sum_{e\in \delta^+(s)}f_e &\\ \mbox{s.t. }&\sum_{e\in \delta^-(i)} f_e=\sum_{e\in * \delta^+(i)} f_e & \forall i\in V\setminus\{s,t\}\\ &0\leq f_e \leq u_e & \forall * e\in E \end{align} \] Here $\delta^+(i)$ resp $\delta^-(i)$ denote resp the outgoing and incoming * edges of vertex $i$. *

* When the input graph is undirected, an edge $(i,j)$ is treated as two directed arcs: $(i,j)$ and * $(j,i)$. In such a case, there is the additional restriction that the flow can only go in one * direction: the flow either goes form $i$ to $j$, or from $j$ to $i$, but there cannot be a * positive flow on $(i,j)$ and $(j,i)$ simultaneously. *

* The runtime complexity of this class is $O(nm^2)$, where $n$ is the number of vertices and $m$ * the number of edges in the graph. For a more efficient algorithm, consider using * {@link PushRelabelMFImpl} instead. * *

* This class can also compute minimum s-t cuts. Effectively, to compute a minimum s-t cut, the * implementation first computes a minimum s-t flow, after which a BFS is run on the residual graph. * *

* For more details see Andrew V. Goldberg's Combinatorial Optimization (Lecture Notes). * * Note: even though the algorithm accepts any kind of graph, currently only Simple directed and * undirected graphs are supported (and tested!). * * @param the graph vertex type * @param the graph edge type * * @author Ilya Razensteyn */ public final class EdmondsKarpMFImpl extends MaximumFlowAlgorithmBase { /* current source vertex */ private VertexExtension currentSource; /* current sink vertex */ private VertexExtension currentSink; private final ExtensionFactory vertexExtensionsFactory; private final ExtensionFactory edgeExtensionsFactory; /** * Constructs MaximumFlow instance to work with a copy of * network. Current source and sink are set to null. If * network is weighted, then capacities are weights, otherwise all capacities are * equal to one. Doubles are compared using * DEFAULT_EPSILON tolerance. * * @param network network, where maximum flow will be calculated */ public EdmondsKarpMFImpl(Graph network) { this(network, DEFAULT_EPSILON); } /** * Constructs MaximumFlow instance to work with a copy of * network. Current source and sink are set to null. If * network is weighted, then capacities are weights, otherwise all capacities are * equal to one. * * @param network network, where maximum flow will be calculated * @param epsilon tolerance for comparing doubles */ public EdmondsKarpMFImpl(Graph network, double epsilon) { super(network, epsilon); this.vertexExtensionsFactory = () -> new VertexExtension(); this.edgeExtensionsFactory = () -> new AnnotatedFlowEdge(); if (network == null) { throw new NullPointerException("network is null"); } if (epsilon <= 0) { throw new IllegalArgumentException("invalid epsilon (must be positive)"); } for (E e : network.edgeSet()) { if (network.getEdgeWeight(e) < -epsilon) { throw new IllegalArgumentException("invalid capacity (must be non-negative)"); } } } /** * Sets current source to source, current sink to sink, then * calculates maximum flow from source to sink. Note, that * source and sink must be vertices of the * network passed to the constructor, and they must be different. * * @param source source vertex * @param sink sink vertex * * @return a maximum flow */ public MaximumFlow getMaximumFlow(V source, V sink) { this.calculateMaximumFlow(source, sink); maxFlow = composeFlow(); return new MaximumFlowImpl<>(maxFlowValue, maxFlow); } /** * Sets current source to source, current sink to sink, then * calculates maximum flow from source to sink. Note, that * source and sink must be vertices of the * network passed to the constructor, and they must be different. If desired, a flow map * can be queried afterwards; this will not require a new invocation of the algorithm. * * @param source source vertex * @param sink sink vertex * * @return the value of the maximum flow */ public double calculateMaximumFlow(V source, V sink) { super.init(source, sink, vertexExtensionsFactory, edgeExtensionsFactory); if (!network.containsVertex(source)) { throw new IllegalArgumentException("invalid source (null or not from this network)"); } if (!network.containsVertex(sink)) { throw new IllegalArgumentException("invalid sink (null or not from this network)"); } if (source.equals(sink)) { throw new IllegalArgumentException("source is equal to sink"); } currentSource = getVertexExtension(source); currentSink = getVertexExtension(sink); for (;;) { breadthFirstSearch(); if (!currentSink.visited) { break; } maxFlowValue += augmentFlow(); } return maxFlowValue; } /** * Method which finds a path from source to sink the in the residual graph. Note that this * method tries to find multiple paths at once. Once a single path has been discovered, no new * nodes are added to the queue, but nodes which are already in the queue are fully explored. As * such there's a chance that multiple paths are discovered. */ private void breadthFirstSearch() { for (V v : network.vertexSet()) { getVertexExtension(v).visited = false; getVertexExtension(v).lastArcs = null; } Queue queue = new ArrayDeque<>(); queue.offer(currentSource); currentSource.visited = true; currentSource.excess = Double.POSITIVE_INFINITY; currentSink.excess = 0.0; boolean seenSink = false; while (queue.size() != 0) { VertexExtension ux = queue.poll(); for (AnnotatedFlowEdge ex : ux.getOutgoing()) { if (comparator.compare(ex.flow, ex.capacity) < 0) { VertexExtension vx = ex.getTarget(); if (vx == currentSink) { vx.visited = true; if (vx.lastArcs == null) { vx.lastArcs = new ArrayList<>(); } vx.lastArcs.add(ex); vx.excess += Math.min(ux.excess, ex.capacity - ex.flow); seenSink = true; } else if (!vx.visited) { vx.visited = true; vx.excess = Math.min(ux.excess, ex.capacity - ex.flow); vx.lastArcs = Collections.singletonList(ex); if (!seenSink) { queue.add(vx); } } } } } } /** * For all paths which end in the sink. trace them back to the source and push flow through * them. * * @return total increase in flow from source to sink */ private double augmentFlow() { double flowIncrease = 0; Set seen = new HashSet<>(); for (AnnotatedFlowEdge ex : currentSink.lastArcs) { double deltaFlow = Math.min(ex.getSource().excess, ex.capacity - ex.flow); if (augmentFlowAlongInternal(deltaFlow, ex. getSource(), seen)) { pushFlowThrough(ex, deltaFlow); flowIncrease += deltaFlow; } } return flowIncrease; } private boolean augmentFlowAlongInternal( double deltaFlow, VertexExtension node, Set seen) { if (node == currentSource) { return true; } if (seen.contains(node)) { return false; } seen.add(node); AnnotatedFlowEdge prev = node.lastArcs.get(0); if (augmentFlowAlongInternal(deltaFlow, prev. getSource(), seen)) { pushFlowThrough(prev, deltaFlow); return true; } return false; } private VertexExtension getVertexExtension(V v) { return (VertexExtension) vertexExtensionManager.getExtension(v); } class VertexExtension extends VertexExtensionBase { boolean visited; // this mark is used during BFS to mark visited nodes List lastArcs; // last arc(-s) in the shortest path used to reach this // vertex } } GusfieldEquivalentFlowTree.java000066400000000000000000000212321402514743400336330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * This class computes an Equivalent Flow Tree (EFT) using the algorithm proposed by Dan Gusfield. * EFTs can be used to efficiently calculate the maximum flow for all pairs of vertices. The * algorithm is described in: Gusfield, D, Very simple methods for all pairs network flow * analysis. SIAM Journal on Computing, 19(1), p142-155, 1990
* In an undirected graph, there exist $frac{n(n-1)}{2}$ different vertex pairs. This class computes * the maximum flow between each of these pairs efficiently by performing exactly $(n-1)$ minimum * $s-t$ cut computations. If your application requires fewer than $(n-1)$ flow calculations, * consider computing the maximum flows manually through {@link MaximumFlowAlgorithm}. * * *

* The runtime complexity of this class is $O((V-1)Q)$, where $Q$ is the runtime complexity of the * algorithm used to compute $s-t$ cuts in the graph. By default, this class uses the * {@link PushRelabelMFImpl} implementation to calculate minimum $s-t$ cuts. This class has a * runtime complexity of $O(V^3)$, resulting in a $O(V^4)$ runtime complexity for the overal * algorithm. * * *

* Note: this class performs calculations in a lazy manner. The EFT is not calculated until the * first invocation of {@link GusfieldEquivalentFlowTree#getMaximumFlowValue(Object, Object)} or * {@link GusfieldEquivalentFlowTree#getEquivalentFlowTree()}. Moreover, this class only * calculates the value of the maximum flow between a source-destination pair; it does not calculate * the corresponding flow per edge. If you need to know the exact flow through an edge, use one of * the alternative {@link MaximumFlowAlgorithm} implementations. * *

* Warning: EFTs do not allow you to calculate minimum cuts for all pairs of vertex! For that, * Gomory-Hu cut trees are required! Use the {@link GusfieldGomoryHuCutTree} implementation instead. * *

* This class does not support changes to the underlying graph. The behavior of this class is * undefined when the graph is modified after instantiating this class. * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class GusfieldEquivalentFlowTree implements MaximumFlowAlgorithm { /* Number of vertices in the graph */ private final int N; /* Algorithm used to computed the Maximum s-t flows */ private final MinimumSTCutAlgorithm minimumSTCutAlgorithm; /* Data structures for computations */ private List vertexList = new ArrayList<>(); private Map indexMap = new HashMap<>(); private int[] p; // See vector p in the paper description private int[] neighbors; /* Matrix containing the flow values for every s-t pair */ private double[][] flowMatrix = null; private V lastInvokedSource = null; private V lastInvokedTarget = null; /** * Constructs a new GusfieldEquivalentFlowTree instance. * * @param network input graph */ public GusfieldEquivalentFlowTree(Graph network) { this(network, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } /** * Constructs a new GusfieldEquivalentFlowTree instance. * * @param network input graph * @param epsilon precision */ public GusfieldEquivalentFlowTree(Graph network, double epsilon) { this(network, new PushRelabelMFImpl<>(network, epsilon)); } /** * Constructs a new GusfieldEquivalentFlowTree instance. * * @param network input graph * @param minimumSTCutAlgorithm algorithm used to compute the minimum $s-t$ cuts */ public GusfieldEquivalentFlowTree( Graph network, MinimumSTCutAlgorithm minimumSTCutAlgorithm) { GraphTests.requireUndirected(network); this.N = network.vertexSet().size(); if (N < 2) throw new IllegalArgumentException("Graph must have at least 2 vertices"); this.minimumSTCutAlgorithm = minimumSTCutAlgorithm; vertexList.addAll(network.vertexSet()); for (int i = 0; i < vertexList.size(); i++) indexMap.put(vertexList.get(i), i); } /** * Runs the algorithm */ private void calculateEquivalentFlowTree() { flowMatrix = new double[N][N]; p = new int[N]; neighbors = new int[N]; for (int s = 1; s < N; s++) { int t = p[s]; neighbors[s] = t; double flowValue = minimumSTCutAlgorithm.calculateMinCut(vertexList.get(s), vertexList.get(t)); Set sourcePartition = minimumSTCutAlgorithm.getSourcePartition(); // Set X in the // paper for (int i = s; i < N; i++) if (sourcePartition.contains(vertexList.get(i)) && p[i] == t) p[i] = s; // populate the flow matrix flowMatrix[s][t] = flowMatrix[t][s] = flowValue; for (int i = 0; i < s; i++) if (i != t) flowMatrix[s][i] = flowMatrix[i][s] = Math.min(flowMatrix[s][t], flowMatrix[t][i]); } } /** * Returns the Equivalent Flow Tree as an actual tree (graph). Note that this tree is not * necessarily unique. The edge weights represent the flow values/cut weights. This method runs * in $O(n)$ time * * @return Equivalent Flow Tree */ public SimpleWeightedGraph getEquivalentFlowTree() { if (p == null) // Lazy invocation of the algorithm this.calculateEquivalentFlowTree(); SimpleWeightedGraph equivalentFlowTree = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(equivalentFlowTree, vertexList); for (int i = 1; i < N; i++) { DefaultWeightedEdge e = equivalentFlowTree.addEdge(vertexList.get(i), vertexList.get(neighbors[i])); equivalentFlowTree.setEdgeWeight(e, flowMatrix[i][neighbors[i]]); } return equivalentFlowTree; } /** * Unsupported operation * * @param source source of the flow inside the network * @param sink sink of the flow inside the network * * @return nothing */ @Override public MaximumFlow getMaximumFlow(V source, V sink) { throw new UnsupportedOperationException( "Flows calculated via Equivalent Flow trees only provide a maximum flow value, not the exact flow per edge/arc."); } /** * Returns the Maximum flow between source and sink. The algorithm is only executed once; * successive invocations of this method will return in $O(1)$ time. * * @param source source vertex * @param sink sink vertex * @return the Maximum flow between source and sink. */ @Override public double getMaximumFlowValue(V source, V sink) { assert indexMap.containsKey(source) && indexMap.containsKey(sink); lastInvokedSource = source; lastInvokedTarget = sink; if (p == null) // Lazy invocation of the algorithm this.calculateEquivalentFlowTree(); return flowMatrix[indexMap.get(source)][indexMap.get(sink)]; } /** * Unsupported operation * * @return nothing */ @Override public Map getFlowMap() { throw new UnsupportedOperationException( "Flows calculated via Equivalent Flow trees only provide a maximum flow value, not the exact flow per edge/arc."); } /** * Unsupported operation * * @param e edge * @return nothing */ @Override public V getFlowDirection(E e) { throw new UnsupportedOperationException( "Flows calculated via Equivalent Flow trees only provide a maximum flow value, not the exact flow per edge/arc."); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/GusfieldGomoryHuCutTree.java000066400000000000000000000342261402514743400332010ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * This class computes a Gomory-Hu tree (GHT) using the algorithm proposed by Dan Gusfield. For a * definition of GHTs, refer to: Gomory, R., Hu, T. Multi-terminal network flows. Journal of the * Socieity for Industrial and Applied mathematics, 9(4), p551-570, 1961. GHTs can be used to * efficiently query the maximum flows and minimum cuts for all pairs of vertices. The algorithm is * described in: Gusfield, D, Very simple methods for all pairs network flow analysis. SIAM * Journal on Computing, 19(1), p142-155, 1990
* In an undirected graph, there exist $\frac{n(n-1)}{2}$ different vertex pairs. This class * computes the maximum flow/minimum cut between each of these pairs efficiently by performing * exactly $(n-1)$ minimum $s-t$ cut computations. If your application needs fewer than $n-1$ * flow/cut computations, consider computing the maximum flows/minimum cuts manually through * {@link MaximumFlowAlgorithm}/{@link MinimumSTCutAlgorithm}. * * *

* The runtime complexity of this class is $O((V-1)Q)$, where $Q$ is the runtime complexity of the * algorithm used to compute $s-t$ cuts in the graph. By default, this class uses the * {@link PushRelabelMFImpl} implementation to calculate minimum s-t cuts. This class has a runtime * complexity of $O(V^3)$, resulting in a $O(V^4)$ runtime complexity for the overall algorithm. * * *

* Note: this class performs calculations in a lazy manner. The GHT is not calculated until the * first invocation of {@link GusfieldGomoryHuCutTree#getMaximumFlowValue(Object, Object)} or * {@link GusfieldGomoryHuCutTree#getGomoryHuTree()}. Moreover, this class only calculates * the value of the maximum flow between a source-destination pair; it does not calculate the * corresponding flow per edge. If you need to know the exact flow through an edge, use one of the * alternative {@link MaximumFlowAlgorithm} implementations. * *

* In contrast to an Equivalent Flow Tree ({@link GusfieldEquivalentFlowTree}), Gomory-Hu trees also * provide all minimum cuts for all pairs of vertices! * *

* This class does not support changes to the underlying graph. The behavior of this class is * undefined when the graph is modified after instantiating this class. * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class GusfieldGomoryHuCutTree implements MaximumFlowAlgorithm, MinimumSTCutAlgorithm { private final Graph network; /* Number of vertices in the graph */ private final int N; /* Algorithm used to computed the Maximum $s-t$ flows */ private final MinimumSTCutAlgorithm minimumSTCutAlgorithm; /* Data structures for computations */ private List vertexList = new ArrayList<>(); private Map indexMap = new HashMap<>(); private int[] p; // See vector p in the paper description private double[] fl; // See vector fl in the paper description /* Matrix containing the flow values for every $s-t$ pair */ private double[][] flowMatrix = null; private V lastInvokedSource = null; private V lastInvokedTarget = null; private Set sourcePartitionLastInvokedSource = null; private SimpleWeightedGraph gomoryHuTree = null; /** * Constructs a new GusfieldEquivalentFlowTree instance. * * @param network input graph */ public GusfieldGomoryHuCutTree(Graph network) { this(network, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } /** * Constructs a new GusfieldEquivalentFlowTree instance. * * @param network input graph * @param epsilon precision */ public GusfieldGomoryHuCutTree(Graph network, double epsilon) { this(network, new PushRelabelMFImpl<>(network, epsilon)); } /** * Constructs a new GusfieldEquivalentFlowTree instance. * * @param network input graph * @param minimumSTCutAlgorithm algorithm used to compute the minimum s-t cuts */ public GusfieldGomoryHuCutTree( Graph network, MinimumSTCutAlgorithm minimumSTCutAlgorithm) { this.network = GraphTests.requireUndirected(network); this.N = network.vertexSet().size(); if (N < 2) throw new IllegalArgumentException("Graph must have at least 2 vertices"); this.minimumSTCutAlgorithm = minimumSTCutAlgorithm; vertexList.addAll(network.vertexSet()); for (int i = 0; i < vertexList.size(); i++) indexMap.put(vertexList.get(i), i); } /** * Runs the algorithm */ private void calculateGomoryHuTree() { flowMatrix = new double[N][N]; p = new int[N]; fl = new double[N]; for (int s = 1; s < N; s++) { int t = p[s]; double flowValue = minimumSTCutAlgorithm.calculateMinCut(vertexList.get(s), vertexList.get(t)); Set sourcePartition = minimumSTCutAlgorithm.getSourcePartition(); // Set X in the // paper fl[s] = flowValue; for (int i = 0; i < N; i++) if (i != s && sourcePartition.contains(vertexList.get(i)) && p[i] == t) p[i] = s; if (sourcePartition.contains(vertexList.get(p[t]))) { p[s] = p[t]; p[t] = s; fl[s] = fl[t]; fl[t] = flowValue; } // populate the flow matrix flowMatrix[s][t] = flowMatrix[t][s] = flowValue; for (int i = 0; i < s; i++) if (i != t) flowMatrix[s][i] = flowMatrix[i][s] = Math.min(flowMatrix[s][t], flowMatrix[t][i]); } } /** * Returns the Gomory-Hu Tree as an actual tree (graph). Note that this tree is not necessarily * unique. The edge weights represent the flow values/cut weights. This method runs in $O(n)$ * time. * * @return Gomory-Hu Tree */ public SimpleWeightedGraph getGomoryHuTree() { if (p == null) // Lazy invocation of the algorithm this.calculateGomoryHuTree(); // Compute the tree from scratch. Since we compute a new tree, the user is free to modify // this tree. SimpleWeightedGraph gomoryHuTree = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(gomoryHuTree, vertexList); for (int i = 1; i < N; i++) { Graphs.addEdge(gomoryHuTree, vertexList.get(i), vertexList.get(p[i]), fl[i]); } return gomoryHuTree; } /* ================== Maximum Flow ================== */ /** * Unsupported operation * * @param source source of the flow inside the network * @param sink sink of the flow inside the network * * @return nothing */ @Override public MaximumFlow getMaximumFlow(V source, V sink) { throw new UnsupportedOperationException( "Flows calculated via Gomory-Hu trees only provide a maximum flow value, not the exact flow per edge/arc."); } /** * Returns the Maximum flow between source and sink. The algorithm is only executed once; * successive invocations of this method will return in $O(1)$ time. * * @param source source vertex * @param sink sink vertex * @return the Maximum flow between source and sink. */ @Override public double getMaximumFlowValue(V source, V sink) { assert indexMap.containsKey(source) && indexMap.containsKey(sink); lastInvokedSource = source; lastInvokedTarget = sink; sourcePartitionLastInvokedSource = null; gomoryHuTree = null; if (p == null) // Lazy invocation of the algorithm this.calculateGomoryHuTree(); return flowMatrix[indexMap.get(source)][indexMap.get(sink)]; } /** * Unsupported operation * * @return nothing */ @Override public Map getFlowMap() { throw new UnsupportedOperationException( "Flows calculated via Gomory-Hu trees only provide a maximum flow value, not the exact flow per edge/arc."); } /** * Unsupported operation * * @param e edge * @return nothing */ @Override public V getFlowDirection(E e) { throw new UnsupportedOperationException( "Flows calculated via Gomory-Hu trees only provide a maximum flow value, not the exact flow per edge/arc."); } /* ================== Minimum Cut ================== */ @Override public double calculateMinCut(V source, V sink) { return getMaximumFlowValue(source, sink); } /** * Calculates the minimum cut in the graph, that is, the minimum cut over all $s-t$ pairs. The * same result can be obtained with the {@link org.jgrapht.alg.StoerWagnerMinimumCut} * implementation. After invoking this method, the source/sink partitions corresponding to the * minimum cut can be queried through the {@link #getSourcePartition()} and * {@link #getSinkPartition()} methods. After computing the Gomory-Hu Cut tree, this method runs * in $O(N)$ time. * * @return weight of the minimum cut in the graph */ public double calculateMinCut() { if (this.gomoryHuTree == null) this.gomoryHuTree = this.getGomoryHuTree(); DefaultWeightedEdge cheapestEdge = gomoryHuTree .edgeSet().stream().min(Comparator.comparing(gomoryHuTree::getEdgeWeight)) .orElseThrow(() -> new RuntimeException("graph is empty?!")); lastInvokedSource = gomoryHuTree.getEdgeSource(cheapestEdge); lastInvokedTarget = gomoryHuTree.getEdgeTarget(cheapestEdge); sourcePartitionLastInvokedSource = null; return gomoryHuTree.getEdgeWeight(cheapestEdge); } @Override public double getCutCapacity() { return calculateMinCut(lastInvokedSource, lastInvokedTarget); } @Override public Set getSourcePartition() { if (sourcePartitionLastInvokedSource != null) return sourcePartitionLastInvokedSource; if (this.gomoryHuTree == null) this.gomoryHuTree = this.getGomoryHuTree(); Set pathEdges = this.findPathBetween(gomoryHuTree, lastInvokedSource, lastInvokedTarget); DefaultWeightedEdge cheapestEdge = pathEdges .stream().min(Comparator.comparing(gomoryHuTree::getEdgeWeight)) .orElseThrow(() -> new RuntimeException("path is empty?!")); // Remove the selected edge from the gomoryHuTree graph. The resulting graph consists of 2 // components V source = gomoryHuTree.getEdgeSource(cheapestEdge); V target = gomoryHuTree.getEdgeTarget(cheapestEdge); gomoryHuTree.removeEdge(cheapestEdge); // Return the vertices in the component with the source vertex sourcePartitionLastInvokedSource = new ConnectivityInspector<>(gomoryHuTree).connectedSetOf(lastInvokedSource); // Restore the internal tree structure by putting the edge back gomoryHuTree.addEdge(source, target, cheapestEdge); return sourcePartitionLastInvokedSource; } /** * BFS method to find the edges in the shortest path from a source to a target vertex in a tree * graph. * * @param tree input graph * @param source source * @param target target * @return edges constituting the shortest path between source and target */ private Set findPathBetween( SimpleWeightedGraph tree, V source, V target) { boolean[] visited = new boolean[vertexList.size()]; Map predecessorMap = new HashMap(); Queue queue = new ArrayDeque<>(); queue.add(source); boolean found = false; while (!found && !queue.isEmpty()) { V next = queue.poll(); for (V v : Graphs.neighborListOf(tree, next)) { if (!visited[indexMap.get(v)]) { predecessorMap.put(v, next); queue.add(v); } if (v == target) { found = true; break; } } visited[indexMap.get(next)] = true; } Set edges = new LinkedHashSet<>(); V v = target; while (v != source) { V pred = predecessorMap.get(v); edges.add(tree.getEdge(v, pred)); v = pred; } return edges; } @Override public Set getSinkPartition() { Set sinkPartition = new LinkedHashSet<>(network.vertexSet()); sinkPartition.removeAll(this.getSourcePartition()); return sinkPartition; } @Override public Set getCutEdges() { Set cutEdges = new LinkedHashSet<>(); Set sourcePartion = this.getSourcePartition(); for (E e : network.edgeSet()) { V source = network.getEdgeSource(e); V sink = network.getEdgeTarget(e); if (sourcePartion.contains(source) ^ sourcePartion.contains(sink)) cutEdges.add(e); } return cutEdges; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/MaximumFlowAlgorithmBase.java000066400000000000000000000401611402514743400333530ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Alexey Kudinkin, Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.util.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.alg.util.extension.*; import java.util.*; import java.util.stream.*; /** * Base class backing algorithms allowing to derive * maximum-flow from the supplied * flow network * * @param the graph vertex type * @param the graph edge type * * @author Alexey Kudinkin * @author Joris Kinable */ public abstract class MaximumFlowAlgorithmBase implements MaximumFlowAlgorithm, MinimumSTCutAlgorithm { /** * Default tolerance. */ public static final double DEFAULT_EPSILON = 1e-9; /* input network */ protected Graph network; /* indicates whether the input graph is directed or not */ protected final boolean directedGraph; /* Used to compare floating point values */ protected Comparator comparator; protected ExtensionManager vertexExtensionManager; protected ExtensionManager edgeExtensionManager; /* Source used during the last invocation of this algorithm */ protected V source = null; /* Sink used during the last invocation of this algorithm */ protected V sink = null; /* Max flow established after last invocation of the algorithm. */ protected double maxFlowValue = -1; /* Mapping of the flow on each edge. */ protected Map maxFlow = null; /* Source parition of S-T cut */ protected Set sourcePartition; /* Sink parition of S-T cut */ protected Set sinkPartition; /* Cut edges */ protected Set cutEdges; /** * Construct a new maximum flow * * @param network the network * @param epsilon the tolerance for the comparison of floating point values */ public MaximumFlowAlgorithmBase(Graph network, double epsilon) { this.network = network; this.directedGraph = network.getType().isDirected(); this.comparator = new ToleranceDoubleComparator(epsilon); } /** * Prepares all data structures to start a new invocation of the Maximum Flow or Minimum Cut * algorithms * * @param source source * @param sink sink * @param vertexExtensionFactory vertex extension factory * @param edgeExtensionFactory edge extension factory * @param vertex extension type */ protected void init( V source, V sink, ExtensionFactory vertexExtensionFactory, ExtensionFactory edgeExtensionFactory) { vertexExtensionManager = new ExtensionManager<>(vertexExtensionFactory); edgeExtensionManager = new ExtensionManager<>(edgeExtensionFactory); buildInternal(); this.source = source; this.sink = sink; maxFlowValue = 0; maxFlow = null; sourcePartition = null; sinkPartition = null; cutEdges = null; } /** * Create internal data structure */ private void buildInternal() { if (directedGraph) { // Directed graph for (V v : network.vertexSet()) { VertexExtensionBase vx = vertexExtensionManager.getExtension(v); vx.prototype = v; } for (V u : network.vertexSet()) { VertexExtensionBase ux = vertexExtensionManager.getExtension(u); for (E e : network.outgoingEdgesOf(u)) { V v = network.getEdgeTarget(e); VertexExtensionBase vx = vertexExtensionManager.getExtension(v); AnnotatedFlowEdge forwardEdge = createEdge(ux, vx, e, network.getEdgeWeight(e)); AnnotatedFlowEdge backwardEdge = createBackwardEdge(forwardEdge); ux.getOutgoing().add(forwardEdge); if (backwardEdge.prototype == null) { vx.getOutgoing().add(backwardEdge); } } } } else { // Undirected graph for (V v : network.vertexSet()) { VertexExtensionBase vx = vertexExtensionManager.getExtension(v); vx.prototype = v; } for (E e : network.edgeSet()) { VertexExtensionBase ux = vertexExtensionManager.getExtension(network.getEdgeSource(e)); VertexExtensionBase vx = vertexExtensionManager.getExtension(network.getEdgeTarget(e)); AnnotatedFlowEdge forwardEdge = createEdge(ux, vx, e, network.getEdgeWeight(e)); AnnotatedFlowEdge backwardEdge = createBackwardEdge(forwardEdge); ux.getOutgoing().add(forwardEdge); vx.getOutgoing().add(backwardEdge); } } } private AnnotatedFlowEdge createEdge( VertexExtensionBase source, VertexExtensionBase target, E e, double weight) { AnnotatedFlowEdge ex = edgeExtensionManager.getExtension(e); ex.source = source; ex.target = target; ex.capacity = weight; ex.prototype = e; return ex; } private AnnotatedFlowEdge createBackwardEdge(AnnotatedFlowEdge forwardEdge) { AnnotatedFlowEdge backwardEdge; E backwardPrototype = network.getEdge(forwardEdge.target.prototype, forwardEdge.source.prototype); if (directedGraph && backwardPrototype != null) { // if edge exists in directed input graph backwardEdge = createEdge( forwardEdge.target, forwardEdge.source, backwardPrototype, network.getEdgeWeight(backwardPrototype)); } else { backwardEdge = edgeExtensionManager.createExtension(); backwardEdge.source = forwardEdge.target; backwardEdge.target = forwardEdge.source; if (!directedGraph) { // Undirected graph: if (u,v) exists, then so much (v,u) backwardEdge.capacity = network.getEdgeWeight(backwardPrototype); backwardEdge.prototype = backwardPrototype; } } forwardEdge.inverse = backwardEdge; backwardEdge.inverse = forwardEdge; return backwardEdge; } /** * Increase flow in the direction denoted by edge $(u,v)$. Any existing flow in the reverse * direction $(v,u)$ gets reduced first. More precisely, let $f_2$ be the existing flow in the * direction $(v,u)$, and $f_1$ be the desired increase of flow in direction $(u,v)$. If $f_1 * \geq f_2$, then the flow on $(v,u)$ becomes $0$, and the flow on $(u,v)$ becomes $f_1-f_2$. * Else, if $f_1 \textlptr f_2$, the flow in the direction $(v, u)$ is reduced, i.e. the flow on * $(v, u)$ becomes $f_2 - f_1$, whereas the flow on $(u,v)$ remains zero. * * @param edge desired direction in which the flow is increased * @param flow increase of flow in the the direction indicated by the forwardEdge */ protected void pushFlowThrough(AnnotatedFlowEdge edge, double flow) { AnnotatedFlowEdge inverseEdge = edge.getInverse(); assert ((comparator.compare(edge.flow, 0.0) == 0) || (comparator.compare(inverseEdge.flow, 0.0) == 0)); if (comparator.compare(inverseEdge.flow, flow) < 0) { // If f_1 >= f_2 double flowDifference = flow - inverseEdge.flow; edge.flow += flowDifference; edge.capacity -= inverseEdge.flow; // Capacity on edge (u,v) PLUS flow on (v,u) gives // the MAXIMUM flow in the direction (u,v) i.e // edge.weight in the graph 'network'. inverseEdge.flow = 0; inverseEdge.capacity += flowDifference; } else { // If f1 < f2 edge.capacity -= flow; inverseEdge.flow -= flow; } } /** * Create a map which specifies for each edge in the input map the amount of flow that flows * through it * * @return a map which specifies for each edge in the input map the amount of flow that flows * through it */ protected Map composeFlow() { Map maxFlow = new HashMap<>(); for (E e : network.edgeSet()) { AnnotatedFlowEdge annotatedFlowEdge = edgeExtensionManager.getExtension(e); maxFlow .put( e, directedGraph ? annotatedFlowEdge.flow : Math.max(annotatedFlowEdge.flow, annotatedFlowEdge.inverse.flow)); } return maxFlow; } class VertexExtensionBase implements Extension { private final List outgoing = new ArrayList<>(); V prototype; double excess; public List getOutgoing() { return outgoing; } } class AnnotatedFlowEdge implements Extension { /* Edge source */ private VertexExtensionBase source; /* Edge target */ private VertexExtensionBase target; /* Inverse edge */ private AnnotatedFlowEdge inverse; E prototype; // Edge double capacity; // Maximum by which the flow in the direction can be increased (on top of // the flow already in this direction). double flow; // Flow in the direction denoted by this edge public VE getSource() { return TypeUtil.uncheckedCast(source); } public void setSource(VertexExtensionBase source) { this.source = source; } public VE getTarget() { return TypeUtil.uncheckedCast(target); } public void setTarget(VertexExtensionBase target) { this.target = target; } public AnnotatedFlowEdge getInverse() { return inverse; } public boolean hasCapacity() { return comparator.compare(capacity, flow) > 0; } public double getResidualCapacity() { return capacity - flow; } @Override public String toString() { return "(" + (source == null ? null : source.prototype) + "," + (target == null ? null : target.prototype) + ",c:" + capacity + " f: " + flow + ")"; } } /** * Returns current source vertex, or null if there was no * calculateMaximumFlow calls. * * @return current source */ public V getCurrentSource() { return source; } /** * Returns current sink vertex, or null if there was no * calculateMaximumFlow calls. * * @return current sink */ public V getCurrentSink() { return sink; } /** * Returns maximum flow value, that was calculated during last * calculateMaximumFlow call. * * @return maximum flow value */ public double getMaximumFlowValue() { return maxFlowValue; } /** * Returns maximum flow, that was calculated during last * calculateMaximumFlow call, or null, if there was no * calculateMaximumFlow calls. * * @return read-only mapping from edges to doubles - flow values */ public Map getFlowMap() { if (maxFlow == null) // Lazily calculate the max flow map maxFlow = composeFlow(); return maxFlow; } /** * Returns the direction of the flow on an edge $(u,v)$. In case $(u,v)$ is a directed edge * (arc), this function will always return the edge target $v$. However, if $(u,v)$ is an edge * in an undirected graph, flow may go through the edge in either side. If the flow goes from * $u$ to $v$, we return $v$, otherwise $u$. If the flow on an edge equals $0$, the returned * value has no meaning. * * @param e edge * @return the vertex where the flow leaves the edge */ public V getFlowDirection(E e) { if (!network.containsEdge(e)) throw new IllegalArgumentException( "Cannot query the flow on an edge which does not exist in the input graph!"); AnnotatedFlowEdge annotatedFlowEdge = edgeExtensionManager.getExtension(e); if (directedGraph) return annotatedFlowEdge.getTarget().prototype; AnnotatedFlowEdge inverseEdge = annotatedFlowEdge.getInverse(); if (annotatedFlowEdge.flow > inverseEdge.flow) return annotatedFlowEdge.getTarget().prototype; else return inverseEdge.getTarget().prototype; } /*---------------- Minimum s-t cut related methods -------------------*/ @Override public double calculateMinCut(V source, V sink) { return this.getMaximumFlowValue(source, sink); } @Override public double getCutCapacity() { return getMaximumFlowValue(); } @Override public Set getSourcePartition() { if (sourcePartition == null) calculateSourcePartition(); return sourcePartition; } @Override public Set getSinkPartition() { if (sinkPartition == null) { sinkPartition = new LinkedHashSet<>(network.vertexSet()); sinkPartition.removeAll(this.getSourcePartition()); } return sinkPartition; } @Override public Set getCutEdges() { if (cutEdges != null) return cutEdges; cutEdges = new LinkedHashSet<>(); Set p1 = getSourcePartition(); if (directedGraph) { for (V vertex : p1) { cutEdges .addAll( network .outgoingEdgesOf(vertex).stream() .filter(edge -> !p1.contains(network.getEdgeTarget(edge))) .collect(Collectors.toList())); } } else { cutEdges .addAll( network .edgeSet().stream() .filter( e -> p1.contains(network.getEdgeSource(e)) ^ p1.contains(network.getEdgeTarget(e))) .collect(Collectors.toList())); } return cutEdges; } /** * Calculate the set of reachable vertices from $s$ in the residual graph. */ protected void calculateSourcePartition() { // the source partition contains all vertices reachable from s in the residual graph this.sourcePartition = new LinkedHashSet<>(); Queue processQueue = new ArrayDeque<>(); processQueue.add(vertexExtensionManager.getExtension(getCurrentSource())); while (!processQueue.isEmpty()) { VertexExtensionBase vx = processQueue.poll(); if (sourcePartition.contains(vx.prototype)) continue; sourcePartition.add(vx.prototype); for (AnnotatedFlowEdge ex : vx.getOutgoing()) { if (ex.hasCapacity()) processQueue.add(ex.getTarget()); } } } } PadbergRaoOddMinimumCutset.java000066400000000000000000000333131402514743400335470ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * Implementation of the algorithm by Padberg and Rao to compute Odd Minimum Cut-Sets. Let $G=(V,E)$ * be an undirected, simple weighted graph, where all edge weights are positive. Let $T \subset V$ * with $|T|$ even, be a set of vertices that are labelled odd. A cut-set $(U:V-U)$ is called * odd if $|T \cap U|$ is an odd number. Let $c(U:V-U)$ be the weight of the cut, that is, the sum * of weights of the edges which have exactly one endpoint in $U$ and one endpoint in $V-U$. The * problem of finding an odd minimum cut-set in $G$ is stated as follows: Find $W \subseteq V$ such * that $c(W:V-W)=min(c(U:V-U)|U \subseteq V, |T \cap U|$ is odd). * *

* The algorithm has been published in: Padberg, M. Rao, M. Odd Minimum Cut-Sets and b-Matchings. * Mathematics of Operations Research, 7(1), p67-80, 1982. A more concise description is published * in: Letchford, A. Reinelt, G. Theis, D. Odd minimum cut-sets and b-matchings revisited. SIAM * Journal of Discrete Mathematics, 22(4), p1480-1487, 2008. * *

* The runtime complexity of this algorithm is dominated by the runtime complexity of the algorithm * used to compute A Gomory-Hu tree on graph $G$. Consequently, the runtime complexity of this class * is $O(V^4)$. * *

* This class does not support changes to the underlying graph. The behavior of this class is * undefined when the graph is modified after instantiating this class. * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class PadbergRaoOddMinimumCutset { /* Input graph */ private final Graph network; /* Set of vertices which are labeled 'odd' (set T in the paper) */ private Set oddVertices; /* Algorithm used to calculate the Gomory-Hu Cut-tree */ private final GusfieldGomoryHuCutTree gusfieldGomoryHuCutTreeAlgorithm; /* The Gomory-Hu tree */ private SimpleWeightedGraph gomoryHuTree; /* Weight of the minimum odd cut-set */ private double minimumCutWeight = Double.MAX_VALUE; /* Source partition constituting the minimum odd cut-set */ private Set sourcePartitionMinimumCut; /** * Creates a new instance of the PadbergRaoOddMinimumCutset algorithm. * * @param network input graph */ public PadbergRaoOddMinimumCutset(Graph network) { this(network, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } /** * Creates a new instance of the PadbergRaoOddMinimumCutset algorithm. * * @param network input graph * @param epsilon tolerance */ public PadbergRaoOddMinimumCutset(Graph network, double epsilon) { this(network, new PushRelabelMFImpl<>(network, epsilon)); } /** * Creates a new instance of the PadbergRaoOddMinimumCutset algorithm. * * @param network input graph * @param minimumSTCutAlgorithm algorithm used to calculate the Gomory-Hu tree */ public PadbergRaoOddMinimumCutset( Graph network, MinimumSTCutAlgorithm minimumSTCutAlgorithm) { this.network = GraphTests.requireUndirected(network); gusfieldGomoryHuCutTreeAlgorithm = new GusfieldGomoryHuCutTree<>(network, minimumSTCutAlgorithm); } /** * Calculates the minimum odd cut. The implementation follows Algorithm 1 in the paper Odd * minimum cut sets and b-matchings revisited by Adam Letchford, Gerhard Reinelt and Dirk Theis. * The original algorithm runs on a compressed Gomory-Hu tree: a cut-tree with the odd vertices * as terminal vertices. This tree has $|T|-1$ edges as opposed to $|V|-1$ for a Gomory-Hu tree * defined on the input graph $G$. This compression step can however be skipped. If you want to * run the original algorithm in the paper, set the parameter useTreeCompression to * true. Alternatively, experiment which setting of this parameter produces the fastest results. * Both settings are guaranteed to find the optimal cut. Experiments on random graphs showed * that setting useTreeCompression to false was on average a bit faster. * * @param oddVertices Set of vertices which are labeled 'odd'. Note that the number of vertices * in this set must be even! * @param useTreeCompression parameter indicating whether tree compression should be used * (recommended: false). * @return weight of the minimum odd cut. */ public double calculateMinCut(Set oddVertices, boolean useTreeCompression) { minimumCutWeight = Double.MAX_VALUE; this.oddVertices = oddVertices; if (oddVertices.size() % 2 == 1) throw new IllegalArgumentException("There needs to be an even number of odd vertices"); assert network.vertexSet().containsAll(oddVertices); // All odd vertices must be contained // in the graph // all edge weights must be non-negative assert network.edgeSet().stream().noneMatch(e -> network.getEdgeWeight(e) < 0); gomoryHuTree = gusfieldGomoryHuCutTreeAlgorithm.getGomoryHuTree(); if (useTreeCompression) return calculateMinCutWithTreeCompression(); else return calculateMinCutWithoutTreeCompression(); } /** * Modified implementation of the algorithm proposed in Odd Minimum Cut-sets and b-matchings by * Padberg and Rao. The optimal cut is directly computed on the Gomory-Hu tree computed for * graph $G$. This approach iterates efficiently over all possible cuts of the graph (there are * $|V|$ such cuts). * * @return weight of the minimum odd cut. */ private double calculateMinCutWithoutTreeCompression() { Set edges = new LinkedHashSet<>(gomoryHuTree.edgeSet()); for (DefaultWeightedEdge edge : edges) { V source = gomoryHuTree.getEdgeSource(edge); V target = gomoryHuTree.getEdgeTarget(edge); double edgeWeight = gomoryHuTree.getEdgeWeight(edge); if (edgeWeight >= minimumCutWeight) continue; gomoryHuTree.removeEdge(edge); // Temporarily remove edge Set sourcePartition = new ConnectivityInspector<>(gomoryHuTree).connectedSetOf(source); if (PadbergRaoOddMinimumCutset.isOddVertexSet(sourcePartition, oddVertices)) { // If the // source // partition // forms // an odd // cutset, // check // whether // the // cut // isn't // better // than // the // one we // already // found. minimumCutWeight = edgeWeight; sourcePartitionMinimumCut = sourcePartition; } gomoryHuTree.addEdge(source, target, edge); // Place edge back } return minimumCutWeight; } /** * Implementation of the algorithm proposed in Odd Minimum Cut-sets and b-matchings by Padberg * and Rao. The algorithm evaluates at most $|T|$ cuts in the Gomory-Hu tree. * * @return weight of the minimum odd cut. */ private double calculateMinCutWithTreeCompression() { Queue> queue = new ArrayDeque<>(); queue.add(oddVertices); // Keep splitting the clusters until each resulting cluster containes exactly one vertex. while (!queue.isEmpty()) { Set nextCluster = queue.poll(); this.splitCluster(nextCluster, queue); } return minimumCutWeight; } /** * Takes a set of odd vertices with cardinality $2$ or more, and splits them into $2$ new * non-empty sets. * * @param cluster group of odd vertices * @param queue clusters with cardinality $2$ or more */ private void splitCluster(Set cluster, Queue> queue) { assert cluster.size() >= 2; // Choose 2 random odd nodes Iterator iterator = cluster.iterator(); V oddNode1 = iterator.next(); V oddNode2 = iterator.next(); // Calculate the minimum cut separating these two nodes. double cutWeight = gusfieldGomoryHuCutTreeAlgorithm.calculateMinCut(oddNode1, oddNode2); Set sourcePartition = null; if (cutWeight < minimumCutWeight) { sourcePartition = gusfieldGomoryHuCutTreeAlgorithm.getSourcePartition(); if (PadbergRaoOddMinimumCutset.isOddVertexSet(sourcePartition, oddVertices)) { this.minimumCutWeight = cutWeight; this.sourcePartitionMinimumCut = sourcePartition; } } if (cluster.size() == 2) return; if (sourcePartition == null) sourcePartition = gusfieldGomoryHuCutTreeAlgorithm.getSourcePartition(); Set split1 = this.intersection(cluster, sourcePartition); Set split2 = new HashSet<>(cluster); split2.removeAll(split1); if (split1.size() > 1) queue.add(split1); if (split2.size() > 1) queue.add(split2); } /** * Efficient way to compute the intersection between two sets * * @param set1 set $1$ * @param set2 set $2$ * @return intersection of set $1$ and $2$ */ private Set intersection(Set set1, Set set2) { Set a; Set b; if (set1.size() <= set2.size()) { a = set1; b = set2; } else { a = set2; b = set1; } return a.stream().filter(b::contains).collect(Collectors.toSet()); } /** * Convenience method which test whether the given set contains an odd number of odd-labeled * nodes. * * @param vertex type * @param vertices input set * @param oddVertices subset of vertices which are labeled odd * @return true if the given set contains an odd number of odd-labeled nodes. */ public static boolean isOddVertexSet(Set vertices, Set oddVertices) { if (vertices.size() < oddVertices.size()) return vertices.stream().filter(oddVertices::contains).count() % 2 == 1; else return oddVertices.stream().filter(vertices::contains).count() % 2 == 1; } /** * Returns partition $W$ of the cut obtained after the last invocation of * {@link #calculateMinCut(Set, boolean)} * * @return partition $W$ */ public Set getSourcePartition() { return sourcePartitionMinimumCut; } /** * Returns partition $V-W$ of the cut obtained after the last invocation of * {@link #calculateMinCut(Set, boolean)} * * @return partition $V-W$ */ public Set getSinkPartition() { Set sinkPartition = new LinkedHashSet<>(network.vertexSet()); sinkPartition.removeAll(sourcePartitionMinimumCut); return sinkPartition; } /** * Returns the set of edges which run from the source partition to the sink partition, in the * $s-t$ cut obtained after the last invocation of {@link #calculateMinCut(Set, boolean)} * * @return set of edges which have one endpoint in the source partition and one endpoint in the * sink partition. */ public Set getCutEdges() { Predicate predicate = e -> sourcePartitionMinimumCut.contains(network.getEdgeSource(e)) ^ sourcePartitionMinimumCut.contains(network.getEdgeTarget(e)); return network .edgeSet().stream().filter(predicate) .collect(Collectors.toCollection(LinkedHashSet::new)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/PushRelabelMFImpl.java000066400000000000000000000414041402514743400317200ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.alg.util.extension.*; import java.lang.reflect.*; import java.util.*; /** *

* Push-relabel * maximum flow algorithm designed by Andrew V. Goldberg and Robert Tarjan. Current * implementation complexity upper-bound is $O(V^3)$. For more details see: "A new approach to * the maximum flow problem" by Andrew V. Goldberg and Robert Tarjan STOC '86: Proceedings of * the eighteenth annual ACM symposium on Theory of computing *

* *

* This implementation is based on On Implementing the Push—Relabel Method for the Maximum Flow * Problem by B. V. Cherkassky and A.V. Goldberg (Cherkassky, B. & Goldberg, A. Algorithmica * (1997) 19: 390. https://doi.org/10.1007/PL00009180) and Introduction to Algorithms (3rd * Edition). *

* *

* This class can also computes minimum $s-t$ cuts. Effectively, to compute a minimum $s-t$ cut, the * implementation first computes a minimum $s-t$ flow, after which a BFS is run on the residual * graph. *

* * Note: even though the algorithm accepts any kind of graph, currently only Simple directed and * undirected graphs are supported (and tested!). * * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu * @author Alexey Kudinkin * */ public class PushRelabelMFImpl extends MaximumFlowAlgorithmBase { // Diagnostic private static final boolean DIAGNOSTIC_ENABLED = false; public static boolean USE_GLOBAL_RELABELING_HEURISTIC = true; public static boolean USE_GAP_RELABELING_HEURISTIC = true; private final ExtensionFactory vertexExtensionsFactory; private final ExtensionFactory edgeExtensionsFactory; // countHeight[h] = number of vertices with height h private int[] countHeight; // queue of active vertices private Queue activeVertices; private PushRelabelDiagnostic diagnostic; // number of vertices private final int N; private final VertexExtension[] vertexExtension; // number of relabels already performed private int relabelCounter; private static ToleranceDoubleComparator comparator = new ToleranceDoubleComparator(); /** * Construct a new push-relabel algorithm. * * @param network the network */ public PushRelabelMFImpl(Graph network) { this(network, DEFAULT_EPSILON); } /** * Construct a new push-relabel algorithm. * * @param network the network * @param epsilon tolerance used when comparing floating-point values */ @SuppressWarnings("unchecked") public PushRelabelMFImpl(Graph network, double epsilon) { super(network, epsilon); this.vertexExtensionsFactory = VertexExtension::new; this.edgeExtensionsFactory = AnnotatedFlowEdge::new; if (DIAGNOSTIC_ENABLED) { this.diagnostic = new PushRelabelDiagnostic(); } this.N = network.vertexSet().size(); this.vertexExtension = (VertexExtension[]) Array.newInstance(VertexExtension.class, N); } private void enqueue(VertexExtension vx) { if (!vx.active && vx.hasExcess()) { vx.active = true; activeVertices.add(vx); } } /** * Prepares all data structures to start a new invocation of the Maximum Flow or Minimum Cut * algorithms * * @param source source * @param sink sink */ void init(V source, V sink) { super.init(source, sink, vertexExtensionsFactory, edgeExtensionsFactory); this.countHeight = new int[2 * N + 1]; int id = 0; for (V v : network.vertexSet()) { VertexExtension vx = getVertexExtension(v); vx.id = id; vertexExtension[id] = vx; id++; } } /** * Initialization * * @param source the source * @param sink the sink * @param active resulting queue with all active vertices */ public void initialize( VertexExtension source, VertexExtension sink, Queue active) { this.activeVertices = active; for (int i = 0; i < N; i++) { vertexExtension[i].excess = 0; vertexExtension[i].height = 0; vertexExtension[i].active = false; vertexExtension[i].currentArc = 0; } source.height = N; source.active = true; sink.active = true; countHeight[N] = 1; countHeight[0] = N - 1; for (AnnotatedFlowEdge ex : source.getOutgoing()) { source.excess += ex.capacity; push(ex); } if (USE_GLOBAL_RELABELING_HEURISTIC) { recomputeHeightsHeuristic(); this.relabelCounter = 0; } } @Override public MaximumFlow getMaximumFlow(V source, V sink) { this.calculateMaximumFlow(source, sink); maxFlow = composeFlow(); return new MaximumFlowImpl<>(maxFlowValue, maxFlow); } /** * Sets current source to source, current sink to sink, then * calculates maximum flow from source to sink. Note, that * source and sink must be vertices of the * network passed to the constructor, and they must be different. * * @param source source vertex * @param sink sink vertex * @return the value of the maximum flow */ public double calculateMaximumFlow(V source, V sink) { /* * Note: this implementation uses the FIFO selection rule (check wiki for more details) */ init(source, sink); this.activeVertices = new ArrayDeque<>(N); initialize(getVertexExtension(source), getVertexExtension(sink), this.activeVertices); // while (!activeVertices.isEmpty()) { VertexExtension vx = activeVertices.poll(); vx.active = false; discharge(vx); } // Calculate the max flow that reaches the sink. There may be more efficient ways to do // this. for (E e : network.edgesOf(sink)) { AnnotatedFlowEdge edge = edgeExtensionManager.getExtension(e); maxFlowValue += (directedGraph ? edge.flow : edge.flow + edge.getInverse().flow); } if (DIAGNOSTIC_ENABLED) { diagnostic.dump(); } return maxFlowValue; } /** * Push flow through an edge. * * @param ex the edge * @param f the amount of flow to push through */ protected void pushFlowThrough(AnnotatedFlowEdge ex, double f) { ex.getSource().excess -= f; ex.getTarget().excess += f; assert ((ex.getSource().excess >= 0.0) && (ex.getTarget().excess >= 0)); super.pushFlowThrough(ex, f); } /* * The basic operation PUSH(u, v) is applied if u in an overflowing vertex (i.e. has excess) and * u.height = v.height + 1. * * The operation can be either saturating (if ux.excess >= ex.capacity - ex.flow) or * nonsaturating (otherwise). */ private void push(AnnotatedFlowEdge ex) { VertexExtension ux = ex.getSource(); VertexExtension vx = ex.getTarget(); double delta = Math.min(ux.excess, ex.capacity - ex.flow); // if v is not downhill from u or there is nothing to push (i.e. delta == 0) stop if (ux.height <= vx.height || comparator.compare(delta, 0.0) <= 0) return; if (DIAGNOSTIC_ENABLED) { diagnostic.incrementDischarges(ex); } pushFlowThrough(ex, delta); // check if we can 'activate' v enqueue(vx); } private void gapHeuristic(int l) { for (int i = 0; i < N; i++) { if (l < vertexExtension[i].height && vertexExtension[i].height < N) { countHeight[vertexExtension[i].height]--; vertexExtension[i].height = Math.max(vertexExtension[i].height, N + 1); countHeight[vertexExtension[i].height]++; } } } /* * The basic operation RELABEL(u) is applied if u is overflowing (i.e. has excess) and if * u.height <= v.height + 1. * * We can relabel an overflowing vertex $u$ if for every vertex v for which there is residual * capacity from u to v, flow cannot be pushed from u to v because v is not downhill from u. */ private void relabel(VertexExtension ux) { int oldHeight = ux.height; // Increase the height of u; u.h = 1 + min(v.h : (u, v) in Ef) countHeight[ux.height]--; ux.height = 2 * N; for (AnnotatedFlowEdge ex : ux.getOutgoing()) { if (ex.hasCapacity()) { ux.height = Math.min(ux.height, ex. getTarget().height + 1); } } countHeight[ux.height]++; if (USE_GAP_RELABELING_HEURISTIC) { /* * The gap heuristic detects gaps in the height function. If there is a height 0 < h < * |V| for which there is no node u such that u.height = h, then any node v with h < * v.height < |V| has been disconnected from sink and can be relabeled to (|V| + 1). */ if (0 < oldHeight && oldHeight < N && countHeight[oldHeight] == 0) { gapHeuristic(oldHeight); } } if (DIAGNOSTIC_ENABLED) { diagnostic.incrementRelabels(ux.height, ux.height); } } private void bfs(Queue queue, boolean[] visited) { while (!queue.isEmpty()) { int vertexID = queue.poll(); for (AnnotatedFlowEdge flowEdge : vertexExtension[vertexID].getOutgoing()) { VertexExtension vx = flowEdge.getTarget(); if (!visited[vx.id] && flowEdge.getInverse().hasCapacity()) { vx.height = vertexExtension[vertexID].height + 1; visited[vx.id] = true; queue.add(vx.id); } } } } /* * The global relabeling heuristic updates the height function by computing shortest path * distances in the residual graph from all nodes to the sink. * * This can be done in linear time by a backwards breadth-first search. */ private void recomputeHeightsHeuristic() { Arrays.fill(countHeight, 0); Queue queue = new ArrayDeque<>(N); boolean[] visited = new boolean[N]; for (int i = 0; i < N; i++) { vertexExtension[i].height = 2 * N; } final int sinkID = getVertexExtension(getCurrentSink()).id; final int sourceID = getVertexExtension(getCurrentSource()).id; vertexExtension[sourceID].height = N; visited[sourceID] = true; vertexExtension[sinkID].height = 0; visited[sinkID] = true; queue.add(sinkID); bfs(queue, visited); queue.add(sourceID); bfs(queue, visited); for (int i = 0; i < N; i++) { ++countHeight[vertexExtension[i].height]; } } /* * An overflowing vertex u is discharged by pushing all of its excess flow through admissible * edges to neighboring vertices, relabeling u as necessary to cause edges leaving u to become * admissible, */ private void discharge(VertexExtension ux) { while (ux.hasExcess()) { // If there are no more edges if (ux.currentArc >= ux.getOutgoing().size()) { // then we relabel u relabel(ux); if (USE_GLOBAL_RELABELING_HEURISTIC) { // If we already relabeled |V| vertices, then we do a global relabeling // Note: Global relabelings are performed periodically if ((++relabelCounter) == N) { recomputeHeightsHeuristic(); for (int i = 0; i < N; i++) vertexExtension[i].currentArc = 0; relabelCounter = 0; } } // rewind the pointer to the next edge ux.currentArc = 0; } else { AnnotatedFlowEdge flowEdge = ux.getOutgoing().get(ux.currentArc); /* * Check if the edge is admissible. If it is then do a PUSH operation. Otherwise, * make currentArc point to the next edge. */ if (isAdmissible(flowEdge)) push(flowEdge); else ux.currentArc++; } } } private boolean isAdmissible(AnnotatedFlowEdge e) { return e.hasCapacity() && (e . getSource().height == (e. getTarget().height + 1)); } private VertexExtension getVertexExtension(V v) { assert vertexExtensionManager != null; return (VertexExtension) vertexExtensionManager.getExtension(v); } private class PushRelabelDiagnostic { // Discharges Map, Integer> discharges = new HashMap<>(); long dischargesCounter = 0; // Relabels Map, Integer> relabels = new HashMap<>(); long relabelsCounter = 0; private void incrementDischarges(AnnotatedFlowEdge ex) { Pair p = Pair.of(ex.getSource().prototype, ex.getTarget().prototype); if (!discharges.containsKey(p)) { discharges.put(p, 0); } discharges.put(p, discharges.get(p) + 1); dischargesCounter++; } private void incrementRelabels(int from, int to) { Pair p = Pair.of(from, to); if (!relabels.containsKey(p)) { relabels.put(p, 0); } relabels.put(p, relabels.get(p) + 1); relabelsCounter++; } void dump() { Map labels = new HashMap<>(); for (V v : network.vertexSet()) { VertexExtension vx = getVertexExtension(v); if (!labels.containsKey(vx.height)) { labels.put(vx.height, 0); } labels.put(vx.height, labels.get(vx.height) + 1); } System.out.println("LABELS "); System.out.println("------ "); System.out.println(labels); List, Integer>> relabelsSorted = new ArrayList<>(relabels.entrySet()); relabelsSorted.sort((o1, o2) -> -(o1.getValue() - o2.getValue())); System.out.println("RELABELS "); System.out.println("-------- "); System.out.println(" Count: " + relabelsCounter); System.out.println(" " + relabelsSorted); List, Integer>> dischargesSorted = new ArrayList<>(discharges.entrySet()); dischargesSorted.sort((one, other) -> -(one.getValue() - other.getValue())); System.out.println("DISCHARGES "); System.out.println("---------- "); System.out.println(" Count: " + dischargesCounter); System.out.println(" " + dischargesSorted); } } /** * Vertex extension for the push-relabel algorithm, which contains an additional height. */ public class VertexExtension extends VertexExtensionBase { private int id; private int height; // also called label (or distance label) in some papers private boolean active; private int currentArc; private boolean hasExcess() { return comparator.compare(excess, 0.0) > 0; } @Override public String toString() { return prototype.toString() + String.format(" { HGT: %d } ", height); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/000077500000000000000000000000001402514743400272535ustar00rootroot00000000000000CapacityScalingMinimumCostFlow.java000066400000000000000000001125231402514743400361160ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow.mincost; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.util.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; /** * This class computes a solution to a * minimum cost flow problem * using the successive shortest path algorithm with capacity scaling. More precisely, this class * computes a b-flow of minimum cost, i.e. for each node $v$ in the network the sum of all * outgoing flows minus the sum of all incoming flows should be equal to the node supply $b_v$ *

* The minimum cost flow problem is defined as follows: \[ \begin{align} \mbox{minimize}~& * \sum_{e\in \delta^+(s)}c_e\cdot f_e &\\ \mbox{s.t. }&\sum_{e\in \delta^-(i)} f_e - * \sum_{e\in \delta^+(i)} f_e = b_e & \forall i\in V\\ &l_e\leq f_e \leq u_e & \forall * e\in E \end{align} \] Here $\delta^+(i)$ and $\delta^-(i)$ denote the outgoing and incoming edges * of vertex $i$ respectively. The parameters $c_{e}$ define a cost for each unit of flow on the arc * $e$, $l_{e}$ define minimum arc flow and $u_{e}$ define maximum arc flow. If $u_{e}$ is equal to * {@link CapacityScalingMinimumCostFlow#CAP_INF}, then arbitrary large flow can be sent across the * arc $e$. Parameters $b_{e}$ define the nodes demands: positive demand means that a node is a * supply node, 0 demand means that it is a transhipment node, negative demand means that it is a * demand node. Parameters $b_{e}$, $l_{e}$ and $u_{e}$ can be specified via * {@link MinimumCostFlowProblem}, graph edge weights are considered to be parameters $c_{e}$, which * can be negative. *

* This algorithm supports two modes: with and without scaling. An integral scaling factor can be * specified during construction time. If the specified scaling factor is less than 2, then the * algorithm solves the specified problem using regular successive shortest path. The default * scaling factor is {@link CapacityScalingMinimumCostFlow#DEFAULT_SCALING_FACTOR}. *

* Essentially, the capacity scaling technique is breaking down the solution of the problem into * $O(\log U)$ phases $\left[\Delta_i, \Delta_{i +1}\right],\ \Delta_i = 2^{i}, i = 0, 1, \dots, * \log_a(U) - 1$. At each phase the algorithm carries at least $\delta_i$ units of flow. This * technique ensures weakly polynomial time bound on the running time complexity of the algorithm. * Smaller scaling factors guarantee smaller constant in the asymptotic time bound. The best choice * of scaling factor is between $2$ and $16$, which depends on the characteristics of the flow * network. Choosing $100$ as a scaling factor is almost equivalent to using the algorithm without * scaling. In the case the algorithm is used without scaling, it has pseudo-polynomial time * complexity $\mathcal{O}(nU(m + n)\log n)$. *

* Currently the algorithm doesn't support undirected flow networks. The algorithm also imposes two * constraints on the directed flow networks, namely, is doesn't support infinite capacity arcs with * negative cost and self-loops. Note, that in the case the network contains an infinite capacity * arc with negative cost, the cost of a flow on the network can be bounded from below by some * constant, i.e. a feasible finite weight solution can exist. *

* An arc with capacity greater that or equal to {@link CapacityScalingMinimumCostFlow#CAP_INF} is * considered to be an infinite capacity arc. The algorithm also uses * {@link CapacityScalingMinimumCostFlow#COST_INF} during the computation, therefore, the magnitude * of the cost of any arc can't exceed this values. *

* In the capacity scaling mode, the algorithm performs $\mathcal{O}(log_a U)$ $\Delta$-scaling * phases, where $U$ is the largest magnitude of any supply/demand or finite arc capacity, and $a$ * is a scaling factor, which is considered to be constant. During each $\Delta$-scaling phase the * algorithm first ensures that all arc with capacity with capacity greater than or equal to * $\Delta$ satisfy optimality condition, i.e. its reduced cost must be non-negative (saturated arcs * don't belong to the residual network). After saturating all arcs in the $\Delta$-residual network * with negative reduced cost the sum of the excesses is bounded by $2\Delta(m + n)$. Since the * algorithm ensures that each augmentation carries at least $\Delta$ units of flow, at most * $\mathcal{O}(m)$ flow augmentations are performed during each scaling phase. Therefore, the * overall running time of the algorithm with capacity scaling is $\mathcal{O}(m\log_a U(m + n)\log * n)$, which is a weakly polynomial time bound. *

* If the algorithm is used without scaling, each flow augmentation carries at least * $\mathcal{O}(1)$ flow units, therefore the overall time complexity if $\mathcal{O}(nU(m + n)\log * n)$, which is a pseudo-polynomial time bound. *

* For more information about the capacity scaling algorithm see: K. Ahuja, Ravindra & L. * Magnanti, Thomas & Orlin, James. (1993). Network Flows. This implementation is based on * the algorithm description presented in this book. * * @param graph vertex type * @param graph edge type * @author Timofey Chudakov * @see MinimumCostFlowProblem * @see MinimumCostFlowAlgorithm */ public class CapacityScalingMinimumCostFlow implements MinimumCostFlowAlgorithm { /** * A capacity which is considered to be infinite. Every arc, which has upper capacity greater * that or equal to this value is considered to be an infinite capacity arc. */ public static final int CAP_INF = 1000 * 1000 * 1000; /** * A cost which is considered to be infinite. This value is used internally for flow network * transformation. That is why arcs with cost magnitude greater than or equal to this value are * not allowed. */ public static final double COST_INF = 1e9; /** * Default scaling factor */ public static final int DEFAULT_SCALING_FACTOR = 8; /** * Debug variable */ private static final boolean DEBUG = false; /** * Scaling factor of this algorithm */ private final int scalingFactor; /** * Number of vertices in the network */ private int n; /** * Number of edges in the network */ private int m; /** * Variable that is used to determine whether a vertex has been labeled temporarily or * permanently during Dijkstra's algorithm */ private int counter = 1; /** * Specified minimum cost flow problem */ private MinimumCostFlowProblem problem; /** * Computed minimum cost flow */ private MinimumCostFlow minimumCostFlow; /** * Array of internal nodes used by the algorithm. Node: these nodes are stored in the same order * as vertices of the specified flow network. This allows to determine quickly their * counterparts in the network. */ private Node[] nodes; /** * Array of internal arcs. Note: these arcs are stored in the same order as edges of the * specified flow network. This allows to determine quickly their counterparts in the network. */ private Arc[] arcs; /** * List of vertices of the flow network. */ private List graphVertices; /** * List of edges of the flow network. */ private List graphEdges; /** * Constructs a new instance of the algorithm which uses default scaling factor. */ public CapacityScalingMinimumCostFlow() { this(DEFAULT_SCALING_FACTOR); } /** * Constructs a new instance of the algorithm with custom {@code scalingFactor}. If the * {@code scalingFactor} is less than 2, the algorithm doesn't use scaling. * * @param scalingFactor custom scaling factor */ public CapacityScalingMinimumCostFlow(int scalingFactor) { this.scalingFactor = scalingFactor; Node.ID = 0; // for debug } /** * Returns mapping from edge to flow value through this particular edge * * @return maximum flow mapping, or null if a MinimumCostFlowProblem has not yet been solved. */ @Override public Map getFlowMap() { return minimumCostFlow == null ? null : this.minimumCostFlow.getFlowMap(); } /** * {@inheritDoc} */ @Override public V getFlowDirection(E edge) { return problem.getGraph().getEdgeTarget(edge); } /** * {@inheritDoc} */ @Override public MinimumCostFlow getMinimumCostFlow( final MinimumCostFlowProblem minimumCostFlowProblem) { this.problem = Objects.requireNonNull(minimumCostFlowProblem); if (problem.getGraph().getType().isUndirected()) { throw new IllegalArgumentException( "The algorithm doesn't support undirected flow networks"); } n = problem.getGraph().vertexSet().size(); m = problem.getGraph().edgeSet().size(); calculateMinimumCostFlow(); return minimumCostFlow; } /** * Returns solution to the dual linear program formulated on the network. Serves as a * certificate of optimality. *

* It is represented as a mapping from graph nodes to their potentials (dual variables). Reduced * cost of a arc $(a, b)$ is defined as $cost((a, b)) + potential(b) - potential(b)$. According * to the reduced cost optimality conditions, a feasible solution to the minimum cost flow * problem is optimal if and only if reduced cost of every non-saturated arc is greater than or * equal to $0$. * * @return solution to the dual linear program formulated on the network, or null if a * MinimumCostFlowProblem has not yet been solved. */ public Map getDualSolution() { if (minimumCostFlow == null) return null; Map dualVariables = new HashMap<>(); for (int i = 0; i < n; i++) { dualVariables.put(graphVertices.get(i), nodes[i].potential); } return dualVariables; } /** * Calculated a solution to the specified minimum cost flow problem. If the scaling factor is * greater than 1, performs scaling phases, otherwise uses simple capacity scaling algorithm. */ private void calculateMinimumCostFlow() { init(); if (scalingFactor > 1) { // run with scaling int U = getU(); int delta = scalingFactor; while (U >= delta) { delta *= scalingFactor; } delta /= scalingFactor; while (delta >= 1) { Pair, Set> pair = scale(delta); pushAllFlow(pair.getFirst(), pair.getSecond(), delta); delta /= scalingFactor; } } else { // run without scaling Pair, Set> pair = scale(1); pushAllFlow(pair.getFirst(), pair.getSecond(), 1); } minimumCostFlow = finish(); } /** * Converts the flow network in the form convenient for the algorithm. Validated the arc * capacities and costs. *

* Also, adds a dummy node to the network and arcs from every node to this dummy node, and from * this dummy node to every other node. These added arcs have infinite capacities * {@link CapacityScalingMinimumCostFlow#CAP_INF} and infinite costs * {@link CapacityScalingMinimumCostFlow#COST_INF}. This ensures, that every search for an * augmenting path to send at least $\Delta$ units of flow succeeds. *

* If the flow network has a feasible solution, at the end there will be no flow on the added * arcs. Otherwise, the specified problem has no feasible solution. */ private void init() { int supplySum = 0; // initialize data structures nodes = new Node[n + 1]; nodes[n] = new Node(0); // dummy node arcs = new Arc[m]; graphEdges = new ArrayList<>(m); graphVertices = new ArrayList<>(n); Map nodeMap = CollectionUtil.newHashMapWithExpectedSize(n); Graph graph = problem.getGraph(); // convert vertices into internal nodes int i = 0; for (V vertex : graph.vertexSet()) { graphVertices.add(vertex); int supply = problem.getNodeSupply().apply(vertex); supplySum += supply; nodes[i] = new Node(supply); nodeMap.put(vertex, nodes[i]); // reduction nodes[i].addArcTo(nodes[n], CAP_INF, COST_INF); nodes[n].addArcTo(nodes[i], CAP_INF, COST_INF); ++i; } if (Math.abs(supplySum) > 0) { throw new IllegalArgumentException("Total node supply isn't equal to 0"); } i = 0; // convert edges into their internal counterparts for (E edge : graph.edgeSet()) { graphEdges.add(edge); Node node = nodeMap.get(graph.getEdgeSource(edge)); Node opposite = nodeMap.get(graph.getEdgeTarget(edge)); int upperCap = problem.getArcCapacityUpperBounds().apply(edge); int lowerCap = problem.getArcCapacityLowerBounds().apply(edge); double cost = graph.getEdgeWeight(edge); if (upperCap < 0) { throw new IllegalArgumentException("Negative edge capacities are not allowed"); } else if (lowerCap > upperCap) { throw new IllegalArgumentException( "Lower edge capacity must not exceed upper edge capacity"); } else if (lowerCap >= CAP_INF) { throw new IllegalArgumentException( "The problem is unbounded due to the infinite lower capacity"); } else if (upperCap >= CAP_INF && cost < 0) { throw new IllegalArgumentException( "The algorithm doesn't support infinite capacity arcs with negative cost"); } else if (Math.abs(cost) >= COST_INF) { throw new IllegalArgumentException( "Specified flow network contains an edge of infinite cost"); } else if (node == opposite) { throw new IllegalArgumentException("Self-loops aren't allowed"); } // remove non-zero lower capacity node.excess -= lowerCap; opposite.excess += lowerCap; if (cost < 0) { // removing negative edge costs node.excess -= upperCap - lowerCap; opposite.excess += upperCap - lowerCap; Node t = node; node = opposite; opposite = t; cost *= -1; } arcs[i] = node.addArcTo(opposite, upperCap - lowerCap, cost); if (DEBUG) { System.out.println(arcs[i]); } ++i; } if (DEBUG) { System.out.println("Printing mapping"); for (Map.Entry entry : nodeMap.entrySet()) { System.out.println(entry + " -> " + entry); } } } /** * Returns the largest magnitude of any supply/demand or finite arc capacity. * * @return the largest magnitude of any supply/demand or finite arc capacity. */ private int getU() { int result = 0; for (Node node : nodes) { result = Math.max(result, Math.abs(node.excess)); } for (Arc arc : arcs) { if (!arc.isInfiniteCapacityArc()) { result = Math.max(result, arc.residualCapacity); } } return result; } /** * Performs a scaling phase by saturating all negative reduced cost arcs with residual capacity * greater than or equal to the {@code delta}, so that they don't belong to the * $\Delta$-residual network and, hence, don't violate optimality conditions. After that this * method computes and returns nodes with positive excess greater than or equal to the * {@code delta} and nodes with negative excesses that are less than or equal to {@code delta} * * @param delta current value of $\Delta$ * @return the nodes with excesses no less than {@code delta} and no greater than {@code -delta} */ private Pair, Set> scale(int delta) { if (DEBUG) { System.out.println(String.format("Current delta = %d", delta)); } // saturate all non-saturated arcs with negative edge costs in the delta-residual network for (Node node : nodes) { Arc nextArc = node.firstNonSaturated; for (Arc arc = nextArc; arc != null; arc = nextArc) { nextArc = nextArc.next; int residualCapacity = arc.residualCapacity; if (arc.residualCapacity >= delta && arc.getReducedCost() < 0) { if (DEBUG) { System.out.println("Saturating arc " + arc); } arc.sendFlow(residualCapacity); arc.head.excess += residualCapacity; arc.revArc.head.excess -= residualCapacity; } } } // finding all nodes with excess magnitude no less than delta List positiveExcessNodes = new ArrayList<>(); Set negativeExcessNodes = new HashSet<>(); for (Node node : nodes) { if (node.excess >= delta) { positiveExcessNodes.add(node); } else if (node.excess <= -delta) { negativeExcessNodes.add(node); } } return new Pair<>(positiveExcessNodes, negativeExcessNodes); } /** * For every node in the {@code positiveExcessNodes} pushes all flow away from it until its * excess is less than {@code delta}. This is always possible due to the performed flow network * reduction during the initialization phase. * * @param positiveExcessNodes nodes from the network with positive excesses no less than * {@code delta} * @param negativeExcessNodes nodes from the network with negative excesses no greater than * {@code delta} * @param delta the current value of $\Delta$ */ private void pushAllFlow( List positiveExcessNodes, Set negativeExcessNodes, int delta) { for (Node node : positiveExcessNodes) { while (node.excess >= delta) { if (negativeExcessNodes.isEmpty()) { return; } pushDijkstra(node, negativeExcessNodes, delta); } } } /** * Runs the Dijkstra's algorithm in the residual network using {@link Arc#getReducedCost()} as * arc distances. *

* After reaching a node with excess no greater than {@code -delta}, augments it. Since the * search is performed in the $\Delta$-residual network, the augmentation carries at least * {@code delta} units of flow. The search always succeeds due to the flow network reduction * performed during the initialization phase. *

* Updates the potentials of the nodes so that they: *

    *
  • Satisfy optimality conditions in the $\Delta$-residual network
  • *
  • The reduced cost of the augmented path is equal to $0$
  • *
*

* Let us denote some permanently labeled vertex as $u$, and the first * permanently labeled vertex with negative excess as $v$. Let $dist(x)$ be the * distance function in the residual network. Then we use the following formula to update the * node potentials: $v.potential = v.potential + dist(v) - dist(u)$. The potentials of the * temporarily labeled and unvisited vertices stay unchanged. * * @param start the start node for Dijkstra's algorithm * @param negativeExcessNodes nodes from the network with negative excesses no greater than * {@code delta} * @param delta the current value of $\Delta$ */ private void pushDijkstra(Node start, Set negativeExcessNodes, int delta) { int TEMPORARILY_LABELED = counter++; int PERMANENTLY_LABELED = counter++; AddressableHeap.Handle currentFibNode; AddressableHeap heap = new PairingHeap<>(); List permanentlyLabeled = new LinkedList<>(); start.parentArc = null; start.handle = heap.insert(0d, start); while (!heap.isEmpty()) { currentFibNode = heap.deleteMin(); double distance = currentFibNode.getKey(); Node currentNode = currentFibNode.getValue(); if (negativeExcessNodes.contains(currentNode)) { // the path to push at least delta units of flow is found augmentPath(start, currentNode); if (currentNode.excess > -delta) { negativeExcessNodes.remove(currentNode); } // updating potentials for (Node node : permanentlyLabeled) { node.potential += distance; } if (DEBUG) { System.out.println(String.format("Distance = %.1f", distance)); for (Node node : nodes) { System.out .println( String .format("Id = %d, potential = %.1f", node.id, node.potential)); } } return; } currentNode.labelType = PERMANENTLY_LABELED; // currentNode becomes permanently labeled permanentlyLabeled.add(currentNode); for (Arc currentArc = currentNode.firstNonSaturated; currentArc != null; currentArc = currentArc.next) { // looking only for arcs with residual capacity greater than delta if (currentArc.residualCapacity < delta) { continue; } Node opposite = currentArc.head; if (opposite.labelType != PERMANENTLY_LABELED) { if (opposite.labelType == TEMPORARILY_LABELED) { // opposite has been labeled already if (distance + currentArc.getReducedCost() < opposite.handle.getKey()) { opposite.handle.decreaseKey(distance + currentArc.getReducedCost()); opposite.parentArc = currentArc; } } else { // opposite is encountered for the first time opposite.labelType = TEMPORARILY_LABELED; opposite.handle = heap.insert(distance + currentArc.getReducedCost(), opposite); opposite.parentArc = currentArc; } } } currentNode.potential -= distance; // allows not to store the distances of the nodes } } /** * Augments the path from {@code start} to the {@code end} sending as much flow as possible. * Uses {@link Node#parentArc} computed by the Dijkstra's algorithm. Updates the excesses of the * {@code start} and the {@code end} nodes. * * @param start the start of the augmenting path * @param end the end of the augmenting path */ private void augmentPath(Node start, Node end) { // compute delta to augment int valueToAugment = Math.min(start.excess, -end.excess); for (Arc arc = end.parentArc; arc != null; arc = arc.revArc.head.parentArc) { valueToAugment = Math.min(valueToAugment, arc.residualCapacity); } if (DEBUG) { ArrayList stack = new ArrayList<>(); for (Arc arc = end.parentArc; arc != null; arc = arc.revArc.head.parentArc) { stack.add(arc.head); } stack.add(start); System.out.println("Printing augmenting path"); for (int i = stack.size() - 1; i > 0; i--) { System.out.print(stack.get(i).id + " -> "); } System.out.println(stack.get(0).id + ", delta = " + valueToAugment); } // augmenting the flow end.excess += valueToAugment; for (Arc arc = end.parentArc; arc != null; arc = arc.revArc.head.parentArc) { arc.sendFlow(valueToAugment); } start.excess -= valueToAugment; } /** * Finishes the computation by checking the flow feasibility, computing arc flows, and creating * an instance of {@link MinimumCostFlow}. The resulting flow mapping contains all edges of the * specified minimum cost flow problem. * * @return the solution to the minimum cost flow problem */ private MinimumCostFlow finish() { Map flowMap = CollectionUtil.newHashMapWithExpectedSize(m); double totalCost = 0; // check feasibility for (Arc arc = nodes[n].firstNonSaturated; arc != null; arc = arc.next) { if (arc.revArc.residualCapacity > 0) { throw new IllegalArgumentException( "Specified flow network problem has no feasible solution"); } } // create the solution object for (int i = 0; i < m; i++) { E graphEdge = graphEdges.get(i); Arc arc = arcs[i]; double flowOnArc = arc.revArc.residualCapacity; // this value equals to the flow on the // initial arc if (problem.getGraph().getEdgeWeight(graphEdge) < 0) { // the initial arc goes in the opposite direction flowOnArc = problem.getArcCapacityUpperBounds().apply(graphEdge) - problem.getArcCapacityLowerBounds().apply(graphEdge) - flowOnArc; } flowOnArc += problem.getArcCapacityLowerBounds().apply(graphEdge); flowMap.put(graphEdge, flowOnArc); totalCost += flowOnArc * problem.getGraph().getEdgeWeight(graphEdge); } return new MinimumCostFlowImpl<>(totalCost, flowMap); } /** * Tests the optimality conditions after a flow of minimum cost has been computed. *

* More precisely, tests, whether the reduced cost of every non-saturated arc in the residual * network is non-negative. This validation is performed with precision of {@code eps}. If the * solution doesn't meet this condition, returns, false. *

* In general, this method should always return true unless the algorithm implementation has a * bug. * * @param eps the precision to use * @return true, if the computed solution is optimal, false otherwise. */ public boolean testOptimality(double eps) { if (minimumCostFlow == null) throw new RuntimeException( "Cannot return a dual solution before getMinimumCostFlow(MinimumCostFlowProblem minimumCostFlowProblem) is invoked!"); for (Node node : nodes) { for (Arc arc = node.firstNonSaturated; arc != null; arc = arc.next) { if (arc.getReducedCost() < -eps) { return false; } } } return true; } /** * Supporting data structure for the {@link CapacityScalingMinimumCostFlow}. *

* Is used as an internal representation of the vertices of the flow network. Contains all * information needed during the computation. * * @author Timofey Chudakov * @since July 2018 */ private static class Node { /** * Variable for debug purposes */ private static int ID = 0; /** * Reference to the {@link FibonacciHeapNode} this node is contained in */ AddressableHeap.Handle handle; /** * An arc on the augmenting path which head is this node. */ Arc parentArc; /** * The label of this node. Is used to distinguish temporarily and permanently labeled nodes * during the Dijkstra's algorithm */ int labelType; /** * The excess of this node. If this value is positive, then this is a source node. If this * value is 0, then this is a transhipment node. If this value if negative, this is a sink * node. */ int excess; /** * The dual variable of this node. This is used to search for an augmenting path in the * residual network using the reduced costs of the arcs as arc lengths. */ double potential; /** * Reference of the first outgoing saturated arc (with zero residual capacity) * incident to this node */ Arc firstSaturated; /** * Reference of the first outgoing non-saturated arc (with positive residual * capacity) incident to this node. */ Arc firstNonSaturated; /** * Variable for debug purposes */ private int id = ID++; /** * Constructs a new node with {@code excess} * * @param excess the excess of this node */ public Node(int excess) { this.excess = excess; } /** * Adds a new arc with {@code capacity}, {@code cost} to the {@code opposite}. This method * also creates a reverse arc with zero capacity and {@code -cost}. * * @param opposite the head of the resulting arc. * @param capacity the capacity of the resulting arc. * @param cost the cost of the resulting arc * @return the resulting arc to the {@code opposite} node */ Arc addArcTo(Node opposite, int capacity, double cost) { Arc forwardArc = new Arc(opposite, capacity, cost); if (capacity > 0) { // forward arc becomes the first arc in the linked list of non-saturated arcs if (firstNonSaturated != null) { firstNonSaturated.prev = forwardArc; } forwardArc.next = firstNonSaturated; firstNonSaturated = forwardArc; } else { // forward arc becomes the first arc in the linked list of saturated arcs if (firstSaturated != null) { firstSaturated.prev = forwardArc; } forwardArc.next = firstSaturated; firstSaturated = forwardArc; } Arc reverseArc = new Arc(this, 0, -cost); if (opposite.firstSaturated != null) { opposite.firstSaturated.prev = reverseArc; } reverseArc.next = opposite.firstSaturated; opposite.firstSaturated = reverseArc; forwardArc.revArc = reverseArc; reverseArc.revArc = forwardArc; return forwardArc; } /** * {@inheritDoc} */ @Override public String toString() { return String.format("Id = %d, excess = %d, potential = %.1f", id, excess, potential); } } /** * Supporting data structure for the {@link CapacityScalingMinimumCostFlow}. *

* Represents a directed edge (arc) in the residual flow network. Contains all information * needed during the computation. * * @author Timofey Chudakov * @since July 2018 */ private static class Arc { /** * The head (target) of this arc. */ final Node head; /** * The cost of sending one unit of flow across this arc. This value is positive for initial * network arcs, negative - for the reverse residual arcs, and equals to the * {@link CapacityScalingMinimumCostFlow#COST_INF} for the arcs used for the reduction. */ final double cost; /** * The reverse counterpart of this arc. */ Arc revArc; /** * The previous arc. This variable is used to maintain the presence of this arc in the * linked list of arc which are either saturated or not. */ Arc prev; /** * The next arc. This variable is used to maintain the presence of this arc in the linked * list of arc which are either saturated or not. */ Arc next; /** * The residual capacity of this arc. For forward arcs $(i, j)$ it equals $c_{i, j} - x_{i, * j}$ where $x_{i, j}$ is the flow on this arc. For reverse arcs it equals $x_{i,j}$. */ int residualCapacity; /** * Creates a new arc * * @param head the head (target) of this arc * @param residualCapacity its residual capacity * @param cost its cost */ Arc(Node head, int residualCapacity, double cost) { this.head = head; this.cost = cost; this.residualCapacity = residualCapacity; } /** * Returns reduced cost of this arc. * * @return reduced cost of this arc. */ double getReducedCost() { return cost + head.potential - revArc.head.potential; } /** * Sends {@code value units of flow across this arc}. * * @param value how many units of flow to send */ void sendFlow(int value) { decreaseResidualCapacity(value); revArc.increaseResidualCapacity(value); } /** * Decreases residual capacity of this arc by {@code value} units of flow. Moves this arc * from list of non-saturated arc to the list of saturated arcs if necessary. * * @param value the value to subtract from the residual capacity of this arc */ private void decreaseResidualCapacity(int value) { if (residualCapacity >= CAP_INF) { return; } residualCapacity -= value; if (residualCapacity == 0) { // need to move this arc from list of non-saturated arcs to list of saturated arcs Node tail = revArc.head; if (next != null) { next.prev = prev; } if (prev != null) { prev.next = next; } else { tail.firstNonSaturated = next; } next = tail.firstSaturated; if (tail.firstSaturated != null) { tail.firstSaturated.prev = this; } tail.firstSaturated = this; prev = null; } } /** * Increases residual capacity of this arc by {@code value} units of flow. Moves this arc * from list of saturated arc to the list of non-saturated arcs if necessary. * * @param value the value to add to the residual capacity of this arc */ private void increaseResidualCapacity(int value) { if (residualCapacity >= CAP_INF) { return; } if (residualCapacity == 0) { // need to move this arc from list of saturated arcs to list of non-saturated arcs Node tail = revArc.head; if (next != null) { next.prev = prev; } if (prev != null) { prev.next = next; } else { tail.firstSaturated = next; } next = tail.firstNonSaturated; if (tail.firstNonSaturated != null) { tail.firstNonSaturated.prev = this; } tail.firstNonSaturated = this; prev = null; } residualCapacity += value; } /** * Returns true if the arc has infinite capacity, false otherwise. * * @return true if the arc has infinite capacity, false otherwise. */ public boolean isInfiniteCapacityArc() { return residualCapacity >= CAP_INF; } /** * {@inheritDoc} */ @Override public String toString() { return String .format( "(%d, %d), residual capacity = %s, reduced cost = %.1f, cost = %.1f", revArc.head.id, head.id, residualCapacity >= CAP_INF ? "INF" : String.valueOf(residualCapacity), getReducedCost(), cost); } } } MinimumCostFlowProblem.java000066400000000000000000000153131402514743400344570ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow.mincost; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; import java.util.function.*; /** * This class represents a * minimum cost flow problem. It serves as input for the minimum cost flow algorithms. *

* The minimum cost flow problem is defined as follows: \[ \begin{align} \mbox{minimize}~& * \sum_{e\in \delta^+(s)}c_e\cdot f_e &\\ \mbox{s.t. }&\sum_{e\in \delta^-(i)} f_e - * \sum_{e\in \delta^+(i)} f_e = b_e & \forall i\in V\\ &l_e\leq f_e \leq u_e & \forall * e\in E \end{align} \] Here $\delta^+(i)$ and $\delta^-(i)$ denote the outgoing and incoming edges * of vertex $i$ respectively. The parameters $c_{e}$ define a cost for each unit of flow on the arc * $e$, $l_{e}$ define minimum arc flow and $u_{e}$ define maximum arc flow. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see MinimumCostFlowAlgorithm */ public interface MinimumCostFlowProblem { /** * Returns the flow network * * @return the flow network */ Graph getGraph(); /** * Returns a function which defines the supply and demand of each node in that network. Supplies * can be positive, negative or 0. Nodes with positive negative supply are the demand nodes, * nodes with zero supply are the transhipment nodes. Flow is always directed from nodes with * positive supply to nodes with negative supply. Summed over all nodes, the total demand should * equal 0. * * @return supply function */ Function getNodeSupply(); /** * Returns a function which specifies the minimum capacity of an arc. The minimum capacity is * the minimum amount of flow that has to go through an arc. * * @return arc capacity lower bounding function */ Function getArcCapacityLowerBounds(); /** * Returns a function which specifies the maximum capacity of an arc. The flow through an arc * cannot exceed this upper bound. * * @return arc capacity upper bounding function */ Function getArcCapacityUpperBounds(); /** * Returns a function which specifies the network arc costs. Every unit of flow through an arc * will have the price of the cost of this arc. * * @return arc cost function */ Function getArcCosts(); /** * Default implementation of a Minimum Cost Flow Problem * * @param the graph vertex type * @param the graph edge type */ class MinimumCostFlowProblemImpl implements MinimumCostFlowProblem { private final Graph graph; private final Function nodeSupplies; private final Function arcCapacityLowerBounds; private final Function arcCapacityUpperBounds; private final Function arcCosts; /** * Constructs a new minimum cost flow problem without arc capacity lower bounds. * * @param graph the flow network * @param supplyMap the node demands * @param arcCapacityUpperBounds the arc capacity upper bounds */ public MinimumCostFlowProblemImpl( Graph graph, Function supplyMap, Function arcCapacityUpperBounds) { this(graph, supplyMap, arcCapacityUpperBounds, a -> 0); } /** * Constructs a new minimum cost flow problem * * @param graph the flow network * @param nodeSupplies the node demands * @param arcCapacityUpperBounds the arc capacity upper bounds * @param arcCapacityLowerBounds the arc capacity lower bounds */ public MinimumCostFlowProblemImpl( Graph graph, Function nodeSupplies, Function arcCapacityUpperBounds, Function arcCapacityLowerBounds) { this( graph, nodeSupplies, arcCapacityUpperBounds, arcCapacityLowerBounds, graph::getEdgeWeight); } /** * Constructs a new minimum cost flow problem * * @param graph the flow network * @param nodeSupplies the node demands * @param arcCapacityUpperBounds the arc capacity upper bounds * @param arcCapacityLowerBounds the arc capacity lower bounds * @param arcCosts the arc costs */ public MinimumCostFlowProblemImpl( Graph graph, Function nodeSupplies, Function arcCapacityUpperBounds, Function arcCapacityLowerBounds, Function arcCosts) { this.graph = Objects.requireNonNull(graph); this.nodeSupplies = Objects.requireNonNull(nodeSupplies); this.arcCapacityUpperBounds = Objects.requireNonNull(arcCapacityUpperBounds); this.arcCapacityLowerBounds = Objects.requireNonNull(arcCapacityLowerBounds); this.arcCosts = Objects.requireNonNull(arcCosts); } /** * {@inheritDoc} */ @Override public Graph getGraph() { return graph; } /** * {@inheritDoc} */ @Override public Function getNodeSupply() { return nodeSupplies; } /** * {@inheritDoc} */ @Override public Function getArcCapacityLowerBounds() { return arcCapacityLowerBounds; } /** * {@inheritDoc} */ @Override public Function getArcCapacityUpperBounds() { return arcCapacityUpperBounds; } /** * {@inheritDoc} */ @Override public Function getArcCosts() { return arcCosts; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/mincost/package-info.java000066400000000000000000000001221402514743400324350ustar00rootroot00000000000000/** * Algorithms for minimum cost flow */ package org.jgrapht.alg.flow.mincost; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/flow/package-info.java000066400000000000000000000001021402514743400307570ustar00rootroot00000000000000/** * Flow related algorithms. */ package org.jgrapht.alg.flow; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/independentset/000077500000000000000000000000001402514743400276415ustar00rootroot00000000000000ChordalGraphIndependentSetFinder.java000066400000000000000000000121731402514743400367510ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/independentset/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.independentset; import org.jgrapht.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.traverse.*; import java.util.*; /** * Calculates a maximum * cardinality independent set in a * chordal graph. A chordal graph is a * simple graph in which all cycles of * four or more vertices have a chord. A * chord is an edge that is not part of the cycle but connects two vertices of the cycle. * * To compute the independent set, this implementation relies on the {@link ChordalityInspector} to * compute a * perfect elimination order. * * The maximum cardinality independent set for a chordal graph is computed in $\mathcal{O}(|V| + * |E|)$ time. * * All the methods in this class are invoked in a lazy fashion, meaning that computations are only * started once the method gets invoked. * * @param the graph vertex type. * @param the graph edge type. * * @author Timofey Chudakov */ public class ChordalGraphIndependentSetFinder implements IndependentSetAlgorithm { private final Graph graph; private final ChordalityInspector chordalityInspector; private IndependentSet maximumIndependentSet; /** * Creates a new ChordalGraphIndependentSetFinder instance. The {@link ChordalityInspector} used * in this implementation uses the default {@link MaximumCardinalityIterator} iterator. * * @param graph graph */ public ChordalGraphIndependentSetFinder(Graph graph) { this(graph, ChordalityInspector.IterationOrder.MCS); } /** * Creates a new ChordalGraphIndependentSetFinder instance. The {@link ChordalityInspector} used * in this implementation uses either the {@link MaximumCardinalityIterator} iterator or the * {@link LexBreadthFirstIterator} iterator, depending on the parameter {@code iterationOrder}. * * @param graph graph * @param iterationOrder constant which defines iterator to be used by the * {@code ChordalityInspector} in this implementation. */ public ChordalGraphIndependentSetFinder( Graph graph, ChordalityInspector.IterationOrder iterationOrder) { this.graph = Objects.requireNonNull(graph); chordalityInspector = new ChordalityInspector<>(graph, iterationOrder); } /** * Lazily computes a maximum independent set of the inspected {@code graph}. */ private void lazyComputeMaximumIndependentSet() { if (maximumIndependentSet == null && chordalityInspector.isChordal()) { // iterate the order from the end to the beginning // chooses vertices, that don't have neighbors in the current independent set // adds all its neighbors to the restricted set Set restricted = new HashSet<>(); Set is = new HashSet<>(); List perfectEliminationOrder = chordalityInspector.getPerfectEliminationOrder(); ListIterator reverse = perfectEliminationOrder.listIterator(perfectEliminationOrder.size()); while (reverse.hasPrevious()) { V previous = reverse.previous(); if (!restricted.contains(previous)) { is.add(previous); for (E edge : graph.edgesOf(previous)) { V opposite = Graphs.getOppositeVertex(graph, edge, previous); if (!previous.equals(opposite)) { restricted.add(opposite); } } } } maximumIndependentSet = new IndependentSetImpl<>(is); } } /** * Returns a maximum * cardinality independent set of the inspected {@code graph}. If the graph isn't chordal, * returns null. * * @return a maximum independent set of the {@code graph} if it is chordal, null otherwise. */ @Override public IndependentSet getIndependentSet() { lazyComputeMaximumIndependentSet(); return maximumIndependentSet; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/independentset/package-info.java000066400000000000000000000002461402514743400330320ustar00rootroot00000000000000/** * Algorithms for Independent * Set in a graph. */ package org.jgrapht.alg.independentset; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/000077500000000000000000000000001402514743400267535ustar00rootroot00000000000000AStarAdmissibleHeuristic.java000066400000000000000000000061041402514743400344270ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2015-2021, by Joris Kinable, Jon Robison, Thomas Breitbart and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; /** * Interface for an admissible heuristic used in A* search. * * @param vertex type * @author Joris Kinable * @author Jon Robison * @author Thomas Breitbart */ public interface AStarAdmissibleHeuristic { /** * An admissible "heuristic estimate" of the distance from $x$, the sourceVertex, to the goal * (usually denoted $h(x)$). This is the good guess function which must never overestimate the * distance. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @return an estimate of the distance from the source to the target vertex */ double getCostEstimate(V sourceVertex, V targetVertex); /** * Returns true if the heuristic is a consistent or monotone heuristic wrt the * provided {@code graph}. A heuristic is monotonic if its estimate is always less than or equal * to the estimated distance from any neighboring vertex to the goal, plus the step cost of * reaching that neighbor. For details, refer to https://en.wikipedia.org/wiki/Consistent_heuristic. * In short, a heuristic is consistent iff h(u)≤ d(u,v)+h(v), for every edge * $(u,v)$, where $d(u,v)$ is the weight of edge $(u,v)$ and $h(u)$ is the estimated cost to * reach the target node from vertex u. Most natural admissible heuristics, such as Manhattan or * Euclidean distance, are consistent heuristics. * * @param graph graph to test heuristic on * @param graph edges type * @return true iff the heuristic is consistent wrt the {@code graph}, false otherwise */ default boolean isConsistent(Graph graph) { if (graph == null) { throw new IllegalArgumentException("Graph cannot be null!"); } for (V targetVertex : graph.vertexSet()) { for (E e : graph.edgeSet()) { double weight = graph.getEdgeWeight(e); V edgeSource = graph.getEdgeSource(e); V edgeTarget = graph.getEdgeTarget(e); double h_x = getCostEstimate(edgeSource, targetVertex); double h_y = getCostEstimate(edgeTarget, targetVertex); if (h_x > weight + h_y) return false; } } return true; } } CapacitatedSpanningTreeAlgorithm.java000066400000000000000000000202111402514743400361220ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Christoph Grüne and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import java.io.*; import java.util.*; /** * An algorithm which computes a capacitated (minimum) spanning tree of a given connected graph with * a designated root vertex. The input is a connected undirected graph G = (V, E) with a designated * root r \in V, a capacity constraint K \in \mathbb{N}, a demand function d: V \rightarrow * \mathbb{N} and a capacity function c: E \rightarrow \mathbb{N}. A * Capacitated Minimum * Spanning Tree (CMST) is a rooted minimal cost spanning tree that satisfies the capacity * constraint on all trees that are connected to the designated root. That is, the sum of the * demands of all vertices is smaller or equal than K. These trees build up a partition on the * vertex set of the graph. The problem is NP-hard. * * @param the graph vertex type * @param the graph edge type */ public interface CapacitatedSpanningTreeAlgorithm { /** * Computes a capacitated spanning tree. * * @return a capacitated spanning tree */ CapacitatedSpanningTree getCapacitatedSpanningTree(); /** * A spanning tree. * * @param the graph vertex type * @param the graph edge type */ interface CapacitatedSpanningTree extends Iterable, SpanningTreeAlgorithm.SpanningTree { /** * Tests whether cmst is a CMST on graph with root * root, capacity capacity and demand function * demands. * * @param graph the graph * @param root the expected root of cmst * @param capacity the expected capacity of cmst * @param demands the demand function * * @return whether cmst is a CMST */ boolean isCapacitatedSpanningTree( Graph graph, V root, double capacity, Map demands); /** * Return the set of labels of the underlying partition of the capacitated spanning tree. * The labels are a key to the vertex sets of the partition. * * @return the label set of the capacitated spanning tree. */ Map getLabels(); /** * Return the label-to-partition map of the underlying partition of capacitated spanning * tree. * * @return map from labels to the subsets of the partition of the capacitated spanning tree. */ Map, Double>> getPartition(); } /** * Default implementation of the spanning tree interface. * * @param the graph vertex type * @param the graph edge type */ class CapacitatedSpanningTreeImpl implements CapacitatedSpanningTree, Serializable { private static final long serialVersionUID = 7088989899889893333L; private final Map labels; private final Map, Double>> partition; private final double weight; private final Set edges; /** * Construct a new capacitated spanning tree. * * @param labels the labelling of the vertices marking their subset membership in the * partition * @param partition the implicitly defined partition of the vertices in the capacitated * spanning tree * @param edges the edge set of the capacitated spanning tree * @param weight the weight of the capacitated spanning tree, i.e. the sum of all edge * weights */ public CapacitatedSpanningTreeImpl( Map labels, Map, Double>> partition, Set edges, double weight) { this.labels = labels; this.partition = partition; this.edges = edges; this.weight = weight; } @Override public boolean isCapacitatedSpanningTree( Graph graph, V root, double capacity, Map demands) { if (this.getEdges().size() != graph.vertexSet().size() - 1) { return false; } // check for disjointness for (Pair, Double> set1 : this.getPartition().values()) { for (Pair, Double> set2 : this.getPartition().values()) { if (set1 != set2 && !Collections.disjoint(set1.getFirst(), set2.getFirst())) { return false; } } } // check demands and number of vertices int numberOfNodesExplored = 0; for (Pair, Double> set1 : this.getPartition().values()) { int currentCapacity = 0; for (V v : set1.getFirst()) { currentCapacity += demands.get(v); numberOfNodesExplored++; } if (currentCapacity > capacity) { return false; } } if (graph.vertexSet().size() - 1 != numberOfNodesExplored) { return false; } // check if partition and tree correspond to each other Graph spanningTreeGraph = new AsSubgraph<>(graph, graph.vertexSet(), this.getEdges()); DepthFirstIterator depthFirstIterator = new DepthFirstIterator<>(spanningTreeGraph, root); if (depthFirstIterator.hasNext()) { depthFirstIterator.next(); } int numberOfRootEdgesExplored = 0; Set currentSubtree = new HashSet<>(); while (depthFirstIterator.hasNext()) { V next = depthFirstIterator.next(); if (spanningTreeGraph.containsEdge(root, next)) { if (!currentSubtree.isEmpty()) { if (!currentSubtree .equals( this .getPartition() .get(this.getLabels().get(currentSubtree.iterator().next())) .getFirst())) { return false; } currentSubtree = new HashSet<>(); } numberOfRootEdgesExplored++; } currentSubtree.add(next); } if (numberOfRootEdgesExplored != spanningTreeGraph.degreeOf(root)) { return false; } return true; } @Override public Map getLabels() { return labels; } @Override public Map, Double>> getPartition() { return partition; } @Override public double getWeight() { return weight; } @Override public Set getEdges() { return edges; } @Override public String toString() { return "Capacitated Spanning-Tree [weight=" + weight + ", edges=" + edges + ", labels=" + labels + ", partition=" + partition + "]"; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CliqueAlgorithm.java000066400000000000000000000041551402514743400327140ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.util.*; import java.util.*; /** * Algorithm to compute a (weighted) Clique * in a graph. * * @param vertex the graph vertex type * * @author Joris Kinable */ public interface CliqueAlgorithm { /** * Computes a clique. * * @return a clique */ Clique getClique(); /** * A Clique * * @param the vertex type */ interface Clique extends Set { /** * Returns the weight of the clique. When solving a weighted clique problem, the weight * returned is the sum of the weights of the vertices in the clique. When solving the * unweighted variant, the cardinality of the clique is returned instead. * * @return weight of the independent set */ double getWeight(); } /** * Default implementation of a (weighted) clique * * @param the vertex type */ class CliqueImpl extends WeightedUnmodifiableSet implements Clique { private static final long serialVersionUID = -4336873008459736342L; public CliqueImpl(Set clique) { super(clique); } public CliqueImpl(Set clique, double weight) { super(clique, weight); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/ClusteringAlgorithm.java000066400000000000000000000050031402514743400336020ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.io.*; import java.util.*; /** * An algorithm which computes a graph vertex clustering. * * @param the graph vertex type */ public interface ClusteringAlgorithm { /** * Computes a clustering. * * @return a clustering */ Clustering getClustering(); /** * A clustering. The clusters are integers starting from $0$. * * @param the graph vertex type */ interface Clustering extends Iterable> { /** * Get the number of clusters. * * @return the number of clusters */ int getNumberClusters(); /** * Get the clusters. * * @return a list of clusters */ List> getClusters(); } /** * Default implementation of the clustering interface. * * @param the graph vertex type */ class ClusteringImpl implements Clustering, Serializable { private static final long serialVersionUID = -5718903410443848101L; private final List> clusters; /** * Construct a new clustering. * * @param clusters clusters */ public ClusteringImpl(List> clusters) { this.clusters = clusters; } @Override public int getNumberClusters() { return clusters.size(); } @Override public List> getClusters() { return clusters; } @Override public String toString() { return "Clustering [k=" + clusters.size() + ", clusters=" + clusters + "]"; } @Override public Iterator> iterator() { return clusters.iterator(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/CycleBasisAlgorithm.java000066400000000000000000000116621402514743400335140ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; import org.jgrapht.alg.cycle.*; import java.io.*; import java.util.*; /** * Allows to derive an undirected cycle * basis of a given graph. * *

* Note that undirected cycle bases are defined for both undirected and directed graphs. For a * discussion of different kinds of cycle bases in graphs see the following paper. *

    *
  • Christian Liebchen, and Romeo Rizzi. Classes of Cycle Bases. Discrete Applied Mathematics, * 155(3), 337-355, 2007.
  • *
* * @param vertex the graph vertex type * @param edge the graph edge type * * @author Dimitrios Michail */ public interface CycleBasisAlgorithm { /** * Return a list of cycles forming an undirected cycle basis of a graph. * * @return an undirected cycle basis */ CycleBasis getCycleBasis(); /** * An undirected cycle basis. * * @param the graph vertex type * @param the graph edge type */ interface CycleBasis { /** * Return the set of cycles of the cycle basis. * * @return the set of cycles of the cycle basis */ Set> getCycles(); /** * Get the length of the cycle basis. The length of the cycle basis is the sum of the * lengths of its cycles. The length of a cycle is the total number of edges of the cycle. * * @return the length of the cycles basis */ int getLength(); /** * Get the weight of the cycle basis. The weight of the cycle basis is the sum of the * weights of its cycles. The weight of a cycle is the sum of the weights of its edges. * * @return the length of the cycles basis */ double getWeight(); /** * Return the set of cycles of the cycle basis. * * @return the set of cycles of the cycle basis */ Set> getCyclesAsGraphPaths(); } /** * Default implementation of the undirected cycle basis interface. * * @param the graph vertex type * @param the graph edge type */ class CycleBasisImpl implements CycleBasis, Serializable { private static final long serialVersionUID = -1420882459022219505L; private final Graph graph; private final Set> cycles; private Set> graphPaths; private final int length; private final double weight; /** * Construct a new instance. * * @param graph the graph */ public CycleBasisImpl(Graph graph) { this(graph, Collections.emptySet(), 0, 0d); } /** * Construct a new instance. * * @param graph the graph * @param cycles the cycles of the basis * @param length the length of the cycle basis * @param weight the weight of the cycle basis */ public CycleBasisImpl(Graph graph, Set> cycles, int length, double weight) { this.graph = graph; this.cycles = Collections.unmodifiableSet(cycles); this.length = length; this.weight = weight; } /** * {@inheritDoc} */ @Override public Set> getCycles() { return cycles; } /** * {@inheritDoc} */ @Override public int getLength() { return length; } /** * {@inheritDoc} */ @Override public double getWeight() { return weight; } /** * {@inheritDoc} */ @Override public Set> getCyclesAsGraphPaths() { // lazily construct if (graphPaths == null) { graphPaths = new LinkedHashSet<>(); for (List cycle : cycles) { graphPaths.add(Cycles.simpleCycleToGraphPath(graph, cycle)); } } return Collections.unmodifiableSet(graphPaths); } } } EdgeScoringAlgorithm.java000066400000000000000000000022471402514743400336040ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.util.Map; /** * An interface for all algorithms which assign scores to edges of a graph. * * @param the edge type * @param the score type * * @author Dimitrios Michail */ public interface EdgeScoringAlgorithm { /** * Get a map with the scores of all edges * * @return a map with all scores */ Map getScores(); /** * Get an edge score * * @param e the edge * @return the score */ D getEdgeScore(E e); } EulerianCycleAlgorithm.java000066400000000000000000000025511402514743400341350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; /** * Computes an Eulerian cycle of an Eulerian graph. An * Eulerian graph is a graph * containing an Eulerian cycle. * * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public interface EulerianCycleAlgorithm { /** * Compute an Eulerian cycle of a graph. * * @param graph the input graph * @return an Eulerian cycle * @throws IllegalArgumentException in case the graph is not Eulerian */ GraphPath getEulerianCycle(Graph graph); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/FlowAlgorithm.java000066400000000000000000000063161402514743400324020ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.util.*; /** * Interface for flow algorithms * * @author Joris Kinable * * @param the graph vertex type * @param the graph edge type */ public interface FlowAlgorithm { /** * Result object of a flow algorithm * * @return flow */ default Flow getFlow() { return new FlowImpl<>(this.getFlowMap()); } /** * Returns a read-only mapping from edges to the corresponding flow values. * * @return a read-only mapping from edges to the corresponding flow values. */ Map getFlowMap(); /** * For the specified {@code edge} $(u, v)$ returns vertex $v$ if the flow goes from $u$ to $v$, * or returns vertex $u$ otherwise. For directed flow networks the result is always the head of * the specified arc. *

* Note: not all flow algorithms may support undirected graphs. * * @param edge an edge from the specified flow network * @return the direction of the flow on the {@code edge} */ V getFlowDirection(E edge); /** * Represents a flow. * * @param graph edge type */ interface Flow { /** * Returns the flow on the {@code edge} * * @param edge an edge from the flow network * @return the flow on the {@code edge} */ default double getFlow(E edge) { return getFlowMap().get(edge); } /** * Returns a mapping from the network flow edges to the corresponding flow values. The * mapping contains all edges of the flow network regardless of whether there is a non-zero * flow on an edge or not. * * @return a read-only map that defines a feasible flow. */ Map getFlowMap(); } /** * Default implementation of {@link Flow} * * @param graph edge type */ class FlowImpl implements Flow { /** * A mapping defining the flow on the network */ private Map flowMap; /** * Constructs a new flow * * @param flowMap the mapping defining the flow on the network */ public FlowImpl(Map flowMap) { this.flowMap = Collections.unmodifiableMap(flowMap); } /** * {@inheritDoc} */ @Override public Map getFlowMap() { return flowMap; } } } HamiltonianCycleAlgorithm.java000066400000000000000000000025311402514743400346320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; /** * An algorithm solving the Hamiltonian * cycle problem. * *

* A Hamiltonian cycle, also called a Hamiltonian circuit, Hamilton cycle, or Hamilton circuit, is a * graph cycle (i.e., closed loop) through a graph that visits each node exactly once (Skiena 1990, * p. 196). * * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public interface HamiltonianCycleAlgorithm { /** * Computes a tour. * * @param graph the input graph * @return a tour */ GraphPath getTour(Graph graph); } HamiltonianCycleImprovementAlgorithm.java000066400000000000000000000030771402514743400370660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; /** * An algorithm improving the result of solving the * Hamiltonian cycle problem. * *

* A Hamiltonian cycle, also called a Hamiltonian circuit, Hamilton cycle, or Hamilton circuit, is a * graph cycle (i.e., closed loop) through a graph that visits each node exactly once (Skiena 1990, * p. 196). * * An improvement algorithm could be one that optimises the cycle for lower cost, or that updates * the cycle to match changes in the graph. * * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu * @author Peter Harman */ public interface HamiltonianCycleImprovementAlgorithm { /** * Improves a tour. * * @param tour the current tour * @return the tour improved */ public GraphPath improveTour(GraphPath tour); } IndependentSetAlgorithm.java000066400000000000000000000046061402514743400343250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.util.*; import java.util.*; /** * Algorithm to compute an * Independent Set in a graph. * * @param vertex the graph vertex type * * @author Joris Kinable */ public interface IndependentSetAlgorithm { /** * Computes an independent set; all vertices are considered to have equal weight. * * @return a vertex independent set */ IndependentSet getIndependentSet(); /** * A (weighted) Independent * Set * * @param the vertex type */ interface IndependentSet extends Set { /** * Returns the weight of the independent set. When solving a weighted independent set * problem, the weight returned is the sum of the weights of the vertices in the independent * set. When solving the unweighted variant, the cardinality of the independent set is * returned instead. * * @return weight of the independent set */ double getWeight(); } /** * Default implementation of a (weighted) independent set * * @param the vertex type */ class IndependentSetImpl extends WeightedUnmodifiableSet implements IndependentSet { private static final long serialVersionUID = 4572451196544323306L; public IndependentSetImpl(Set independentSet) { super(independentSet); } public IndependentSetImpl(Set independentSet, double weight) { super(independentSet, weight); } } } KShortestPathAlgorithm.java000066400000000000000000000025241402514743400341540ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; import java.util.*; /** * An algorithm which computes $k$-shortest paths between vertices. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public interface KShortestPathAlgorithm { /** * Get a list of k-shortest paths from a source vertex to a sink vertex. If no such paths exist * this method returns an empty list. * * @param source the source vertex * @param sink the target vertex * @param k the number of shortest paths to return * @return a list of the k-shortest paths */ List> getPaths(V source, V sink, int k); } LinkPredictionAlgorithm.java000066400000000000000000000051171402514743400343300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.util.ArrayList; import java.util.List; import org.jgrapht.alg.util.Pair; import org.jgrapht.alg.util.Triple; /** * A link prediction algorithm. * *

* A link prediction algorithm provides a score $s_{uv}$ for any pair of vertices $u,v \in V$ in the * graph such that $e=(u,v) \notin E$. The nature, the magnitude and possible interpretation of such * a score depends solely on the actual algorithm, meaning that it might be a similarity score, a * distance metric, a probability, or even something completely unrelated. * * Depending on the particular algorithm, a possible interpretation of the scores might be that they * measure similarity between vertices $u$ and $v$. Thus, given such scores one could sort the edges * in decreasing order and pick the top-k as links (edges) which are likely to exist. *

* * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public interface LinkPredictionAlgorithm { /** * Predict an edge between a set of vertex pairs. The magnitude and the interpretation of the * returned scores depend solely on the algorithm. * * @param queries a list of vertex pairs * @return a list of vertex triples where the last component is an edge prediction score */ default List> predict(List> queries) { List> result = new ArrayList<>(); for (Pair q : queries) { result .add(Triple.of(q.getFirst(), q.getSecond(), predict(q.getFirst(), q.getSecond()))); } return result; } /** * Predict an edge between two vertices. The magnitude and the interpretation of the returned * score depend solely on the algorithm. * * @param u first vertex * @param v second vertex * @return a prediction score */ double predict(V u, V v); } LowestCommonAncestorAlgorithm.java000066400000000000000000000047651402514743400355470ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.alg.util.*; import java.util.*; import java.util.stream.*; /** * Algorithm to compute a lowest * common ancestor in a tree, forest or DAG. * * @param vertex the graph vertex type * * @author Alexandru Valeanu */ public interface LowestCommonAncestorAlgorithm { /** * Return the LCA of a and b * * @param a the first element to find LCA for * @param b the other element to find the LCA for * * @return the LCA of a and b, or null if there is no LCA. */ V getLCA(V a, V b); /** * Return a list of LCAs for a batch of queries * * @param queries a list of pairs of vertices * @return a list L of LCAs where L(i) is the LCA for pair queries(i) */ default List getBatchLCA(List> queries) { return queries .stream().map(p -> getLCA(p.getFirst(), p.getSecond())).collect(Collectors.toList()); } /** * Return the computed set of LCAs of a and b * * @param a the first element to find LCA for * @param b the other element to find the LCA for * * @return the set LCAs of a and b, or empty set if there is no LCA computed. * @throws UnsupportedOperationException - if the operation is not supported by the implementing * class */ Set getLCASet(V a, V b); /** * Return a list of computed sets of LCAs for a batch of queries * * @param queries a list of pairs of vertices * @return a list L of LCAs where L(i) is the computed set of LCAs for pair queries(i) */ default List> getBatchLCASet(List> queries) { return queries .stream().map(p -> getLCASet(p.getFirst(), p.getSecond())).collect(Collectors.toList()); } } ManyToManyShortestPathsAlgorithm.java000066400000000000000000000113161402514743400362000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; import java.util.*; /** * An algorithm which computes shortest paths from all sources to all targets. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov */ public interface ManyToManyShortestPathsAlgorithm extends ShortestPathAlgorithm { /** * Computes shortest paths from all vertices in {@code sources} to all vertices in * {@code targets}. * * @param sources list of sources vertices * @param targets list of target vertices * @return computed shortest paths */ ManyToManyShortestPaths getManyToManyPaths(Set sources, Set targets); /** * A set of paths from all sources vertices to all target vertices. * * @param the graph vertices type * @param the graph edge type */ interface ManyToManyShortestPaths { /** * Returns the set of source vertices for which this many-to-many shortest paths were * computed. * * @return the set of source vertices */ Set getSources(); /** * Returns the set of target vertices for which this many-to-many shortest paths were * computed. * * @return the set of target vertices */ Set getTargets(); /** * Return the path from the {@code source} vertex to the {@code target} vertex. If no such * path exists, null is returned. * * @param source source vertex * @param target target vertex * @return path between {@code source} and {@code target} or null if no such path exists */ GraphPath getPath(V source, V target); /** * Return the weight of the path from the {@code source} vertex to the {@code target}vertex * or {@link Double#POSITIVE_INFINITY} if there is no such path in the graph. The weight of * the path between a vertex and itself is always zero. * * @param source source vertex * @param target target vertex * @return the weight of the path between source and sink vertices or * {@link Double#POSITIVE_INFINITY} in case no such path exists */ double getWeight(V source, V target); } /** * Base class for many-to-many shortest paths implementations. * * @param the graph vertex type * @param the graph edge type */ abstract class BaseManyToManyShortestPathsImpl implements ManyToManyShortestPaths { /** * Set of source vertices. */ private final Set sources; /** * Set of source vertices. */ private final Set targets; @Override public Set getSources() { return sources; } @Override public Set getTargets() { return targets; } /** * Constructs an instance for the given {@code sources} and {@code targets}. * * @param sources source vertices * @param targets target vertices */ protected BaseManyToManyShortestPathsImpl(Set sources, Set targets) { this.sources = sources; this.targets = targets; } /** * Checks that {@code source} and {@code target} are not null and are present in the * {@code graph}. * * @param source a source vertex * @param target a target vertex */ protected void assertCorrectSourceAndTarget(V source, V target) { Objects.requireNonNull(source, "source should not be null!"); Objects.requireNonNull(target, "target should not be null!"); if (!sources.contains(source) || !targets.contains(target)) { throw new IllegalArgumentException( "paths between " + source + " and " + target + " is not computed"); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/MatchingAlgorithm.java000066400000000000000000000120721402514743400332210ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; import java.io.*; import java.util.*; /** * Allows to derive a matching of * a given graph. * * @param the graph vertex type * @param the graph edge type */ public interface MatchingAlgorithm { /** * Default tolerance used by algorithms comparing floating point values. */ double DEFAULT_EPSILON = 1e-9; /** * Compute a matching for a given graph. * * @return a matching */ Matching getMatching(); /** * A graph matching. * * @param the graph vertex type * @param the graph edge type */ interface Matching extends Iterable { /** * Returns the graph over which this matching is defined. * * @return the graph */ Graph getGraph(); /** * Returns the weight of the matching. * * @return the weight of the matching */ double getWeight(); /** * Get the edges of the matching. * * @return the edges of the matching */ Set getEdges(); /** * Returns true if vertex v is incident to an edge in this matching. * * @param v vertex * @return true if vertex v is incident to an edge in this matching. */ default boolean isMatched(V v) { Set edges = getEdges(); return getGraph().edgesOf(v).stream().anyMatch(edges::contains); } /** * Returns true if the matching is a perfect matching. A matching is perfect if every vertex * in the graph is incident to an edge in the matching. * * @return true if the matching is perfect. By definition, a perfect matching consists of * exactly $\frac{1}{2|V|}$ edges, and the number of vertices in the graph must be * even. */ default boolean isPerfect() { return getEdges().size() == getGraph().vertexSet().size() / 2.0; } /** * Returns an iterator over the edges in the matching. * * @return iterator over the edges in the matching. */ @Override default Iterator iterator() { return getEdges().iterator(); } } /** * A default implementation of the matching interface. * * @param the graph vertex type * @param the graph edge type */ class MatchingImpl implements Matching, Serializable { private static final long serialVersionUID = 4767675421846527768L; private Graph graph; private Set edges; private double weight; private Set matchedVertices = null; /** * Construct a new instance * * @param graph graph on which the matching is defined * @param edges the edges of the matching * @param weight the weight of the matching */ public MatchingImpl(Graph graph, Set edges, double weight) { this.graph = graph; this.edges = edges; this.weight = weight; } @Override public Graph getGraph() { return graph; } /** * {@inheritDoc} */ @Override public double getWeight() { return weight; } /** * {@inheritDoc} */ @Override public Set getEdges() { return edges; } /** * {@inheritDoc} */ @Override public boolean isMatched(V v) { if (matchedVertices == null) { // lazily index the vertices that have been matched matchedVertices = new HashSet<>(); for (E e : edges) { matchedVertices.add(graph.getEdgeSource(e)); matchedVertices.add(graph.getEdgeTarget(e)); } } return matchedVertices.contains(v); } /** * {@inheritDoc} */ @Override public String toString() { return "Matching [edges=" + edges + ", weight=" + weight + "]"; } } } MaximalCliqueEnumerationAlgorithm.java000066400000000000000000000021531402514743400363510ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.util.*; /** * A maximal clique enumeration algorithm. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public interface MaximalCliqueEnumerationAlgorithm extends Iterable> { /** * Returns an iterator over all maximal cliques. * * @return an iterator over all maximal cliques */ @Override Iterator> iterator(); } MaximumDensitySubgraphAlgorithm.java000066400000000000000000000023421402514743400360600ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Andre Immig and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; /** * Interface for algorithms computing the maximum density subgraph * * @param the graph vertex type * @param the graph edge type * * @author Andre Immig */ public interface MaximumDensitySubgraphAlgorithm { /** * Calculate a maximum density subgraph * * @return the maximum density subgraph */ Graph calculateDensest(); /** * Computes density of a maximum density subgraph. * * @return the actual density of the maximum density subgraph */ double getDensity(); } MaximumFlowAlgorithm.java000066400000000000000000000063531402514743400336620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2015-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.util.*; /** * Allows to derive maximum-flow * from the supplied flow network * * @author Alexey Kudinkin * @author Joris Kinable * * @param the graph vertex type * @param the graph edge type * */ public interface MaximumFlowAlgorithm extends FlowAlgorithm { /** * Sets current source to source, current sink to sink, then * calculates maximum flow from source to sink. Returns an object * containing detailed information about the flow. * * @param source source of the flow inside the network * @param sink sink of the flow inside the network * * @return maximum flow */ MaximumFlow getMaximumFlow(V source, V sink); /** * Sets current source to source, current sink to sink, then * calculates maximum flow from source to sink. Note, that * source and sink must be vertices of the * network passed to the constructor, and they must be different. * * @param source source vertex * @param sink sink vertex * @return the value of the maximum flow */ default double getMaximumFlowValue(V source, V sink) { return getMaximumFlow(source, sink).getValue(); } /** * A maximum flow * * @param the graph edge type */ interface MaximumFlow extends Flow { /** * Returns value of the maximum-flow for the given network * * @return value of the maximum-flow */ Double getValue(); } /** * Default implementation of the maximum flow * * @param the graph edge type */ class MaximumFlowImpl extends FlowImpl implements MaximumFlow { private Double value; /** * Create a new maximum flow * * @param value the flow value * @param flow the flow map */ public MaximumFlowImpl(Double value, Map flow) { super(flow); this.value = value; } @Override public Double getValue() { return value; } @Override public String toString() { return "Flow Value: " + value + "\nFlow map:\n" + getFlowMap(); } } } MinimumCostFlowAlgorithm.java000066400000000000000000000060761402514743400345130ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.alg.flow.mincost.*; import java.util.*; /** * Allows to calculate minimum cost flow on the specified * minimum cost flow problem. *

* For more information see: K. Ahuja, Ravindra & L. Magnanti, Thomas & Orlin, James. * (1993). Network Flows. * * @param graph vertex type * @param graph edge type * @author Timofey Chudakov */ public interface MinimumCostFlowAlgorithm extends FlowAlgorithm { /** * Calculates feasible flow of minimum cost for the minimum cost flow problem. * * @param minimumCostFlowProblem minimum cost flow problem * @return minimum cost flow */ MinimumCostFlow getMinimumCostFlow(MinimumCostFlowProblem minimumCostFlowProblem); /** * Returns the objective value (cost) of a solution to the minimum cost flow problem. * * @param minimumCostFlowProblem minimum cost flow problem * @return the objective value (cost) of a solution to the minimum cost flow problem. */ default double getFlowCost(MinimumCostFlowProblem minimumCostFlowProblem) { return getMinimumCostFlow(minimumCostFlowProblem).getCost(); } /** * Represents a minimum cost flow. * * @param graph edge type */ interface MinimumCostFlow extends Flow { /** * Returns the cost of the flow * * @return the cost of the flow */ double getCost(); } /** * Default implementation of the {@link MinimumCostFlow} * * @param graph edge type */ class MinimumCostFlowImpl extends FlowImpl implements MinimumCostFlow { /** * The cost of the flow defined by the mapping {@code flowMap} */ double cost; /** * Constructs a new instance of minimum cost flow * * @param cost the cost of the flow * @param flowMap the mapping defining the flow on the network */ public MinimumCostFlowImpl(double cost, Map flowMap) { super(flowMap); this.cost = cost; } /** * {@inheritDoc} */ @Override public double getCost() { return cost; } } } MinimumCycleMeanAlgorithm.java000066400000000000000000000026671402514743400346150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.GraphPath; /** * The algorithm for finding minimum cycle mean in a graph. * *

* Consider a cycle $C$ in a graph. The mean of cycle $C$ is defined as $\lambda * (C)=\frac{w(C)}{|C|}$, where $w(C)$ is the total weight of $C$ and $|C|$ is the length of $C$. * * @param graph vertex type * @param graph edge type */ public interface MinimumCycleMeanAlgorithm { /** * Computes minimum mean among all cycle. Returns {@link Double#POSITIVE_INFINITY} if no cycle * has been found. * * @return minimum mean */ double getCycleMean(); /** * Computes cycle with minimum mean. Returns $null$ if no cycle has been found. * * @return cycle with minimum mean */ GraphPath getCycle(); } MinimumSTCutAlgorithm.java000066400000000000000000000063031402514743400337460ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.util.*; /** * Given a weighted graph $G(V,E)$ (directed or undirected). This class computes a minimum $s-t$ * cut. A cut is a partitioning of the vertices into two disjoint sets $S, T $such that $s \in S, t * \in T$, and that $S \cup T = V%. The capacity of a cut is defined as the sum of the * weights of the edges from $S$ to $T$. In case of a directed graph, only the edges with their tail * in $S$ and their head in $T$ are counted. In cased of a undirected graph, all edges with one * endpoint in $S$ and one endpoint in $T$ are counted. For a given $s$ and $t$, this class computes * two partitions $S$ and $T$ such that the capacity of the cut is minimized. When each edge has * equal weight, by definition this class minimizes the number of edges from $S$ to $T$. * * Note: it is not recommended to use this class to calculate the overall minimum cut in a graph by * iteratively invoking this class for all source-sink pairs. This is computationally expensive. * Instead, use the StoerWagnerMinimumCut implementation. * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public interface MinimumSTCutAlgorithm { /** * Computes a minimum capacity $s-t$ cut. * * @param source s * @param sink t * @return capacity of the cut */ double calculateMinCut(V source, V sink); /** * Returns the capacity of the cut obtained after the last invocation of * {@link #calculateMinCut(Object, Object)} * * @return capacity of the cut */ double getCutCapacity(); /** * Returns the source partition $S$, $s \in S$, of the cut obtained after the last invocation of * {@link #calculateMinCut(Object, Object)} * * @return source partition S */ Set getSourcePartition(); /** * Returns the sink partition $T$, $t \in T$, of the cut obtained after the last invocation of * {@link #calculateMinCut(Object, Object)} * * @return source partition T */ Set getSinkPartition(); /** * Returns the set of edges which run from $S$ to $T$, in the $s-t$ cut obtained after the last * invocation of {@link #calculateMinCut(Object, Object)} In case of a directed graph, only the * edges with their tail in $S$ and their head in $T$ are returned. In cased of a undirected * graph, all edges with one endpoint in $S$ and one endpoint in $T$ are returned. * * @return set of edges which run from $S$ to $T$ */ Set getCutEdges(); } MultiObjectiveShortestPathAlgorithm.java000066400000000000000000000044331402514743400367100ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; import java.util.*; /** * An algorithm which computes multi-objective shortest paths between vertices. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public interface MultiObjectiveShortestPathAlgorithm { /** * Get a shortest path from a source vertex to a sink vertex. * * @param source the source vertex * @param sink the target vertex * @return a shortest path or null if no path exists */ List> getPaths(V source, V sink); /** * Compute all shortest paths starting from a single source vertex. * * @param source the source vertex * @return the shortest paths */ MultiObjectiveSingleSourcePaths getPaths(V source); /** * A set of paths starting from a single source vertex. * * @param the graph vertex type * @param the graph edge type */ interface MultiObjectiveSingleSourcePaths { /** * Returns the graph over which this set of paths is defined. * * @return the graph */ Graph getGraph(); /** * Returns the single source vertex. * * @return the single source vertex */ V getSourceVertex(); /** * Return the path from the source vertex to the sink vertex. * * @param sink the sink vertex * @return the path from the source vertex to the sink vertex or null if no such path exists */ List> getPaths(V sink); } } PartitioningAlgorithm.java000066400000000000000000000124371402514743400340640ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.io.*; import java.util.*; import java.util.stream.*; /** * Algorithm to compute a vertex partitioning of a graph. * * @param vertex the graph vertex type * * @author Alexandru Valeanu */ public interface PartitioningAlgorithm { /** * Computes a vertex partitioning. * * @return a vertex partitioning */ Partitioning getPartitioning(); /** * Check if the given vertex partitioning is valid. * * @param partitioning the input vertex partitioning * @return true if the input partitioning is valid, false otherwise */ boolean isValidPartitioning(Partitioning partitioning); /** * A graph partitioning. * * @param the vertex type */ interface Partitioning extends Iterable> { /** * Get the number of partitions. * * @return the number of partitions */ int getNumberPartitions(); /** * Get the index-th partition (0-based). * * @param index index of the partition to return * @return the index-th partition * @throws IndexOutOfBoundsException if the index is out of range * (index < 0 || index >= getNumberPartitions()) */ Set getPartition(int index); /** * Get the partitions. This method returns a partitioning of the vertices in the graph into * disjoint partitions. * * @return a list of partitions */ default List> getPartitions() { final int n = getNumberPartitions(); List> partitions = new ArrayList<>(n); for (int i = 0; i < n; i++) { partitions.add(getPartition(i)); } return partitions; } } /** * Default implementation of a vertex partition * * @param the vertex type */ class PartitioningImpl implements Partitioning, Serializable { private static final long serialVersionUID = 3702471090706836080L; /* Partitioning classes */ private final List> classes; /** * Construct a new vertex partitioning. * * @param classes the partition classes * @throws NullPointerException if {@code classes} is {@code null} */ public PartitioningImpl(List> classes) { this.classes = Collections .unmodifiableList( Objects .requireNonNull(classes).stream().map(Collections::unmodifiableSet) .collect(Collectors.toList())); } /** * Construct a new vertex partitioning. * * @param vertexToPartitionMap the vertex to partition index map * @throws NullPointerException if {@code vertexToPartitionMap} is {@code null} */ public PartitioningImpl(Map vertexToPartitionMap) { Objects.requireNonNull(vertexToPartitionMap); Map> partitionIndexToVertexMap = new HashMap<>(); for (Map.Entry entry : vertexToPartitionMap.entrySet()) { partitionIndexToVertexMap .computeIfAbsent(entry.getValue(), x -> new HashSet<>()).add(entry.getKey()); } this.classes = Collections .unmodifiableList( partitionIndexToVertexMap .values().stream().map(Collections::unmodifiableSet) .collect(Collectors.toList())); } /** * {@inheritDoc} */ @Override public int getNumberPartitions() { return classes.size(); } /** * {@inheritDoc} */ @Override public Set getPartition(int index) { if (index < 0 || index >= classes.size()) { throw new IndexOutOfBoundsException(index + " is not valid"); } return classes.get(index); } /** * {@inheritDoc} */ @Override public String toString() { return "Partition [number-of-partitions=" + getNumberPartitions() + ", partitions=" + classes + "]"; } /** * {@inheritDoc} */ @Override public Iterator> iterator() { return classes.iterator(); } } } PlanarityTestingAlgorithm.java000066400000000000000000000127351402514743400347170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; import java.util.*; import java.util.stream.*; /** * Allows to check the planarity of the graph. A graph is defined to be * planar if it can be drawn on a * two-dimensional plane without any of its edges crossing. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov */ public interface PlanarityTestingAlgorithm { /** * Tests the planarity of the {@code graph}. Returns true if the input graph is planar, false * otherwise. If this method returns true, the combinatorial embedding of the {@code graph} is * provided after the call to the {@link PlanarityTestingAlgorithm#getEmbedding()}. Otherwise, a * Kuratowski subdivision is provided after the call to the * {@link PlanarityTestingAlgorithm#getKuratowskiSubdivision()}. * * @return {@code true} if the {@code graph} is planar, false otherwise */ boolean isPlanar(); /** * Computes combinatorial embedding of the input {@code graph}. This method will return a valid * result only if the {@code graph} is planar. For more information on the combinatorial * embedding, see {@link PlanarityTestingAlgorithm.Embedding} * * @return combinatorial embedding of the input {@code graph} */ Embedding getEmbedding(); /** * Extracts a Kuratowski subdivision from the {@code graph}. The returned value certifies the * nonplanarity of the graph. The returned certificate can be verified through the call to the * {@link org.jgrapht.GraphTests#isKuratowskiSubdivision(Graph)}. This method will return a * valid result only if the {@code graph} is not planar. * * @return a Kuratowski subdivision from the {@code graph} */ Graph getKuratowskiSubdivision(); /** * A * combinatorial * embedding of the graph. It is represented as the edges ordered clockwise around * the vertices. The edge order around the vertices is sufficient to embed the graph on a plane, * i.e. assign coordinates to its vertices and draw its edges such that none of the cross. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov */ interface Embedding { /** * Returns the clockwise order of edges incident to the {@code vertex} * * @param vertex the vertex whose incident edges are returned * @return the clockwise order of edges incident to the {@code vertex} */ List getEdgesAround(V vertex); /** * Returns the underlying {@code graph} * * @return the underlying {@code graph} */ Graph getGraph(); } /** * Implementation of the {@link PlanarityTestingAlgorithm.Embedding}. * * @param the graph vertex type * @param the graph edge type */ class EmbeddingImpl implements Embedding { /** * The underlying {@code graph} */ private Graph graph; /** * The map from vertices of the {@code graph} to the clockwise order of edges */ private Map> embeddingMap; /** * Creates new embedding of the {@code graph} * * @param graph the {@code graph} * @param embeddingMap map from vertices of {@code graph} to the clockwise order of edges */ public EmbeddingImpl(Graph graph, Map> embeddingMap) { this.graph = graph; this.embeddingMap = embeddingMap; } /** * {@inheritDoc} */ @Override public List getEdgesAround(V vertex) { return embeddingMap.get(vertex); } /** * {@inheritDoc} */ @Override public Graph getGraph() { return graph; } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder("["); for (Map.Entry> entry : embeddingMap.entrySet()) { builder .append(entry.getKey().toString()).append(" -> ") .append( entry .getValue().stream() .map(e -> Graphs.getOppositeVertex(graph, e, entry.getKey()).toString()) .collect(Collectors.joining(", ", "[", "]"))) .append(", "); } return builder.append("]").toString(); } } } ShortestPathAlgorithm.java000066400000000000000000000062001402514743400340340ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; /** * An algorithm which computes shortest paths between vertices. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public interface ShortestPathAlgorithm { /** * Get a shortest path from a source vertex to a sink vertex. * * @param source the source vertex * @param sink the target vertex * @return a shortest path or null if no path exists */ GraphPath getPath(V source, V sink); /** * Get the weight of the shortest path from a source vertex to a sink vertex. Returns * {@link Double#POSITIVE_INFINITY} if no path exists. * * @param source the source vertex * @param sink the sink vertex * @return the weight of the shortest path from a source vertex to a sink vertex, or * {@link Double#POSITIVE_INFINITY} if no path exists */ double getPathWeight(V source, V sink); /** * Compute all shortest paths starting from a single source vertex. * * @param source the source vertex * @return the shortest paths */ SingleSourcePaths getPaths(V source); /** * A set of paths starting from a single source vertex. * * @param the graph vertex type * @param the graph edge type */ interface SingleSourcePaths { /** * Returns the graph over which this set of paths is defined. * * @return the graph */ Graph getGraph(); /** * Returns the single source vertex. * * @return the single source vertex */ V getSourceVertex(); /** * Return the weight of the path from the source vertex to the sink vertex. If no such path * exists, {@link Double#POSITIVE_INFINITY} is returned. The weight of the path between a * vertex and itself is always zero. * * @param sink the sink vertex * @return the weight of the path between source and sink vertices or * {@link Double#POSITIVE_INFINITY} in case no such path exists */ double getWeight(V sink); /** * Return the path from the source vertex to the sink vertex. * * @param sink the sink vertex * @return the path from the source vertex to the sink vertex or null if no such path exists */ GraphPath getPath(V sink); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/SpannerAlgorithm.java000066400000000000000000000045211402514743400330750ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.util.*; import java.io.*; import java.util.*; /** * An algorithm which computes a * graph spanner of a * given graph. * * @param edge the graph edge type * * @author Dimitrios Michail */ public interface SpannerAlgorithm { /** * Computes a graph spanner. * * @return a graph spanner */ Spanner getSpanner(); /** * A graph spanner. * * @param the graph edge type */ interface Spanner extends Set { /** * Returns the weight of the graph spanner. * * @return weight of the graph spanner */ double getWeight(); } /** * Default implementation of the spanner interface. * * @param the graph edge type */ class SpannerImpl extends WeightedUnmodifiableSet implements Spanner, Serializable { private static final long serialVersionUID = 5951646499902668516L; /** * Construct a new spanner * * @param edges the edges */ public SpannerImpl(Set edges) { super(edges); } /** * Construct a new spanner * * @param edges the edges * @param weight the weight */ public SpannerImpl(Set edges, double weight) { super(edges, weight); } @Override public String toString() { return "Spanner [weight=" + weight + ", edges=" + this + "]"; } } } SpanningTreeAlgorithm.java000066400000000000000000000056201402514743400340060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2013-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.io.*; import java.util.*; /** * An algorithm which computes a spanning * tree of a given connected graph. In the case of disconnected graphs it would rather derive a * spanning forest. * * @param the graph edge type */ public interface SpanningTreeAlgorithm { /** * Computes a spanning tree. * * @return a spanning tree */ SpanningTree getSpanningTree(); /** * A spanning tree. * * @param the graph edge type */ interface SpanningTree extends Iterable { /** * Returns the weight of the spanning tree. * * @return weight of the spanning tree */ double getWeight(); /** * Set of edges of the spanning tree. * * @return edge set of the spanning tree */ Set getEdges(); /** * Returns an iterator over the edges in the spanning tree. * * @return iterator over the edges in the spanning tree. */ @Override default Iterator iterator() { return getEdges().iterator(); } } /** * Default implementation of the spanning tree interface. * * @param the graph edge type */ class SpanningTreeImpl implements SpanningTree, Serializable { private static final long serialVersionUID = 402707108331703333L; private final double weight; private final Set edges; /** * Construct a new spanning tree. * * @param edges the edges * @param weight the weight */ public SpanningTreeImpl(Set edges, double weight) { this.edges = edges; this.weight = weight; } @Override public double getWeight() { return weight; } @Override public Set getEdges() { return edges; } @Override public String toString() { return "Spanning-Tree [weight=" + weight + ", edges=" + edges + "]"; } } } StrongConnectivityAlgorithm.java000066400000000000000000000045601402514743400352660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2013-2021, by Sarah Komla-Ebri and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; /** * A strong connectivity inspector algorithm. * * @param the graph vertex type * @param the graph edge type * * @author Sarah Komla-Ebri */ public interface StrongConnectivityAlgorithm { /** * Return the underlying graph. * * @return the underlying graph */ Graph getGraph(); /** * Returns true if the graph is strongly connected, false otherwise. * * @return true if the graph is strongly connected, false otherwise */ boolean isStronglyConnected(); /** * Computes a {@link List} of {@link Set}s, where each set contains vertices which together form * a strongly connected component within the given graph. * * @return List of Set s containing the strongly connected components */ List> stronglyConnectedSets(); /** * Computes a list of subgraphs of the given graph. Each subgraph will represent a strongly * connected component and will contain all vertices of that component. The subgraph will have * an edge $(u,v)$ iff $u$ and $v$ are contained in the strongly connected component. * * @return a list of subgraphs representing the strongly connected components */ List> getStronglyConnectedComponents(); /** * Compute the condensation of the given graph. If each strongly connected component is * contracted to a single vertex, the resulting graph is a directed acyclic graph, the * condensation of the graph. * * @return the condensation of the given graph */ Graph, DefaultEdge> getCondensation(); } TreeToPathDecompositionAlgorithm.java000066400000000000000000000064371402514743400361740ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.stream.*; /** * An algorithm which computes a decomposition into disjoint paths for a given tree/forest * * @param the graph vertex type * @param the graph edge type */ public interface TreeToPathDecompositionAlgorithm { /** * Computes a path decomposition. * * @return a path decomposition */ PathDecomposition getPathDecomposition(); /** * A path decomposition. * * @param the graph vertex type * @param the graph edge type */ interface PathDecomposition { /** * Set of edges of the path decomposition. * * @return edge set of the path decomposition */ Set getEdges(); /** * Set of disjoint paths of the decomposition * * @return list of vertex paths */ Set> getPaths(); /** * @return number of paths in the decomposition */ default int numberOfPaths() { return getPaths().size(); } } /** * Default implementation of the path decomposition interface. * * @param the graph vertex type * @param the graph edge type */ class PathDecompositionImpl implements PathDecomposition, Serializable { private static final long serialVersionUID = 8468626434814461297L; private final Set edges; private final Set> paths; /** * Construct a new path decomposition. * * @param graph the graph * @param edges the edges * @param paths the vertex paths */ public PathDecompositionImpl(Graph graph, Set edges, List> paths) { this.edges = edges; Set> arrayUnenforcedSet = paths .stream().map(path -> new GraphWalk<>(graph, path, path.size())) .collect(Collectors.toCollection(ArrayUnenforcedSet::new)); this.paths = Collections.unmodifiableSet(arrayUnenforcedSet); } @Override public Set getEdges() { return edges; } @Override public Set> getPaths() { return paths; } @Override public String toString() { return "Path-Decomposition [edges=" + edges + "," + "paths=" + getPaths() + "]"; } } } VertexColoringAlgorithm.java000066400000000000000000000070621402514743400343650ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.io.*; import java.util.*; /** * An algorithm which computes a graph vertex coloring. * * @param the graph vertex type */ public interface VertexColoringAlgorithm { /** * Computes a vertex coloring. * * @return a vertex coloring */ Coloring getColoring(); /** * A coloring. The colors are between 0 and $n-1$ where $n$ is the number of vertices of the * graph. * * @param the graph vertex type */ interface Coloring { /** * Get the number of colors. * * @return the number of colors */ int getNumberColors(); /** * Get the color map. * * @return the color map */ Map getColors(); /** * Get the color classes. A subset of vertices assigned to the same color is called a color * class; every such class forms an independent set. This method returns a partitioning of * the vertices in the graph in disjoint color classes. * * @return a list of color classes */ List> getColorClasses(); } /** * Default implementation of the coloring interface. * * @param the graph vertex type */ class ColoringImpl implements Coloring, Serializable { private static final long serialVersionUID = -8456580091672353150L; private final int numberColors; private final Map colors; /** * Construct a new vertex coloring. * * @param colors the color map * @param numberColors the total number of colors used */ public ColoringImpl(Map colors, int numberColors) { this.numberColors = numberColors; this.colors = colors; } /** * {@inheritDoc} */ @Override public int getNumberColors() { return numberColors; } /** * {@inheritDoc} */ @Override public Map getColors() { return colors; } /** * {@inheritDoc} */ @Override public List> getColorClasses() { Map> groups = new HashMap<>(); colors.forEach((v, color) -> { Set g = groups.computeIfAbsent(color, k -> new HashSet<>()); g.add(v); }); List> classes = new ArrayList<>(numberColors); classes.addAll(groups.values()); return classes; } /** * {@inheritDoc} */ @Override public String toString() { return "Coloring [number-of-colors=" + numberColors + ", colors=" + colors + "]"; } } } VertexCoverAlgorithm.java000066400000000000000000000057061402514743400336720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import org.jgrapht.util.*; import java.util.*; /** * Computes a (weighted) vertex cover in * an undirected graph. A vertex cover of a graph is a set of vertices such that each edge of the * graph is incident to at least one vertex in the set. A minimum vertex cover is a vertex cover * having the smallest possible number of vertices for a given graph. The size of a minimum vertex * cover of a graph $G$ is known as the vertex cover number. A vertex cover of minimum weight is a * vertex cover where the sum of weights assigned to the individual vertices in the cover has been * minimized. The minimum vertex cover problem is a special case of the minimum weighted vertex * cover problem where all vertices have equal weight. Consequently, any algorithm designed for the * weighted version of the problem can also solve instances of the unweighted version. * * @param vertex type * * @author Joris Kinable */ public interface VertexCoverAlgorithm { /** * Computes a vertex cover. * * @return a vertex cover */ VertexCover getVertexCover(); /** * A Vertex Cover * * @param the vertex type */ interface VertexCover extends Set { /** * Returns the weight of the vertex cover. When solving a weighted vertex cover problem, the * weight returned is the sum of the weights of the vertices in the vertex cover. When * solving the unweighted variant, the cardinality of the vertex cover is returned instead. * * @return weight of the independent set */ double getWeight(); } /** * Default implementation of a (weighted) vertex cover * * @param the vertex type */ class VertexCoverImpl extends WeightedUnmodifiableSet implements VertexCover { private static final long serialVersionUID = 3922451519162460179L; public VertexCoverImpl(Set vertexCover) { super(vertexCover); } public VertexCoverImpl(Set vertexCover, double weight) { super(vertexCover, weight); } } } VertexScoringAlgorithm.java000066400000000000000000000022641402514743400342140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.interfaces; import java.util.*; /** * An interface for all algorithms which assign scores to vertices of a graph. * * @param the vertex type * @param the score type * * @author Dimitrios Michail */ public interface VertexScoringAlgorithm { /** * Get a map with the scores of all vertices * * @return a map with all scores */ Map getScores(); /** * Get a vertex score * * @param v the vertex * @return the score */ D getVertexScore(V v); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/interfaces/package-info.java000066400000000000000000000001151402514743400321370ustar00rootroot00000000000000/** * Algorithm related interfaces. */ package org.jgrapht.alg.interfaces; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/000077500000000000000000000000001402514743400272015ustar00rootroot00000000000000AHUForestIsomorphismInspector.java000066400000000000000000000156131402514743400357140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import java.util.*; /** * This is an implementation of the AHU algorithm for detecting an (unweighted) isomorphism between * two rooted forests. Please see * mathworld.wolfram.com for a * complete definition of the isomorphism problem for general graphs. * *

* The original algorithm was first presented in "Alfred V. Aho and John E. Hopcroft. 1974. The * Design and Analysis of Computer Algorithms (1st ed., page 84). Addison-Wesley Longman Publishing * Co., Inc., Boston, MA, USA." *

* *

* This implementation runs in linear time (in the number of vertices of the input forests) while * using a linear amount of memory. *

* *

* For an implementation that supports rooted trees see {@link AHURootedTreeIsomorphismInspector} * and for one for unrooted trees see {@link AHUUnrootedTreeIsomorphismInspector}. *

* *

* Note: This implementation requires the input graphs to have valid vertex suppliers (see * {@link Graph#getVertexSupplier()}). *

* *

* Note: This inspector only returns a single mapping (chosen arbitrarily) rather than all possible * mappings. *

* * @param the type of the vertices * @param the type of the edges * * @author Alexandru Valeanu */ public class AHUForestIsomorphismInspector implements IsomorphismInspector { private final Graph forest1; private final Graph forest2; private final Set roots1; private final Set roots2; private boolean computed = false; private IsomorphicGraphMapping isomorphicMapping; /** * Construct a new AHU rooted forest isomorphism inspector. * * Note: The constructor does NOT check if the input forests are valid trees. * * @param forest1 the first rooted forest * @param roots1 the roots of the first forest * @param forest2 the second rooted forest * @param roots2 the roots of the second forest * @throws NullPointerException if {@code forest1} or {@code forest2} is {@code null} * @throws NullPointerException if {@code roots1} or {@code roots2} is {@code null} * @throws IllegalArgumentException if {@code forest1} or {@code forest2} is empty * @throws IllegalArgumentException if {@code roots1} or {@code roots2} is empty * @throws IllegalArgumentException if {@code roots1} or {@code roots2} contain an invalid * vertex */ public AHUForestIsomorphismInspector( Graph forest1, Set roots1, Graph forest2, Set roots2) { validateForest(forest1, roots1); this.forest1 = forest1; this.roots1 = roots1; validateForest(forest2, roots2); this.forest2 = forest2; this.roots2 = roots2; } private void validateForest(Graph forest, Set roots) { assert GraphTests.isSimple(forest); Objects.requireNonNull(forest, "input forest cannot be null"); Objects.requireNonNull(roots, "set of roots cannot be null"); if (forest.vertexSet().isEmpty()) { throw new IllegalArgumentException("input forest cannot be empty"); } if (roots.isEmpty()) { throw new IllegalArgumentException("set of roots cannot be empty"); } if (!forest.vertexSet().containsAll(roots)) { throw new IllegalArgumentException("root not contained in forest"); } } /** * {@inheritDoc} */ @Override public Iterator> getMappings() { GraphMapping iterMapping = getMapping(); if (iterMapping == null) return Collections.emptyIterator(); else return Collections.singletonList(iterMapping).iterator(); } /** * {@inheritDoc} */ @Override public boolean isomorphismExists() { return getMapping() != null; } private Pair> createSingleRootGraph(Graph forest, Set roots) { Graph freshForest = GraphTypeBuilder.forGraph(forest).weighted(false).buildGraph(); roots.forEach(freshForest::addVertex); V freshVertex = freshForest.addVertex(); for (V root : roots) freshForest.addEdge(freshVertex, root); return Pair.of(freshVertex, new AsGraphUnion<>(freshForest, forest)); } /** * Get an isomorphism between the input forests or {@code null} if none exists. * * @return isomorphic mapping, {@code null} is none exists */ public IsomorphicGraphMapping getMapping() { if (computed) { return isomorphicMapping; } if (roots1.size() == 1 && roots2.size() == 1) { V root1 = roots1.iterator().next(); V root2 = roots2.iterator().next(); isomorphicMapping = new AHURootedTreeIsomorphismInspector<>(forest1, root1, forest2, root2) .getMapping(); } else { Pair> pair1 = createSingleRootGraph(forest1, roots1); Pair> pair2 = createSingleRootGraph(forest2, roots2); V fresh1 = pair1.getFirst(); Graph freshForest1 = pair1.getSecond(); V fresh2 = pair2.getFirst(); Graph freshForest2 = pair2.getSecond(); IsomorphicGraphMapping mapping = new AHURootedTreeIsomorphismInspector<>(freshForest1, fresh1, freshForest2, fresh2) .getMapping(); if (mapping != null) { Map newForwardMapping = new HashMap<>(mapping.getForwardMapping()); Map newBackwardMapping = new HashMap<>(mapping.getBackwardMapping()); // remove the mapping from fresh1 to fresh 2 (and vice-versa) newForwardMapping.remove(fresh1); newBackwardMapping.remove(fresh2); isomorphicMapping = new IsomorphicGraphMapping<>( newForwardMapping, newBackwardMapping, forest1, forest2); } } computed = true; return isomorphicMapping; } } AHURootedTreeIsomorphismInspector.java000066400000000000000000000231441402514743400365240ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.lang.reflect.*; import java.util.*; /** * This is an implementation of the AHU algorithm for detecting an (unweighted) isomorphism between * two rooted trees. Please see * mathworld.wolfram.com for a * complete definition of the isomorphism problem for general graphs. * *

* The original algorithm was first presented in "Alfred V. Aho and John E. Hopcroft. 1974. The * Design and Analysis of Computer Algorithms (1st ed.). Addison-Wesley Longman Publishing Co., * Inc., Boston, MA, USA." *

* *

* This implementation runs in linear time (in the number of vertices of the input trees) while * using a linear amount of memory. *

* *

* Note: If the input graph is directed, it effectively considers only the subtree reachable from * the specified root. *

* *

* For an implementation that supports unrooted trees see * {@link AHUUnrootedTreeIsomorphismInspector}.
* For an implementation that supports rooted forests see {@link AHUForestIsomorphismInspector}. *

* *

* Note: This inspector only returns a single mapping (chosen arbitrarily) rather than all possible * mappings. *

* * @param the type of the vertices * @param the type of the edges * * @author Alexandru Valeanu */ public class AHURootedTreeIsomorphismInspector implements IsomorphismInspector { private final Graph tree1; private final Graph tree2; private V root1; private V root2; private Map forwardMapping; private Map backwardMapping; /** * Construct a new AHU rooted tree isomorphism inspector. * * Note: The constructor does NOT check if the input trees are valid. * * @param tree1 the first rooted tree * @param root1 the root of the first tree * @param tree2 the second rooted tree * @param root2 the root of the second tree * @throws NullPointerException if {@code tree1} or {@code tree2} is {@code null} * @throws NullPointerException if {@code root1} or {@code root2} is {@code null} * @throws IllegalArgumentException if {@code tree1} or {@code tree2} is empty * @throws IllegalArgumentException if {@code root1} or {@code root2} is an invalid vertex */ public AHURootedTreeIsomorphismInspector(Graph tree1, V root1, Graph tree2, V root2) { validateTree(tree1, root1); this.tree1 = tree1; this.root1 = root1; validateTree(tree2, root2); this.tree2 = tree2; this.root2 = root2; } private void validateTree(Graph tree, V root) { assert GraphTests.isSimple(tree); Objects.requireNonNull(tree, "input forest cannot be null"); Objects.requireNonNull(root, "root cannot be null"); if (tree.vertexSet().isEmpty()) { throw new IllegalArgumentException("tree cannot be empty"); } if (!tree.containsVertex(root)) { throw new IllegalArgumentException("root not contained in forest"); } } private void bfs(Graph graph, V root, List> levels) { BreadthFirstIterator bfs = new BreadthFirstIterator<>(graph, root); while (bfs.hasNext()) { V u = bfs.next(); if (levels.size() < bfs.getDepth(u) + 1) { levels.add(new ArrayList<>()); } levels.get(bfs.getDepth(u)).add(u); } } private List> computeLevels(Graph graph, V root) { List> levels = new ArrayList<>(); bfs(graph, root, levels); return levels; } private void matchVerticesWithSameLabel(V root1, V root2, Map[] canonicalName) { Queue> queue = new ArrayDeque<>(); queue.add(Pair.of(root1, root2)); while (!queue.isEmpty()) { Pair pair = queue.poll(); V u = pair.getFirst(); V v = pair.getSecond(); forwardMapping.put(u, v); backwardMapping.put(v, u); Map> labelList = CollectionUtil.newHashMapWithExpectedSize(tree1.degreeOf(u)); for (E edge : tree1.outgoingEdgesOf(u)) { V next = Graphs.getOppositeVertex(tree1, edge, u); // The check if only needed when the input graph is undirected in order to // avoid walking back "up" the tree. if (!forwardMapping.containsKey(next)) { labelList .computeIfAbsent(canonicalName[0].get(next), x -> new ArrayList<>()) .add(next); } } for (E edge : tree2.outgoingEdgesOf(v)) { V next = Graphs.getOppositeVertex(tree2, edge, v); if (!backwardMapping.containsKey(next)) { List list = labelList.get(canonicalName[1].get(next)); if (list == null || list.isEmpty()) { forwardMapping.clear(); backwardMapping.clear(); return; } V pairedNext = list.remove(list.size() - 1); queue.add(Pair.of(pairedNext, next)); } } } } private boolean isomorphismExists(V root1, V root2) { // already computed? if (forwardMapping != null) { return !forwardMapping.isEmpty(); } this.forwardMapping = new HashMap<>(); this.backwardMapping = new HashMap<>(); @SuppressWarnings("unchecked") Map[] canonicalName = (Map[]) Array.newInstance(Map.class, 2); canonicalName[0] = CollectionUtil.newHashMapWithExpectedSize(tree1.vertexSet().size()); canonicalName[1] = CollectionUtil.newHashMapWithExpectedSize(tree2.vertexSet().size()); List> nodesByLevel1 = computeLevels(tree1, root1); List> nodesByLevel2 = computeLevels(tree2, root2); if (nodesByLevel1.size() != nodesByLevel2.size()) return false; final int maxLevel = nodesByLevel1.size() - 1; Map, Integer> canonicalNameToInt = new HashMap<>(); int freshName = 0; for (int lvl = maxLevel; lvl >= 0; lvl--) { @SuppressWarnings("unchecked") List[] level = (List[]) Array.newInstance(List.class, 2); level[0] = nodesByLevel1.get(lvl); level[1] = nodesByLevel2.get(lvl); if (level[0].size() != level[1].size()) { return false; } final int n = level[0].size(); for (int k = 0; k < 2; k++) { Graph graph = (k == 0) ? tree1 : tree2; for (int i = 0; i < n; i++) { V u = level[k].get(i); List list = new ArrayList<>(); for (E edge : graph.outgoingEdgesOf(u)) { V v = Graphs.getOppositeVertex(graph, edge, u); int name = canonicalName[k].getOrDefault(v, -1); if (name != -1) { list.add(name); } } RadixSort.sort(list); Integer intName = canonicalNameToInt.get(list); if (intName == null) { canonicalNameToInt.put(list, freshName); intName = freshName; freshName++; } canonicalName[k].put(u, intName); } } } matchVerticesWithSameLabel(root1, root2, canonicalName); if (forwardMapping.size() != tree1.vertexSet().size()) { forwardMapping.clear(); backwardMapping.clear(); return false; } return true; } /** * {@inheritDoc} */ @Override public Iterator> getMappings() { GraphMapping iterMapping = getMapping(); if (iterMapping == null) return Collections.emptyIterator(); else return Collections.singletonList(iterMapping).iterator(); } /** * {@inheritDoc} */ @Override public boolean isomorphismExists() { return isomorphismExists(this.root1, this.root2); } /** * Get an isomorphism between the input trees or {@code null} if none exists. * * @return isomorphic mapping, {@code null} is none exists */ public IsomorphicGraphMapping getMapping() { if (isomorphismExists()) return new IsomorphicGraphMapping<>(forwardMapping, backwardMapping, tree1, tree2); else return null; } } AHUUnrootedTreeIsomorphismInspector.java000066400000000000000000000125071402514743400370700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.alg.shortestpath.*; import java.util.*; /** * This is an implementation of the AHU algorithm for detecting an (unweighted) isomorphism between * two unrooted trees. Please see * mathworld.wolfram.com for a * complete definition of the isomorphism problem for general graphs. * *

* The original algorithm was first presented in "Alfred V. Aho and John E. Hopcroft. 1974. The * Design and Analysis of Computer Algorithms (1st ed.). Addison-Wesley Longman Publishing Co., * Inc., Boston, MA, USA." *

* *

* This implementation runs in linear time (in the number of vertices of the input trees) while * using a linear amount of memory. *

* *

* For an implementation that supports rooted trees see {@link AHURootedTreeIsomorphismInspector}. *
* For an implementation that supports rooted forests see {@link AHUForestIsomorphismInspector}. *

* *

* Note: This inspector only returns a single mapping (chosen arbitrarily) rather than all possible * mappings. *

* * @param the type of the vertices * @param the type of the edges * * @author Alexandru Valeanu */ public class AHUUnrootedTreeIsomorphismInspector implements IsomorphismInspector { private final Graph tree1; private final Graph tree2; private boolean computed; private AHURootedTreeIsomorphismInspector ahuRootedTreeIsomorphismInspector; /** * Construct a new AHU unrooted tree isomorphism inspector. * * Note: The constructor does NOT check if the input trees are valid. * * @param tree1 the first tree * @param tree2 the second tree * @throws NullPointerException if {@code tree1} or {@code tree2} is {@code null} * @throws IllegalArgumentException if {@code tree1} or {@code tree2} is not undirected * @throws IllegalArgumentException if {@code tree1} or {@code tree2} is empty */ public AHUUnrootedTreeIsomorphismInspector(Graph tree1, Graph tree2) { validateTree(tree1); this.tree1 = tree1; validateTree(tree2); this.tree2 = tree2; } private void validateTree(Graph tree) { GraphTests.requireUndirected(tree); assert GraphTests.isSimple(tree); if (tree.vertexSet().isEmpty()) { throw new IllegalArgumentException("tree cannot be empty"); } } /** * {@inheritDoc} */ @Override public Iterator> getMappings() { GraphMapping iterMapping = getMapping(); if (iterMapping == null) return Collections.emptyIterator(); else return Collections.singletonList(iterMapping).iterator(); } /** * {@inheritDoc} */ @Override public boolean isomorphismExists() { if (computed) { if (ahuRootedTreeIsomorphismInspector != null) { return ahuRootedTreeIsomorphismInspector.isomorphismExists(); } else { return false; } } computed = true; TreeMeasurer treeMeasurer1 = new TreeMeasurer<>(tree1); List centers1 = new ArrayList<>(treeMeasurer1.getGraphCenter()); TreeMeasurer treeMeasurer2 = new TreeMeasurer<>(tree2); List centers2 = new ArrayList<>(treeMeasurer2.getGraphCenter()); if (centers1.size() == 1 && centers2.size() == 1) { ahuRootedTreeIsomorphismInspector = new AHURootedTreeIsomorphismInspector<>( tree1, centers1.get(0), tree2, centers2.get(0)); } else if (centers1.size() == 2 && centers2.size() == 2) { ahuRootedTreeIsomorphismInspector = new AHURootedTreeIsomorphismInspector<>( tree1, centers1.get(0), tree2, centers2.get(0)); if (!ahuRootedTreeIsomorphismInspector.isomorphismExists()) { ahuRootedTreeIsomorphismInspector = new AHURootedTreeIsomorphismInspector<>( tree1, centers1.get(1), tree2, centers2.get(0)); } } else { // different number of centers return false; } return ahuRootedTreeIsomorphismInspector.isomorphismExists(); } /** * Get an isomorphism between the input trees or {@code null} if none exists. * * @return isomorphic mapping, {@code null} is none exists */ public IsomorphicGraphMapping getMapping() { if (isomorphismExists()) { return ahuRootedTreeIsomorphismInspector.getMapping(); } else { return null; } } } ColorRefinementIsomorphismInspector.java000066400000000000000000000412421402514743400372040ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Christoph Grüne, Dennis Fischer and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.alg.color.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.VertexColoringAlgorithm.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import java.util.*; /** * Implementation of the color refinement algorithm isomorphism test using its feature of detecting * isomorphism between two graphs * as described in C. Berkholz, P. Bonsma, and M. Grohe. Tight lower and upper bounds for the * complexity of canonical colour refinement. Theory of Computing * Systems,doi:10.1007/s00224-016-9686-0, 2016 (color refinement) The complexity of this algorithm * is O(|V| + |E| log |V|). * * @param the type of the vertices * @param the type of the edges * * @author Christoph Grüne * @author Dennis Fischer */ public class ColorRefinementIsomorphismInspector implements IsomorphismInspector { /** * The input graphs */ private Graph graph1, graph2; /** * The isomorphism that is calculated by this color refinement isomorphism inspector */ private IsomorphicGraphMapping isomorphicGraphMapping; /** * contains whether the graphs are isomorphic or not. If we cannot decide whether they are * isomorphic the value will be not present. */ private Boolean isIsomorphic; /** * contains whether the two graphs produce a discrete coloring. Then, we can decide whether the * graphs are isomorphic. */ private boolean isColoringDiscrete; /** * contains whether the two graphs are forests. Forests can be identified to be isomorphic or * not. */ private boolean isForest; /** * contains whether the isomorphism test is executed to ensure that every operation is defined * all the time */ private boolean isomorphismTestExecuted; /** * Constructor for a isomorphism inspector based on color refinement. It checks whether * graph1 and graph2 are isomorphic. * * @param graph1 the first graph * @param graph2 the second graph */ public ColorRefinementIsomorphismInspector(Graph graph1, Graph graph2) { GraphType type1 = graph1.getType(); GraphType type2 = graph2.getType(); if (type1.isAllowingMultipleEdges() || type2.isAllowingMultipleEdges()) { throw new IllegalArgumentException( "graphs with multiple (parallel) edges are not supported"); } if (type1.isMixed() || type2.isMixed()) { throw new IllegalArgumentException("mixed graphs not supported"); } if (type1.isUndirected() && type2.isDirected() || type1.isDirected() && type2.isUndirected()) { throw new IllegalArgumentException( "can not match directed with " + "undirected graphs"); } this.graph1 = graph1; this.graph2 = graph2; this.isomorphicGraphMapping = null; this.isColoringDiscrete = false; this.isomorphismTestExecuted = false; this.isForest = false; } /** * {@inheritDoc} * * @throws IsomorphismUndecidableException if the isomorphism test was not executed and the * inspector cannot decide whether the graphs are isomorphic */ @Override public Iterator> getMappings() { if (!isomorphismTestExecuted) { isomorphismExists(); } ArrayList> iteratorList = new ArrayList<>(1); if (isIsomorphic != null && isIsomorphic) { iteratorList.add(isomorphicGraphMapping); } return iteratorList.iterator(); } /** * {@inheritDoc} * * @throws IsomorphismUndecidableException if the inspector cannot decide whether the graphs are * isomorphic */ @Override public boolean isomorphismExists() { if (isomorphismTestExecuted) { if (isIsomorphic != null) { return isIsomorphic; } else { throw new IsomorphismUndecidableException(); } } if (graph1 == graph2) { isomorphismTestExecuted = true; isIsomorphic = true; isomorphicGraphMapping = IsomorphicGraphMapping.identity(graph1); return isIsomorphic; } if (graph1.vertexSet().size() != graph2.vertexSet().size()) { isomorphismTestExecuted = true; isIsomorphic = false; return isIsomorphic; } Graph, DistinctGraphObject> graph = getDisjointGraphUnion(graph1, graph2); ColorRefinementAlgorithm, DistinctGraphObject> colorRefinementAlgorithm = new ColorRefinementAlgorithm<>(graph); // execute color refinement for graph Coloring> coloring = colorRefinementAlgorithm.getColoring(); isomorphismTestExecuted = true; isIsomorphic = coarseColoringAreEqual(coloring); if (isIsomorphic) { assert isomorphicGraphMapping.isValidIsomorphism(); } return isIsomorphic; } /** * Returns whether the coarse colorings of the two given graphs are discrete. This method is * undefined if the colorings are not the same or graph1 == graph2. * * @return if both colorings are discrete. * * @throws IsomorphismUndecidableException if the isomorphism test was not executed and the * inspector cannot decide whether the graphs are isomorphic */ boolean isColoringDiscrete() { if (!isomorphismTestExecuted) { isomorphismExists(); } return isColoringDiscrete; } /** * Returns whether the two given graphs are forests. This method is undefined if an isomorphism * exists and the coloring is discrete, or graph1 == graph2. * * @return if both graphs are forests. * * @throws IsomorphismUndecidableException if the isomorphism test was not executed and the * inspector cannot decide whether the graphs are isomorphic */ boolean isForest() { if (!isomorphismTestExecuted) { isomorphismExists(); } return isForest; } /** * Checks whether two coarse colorings are equal. Furthermore, it sets * isColoringDiscrete to true iff the colorings are discrete. * * @param coloring the coarse coloring of the union graph * @return if the given coarse colorings are equal */ private boolean coarseColoringAreEqual(Coloring> coloring) throws IsomorphismUndecidableException { Pair, Coloring> coloringPair = splitColoring(coloring); Coloring coloring1 = coloringPair.getFirst(); Coloring coloring2 = coloringPair.getSecond(); if (coloring1.getNumberColors() != coloring2.getNumberColors()) { return false; } List> colorClasses1 = coloring1.getColorClasses(); List> colorClasses2 = coloring2.getColorClasses(); if (colorClasses1.size() != colorClasses2.size()) { return false; } sortColorClasses(colorClasses1, coloring1); sortColorClasses(colorClasses2, coloring2); Iterator> it1 = colorClasses1.iterator(); Iterator> it2 = colorClasses2.iterator(); // check the color classes while (it1.hasNext() && it2.hasNext()) { Set cur1 = it1.next(); Set cur2 = it2.next(); // check if the size for the current color class are the same for both graphs. if (cur1.size() != cur2.size()) { return false; } // safety check whether the color class is not empty. if (cur1.iterator().hasNext()) { // check if the color are not the same (works as colors are integers). if (!coloring1 .getColors().get(cur1.iterator().next()) .equals(coloring2.getColors().get(cur2.iterator().next()))) { // colors are not the same -> graphs are not isomorphic. return false; } } } // no more color classes for both colorings, that is, the graphs have the same coloring. if (!it1.hasNext() && !it2.hasNext()) { /* * Check if the colorings are discrete, that is, the color mapping is injective. Check * if the graphs are forests. In both cases color refinement can decide if there is an * isomorphism. */ if (coloring1.getColorClasses().size() == graph1.vertexSet().size() && coloring2.getColorClasses().size() == graph2.vertexSet().size()) { isColoringDiscrete = true; calculateGraphMapping(coloring1, coloring2); return true; } else if (GraphTests.isForest(graph1) && GraphTests.isForest(graph2)) { isForest = true; calculateGraphMapping(coloring1, coloring2); return true; } isIsomorphic = null; throw new IsomorphismUndecidableException( "Color refinement cannot decide whether the two graphs are isomorphic or not."); } else { return false; } } /** * Splits up the coloring of the union graph into the two colorings of the original graphs * * @param coloring the coloring to split up * @return the two colorings of the original graphs */ private Pair, Coloring> splitColoring( Coloring> coloring) { Map col1 = new HashMap<>(); Map col2 = new HashMap<>(); int index = 0; for (Set> set1 : coloring.getColorClasses()) { for (DistinctGraphObject entry : set1) { if (entry.getGraph() == graph1) { col1.put(entry.getObject(), index); } else { col2.put(entry.getObject(), index); } } index++; } Coloring coloring1 = new VertexColoringAlgorithm.ColoringImpl<>(col1, col1.size()); Coloring coloring2 = new VertexColoringAlgorithm.ColoringImpl<>(col2, col2.size()); return new Pair<>(coloring1, coloring2); } /** * Sorts a list of color classes by the size and the color (integer representation of the color) * and * * @param colorClasses the list of the color classes * @param coloring the coloring */ private void sortColorClasses(List> colorClasses, Coloring coloring) { colorClasses.sort((o1, o2) -> { if (o1.size() == o2.size()) { Iterator it1 = o1.iterator(); Iterator it2 = o2.iterator(); if (!it1.hasNext() || !it2.hasNext()) { return Integer.compare(o1.size(), o2.size()); } return coloring .getColors().get(it1.next()).compareTo(coloring.getColors().get(it2.next())); } return Integer.compare(o1.size(), o2.size()); }); } /** * calculates the graph isomorphism as GraphMapping and assigns it to attribute * isomorphicGraphMapping * * @param coloring1 the discrete vertex coloring of graph1 * @param coloring2 the discrete vertex coloring of graph2 */ private void calculateGraphMapping(Coloring coloring1, Coloring coloring2) { GraphOrdering graphOrdering1 = new GraphOrdering<>(graph1); GraphOrdering graphOrdering2 = new GraphOrdering<>(graph2); int[] core1 = new int[graph1.vertexSet().size()]; int[] core2 = new int[graph2.vertexSet().size()]; Iterator> setIterator1 = coloring1.getColorClasses().iterator(); Iterator> setIterator2 = coloring2.getColorClasses().iterator(); // we only have to check one iterator as the color classes have the same size while (setIterator1.hasNext()) { Iterator vertexIterator1 = setIterator1.next().iterator(); Iterator vertexIterator2 = setIterator2.next().iterator(); while (vertexIterator1.hasNext()) { V v1 = vertexIterator1.next(); V v2 = vertexIterator2.next(); int numberOfV1 = graphOrdering1.getVertexNumber(v1); int numberOfV2 = graphOrdering2.getVertexNumber(v2); core1[numberOfV1] = numberOfV2; core2[numberOfV2] = numberOfV1; } } isomorphicGraphMapping = new IsomorphicGraphMapping<>(graphOrdering1, graphOrdering2, core1, core2); } /** * Calculates and returns a disjoint graph union of graph1 and graph2 * * @param graph1 the first graph of the disjoint union * @param graph2 the second graph of the disjoint union * @return the disjoint union of the two graphs */ private Graph, DistinctGraphObject> getDisjointGraphUnion( Graph graph1, Graph graph2) { return new AsGraphUnion<>(getDistinctObjectGraph(graph1), getDistinctObjectGraph(graph2)); } private Graph, DistinctGraphObject> getDistinctObjectGraph(Graph graph) { Graph, DistinctGraphObject> transformedGraph = GraphTypeBuilder ., DistinctGraphObject> forGraphType(graph.getType()) .buildGraph(); for (V vertex : graph.vertexSet()) { transformedGraph.addVertex(new DistinctGraphObject<>(vertex, graph)); } for (E edge : graph.edgeSet()) { transformedGraph .addEdge( new DistinctGraphObject<>(graph.getEdgeSource(edge), graph), new DistinctGraphObject<>(graph.getEdgeTarget(edge), graph), new DistinctGraphObject<>(edge, graph)); } return transformedGraph; } /** * Representation of a graph vertex in the disjoint union * * @param the vertex type of the graph * @param the edge type of the graph */ private static class DistinctGraphObject { private Pair> pair; private DistinctGraphObject(T object, Graph graph) { this.pair = Pair.of(object, graph); } public T getObject() { return pair.getFirst(); } public Graph getGraph() { return pair.getSecond(); } @Override public String toString() { return pair.toString(); } @Override public boolean equals(Object o) { if (this == o) return true; else if (!(o instanceof DistinctGraphObject)) return false; @SuppressWarnings("unchecked") DistinctGraphObject other = (DistinctGraphObject) o; return Objects.equals(getObject(), other.getObject()) && getGraph() == other.getGraph(); } @Override public int hashCode() { return Objects.hash(getObject(), System.identityHashCode(getGraph())); } public static DistinctGraphObject of(T object, Graph graph) { return new DistinctGraphObject<>(object, graph); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/GraphOrdering.java000066400000000000000000000172311402514743400326030ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.util.*; import java.util.*; /** * This class represents the order on the graph vertices. There are also some helper-functions for * receiving outgoing/incoming edges, etc. * * @param the type of the vertices * @param the type of the edges */ final class GraphOrdering { private final Graph graph; private final Map mapVertexToOrder; private final List mapOrderToVertex; private final int vertexCount; private final int[][] outgoingEdges; private final int[][] incomingEdges; private final E[] edgeCache; /** * if caching is enabled, adjMatrix contains cached information on existing edges, valid values: *
    *
  • 0 - no cached value
  • *
  • 1 - edge exists
  • *
  • -1 - no edge exists
  • *
*/ private final byte[] adjMatrix; private final boolean cacheEdges; /** * @param graph the graph to be ordered * @param orderByDegree should the vertices be ordered by their degree. This speeds up the VF2 * algorithm. * @param cacheEdges if true, the class creates a adjacency matrix and two arrays for incoming * and outgoing edges for fast access. */ @SuppressWarnings("unchecked") public GraphOrdering(Graph graph, boolean orderByDegree, boolean cacheEdges) { this.graph = graph; this.cacheEdges = cacheEdges; List vertexList = new ArrayList<>(graph.vertexSet()); if (orderByDegree) { vertexList.sort(VertexDegreeComparator.of(graph)); } vertexCount = vertexList.size(); mapVertexToOrder = new VertexToIntegerMapping<>(vertexList).getVertexMap(); mapOrderToVertex = vertexList; if (cacheEdges) { outgoingEdges = new int[vertexCount][]; incomingEdges = new int[vertexCount][]; edgeCache = (E[]) new Object[vertexCount * vertexCount]; adjMatrix = new byte[vertexCount * vertexCount]; } else { outgoingEdges = null; incomingEdges = null; edgeCache = null; adjMatrix = null; } } /** * @param graph the graph to be ordered */ public GraphOrdering(Graph graph) { this(graph, false, true); } /** * @return returns the number of vertices in the graph. */ public int getVertexCount() { return this.vertexCount; } /** * @param vertexNumber the number which identifies the vertex $v$ in this order. * * @return the identifying numbers of all vertices which are connected to $v$ by an edge * outgoing from $v$. */ public int[] getOutEdges(int vertexNumber) { if (cacheEdges && (outgoingEdges[vertexNumber] != null)) { return outgoingEdges[vertexNumber]; } V v = getVertex(vertexNumber); Set edgeSet = graph.outgoingEdgesOf(v); int[] vertexArray = new int[edgeSet.size()]; int i = 0; for (E edge : edgeSet) { V source = graph.getEdgeSource(edge), target = graph.getEdgeTarget(edge); vertexArray[i++] = mapVertexToOrder.get(source.equals(v) ? target : source); } if (cacheEdges) { outgoingEdges[vertexNumber] = vertexArray; } return vertexArray; } /** * @param vertexNumber the number which identifies the vertex $v$ in this order. * * @return the identifying numbers of all vertices which are connected to $v$ by an edge * incoming to $v$. */ public int[] getInEdges(int vertexNumber) { if (cacheEdges && (incomingEdges[vertexNumber] != null)) { return incomingEdges[vertexNumber]; } V v = getVertex(vertexNumber); Set edgeSet = graph.incomingEdgesOf(v); int[] vertexArray = new int[edgeSet.size()]; int i = 0; for (E edge : edgeSet) { V source = graph.getEdgeSource(edge), target = graph.getEdgeTarget(edge); vertexArray[i++] = mapVertexToOrder.get(source.equals(v) ? target : source); } if (cacheEdges) { incomingEdges[vertexNumber] = vertexArray; } return vertexArray; } /** * @param v1Number the number of the first vertex $v_1$ * @param v2Number the number of the second vertex $v_2$ * * @return exists the edge from $v_1$ to $v_2$ */ public boolean hasEdge(int v1Number, int v2Number) { int cacheIndex = 0; if (cacheEdges) { cacheIndex = v1Number * vertexCount + v2Number; final byte cache = adjMatrix[cacheIndex]; if (cache != 0) { return cache > 0; } else { // initialize both the adjacency matrix as well as the edge cache final V v1 = getVertex(v1Number); final V v2 = getVertex(v2Number); final E edge = graph.getEdge(v1, v2); if (edge == null) { adjMatrix[cacheIndex] = (byte) -1; return false; } else { adjMatrix[cacheIndex] = (byte) 1; edgeCache[cacheIndex] = edge; return true; } } } V v1 = getVertex(v1Number); V v2 = getVertex(v2Number); boolean containsEdge = graph.containsEdge(v1, v2); return containsEdge; } /** * be careful: there's no check against an invalid vertexNumber * * @param vertexNumber the number identifying the vertex $v$ * * @return $v$ */ public V getVertex(int vertexNumber) { return mapOrderToVertex.get(vertexNumber); } /** * @param v1Number the number identifying the vertex $v_1$ * @param v2Number the number identifying the vertex $v_2$ * * @return the edge from $v_1$ to $v_2$ */ public E getEdge(int v1Number, int v2Number) { if (cacheEdges) { final int cacheIndex = v1Number * vertexCount + v2Number; final byte containsEdge = adjMatrix[cacheIndex]; if (containsEdge == 0) { // edge cache has not been initialized yet for this element hasEdge(v1Number, v2Number); } final E edge = edgeCache[cacheIndex]; return edge; } V v1 = getVertex(v1Number), v2 = getVertex(v2Number); E edge = graph.getEdge(v1, v2); return edge; } public int getVertexNumber(V v) { return mapVertexToOrder.get(v); } public int[] getEdgeNumbers(E e) { V v1 = graph.getEdgeSource(e), v2 = graph.getEdgeTarget(e); int[] edge = new int[2]; edge[0] = mapVertexToOrder.get(v1); edge[1] = mapVertexToOrder.get(v2); return edge; } public Graph getGraph() { return graph; } } IsomorphicGraphMapping.java000066400000000000000000000257001402514743400344030ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * This class represents a GraphMapping between two (subgraph)isomorphic graphs. In the subgraph * isomorphic case, the second one is assumed to be a subgraph of the first one. * * @author Fabian Späh * @author Alexandru Valeanu * * @param the type of the vertices * @param the type of the edges */ public class IsomorphicGraphMapping implements GraphMapping { public static final int NULL_NODE = -1; private final Map forwardMapping; private final Map backwardMapping; private final Graph graph1; private final Graph graph2; /** * Construct a new isomorphic graph mapping * * @param g1 the first graph * @param g2 the second graph which is a possible subgraph of g1 * @param core1 the mapping as array (forwards) * @param core2 the mapping as array (backwards) */ public IsomorphicGraphMapping( GraphOrdering g1, GraphOrdering g2, int[] core1, int[] core2) { this.graph1 = g1.getGraph(); this.graph2 = g2.getGraph(); this.forwardMapping = CollectionUtil.newHashMapWithExpectedSize(this.graph1.vertexSet().size()); this.backwardMapping = CollectionUtil.newHashMapWithExpectedSize(this.graph1.vertexSet().size()); for (V v : graph1.vertexSet()) { int vNumber = g1.getVertexNumber(v); int uNumber = core1[vNumber]; if (uNumber != NULL_NODE) { forwardMapping.put(v, g2.getVertex(uNumber)); } } for (V v : graph2.vertexSet()) { int vNumber = g2.getVertexNumber(v); int uNumber = core2[vNumber]; if (uNumber != NULL_NODE) { backwardMapping.put(v, g1.getVertex(uNumber)); } } } /** * Construct a new isomorphic graph mapping. * * @param forwardMapping the mapping from graph1 to graph2 * @param backwardMapping the mapping from graph2 to graph1 (inverse of forwardMapping) * @param graph1 the first graph * @param graph2 the second graph * */ public IsomorphicGraphMapping( Map forwardMapping, Map backwardMapping, Graph graph1, Graph graph2) { this.forwardMapping = Objects.requireNonNull(forwardMapping); this.backwardMapping = Objects.requireNonNull(backwardMapping); this.graph1 = Objects.requireNonNull(graph1); this.graph2 = Objects.requireNonNull(graph2); } @Override public V getVertexCorrespondence(V v, boolean forward) { if (forward) return forwardMapping.get(v); else return backwardMapping.get(v); } @Override public E getEdgeCorrespondence(E e, boolean forward) { Graph fromGraph; Graph toGraph; if (forward) { fromGraph = graph1; toGraph = graph2; } else { fromGraph = graph2; toGraph = graph1; } V u = fromGraph.getEdgeSource(e); V v = fromGraph.getEdgeTarget(e); V uu = getVertexCorrespondence(u, forward); if (uu == null) { return null; } V vv = getVertexCorrespondence(v, forward); if (vv == null) { return null; } return toGraph.getEdge(uu, vv); } /** * Get an unmodifiable version of the forward mapping function. * * @return the unmodifiable forward mapping function */ public Map getForwardMapping() { return Collections.unmodifiableMap(forwardMapping); } /** * Get an unmodifiable version of the backward mapping function. * * @return the unmodifiable backward mapping function */ public Map getBackwardMapping() { return Collections.unmodifiableMap(backwardMapping); } /** * Get the active domain of the isomorphism. * * @return the set of vertices $v$ for which the mapping is defined */ public Set getMappingDomain() { return Collections.unmodifiableSet(forwardMapping.keySet()); } /** * Get the range of the isomorphism. * * @return the set of vertices $v$ for which a preimage exists */ public Set getMappingRange() { return Collections.unmodifiableSet(backwardMapping.keySet()); } /** * Checks if a vertex $v$ from the first graph has a corresponding vertex in the second graph * * @param v the vertex * @return is there a corresponding vertex to $v$ in the subgraph */ public boolean hasVertexCorrespondence(V v) { return getVertexCorrespondence(v, true) != null; } /** * Checks if a edge e from the first graph has a corresponding edge in the second graph * * @param e the edge * @return is there a corresponding edge to $e$ in the subgraph */ public boolean hasEdgeCorrespondence(E e) { return getEdgeCorrespondence(e, true) != null; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; IsomorphicGraphMapping that = (IsomorphicGraphMapping) o; return Objects.equals(forwardMapping, that.forwardMapping) && Objects.equals(backwardMapping, that.backwardMapping) && graph1 == that.graph1 && graph2 == that.graph2; } @Override public int hashCode() { return Objects .hash( forwardMapping, backwardMapping, System.identityHashCode(graph1), System.identityHashCode(graph2)); } @Override public String toString() { StringBuilder str = new StringBuilder("["); Set vertexSet = graph1.vertexSet(); Map vertexMap = new TreeMap<>(); for (V v : vertexSet) { vertexMap.put(v.toString(), v); } int i = 0; for (Map.Entry entry : vertexMap.entrySet()) { V u = getVertexCorrespondence(entry.getValue(), true); str .append((i++ == 0) ? "" : " ").append(entry.getKey()).append("=") .append((u == null) ? "~~" : u); } return str + "]"; } /** * Determines whether this mapping is indeed a valid isomorphic mapping between the first graph * and the second graph. Note that this method will return false for a homomorphism returned by * a subgraph isomorphism inspector unless the resulting mapping happens to be bijective as well * (mapping all of the vertices and edges from the first graph to the second graph and vice * versa). * * @return true iff this mapping is a valid isomorphism between the two graphs */ public boolean isValidIsomorphism() { for (V v : graph1.vertexSet()) { if (!forwardMapping.containsKey(v) || !graph2.containsVertex(forwardMapping.get(v))) return false; } for (V v : graph2.vertexSet()) { if (!backwardMapping.containsKey(v) || !graph1.containsVertex(backwardMapping.get(v))) return false; } for (E edge : graph1.edgeSet()) { E e = getEdgeCorrespondence(edge, true); V u = graph1.getEdgeSource(e); V v = graph1.getEdgeTarget(e); if (!graph2.containsEdge(u, v)) return false; } for (E edge : graph2.edgeSet()) { E e = getEdgeCorrespondence(edge, false); V u = graph2.getEdgeSource(e); V v = graph2.getEdgeTarget(e); if (!graph1.containsEdge(u, v)) return false; } return true; } /** * Checks for equality. Assuming both are mappings on the same graphs. * * @param rel the corresponding mapping * @return do both relations map to the same vertices */ public boolean isEqualMapping(GraphMapping rel) { for (V v : graph2.vertexSet()) { if (!getVertexCorrespondence(v, false).equals(rel.getVertexCorrespondence(v, false))) { return false; } } return true; } /** * Computes the composition of two isomorphisms. Let $f : V_{G_1} \rightarrow V_{G_2}$ be an * isomorphism from $V_{G_1}$ to $V_{G_2}$ and $g : V_{G_2} \rightarrow V_{G_3}$ one from * $V_{G_2}$ to $V_{G_3}$. * * This method computes an isomorphism $h : V_{G_1} \rightarrow V_{G_3}$ from $V_{G_1}$ to * $V_{G_3}$. * * Note: The composition $g ∘ f$ can be built only if $f$'s codomain equals $g$'s domain; this * implementation only requires that the former is a subset of the latter. * * @param otherMapping the other isomorphism (i.e. function $g$) * @return the composition of the two isomorphism */ public IsomorphicGraphMapping compose(IsomorphicGraphMapping otherMapping) { Map fMap = CollectionUtil.newHashMapWithExpectedSize(forwardMapping.size()); Map bMap = CollectionUtil.newHashMapWithExpectedSize(forwardMapping.size()); for (V v : graph1.vertexSet()) { V u = otherMapping.getVertexCorrespondence(forwardMapping.get(v), true); fMap.put(v, u); bMap.put(u, v); } return new IsomorphicGraphMapping<>(fMap, bMap, graph1, otherMapping.graph2); } /** * Computes an identity automorphism (i.e. a self-mapping of a graph in which each vertex also * maps to itself). * * @param graph the input graph * @param the graph vertex type * @param the graph edge type * @return a mapping from graph to graph */ public static IsomorphicGraphMapping identity(Graph graph) { Map fMap = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); Map bMap = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); for (V v : graph.vertexSet()) { fMap.put(v, v); bMap.put(v, v); } return new IsomorphicGraphMapping<>(fMap, bMap, graph, graph); } } IsomorphismInspector.java000066400000000000000000000030641402514743400341700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import java.util.*; /** * General interface for graph and subgraph isomorphism. * * @param the type of the vertices * @param the type of the edges */ public interface IsomorphismInspector { /** * Get an iterator over all calculated (isomorphic) mappings between two graphs. * * @return an iterator over all calculated (isomorphic) mappings between two graphs * @throws IsomorphismUndecidableException if the inspector cannot decide whether the graphs are * isomorphic */ Iterator> getMappings(); /** * Check if an isomorphism exists. * * @return true if there is an isomorphism, false if there is no isomorphism * @throws IsomorphismUndecidableException if the inspector cannot decide whether the graphs are * isomorphic */ boolean isomorphismExists(); } IsomorphismUndecidableException.java000066400000000000000000000060111402514743400362730ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Christoph Grüne and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; /** * Implementation of IsomorphismUndecidableException to indicate undecidable isomorphism cases in * isomorphism inspectors */ public class IsomorphismUndecidableException extends RuntimeException { private static final long serialVersionUID = 4703220562690821852L; /** * Constructs a new exception with null as its detail message. The cause is not initialized, and * may subsequently be initialized by a call to Throwable.initCause(java.lang.Throwable). */ public IsomorphismUndecidableException() { } /** * Constructs a new exception with the specified detail message. The cause is not initialized, * and may subsequently be initialized by a call to Throwable.initCause(java.lang.Throwable). * * @param message the detail message. The detail message is saved for later retrieval by the * Throwable.getMessage() method. */ public IsomorphismUndecidableException(String message) { super(message); } /** * Constructs a new exception with the specified cause and a detail message of (cause==null ? * null : cause.toString()) (which typically contains the class and detail message of cause). * This constructor is useful for exceptions that are little more than wrappers for other * throwables (for example, PrivilegedActionException). * * @param cause the cause (which is saved for later retrieval by the Throwable.getCause() * method). (A null value is permitted, and indicates that the cause is nonexistent or * unknown.) */ public IsomorphismUndecidableException(Throwable cause) { super(cause); } /** * Constructs a new exception with the specified detail message and cause. Note that the detail * message associated with cause is not automatically incorporated in this exception's detail * message. * * @param message the detail message (which is saved for later retrieval by the * Throwable.getMessage() method). * @param cause the cause (which is saved for later retrieval by the Throwable.getCause() * method). (A null value is permitted, and indicates that the cause is nonexistent or * unknown.) */ public IsomorphismUndecidableException(String message, Throwable cause) { super(message, cause); } } VF2AbstractIsomorphismInspector.java000066400000000000000000000112751402514743400361750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import java.util.*; /** * Base implementation of the VF2 algorithm using its feature of detecting * isomorphism between two graphs * as described in Cordella et al. A (sub)graph isomorphism algorithm for matching large graphs * (2004), DOI:10.1109/TPAMI.2004.75, * * http://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=1323804 * *

* This implementation of the VF2 algorithm does not support graphs with multiple edges. * * @param the type of the vertices * @param the type of the edges */ public abstract class VF2AbstractIsomorphismInspector implements IsomorphismInspector { protected Graph graph1, graph2; protected Comparator vertexComparator; protected Comparator edgeComparator; protected GraphOrdering ordering1, ordering2; /** * Construct a new base implementation of the VF2 isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph * @param vertexComparator comparator for semantic equivalence of vertices * @param edgeComparator comparator for semantic equivalence of edges * @param cacheEdges if true, edges get cached for faster access */ public VF2AbstractIsomorphismInspector( Graph graph1, Graph graph2, Comparator vertexComparator, Comparator edgeComparator, boolean cacheEdges) { GraphType type1 = graph1.getType(); GraphType type2 = graph2.getType(); if (type1.isAllowingMultipleEdges() || type2.isAllowingMultipleEdges()) { throw new IllegalArgumentException( "graphs with multiple (parallel) edges are not supported"); } if (type1.isMixed() || type2.isMixed()) { throw new IllegalArgumentException("mixed graphs not supported"); } if (type1.isUndirected() && type2.isDirected() || type1.isDirected() && type2.isUndirected()) { throw new IllegalArgumentException( "can not match directed with " + "undirected graphs"); } this.graph1 = graph1; this.graph2 = graph2; this.vertexComparator = vertexComparator; this.edgeComparator = edgeComparator; this.ordering1 = new GraphOrdering<>(graph1, true, cacheEdges); this.ordering2 = new GraphOrdering<>(graph2, true, cacheEdges); } /** * Construct a new base implementation of the VF2 isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph * @param vertexComparator comparator for semantic equivalence of vertices * @param edgeComparator comparator for semantic equivalence of edges */ public VF2AbstractIsomorphismInspector( Graph graph1, Graph graph2, Comparator vertexComparator, Comparator edgeComparator) { this(graph1, graph2, vertexComparator, edgeComparator, true); } /** * Construct a new base implementation of the VF2 isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph * @param cacheEdges if true, edges get cached for faster access */ public VF2AbstractIsomorphismInspector( Graph graph1, Graph graph2, boolean cacheEdges) { this(graph1, graph2, null, null, cacheEdges); } /** * Construct a new base implementation of the VF2 isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph */ public VF2AbstractIsomorphismInspector(Graph graph1, Graph graph2) { this(graph1, graph2, true); } @Override public abstract Iterator> getMappings(); @Override public boolean isomorphismExists() { Iterator> iter = getMappings(); return iter.hasNext(); } } VF2GraphIsomorphismInspector.java000066400000000000000000000067031402514743400354730ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import java.util.*; /** * This is an implementation of the VF2 algorithm using its feature of detecting * isomorphism between two graphs * as described in Cordella et al. A (sub)graph isomorphism algorithm for matching large graphs * (2004), DOI:10.1109/TPAMI.2004.75, * * http://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=1323804 * *

* This implementation of the VF2 algorithm does not support graphs with multiple edges. * * @param the type of the vertices * @param the type of the edges */ public class VF2GraphIsomorphismInspector extends VF2AbstractIsomorphismInspector { /** * Construct a new VF2 isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph * @param vertexComparator comparator for semantic equivalence of vertices * @param edgeComparator comparator for semantic equivalence of edges * @param cacheEdges if true, edges get cached for faster access */ public VF2GraphIsomorphismInspector( Graph graph1, Graph graph2, Comparator vertexComparator, Comparator edgeComparator, boolean cacheEdges) { super(graph1, graph2, vertexComparator, edgeComparator, cacheEdges); } /** * Construct a new VF2 isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph * @param vertexComparator comparator for semantic equivalence of vertices * @param edgeComparator comparator for semantic equivalence of edges */ public VF2GraphIsomorphismInspector( Graph graph1, Graph graph2, Comparator vertexComparator, Comparator edgeComparator) { super(graph1, graph2, vertexComparator, edgeComparator, true); } /** * Construct a new VF2 isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph * @param cacheEdges if true, edges get cached for faster access */ public VF2GraphIsomorphismInspector(Graph graph1, Graph graph2, boolean cacheEdges) { super(graph1, graph2, null, null, cacheEdges); } /** * Construct a new VF2 isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph */ public VF2GraphIsomorphismInspector(Graph graph1, Graph graph2) { super(graph1, graph2, true); } @Override public VF2GraphMappingIterator getMappings() { return new VF2GraphMappingIterator<>( ordering1, ordering2, vertexComparator, edgeComparator); } } VF2GraphIsomorphismState.java000066400000000000000000000210161402514743400345770ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import java.util.*; class VF2GraphIsomorphismState extends VF2State { public VF2GraphIsomorphismState( GraphOrdering g1, GraphOrdering g2, Comparator vertexComparator, Comparator edgeComparator) { super(g1, g2, vertexComparator, edgeComparator); } public VF2GraphIsomorphismState(VF2State s) { super(s); } /** * @return true, if the already matched vertices of graph1 plus the first vertex of nextPair are * graph isomorphic to the already matched vertices of graph2 and the second one vertex * of nextPair. */ @Override public boolean isFeasiblePair() { final String pairstr = (DEBUG) ? "(" + g1.getVertex(addVertex1) + ", " + g2.getVertex(addVertex2) + ")" : null; final String abortmsg = (DEBUG) ? pairstr + " does not fit in the current matching" : null; // check for semantic equality of both vertexes if (!areCompatibleVertexes(addVertex1, addVertex2)) { return false; } int termOutPred1 = 0, termOutPred2 = 0, termInPred1 = 0, termInPred2 = 0, newPred1 = 0, newPred2 = 0, termOutSucc1 = 0, termOutSucc2 = 0, termInSucc1 = 0, termInSucc2 = 0, newSucc1 = 0, newSucc2 = 0; // check outgoing edges of addVertex1 final int[] outE1 = g1.getOutEdges(addVertex1); for (int i = 0; i < outE1.length; i++) { final int other1 = outE1[i]; if (core1[other1] != NULL_NODE) { final int other2 = core1[other1]; if (!g2.hasEdge(addVertex2, other2) || !areCompatibleEdges(addVertex1, other1, addVertex2, other2)) { if (DEBUG) showLog( "isFeasiblePair", abortmsg + ": edge from " + g2.getVertex(addVertex2) + " to " + g2.getVertex(other2) + " is missing in the 2nd graph"); return false; } } else { final int in1O1 = in1[other1]; final int out1O1 = out1[other1]; if ((in1O1 == 0) && (out1O1 == 0)) { newSucc1++; continue; } if (in1O1 > 0) { termInSucc1++; } if (out1O1 > 0) { termOutSucc1++; } } } // check outgoing edges of addVertex2 final int[] outE2 = g2.getOutEdges(addVertex2); for (int i = 0; i < outE2.length; i++) { final int other2 = outE2[i]; if (core2[other2] != NULL_NODE) { final int other1 = core2[other2]; if (!g1.hasEdge(addVertex1, other1)) { if (DEBUG) showLog( "isFeasbilePair", abortmsg + ": edge from " + g1.getVertex(addVertex1) + " to " + g1.getVertex(other1) + " is missing in the 1st graph"); return false; } } else { final int in2O2 = in2[other2]; final int out2O2 = out2[other2]; if ((in2O2 == 0) && (out2O2 == 0)) { newSucc2++; continue; } if (in2O2 > 0) { termInSucc2++; } if (out2O2 > 0) { termOutSucc2++; } } } if ((termInSucc1 != termInSucc2) || (termOutSucc1 != termOutSucc2) || (newSucc1 != newSucc2)) { if (DEBUG) { String cause = "", v1 = g1.getVertex(addVertex1).toString(), v2 = g2.getVertex(addVertex2).toString(); if (termInSucc2 > termInSucc1) { cause = "|Tin2 ∩ Succ(Graph2, " + v2 + ")| != |Tin1 ∩ Succ(Graph1, " + v1 + ")|"; } else if (termOutSucc2 > termOutSucc1) { cause = "|Tout2 ∩ Succ(Graph2, " + v2 + ")| != |Tout1 ∩ Succ(Graph1, " + v1 + ")|"; } else if (newSucc2 > newSucc1) { cause = "|N‾ ∩ Succ(Graph2, " + v2 + ")| != |N‾ ∩ Succ(Graph1, " + v1 + ")|"; } showLog("isFeasbilePair", abortmsg + ": " + cause); } return false; } // check incoming edges of addVertex1 final int[] inE1 = g1.getInEdges(addVertex1); for (int i = 0; i < inE1.length; i++) { final int other1 = inE1[i]; if (core1[other1] != NULL_NODE) { final int other2 = core1[other1]; if (!g2.hasEdge(other2, addVertex2) || !areCompatibleEdges(other1, addVertex1, other2, addVertex2)) { if (DEBUG) showLog( "isFeasbilePair", abortmsg + ": edge from " + g2.getVertex(other2) + " to " + g2.getVertex(addVertex2) + " is missing in the 2nd graph"); return false; } } else { final int in1O1 = in1[other1]; final int out1O1 = out1[other1]; if ((in1O1 == 0) && (out1O1 == 0)) { newPred1++; continue; } if (in1O1 > 0) { termInPred1++; } if (out1O1 > 0) { termOutPred1++; } } } // check incoming edges of addVertex2 final int[] inE2 = g2.getInEdges(addVertex2); for (int i = 0; i < inE2.length; i++) { final int other2 = inE2[i]; if (core2[other2] != NULL_NODE) { final int other1 = core2[other2]; if (!g1.hasEdge(other1, addVertex1)) { if (DEBUG) showLog( "isFeasiblePair", abortmsg + ": edge from " + g1.getVertex(other1) + " to " + g1.getVertex(addVertex1) + " is missing in the 1st graph"); return false; } } else { final int in2O2 = in2[other2]; final int out2O2 = out2[other2]; if ((in2O2 == 0) && (out2O2 == 0)) { newPred2++; continue; } if (in2O2 > 0) { termInPred2++; } if (out2O2 > 0) { termOutPred2++; } } } if ((termInPred1 == termInPred2) && (termOutPred1 == termOutPred2) && (newPred1 == newPred2)) { if (DEBUG) showLog("isFeasiblePair", pairstr + " fits"); return true; } else { if (DEBUG) { String cause = "", v1 = g1.getVertex(addVertex1).toString(), v2 = g2.getVertex(addVertex2).toString(); if (termInPred2 > termInPred1) { cause = "|Tin2 ∩ Pred(Graph2, " + v2 + ")| != |Tin1 ∩ Pred(Graph1, " + v1 + ")|"; } else if (termOutPred2 > termOutPred1) { cause = "|Tout2 ∩ Pred(Graph2, " + v2 + ")| != |Tout1 ∩ Pred(Graph1, " + v1 + ")|"; } else if (newPred2 > newPred1) { cause = "|N‾ ∩ Pred(Graph2, " + v2 + ")| != |N‾ ∩ Pred(Graph1, " + v1 + ")|"; } showLog("isFeasbilePair", abortmsg + ": " + cause); } return false; } } } VF2GraphMappingIterator.java000066400000000000000000000054001402514743400343710ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import java.util.*; /** * This class is used to iterate over all existing (isomorphic) mappings between two graphs. It is * used by the {@link VF2GraphIsomorphismInspector}. * * @param the type of the vertices * @param the type of the edges */ class VF2GraphMappingIterator extends VF2MappingIterator { /** * @param ordering1 * @param ordering2 * @param vertexComparator * @param edgeComparator */ public VF2GraphMappingIterator( GraphOrdering ordering1, GraphOrdering ordering2, Comparator vertexComparator, Comparator edgeComparator) { super(ordering1, ordering2, vertexComparator, edgeComparator); } @Override protected IsomorphicGraphMapping match() { VF2State s; if (stateStack.isEmpty()) { Graph g1 = ordering1.getGraph(), g2 = ordering2.getGraph(); if ((g1.vertexSet().size() != g2.vertexSet().size()) || (g1.edgeSet().size() != g2.edgeSet().size())) { return null; } s = new VF2GraphIsomorphismState<>( ordering1, ordering2, vertexComparator, edgeComparator); if (g2.vertexSet().isEmpty()) { return (hadOneMapping != null) ? null : s.getCurrentMapping(); } } else { stateStack.pop().backtrack(); s = stateStack.pop(); } while (true) { while (s.nextPair()) { if (s.isFeasiblePair()) { stateStack.push(s); s = new VF2GraphIsomorphismState<>(s); s.addPair(); if (s.isGoal()) { stateStack.push(s); return s.getCurrentMapping(); } s.resetAddVertexes(); } } if (stateStack.isEmpty()) { return null; } s.backtrack(); s = stateStack.pop(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2MappingIterator.java000066400000000000000000000051071402514743400334720ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import java.util.*; abstract class VF2MappingIterator implements Iterator> { protected Comparator vertexComparator; protected Comparator edgeComparator; protected IsomorphicGraphMapping nextMapping; protected Boolean hadOneMapping; protected GraphOrdering ordering1, ordering2; protected ArrayDeque> stateStack; public VF2MappingIterator( GraphOrdering ordering1, GraphOrdering ordering2, Comparator vertexComparator, Comparator edgeComparator) { this.ordering1 = ordering1; this.ordering2 = ordering2; this.vertexComparator = vertexComparator; this.edgeComparator = edgeComparator; this.stateStack = new ArrayDeque<>(); } /** * This function moves over all mappings between graph1 and graph2. It changes the state of the * whole iterator. * * @return null or one matching between graph1 and graph2 */ protected abstract IsomorphicGraphMapping match(); protected IsomorphicGraphMapping matchAndCheck() { IsomorphicGraphMapping rel = match(); if (rel != null) { hadOneMapping = true; } return rel; } @Override public boolean hasNext() { return nextMapping != null || (nextMapping = matchAndCheck()) != null; } @Override public IsomorphicGraphMapping next() { if (nextMapping != null) { IsomorphicGraphMapping tmp = nextMapping; nextMapping = null; return tmp; } IsomorphicGraphMapping rel = matchAndCheck(); if (rel == null) { throw new NoSuchElementException(); } return rel; } @Override public void remove() { throw new UnsupportedOperationException(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/VF2State.java000066400000000000000000000266401402514743400314520ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import java.util.*; /** * controls the matching between two graphs according to the VF2 algorithm. * * @param the type of the vertices * @param the type of the edges * * @author Fabian Späh */ abstract class VF2State { public static final int NULL_NODE = -1; protected static final boolean DEBUG = false; protected final int[] core1, core2, in1, in2, out1, out2; protected final int n1, n2; protected int coreLen, t1BothLen, t2BothLen, t1InLen, t2InLen, t1OutLen, t2OutLen, addedVertex1, addVertex1, addVertex2; protected final GraphOrdering g1, g2; protected final Comparator vertexComparator; protected final Comparator edgeComparator; /** * @param g1 GraphOrdering on first graph * @param g2 GraphOrdering on second graph (possible subgraph) * @param vertexComparator comparator for semantic equality of vertices * @param edgeComparator comparator for semantic equality of edges */ public VF2State( GraphOrdering g1, GraphOrdering g2, Comparator vertexComparator, Comparator edgeComparator) { this.g1 = g1; this.g2 = g2; this.vertexComparator = vertexComparator; this.edgeComparator = edgeComparator; n1 = g1.getVertexCount(); n2 = g2.getVertexCount(); core1 = new int[n1]; in1 = new int[n1]; out1 = new int[n1]; core2 = new int[n2]; in2 = new int[n2]; out2 = new int[n2]; Arrays.fill(core1, NULL_NODE); Arrays.fill(core2, NULL_NODE); coreLen = 0; addedVertex1 = addVertex1 = addVertex2 = NULL_NODE; t1BothLen = t2BothLen = t1InLen = t2InLen = t1OutLen = t2OutLen = 0; } /** * copy constructor * * @param s */ public VF2State(VF2State s) { g1 = s.g1; g2 = s.g2; core1 = s.core1; core2 = s.core2; in1 = s.in1; in2 = s.in2; out1 = s.out1; out2 = s.out2; coreLen = s.coreLen; n1 = s.n1; n2 = s.n2; t1BothLen = s.t1BothLen; t2BothLen = s.t2BothLen; t1InLen = s.t1InLen; t2InLen = s.t2InLen; t1OutLen = s.t1OutLen; t2OutLen = s.t2OutLen; vertexComparator = s.vertexComparator; edgeComparator = s.edgeComparator; addVertex1 = s.addVertex1; addVertex2 = s.addVertex2; addedVertex1 = s.addedVertex1; } /** * calculates a pair of nodes which may be added to the current matching, according to the VF2 * algorithm. * * @return false, if there are no more pairs left */ public boolean nextPair() { if (addVertex2 == NULL_NODE) { addVertex2 = 0; } if (addVertex1 == NULL_NODE) { addVertex1 = 0; } else { addVertex1++; } // check incoming and outgoing edges if ((t1BothLen > coreLen) && (t2BothLen > coreLen)) { // find minimum for addVertex2 in core2 and t2in/t2out while ((addVertex2 < n2) && ((core2[addVertex2] != NULL_NODE) || (out2[addVertex2] == 0) || (in2[addVertex2] == 0))) { addVertex2++; addVertex1 = 0; } // find first/next vertex for addVertex1 in core1 and t1in/t1out while ((addVertex1 < n1) && ((core1[addVertex1] != NULL_NODE) || (out1[addVertex1] == 0) || (in1[addVertex1] == 0))) { addVertex1++; } } // check outgoing edges else if ((t1OutLen > coreLen) && (t2OutLen > coreLen)) { while ((addVertex2 < n2) && ((core2[addVertex2] != NULL_NODE) || (out2[addVertex2] == 0))) { addVertex2++; addVertex1 = 0; } while ((addVertex1 < n1) && ((core1[addVertex1] != NULL_NODE) || (out1[addVertex1] == 0))) { addVertex1++; } } // check incoming edges else if ((t1InLen > coreLen) && (t2InLen > coreLen)) { while ((addVertex2 < n2) && ((core2[addVertex2] != NULL_NODE) || (in2[addVertex2] == 0))) { addVertex2++; addVertex1 = 0; } while ((addVertex1 < n1) && ((core1[addVertex1] != NULL_NODE) || (in1[addVertex1] == 0))) { addVertex1++; } } // check new edges else { while ((addVertex2 < n2) && (core2[addVertex2] != NULL_NODE)) { addVertex2++; addVertex1 = 0; } while ((addVertex1 < n1) && (core1[addVertex1] != NULL_NODE)) { addVertex1++; } } if ((addVertex1 < n1) && (addVertex2 < n2)) { if (DEBUG) showLog( "nextPair", "next candidate pair: (" + g1.getVertex(addVertex1) + ", " + g2.getVertex(addVertex2) + ")"); return true; } // there are no more pairs.. if (DEBUG) showLog("nextPair", "no more candidate pairs"); addVertex1 = addVertex2 = NULL_NODE; return false; } /** * adds the pair to the current matching. */ public void addPair() { if (DEBUG) showLog( "addPair", "(" + g1.getVertex(addVertex1) + ", " + g2.getVertex(addVertex2) + ") added"); coreLen++; addedVertex1 = addVertex1; if (in1[addVertex1] == 0) { in1[addVertex1] = coreLen; t1InLen++; if (out1[addVertex1] > 0) { t1BothLen++; } } if (out1[addVertex1] == 0) { out1[addVertex1] = coreLen; t1OutLen++; if (in1[addVertex1] > 0) { t1BothLen++; } } if (in2[addVertex2] == 0) { in2[addVertex2] = coreLen; t2InLen++; if (out2[addVertex2] > 0) { t2BothLen++; } } if (out2[addVertex2] == 0) { out2[addVertex2] = coreLen; t2OutLen++; if (in2[addVertex2] > 0) { t2BothLen++; } } core1[addVertex1] = addVertex2; core2[addVertex2] = addVertex1; for (int other : g1.getInEdges(addVertex1)) { if (in1[other] == 0) { in1[other] = coreLen; t1InLen++; if (out1[other] > 0) { t1BothLen++; } } } for (int other : g1.getOutEdges(addVertex1)) { if (out1[other] == 0) { out1[other] = coreLen; t1OutLen++; if (in1[other] > 0) { t1BothLen++; } } } for (int other : g2.getInEdges(addVertex2)) { if (in2[other] == 0) { in2[other] = coreLen; t2InLen++; if (out2[other] > 0) { t2BothLen++; } } } for (int other : g2.getOutEdges(addVertex2)) { if (out2[other] == 0) { out2[other] = coreLen; t2OutLen++; if (in2[other] > 0) { t2BothLen++; } } } } /** * @return is the matching already complete? */ public boolean isGoal() { return coreLen == n2; } /** * @return true, if the already matched vertices of graph1 plus the first vertex of nextPair are * isomorphic to the already matched vertices of graph2 and the second one vertex of * nextPair. */ public abstract boolean isFeasiblePair(); /** * removes the last added pair from the matching */ public void backtrack() { int addedVertex2 = core1[addedVertex1]; if (DEBUG) showLog( "backtrack", "remove (" + g1.getVertex(addedVertex1) + ", " + g2.getVertex(addedVertex2) + ") from the matching"); if (in1[addedVertex1] == coreLen) { in1[addedVertex1] = 0; } for (int other : g1.getInEdges(addedVertex1)) { if (in1[other] == coreLen) { in1[other] = 0; } } if (out1[addedVertex1] == coreLen) { out1[addedVertex1] = 0; } for (int other : g1.getOutEdges(addedVertex1)) { if (out1[other] == coreLen) { out1[other] = 0; } } if (in2[addedVertex2] == coreLen) { in2[addedVertex2] = 0; } for (int other : g2.getInEdges(addedVertex2)) { if (in2[other] == coreLen) { in2[other] = 0; } } if (out2[addedVertex2] == coreLen) { out2[addedVertex2] = 0; } for (int other : g2.getOutEdges(addedVertex2)) { if (out2[other] == coreLen) { out2[other] = 0; } } core1[addedVertex1] = core2[addedVertex2] = NULL_NODE; coreLen--; addedVertex1 = NULL_NODE; } /** * checks the vertices $v_1$ and $v_2$ for semantic equivalence * * @param v1 * @param v2 * * @return v1 and v2 are equivalent */ protected boolean areCompatibleVertexes(int v1, int v2) { return (vertexComparator == null) || (vertexComparator.compare(g1.getVertex(v1), g2.getVertex(v2)) == 0); } /** * checks the edges from $v_1$ to $v_2$ and from $u_1$ to $u_2$ for semantic equivalence * * @param v1 * @param v2 * @param u1 * @param u2 * * @return edges are equivalent */ protected boolean areCompatibleEdges(int v1, int v2, int u1, int u2) { return (edgeComparator == null) || (edgeComparator.compare(g1.getEdge(v1, v2), g2.getEdge(u1, u2)) == 0); } public IsomorphicGraphMapping getCurrentMapping() { return new IsomorphicGraphMapping<>(g1, g2, core1, core2); } public void resetAddVertexes() { addVertex1 = addVertex2 = NULL_NODE; } /** * creates the debug output only if DEBUG is true. * * @param method * @param str */ protected void showLog(String method, String str) { if (!DEBUG) { return; } char[] indent = new char[2 * coreLen]; Arrays.fill(indent, ' '); System.out.println((new String(indent)) + method + "> " + str); } } VF2SubgraphIsomorphismInspector.java000066400000000000000000000111641402514743400362020ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import java.util.*; /** * This is an implementation of the VF2 algorithm using its feature of detecting subgraph * isomorphism between two graphs as described in Cordella et al. A (sub)graph isomorphism algorithm * for matching large graphs (2004), DOI:10.1109/TPAMI.2004.75, * * http://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=1323804 * *

* Note that this inspector only finds isomorphisms between a smaller graph and all * induced subgraphs of a * larger graph. It does not find isomorphisms between the smaller graph and arbitrary subgraphs of * the larger graph. For example, given as input the * cubical graph $Q_{3}$ and the * square graph, isomorphic mappings * will be found between the square and the faces of the cube. However, given the * complete graph $K_{5}$ and the * square graph as input, no isomorphisms will be found since all induced subgraphs of a complete * graph are themselves complete graphs. * *

* Consequently, in the case where both input graphs have the same number of vertices, this * algorithm is equivalent to running {@link VF2GraphIsomorphismInspector}. * *

* This implementation of the VF2 algorithm does not support graphs with multiple (parallel) edges. * * @param the type of the vertices * @param the type of the edges */ public class VF2SubgraphIsomorphismInspector extends VF2AbstractIsomorphismInspector { /** * Construct a new VF2 subgraph isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph (possible induced subgraph of graph1) * @param vertexComparator comparator for semantic equivalence of vertices * @param edgeComparator comparator for semantic equivalence of edges * @param cacheEdges if true, edges get cached for faster access */ public VF2SubgraphIsomorphismInspector( Graph graph1, Graph graph2, Comparator vertexComparator, Comparator edgeComparator, boolean cacheEdges) { super(graph1, graph2, vertexComparator, edgeComparator, cacheEdges); } /** * Construct a new VF2 subgraph isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph (possible induced subgraph of graph1) * @param vertexComparator comparator for semantic equivalence of vertices * @param edgeComparator comparator for semantic equivalence of edges */ public VF2SubgraphIsomorphismInspector( Graph graph1, Graph graph2, Comparator vertexComparator, Comparator edgeComparator) { super(graph1, graph2, vertexComparator, edgeComparator, true); } /** * Construct a new VF2 subgraph isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph (possible induced subgraph of graph1) * @param cacheEdges if true, edges get cached for faster access */ public VF2SubgraphIsomorphismInspector( Graph graph1, Graph graph2, boolean cacheEdges) { super(graph1, graph2, null, null, cacheEdges); } /** * Construct a new VF2 subgraph isomorphism inspector. * * @param graph1 the first graph * @param graph2 the second graph (possible induced subgraph of graph1) */ public VF2SubgraphIsomorphismInspector(Graph graph1, Graph graph2) { super(graph1, graph2, true); } @Override public VF2SubgraphMappingIterator getMappings() { return new VF2SubgraphMappingIterator<>( ordering1, ordering2, vertexComparator, edgeComparator); } } VF2SubgraphIsomorphismState.java000066400000000000000000000207071402514743400353170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import java.util.*; class VF2SubgraphIsomorphismState extends VF2State { public VF2SubgraphIsomorphismState( GraphOrdering g1, GraphOrdering g2, Comparator vertexComparator, Comparator edgeComparator) { super(g1, g2, vertexComparator, edgeComparator); } public VF2SubgraphIsomorphismState(VF2State s) { super(s); } /** * @return true, if the already matched vertices of graph1 plus the first vertex of nextPair are * subgraph isomorphic to the already matched vertices of graph2 and the second one * vertex of nextPair. */ @Override public boolean isFeasiblePair() { final String pairstr = (DEBUG) ? "(" + g1.getVertex(addVertex1) + ", " + g2.getVertex(addVertex2) + ")" : null; final String abortmsg = (DEBUG) ? pairstr + " does not fit in the current matching" : null; // check for semantic equality of both vertexes if (!areCompatibleVertexes(addVertex1, addVertex2)) { return false; } int termOutPred1 = 0, termOutPred2 = 0, termInPred1 = 0, termInPred2 = 0, newPred1 = 0, newPred2 = 0, termOutSucc1 = 0, termOutSucc2 = 0, termInSucc1 = 0, termInSucc2 = 0, newSucc1 = 0, newSucc2 = 0; // check outgoing edges of addVertex1 final int[] outE1 = g1.getOutEdges(addVertex1); for (int i = 0; i < outE1.length; i++) { final int other1 = outE1[i]; if (core1[other1] != NULL_NODE) { final int other2 = core1[other1]; if (!g2.hasEdge(addVertex2, other2) || !areCompatibleEdges(addVertex1, other1, addVertex2, other2)) { if (DEBUG) showLog( "isFeasiblePair", abortmsg + ": edge from " + g2.getVertex(addVertex2) + " to " + g2.getVertex(other2) + " is missing in the 2nd graph"); return false; } } else { final int in1O1 = in1[other1]; final int out1O1 = out1[other1]; if ((in1O1 == 0) && (out1O1 == 0)) { newSucc1++; continue; } if (in1O1 > 0) { termInSucc1++; } if (out1O1 > 0) { termOutSucc1++; } } } // check outgoing edges of addVertex2 final int[] outE2 = g2.getOutEdges(addVertex2); for (int i = 0; i < outE2.length; i++) { final int other2 = outE2[i]; if (core2[other2] != NULL_NODE) { int other1 = core2[other2]; if (!g1.hasEdge(addVertex1, other1)) { if (DEBUG) showLog( "isFeasbilePair", abortmsg + ": edge from " + g1.getVertex(addVertex1) + " to " + g1.getVertex(other1) + " is missing in the 1st graph"); return false; } } else { final int in2O2 = in2[other2]; final int out2O2 = out2[other2]; if ((in2O2 == 0) && (out2O2 == 0)) { newSucc2++; continue; } if (in2O2 > 0) { termInSucc2++; } if (out2O2 > 0) { termOutSucc2++; } } } if ((termInSucc1 < termInSucc2) || (termOutSucc1 < termOutSucc2) || (newSucc1 < newSucc2)) { if (DEBUG) { String cause = "", v1 = g1.getVertex(addVertex1).toString(), v2 = g2.getVertex(addVertex2).toString(); if (termInSucc2 > termInSucc1) { cause = "|Tin2 ∩ Succ(Graph2, " + v2 + ")| > |Tin1 ∩ Succ(Graph1, " + v1 + ")|"; } else if (termOutSucc2 > termOutSucc1) { cause = "|Tout2 ∩ Succ(Graph2, " + v2 + ")| > |Tout1 ∩ Succ(Graph1, " + v1 + ")|"; } else if (newSucc2 > newSucc1) { cause = "|N‾ ∩ Succ(Graph2, " + v2 + ")| > |N‾ ∩ Succ(Graph1, " + v1 + ")|"; } showLog("isFeasbilePair", abortmsg + ": " + cause); } return false; } // check incoming edges of addVertex1 final int[] inE1 = g1.getInEdges(addVertex1); for (int i = 0; i < inE1.length; i++) { final int other1 = inE1[i]; if (core1[other1] != NULL_NODE) { final int other2 = core1[other1]; if (!g2.hasEdge(other2, addVertex2) || !areCompatibleEdges(other1, addVertex1, other2, addVertex2)) { if (DEBUG) showLog( "isFeasbilePair", abortmsg + ": edge from " + g2.getVertex(other2) + " to " + g2.getVertex(addVertex2) + " is missing in the 2nd graph"); return false; } } else { final int in1O1 = in1[other1]; final int out1O1 = out1[other1]; if ((in1O1 == 0) && (out1O1 == 0)) { newPred1++; continue; } if (in1O1 > 0) { termInPred1++; } if (out1O1 > 0) { termOutPred1++; } } } // check incoming edges of addVertex2 final int[] inE2 = g2.getInEdges(addVertex2); for (int i = 0; i < inE2.length; i++) { final int other2 = inE2[i]; if (core2[other2] != NULL_NODE) { final int other1 = core2[other2]; if (!g1.hasEdge(other1, addVertex1)) { if (DEBUG) showLog( "isFeasiblePair", abortmsg + ": edge from " + g1.getVertex(other1) + " to " + g1.getVertex(addVertex1) + " is missing in the 1st graph"); return false; } } else { final int in2O2 = in2[other2]; final int out2O2 = out2[other2]; if ((in2O2 == 0) && (out2O2 == 0)) { newPred2++; continue; } if (in2O2 > 0) { termInPred2++; } if (out2O2 > 0) { termOutPred2++; } } } if ((termInPred1 >= termInPred2) && (termOutPred1 >= termOutPred2) && (newPred1 >= newPred2)) { if (DEBUG) showLog("isFeasiblePair", pairstr + " fits"); return true; } else { if (DEBUG) { String cause = "", v1 = g1.getVertex(addVertex1).toString(), v2 = g2.getVertex(addVertex2).toString(); if (termInPred2 > termInPred1) { cause = "|Tin2 ∩ Pred(Graph2, " + v2 + ")| > |Tin1 ∩ Pred(Graph1, " + v1 + ")|"; } else if (termOutPred2 > termOutPred1) { cause = "|Tout2 ∩ Pred(Graph2, " + v2 + ")| > |Tout1 ∩ Pred(Graph1, " + v1 + ")|"; } else if (newPred2 > newPred1) { cause = "|N‾ ∩ Pred(Graph2, " + v2 + ")| > |N‾ ∩ Pred(Graph1, " + v1 + ")|"; } showLog("isFeasbilePair", abortmsg + ": " + cause); } return false; } } } VF2SubgraphMappingIterator.java000066400000000000000000000052321402514743400351060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import java.util.*; /** * This class is used to iterate over all existing (subgraph isomorphic) mappings between two * graphs. It is used by the {@link VF2SubgraphIsomorphismInspector}. * * @param the type of the vertices * @param the type of the edges */ class VF2SubgraphMappingIterator extends VF2MappingIterator { public VF2SubgraphMappingIterator( GraphOrdering ordering1, GraphOrdering ordering2, Comparator vertexComparator, Comparator edgeComparator) { super(ordering1, ordering2, vertexComparator, edgeComparator); } @Override protected IsomorphicGraphMapping match() { VF2State s; if (stateStack.isEmpty()) { Graph g1 = ordering1.getGraph(), g2 = ordering2.getGraph(); if ((g1.vertexSet().size() < g2.vertexSet().size()) || (g1.edgeSet().size() < g2.edgeSet().size())) { return null; } s = new VF2SubgraphIsomorphismState<>( ordering1, ordering2, vertexComparator, edgeComparator); if (g2.vertexSet().isEmpty()) { return (hadOneMapping != null) ? null : s.getCurrentMapping(); } } else { stateStack.pop().backtrack(); s = stateStack.pop(); } while (true) { while (s.nextPair()) { if (s.isFeasiblePair()) { stateStack.push(s); s = new VF2SubgraphIsomorphismState<>(s); s.addPair(); if (s.isGoal()) { stateStack.push(s); return s.getCurrentMapping(); } s.resetAddVertexes(); } } if (stateStack.isEmpty()) { return null; } s.backtrack(); s = stateStack.pop(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/isomorphism/package-info.java000066400000000000000000000001271402514743400323700ustar00rootroot00000000000000/** * Algorithms for (sub)graph isomorphism. */ package org.jgrapht.alg.isomorphism; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/lca/000077500000000000000000000000001402514743400253675ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/lca/BinaryLiftingLCAFinder.java000066400000000000000000000200171402514743400324430ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.util.*; import java.util.*; import static org.jgrapht.util.MathUtil.log2; /** * Algorithm for computing lowest common ancestors in rooted trees and forests using the binary * lifting method. * *

* The method appears in Bender, Michael A., and Martın Farach-Colton. "The level ancestor * problem simplified." Theoretical Computer Science 321.1 (2004): 5-12 and it is also nicely * presented in the following article on Topcoder * for more details about the algorithm. *

* *

* Algorithm idea:
* We improve on the naive approach by using jump pointers. These are pointers at a node which * reference one of the node’s ancestors. Each node stores jump pointers to ancestors at levels 1, * 2, 4, . . . , 2^k.
* Queries are answered by repeatedly jumping from node to node, each time jumping more than half of * the remaining levels between the current ancestor and the goal ancestor (i.e. the lca). The * worst-case number of jumps is $O(log(|V|))$. *

* * *

* Preprocessing Time complexity: $O(|V| log(|V|))$
* Preprocessing Space complexity: $O(|V| log(|V|))$
* Query Time complexity: $O(log(|V|))$
* Query Space complexity: $O(1)$
*

* *

* For small (i.e. less than 100 vertices) trees or forests, all implementations behave similarly. * For larger trees/forests with less than 50,000 queries you can use either * {@link BinaryLiftingLCAFinder}, {@link HeavyPathLCAFinder} or {@link EulerTourRMQLCAFinder}. Fo * more than that use {@link EulerTourRMQLCAFinder} since it provides $O(1)$ per query.
* Space-wise, {@link HeavyPathLCAFinder} and {@link TarjanLCAFinder} only use a linear amount while * {@link BinaryLiftingLCAFinder} and {@link EulerTourRMQLCAFinder} require linearithmic space.
* For DAGs, use {@link NaiveLCAFinder}. *

* * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public class BinaryLiftingLCAFinder implements LowestCommonAncestorAlgorithm { private final Graph graph; private final Set roots; private final int maxLevel; private Map vertexMap; private List indexList; // ancestors[u][i] = the 2^i ancestor of u (e.g ancestors[u][0] = father(u)) private int[][] ancestors; private int[] timeIn, timeOut; private int clock = 0; private int numberComponent; private int[] component; /** * Construct a new instance of the algorithm. * *

* Note: The constructor will NOT check if the input graph is a valid tree. * * @param graph the input graph * @param root the root of the graph */ public BinaryLiftingLCAFinder(Graph graph, V root) { this(graph, Collections.singleton(Objects.requireNonNull(root, "root cannot be null"))); } /** * Construct a new instance of the algorithm. * *

* Note: If two roots appear in the same tree, an error will be thrown. * *

* Note: The constructor will NOT check if the input graph is a valid forest. * * @param graph the input graph * @param roots the set of roots of the graph */ public BinaryLiftingLCAFinder(Graph graph, Set roots) { this.graph = Objects.requireNonNull(graph, "graph cannot be null"); this.roots = Objects.requireNonNull(roots, "roots cannot be null"); this.maxLevel = log2(graph.vertexSet().size()); if (this.roots.isEmpty()) throw new IllegalArgumentException("roots cannot be empty"); if (!graph.vertexSet().containsAll(roots)) throw new IllegalArgumentException("at least one root is not a valid vertex"); computeAncestorMatrix(); } private void normalizeGraph() { VertexToIntegerMapping vertexToIntegerMapping = Graphs.getVertexToIntegerMapping(graph); vertexMap = vertexToIntegerMapping.getVertexMap(); indexList = vertexToIntegerMapping.getIndexList(); } private void dfs(int u, int parent) { component[u] = numberComponent; timeIn[u] = ++clock; ancestors[0][u] = parent; for (int l = 1; l < maxLevel; l++) { if (ancestors[l - 1][u] != -1) ancestors[l][u] = ancestors[l - 1][ancestors[l - 1][u]]; } V vertexU = indexList.get(u); for (E edge : graph.outgoingEdgesOf(vertexU)) { int v = vertexMap.get(Graphs.getOppositeVertex(graph, edge, vertexU)); if (v != parent) { dfs(v, u); } } timeOut[u] = ++clock; } private void computeAncestorMatrix() { ancestors = new int[maxLevel + 1][graph.vertexSet().size()]; for (int l = 0; l < maxLevel; l++) { Arrays.fill(ancestors[l], -1); } timeIn = new int[graph.vertexSet().size()]; timeOut = new int[graph.vertexSet().size()]; // Ensure that isAncestor(x, y) == false if either x and y hasn't been explored yet for (int i = 0; i < graph.vertexSet().size(); i++) { timeIn[i] = timeOut[i] = -(i + 1); } numberComponent = 0; component = new int[graph.vertexSet().size()]; normalizeGraph(); for (V root : roots) { if (component[vertexMap.get(root)] == 0) { numberComponent++; dfs(vertexMap.get(root), -1); } else { throw new IllegalArgumentException("multiple roots in the same tree"); } } } private boolean isAncestor(int ancestor, int descendant) { return timeIn[ancestor] <= timeIn[descendant] && timeOut[descendant] <= timeOut[ancestor]; } /** * {@inheritDoc} */ @Override public V getLCA(V a, V b) { int indexA = vertexMap.getOrDefault(a, -1); if (indexA == -1) throw new IllegalArgumentException("invalid vertex: " + a); int indexB = vertexMap.getOrDefault(b, -1); if (indexB == -1) throw new IllegalArgumentException("invalid vertex: " + b); // Check if a == b because lca(a, a) == a if (a.equals(b)) return a; // if a and b are in different components then they do not have a lca if (component[indexA] != component[indexB] || component[indexA] == 0) return null; if (isAncestor(indexA, indexB)) return a; if (isAncestor(indexB, indexA)) return b; for (int l = maxLevel - 1; l >= 0; l--) if (ancestors[l][indexA] != -1 && !isAncestor(ancestors[l][indexA], indexB)) indexA = ancestors[l][indexA]; int lca = ancestors[0][indexA]; // if lca is null if (lca == -1) return null; else return indexList.get(lca); } /** * Note: This operation is not supported.
* * {@inheritDoc} * * @throws UnsupportedOperationException if the method is called */ @Override public Set getLCASet(V a, V b) { throw new UnsupportedOperationException(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/lca/EulerTourRMQLCAFinder.java000066400000000000000000000216261402514743400322170ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.util.*; import java.util.*; /** * Algorithm for computing lowest common ancestors in rooted trees and forests based on Berkman, * Omer; Vishkin, Uzi (1993), "Recursive Star-Tree Parallel Data Structure", SIAM Journal on * Computing, 22 (2): 221–242, doi:10.1137/0222017. * *

* The algorithm involves forming an Euler tour of a graph formed from the input tree by doubling * every edge, and using this tour to compute a sequence of level numbers of the nodes in the order * the tour visits them. A lowest common ancestor query can then be transformed into a query that * seeks the minimum value occurring within some subinterval of this sequence of numbers. *

* *

* Preprocessing Time complexity: $O(|V| log(|V|))$
* Preprocessing Space complexity: $O(|V| log(|V|))$
* Query Time complexity: $O(1)$
* Query Space complexity: $O(1)$
*

* *

* For small (i.e. less than 100 vertices) trees or forests, all implementations behave similarly. * For larger trees/forests with less than 50,000 queries you can use either * {@link BinaryLiftingLCAFinder}, {@link HeavyPathLCAFinder} or {@link EulerTourRMQLCAFinder}. Fo * more than that use {@link EulerTourRMQLCAFinder} since it provides $O(1)$ per query.
* Space-wise, {@link HeavyPathLCAFinder} and {@link TarjanLCAFinder} only use a linear amount while * {@link BinaryLiftingLCAFinder} and {@link EulerTourRMQLCAFinder} require linearithmic space.
* For DAGs, use {@link NaiveLCAFinder}. *

* * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public class EulerTourRMQLCAFinder implements LowestCommonAncestorAlgorithm { private final Graph graph; private final Set roots; private final int maxLevel; private Map vertexMap; private List indexList; private int[] eulerTour; private int sizeTour; private int numberComponent; private int[] component; private int[] level; private int[] representative; private int[][] rmq; private int[] log2; /** * Construct a new instance of the algorithm. * *

* Note: The constructor will NOT check if the input graph is a valid tree. * * @param graph the input graph * @param root the root of the graph */ public EulerTourRMQLCAFinder(Graph graph, V root) { this(graph, Collections.singleton(Objects.requireNonNull(root, "root cannot be null"))); } /** * Construct a new instance of the algorithm. * *

* Note: If two roots appear in the same tree, an error will be thrown. * *

* Note: The constructor will NOT check if the input graph is a valid forest. * * @param graph the input graph * @param roots the set of roots of the graph */ public EulerTourRMQLCAFinder(Graph graph, Set roots) { this.graph = Objects.requireNonNull(graph, "graph cannot be null"); this.roots = Objects.requireNonNull(roots, "roots cannot be null"); this.maxLevel = 1 + org.jgrapht.util.MathUtil.log2(graph.vertexSet().size()); if (this.roots.isEmpty()) throw new IllegalArgumentException("roots cannot be empty"); if (!graph.vertexSet().containsAll(roots)) throw new IllegalArgumentException("at least one root is not a valid vertex"); computeAncestorsStructure(); } private void normalizeGraph() { VertexToIntegerMapping vertexToIntegerMapping = Graphs.getVertexToIntegerMapping(graph); vertexMap = vertexToIntegerMapping.getVertexMap(); indexList = vertexToIntegerMapping.getIndexList(); } private void dfsIterative(int u, int startLevel) { // set of vertices for which the part of the if has been performed // (in other words: u ∈ explored iff dfs(u, ...) has been called as some point) Set explored = new HashSet<>(); ArrayDeque> stack = new ArrayDeque<>(); stack.push(Pair.of(u, startLevel)); while (!stack.isEmpty()) { Pair pair = stack.poll(); u = pair.getFirst(); int lvl = pair.getSecond(); if (!explored.contains(u)) { explored.add(u); component[u] = numberComponent; eulerTour[sizeTour] = u; level[sizeTour] = lvl; sizeTour++; V vertexU = indexList.get(u); for (E edge : graph.outgoingEdgesOf(vertexU)) { int child = vertexMap.get(Graphs.getOppositeVertex(graph, edge, vertexU)); // check if child has not been explored (i.e. dfs(child, ...) has not been // called) if (!explored.contains(child)) { // simulate the return from recursion stack.push(pair); stack.push(Pair.of(child, lvl + 1)); } } } else { eulerTour[sizeTour] = u; level[sizeTour] = lvl; sizeTour++; } } } private void computeRMQ() { rmq = new int[maxLevel + 1][sizeTour]; log2 = new int[sizeTour + 1]; for (int i = 0; i < sizeTour; i++) { rmq[0][i] = i; } for (int i = 1; (1 << i) <= sizeTour; i++) { for (int j = 0; j + (1 << i) - 1 < sizeTour; j++) { int p = 1 << (i - 1); if (level[rmq[i - 1][j]] < level[rmq[i - 1][j + p]]) { rmq[i][j] = rmq[i - 1][j]; } else { rmq[i][j] = rmq[i - 1][j + p]; } } } for (int i = 2; i <= sizeTour; ++i) { log2[i] = log2[i / 2] + 1; } } private void computeAncestorsStructure() { normalizeGraph(); eulerTour = new int[2 * graph.vertexSet().size()]; level = new int[2 * graph.vertexSet().size()]; representative = new int[graph.vertexSet().size()]; numberComponent = 0; component = new int[graph.vertexSet().size()]; for (V root : roots) { int u = vertexMap.get(root); if (component[u] == 0) { numberComponent++; dfsIterative(u, -1); } else { throw new IllegalArgumentException("multiple roots in the same tree"); } } Arrays.fill(representative, -1); for (int i = 0; i < sizeTour; i++) { if (representative[eulerTour[i]] == -1) { representative[eulerTour[i]] = i; } } computeRMQ(); } /** * {@inheritDoc} */ @Override public V getLCA(V a, V b) { int indexA = vertexMap.getOrDefault(a, -1); if (indexA == -1) throw new IllegalArgumentException("invalid vertex: " + a); int indexB = vertexMap.getOrDefault(b, -1); if (indexB == -1) throw new IllegalArgumentException("invalid vertex: " + b); // Check if a == b because lca(a, a) == a if (a.equals(b)) return a; // If a and b are in different components then they do not have a lca if (component[indexA] != component[indexB] || component[indexA] == 0) return null; indexA = representative[indexA]; indexB = representative[indexB]; if (indexA > indexB) { int t = indexA; indexA = indexB; indexB = t; } int l = log2[indexB - indexA + 1]; int pwl = 1 << l; int sol = rmq[l][indexA]; if (level[sol] > level[rmq[l][indexB - pwl + 1]]) sol = rmq[l][indexB - pwl + 1]; return indexList.get(eulerTour[sol]); } /** * Note: This operation is not supported.
* * {@inheritDoc} * * @throws UnsupportedOperationException if the method is called */ @Override public Set getLCASet(V a, V b) { throw new UnsupportedOperationException(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/lca/HeavyPathLCAFinder.java000066400000000000000000000145151402514743400316010ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.decomposition.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Algorithm for computing lowest common ancestors in rooted trees and forests based on * {@link HeavyPathDecomposition}. * *

* Preprocessing Time complexity: $O(|V|)$
* Preprocessing Space complexity: $O(|V|)$
* Query Time complexity: $O(log(|V|))$
* Query Space complexity: $O(1)$
*

* *

* For small (i.e. less than 100 vertices) trees or forests, all implementations behave similarly. * For larger trees/forests with less than 50,000 queries you can use either * {@link BinaryLiftingLCAFinder}, {@link HeavyPathLCAFinder} or {@link EulerTourRMQLCAFinder}. Fo * more than that use {@link EulerTourRMQLCAFinder} since it provides $O(1)$ per query.
* Space-wise, {@link HeavyPathLCAFinder} and {@link TarjanLCAFinder} only use a linear amount while * {@link BinaryLiftingLCAFinder} and {@link EulerTourRMQLCAFinder} require linearithmic space.
* For DAGs, use {@link NaiveLCAFinder}. *

* * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public class HeavyPathLCAFinder implements LowestCommonAncestorAlgorithm { private final Graph graph; private final Set roots; private int[] parent; private int[] depth; private int[] path; private int[] positionInPath; private int[] component; private int[] firstNodeInPath; private Map vertexMap; private List indexList; /** * Construct a new instance of the algorithm. * *

* Note: The constructor will NOT check if the input graph is a valid tree. * * @param graph the input graph * @param root the root of the graph */ public HeavyPathLCAFinder(Graph graph, V root) { this(graph, Collections.singleton(Objects.requireNonNull(root, "root cannot be null"))); } /** * Construct a new instance of the algorithm. * *

* Note: If two roots appear in the same tree, an error will be thrown. * *

* Note: The constructor will NOT check if the input graph is a valid forest. * * @param graph the input graph * @param roots the set of roots of the graph */ public HeavyPathLCAFinder(Graph graph, Set roots) { this.graph = Objects.requireNonNull(graph, "graph cannot be null"); this.roots = Objects.requireNonNull(roots, "roots cannot be null"); if (this.roots.isEmpty()) throw new IllegalArgumentException("roots cannot be empty"); if (!graph.vertexSet().containsAll(roots)) throw new IllegalArgumentException("at least one root is not a valid vertex"); computeHeavyPathDecomposition(); } /** * Compute the heavy path decomposition and get the corresponding arrays from the internal * state. */ private void computeHeavyPathDecomposition() { HeavyPathDecomposition heavyPath = new HeavyPathDecomposition<>(graph, roots); HeavyPathDecomposition.InternalState state = heavyPath.getInternalState(); vertexMap = state.getVertexMap(); indexList = state.getIndexList(); parent = state.getParentArray(); depth = state.getDepthArray(); component = state.getComponentArray(); firstNodeInPath = state.getFirstNodeInPathArray(); path = state.getPathArray(); positionInPath = state.getPositionInPathArray(); } /** * {@inheritDoc} */ @Override public V getLCA(V a, V b) { int indexA = vertexMap.getOrDefault(a, -1); if (indexA == -1) throw new IllegalArgumentException("invalid vertex: " + a); int indexB = vertexMap.getOrDefault(b, -1); if (indexB == -1) throw new IllegalArgumentException("invalid vertex: " + b); // Check if a == b because lca(a, a) == a if (a.equals(b)) return a; int componentA = component[indexA]; int componentB = component[indexB]; // If a and b are in different components (or haven't been explored yet) then they do not // have a lca if (componentA != componentB || componentA == -1) return null; /* * Idea: Get a and b on the same vertex path by 'jumping' from one path to another * * while (a and b are on different paths) do if a's path starts lower than b's path (in the * tree) set a := father of the first node in a's path else set b: = father of the first * node in b's path * * now a and b are on the same path * * return a if a is closer to the root than b; otherwise return b */ int pathA = path[indexA]; int pathB = path[indexB]; while (pathA != pathB) { int firstNodePathA = firstNodeInPath[pathA]; int firstNodePathB = firstNodeInPath[pathB]; if (depth[firstNodePathA] < depth[firstNodePathB]) { indexB = parent[firstNodePathB]; pathB = path[indexB]; } else { indexA = parent[firstNodePathA]; pathA = path[indexA]; } } return positionInPath[indexA] < positionInPath[indexB] ? indexList.get(indexA) : indexList.get(indexB); } /** * Note: This operation is not supported.
* * {@inheritDoc} * * @throws UnsupportedOperationException if the method is called */ @Override public Set getLCASet(V a, V b) { throw new UnsupportedOperationException(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/lca/NaiveLCAFinder.java000066400000000000000000000124051402514743400307460ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Leo Crawford and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.Pair; import org.jgrapht.graph.EdgeReversedGraph; import org.jgrapht.traverse.BreadthFirstIterator; import java.util.*; /** * Find the Lowest Common Ancestor of a directed graph. * *

* Find the LCA, defined as "Let $G = (V, E)$ be a DAG, and let $x, y \in V$. Let $G_{x,y}$ be * the subgraph of $G$ induced by the set of all common ancestors of $x$ and $y$. Define SLCA (x, y) * to be the set of out-degree 0 nodes (leafs) in $G_{x,y}$. The lowest common ancestors of $x$ and * $y$ are the elements of SLCA (x, y). " from Michael A. Bender, Martín Farach-Colton, * Giridhar Pemmasani, Steven Skiena, Pavel Sumazin, Lowest common ancestors in trees and directed * acyclic graphs, Journal of Algorithms, Volume 57, Issue 2, 2005, Pages 75-94, ISSN 0196-6774, * https://doi.org/10.1016/j.jalgor.2005.08.001. * *

* The algorithm: * *

    *
  1. Find ancestor sets for nodes $a$ and $b$.
  2. *
  3. Find their intersection.
  4. *
  5. Extract leaf nodes from the intersection set.
  6. *
* * The algorithm is straightforward in the way it finds the LCA set by definition. * *

* Preprocessing Time complexity: $O(1)$
* Preprocessing Space complexity: $O(1)$
* Query Time complexity: $O(|V|)$
* Query Space complexity: $O(|V|)$
*

* *

* For trees or forests please use either {@link BinaryLiftingLCAFinder}, * {@link HeavyPathLCAFinder}, {@link EulerTourRMQLCAFinder} or {@link TarjanLCAFinder}. *

* * @param the graph vertex type * @param the graph edge type * * @author Leo Crawford * @author Alexandru Valeanu */ public class NaiveLCAFinder implements LowestCommonAncestorAlgorithm { private final Graph graph; /** * Create a new instance of the naive LCA finder. * * @param graph the input graph */ public NaiveLCAFinder(Graph graph) { this.graph = GraphTests.requireDirected(graph); } /** * {@inheritDoc} */ @Override public V getLCA(V a, V b) { checkNodes(a, b); Set lcaSet = getLCASet(a, b); if (lcaSet.isEmpty()) { return null; } else { return lcaSet.iterator().next(); } } /** * {@inheritDoc} */ @Override public Set getLCASet(V a, V b) { checkNodes(a, b); Graph edgeReversed = new EdgeReversedGraph<>(graph); Set aAncestors = getAncestors(edgeReversed, a); Set bAncestors = getAncestors(edgeReversed, b); Set commonAncestors; // optimization trick: save the intersection using the smaller set if (aAncestors.size() < bAncestors.size()) { aAncestors.retainAll(bAncestors); commonAncestors = aAncestors; } else { bAncestors.retainAll(aAncestors); commonAncestors = bAncestors; } /* * Find the set of all non-leaves by iterating through the set of common ancestors. When we * encounter a node which is still part of the SLCA(a, b) we remove its parent(s). */ Set leaves = new HashSet<>(); for (V ancestor : commonAncestors) { boolean isLeaf = true; for (E edge : graph.outgoingEdgesOf(ancestor)) { V target = graph.getEdgeTarget(edge); if (commonAncestors.contains(target)) { isLeaf = false; break; } } if (isLeaf) { leaves.add(ancestor); } } return leaves; } /** * Returns a set of nodes reachable from the {@code start}. * * @param graph a graph * @param start a node to start from. * @return returns a set of nodes reachable from the {@code start}. */ private Set getAncestors(Graph graph, V start) { Set ancestors = new HashSet<>(); BreadthFirstIterator bfs = new BreadthFirstIterator<>(graph, start); while (bfs.hasNext()) { ancestors.add(bfs.next()); } return ancestors; } /** * Checks whether both {@code a} and {@code b} belong to the specified graph * * @param a first node * @param b second node */ private void checkNodes(V a, V b) { if (!graph.containsVertex(a)) throw new IllegalArgumentException("invalid vertex: " + a); if (!graph.containsVertex(b)) throw new IllegalArgumentException("invalid vertex: " + b); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/lca/TarjanLCAFinder.java000066400000000000000000000162241402514743400311260ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; /** * Tarjan's offline algorithm for computing lowest common ancestors in rooted trees and forests. * *

* See the article on wikipedia * for more information on the algorithm. * *

* *

* The original algorithm can be found in Gabow, H. N.; Tarjan, R. E. (1983), "A linear-time * algorithm for a special case of disjoint set union", Proceedings of the 15th ACM Symposium on * Theory of Computing (STOC), pp. 246–251, doi:10.1145/800061.808753 *

* *

* Preprocessing Time complexity: $O(1)$
* Preprocessing Space complexity: $O(1)$
* Query Time complexity: $O(|V| log^{*}(|V|) + |Q|)$ where $|Q|$ is the number of queries
* Query Space complexity: $O(|V| + |Q|)$ where $|Q|$ is the number of queries
*

* *

* For small (i.e. less than 100 vertices) trees or forests, all implementations behave similarly. * For larger trees/forests with less than 50,000 queries you can use either * {@link BinaryLiftingLCAFinder}, {@link HeavyPathLCAFinder} or {@link EulerTourRMQLCAFinder}. Fo * more than that use {@link EulerTourRMQLCAFinder} since it provides $O(1)$ per query.
* Space-wise, {@link HeavyPathLCAFinder} and {@link TarjanLCAFinder} only use a linear amount while * {@link BinaryLiftingLCAFinder} and {@link EulerTourRMQLCAFinder} require linearithmic space.
* For DAGs, use {@link NaiveLCAFinder}. *

* * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public class TarjanLCAFinder implements LowestCommonAncestorAlgorithm { private Graph graph; private Set roots; private UnionFind unionFind; private Map ancestors; private Set blackNodes; private HashMap> queryOccurs; private List lowestCommonAncestors; private List> queries; /** * Construct a new instance of the algorithm. * *

* Note: The constructor will NOT check if the input graph is a valid tree. * * @param graph the input graph * @param root the root of the graph */ public TarjanLCAFinder(Graph graph, V root) { this(graph, Collections.singleton(Objects.requireNonNull(root, "root cannot be null"))); } /** * Construct a new instance of the algorithm. * *

* Note: If two roots appear in the same tree, an error will be thrown. * *

* Note: The constructor will NOT check if the input graph is a valid forest. * * @param graph the input graph * @param roots the set of roots of the graph */ public TarjanLCAFinder(Graph graph, Set roots) { this.graph = Objects.requireNonNull(graph, "graph cannot be null"); this.roots = Objects.requireNonNull(roots, "roots cannot be null"); if (this.roots.isEmpty()) throw new IllegalArgumentException("roots cannot be empty"); if (!graph.vertexSet().containsAll(roots)) throw new IllegalArgumentException("at least one root is not a valid vertex"); } /** * {@inheritDoc} */ @Override public V getLCA(V a, V b) { return getBatchLCA(Collections.singletonList(Pair.of(a, b))).get(0); } /** * {@inheritDoc} */ @Override public List getBatchLCA(List> queries) { return computeTarjan(queries); } private void initialize() { unionFind = new UnionFind<>(Collections.emptySet()); ancestors = new HashMap<>(); blackNodes = new HashSet<>(); } private void clear() { unionFind = null; ancestors = null; blackNodes = null; queryOccurs = null; queries = null; lowestCommonAncestors = null; } private List computeTarjan(List> queries) { initialize(); this.queries = queries; this.lowestCommonAncestors = new ArrayList<>(queries.size()); this.queryOccurs = new HashMap<>(); for (int i = 0; i < queries.size(); i++) { V a = this.queries.get(i).getFirst(); V b = this.queries.get(i).getSecond(); if (!graph.containsVertex(a)) throw new IllegalArgumentException("invalid vertex: " + a); if (!graph.containsVertex(b)) throw new IllegalArgumentException("invalid vertex: " + b); if (a.equals(b)) this.lowestCommonAncestors.add(a); else { queryOccurs.computeIfAbsent(a, x -> new HashSet<>()).add(i); queryOccurs.computeIfAbsent(b, x -> new HashSet<>()).add(i); this.lowestCommonAncestors.add(null); } } Set visited = new HashSet<>(); for (V root : roots) { if (visited.contains(root)) throw new IllegalArgumentException("multiple roots in the same tree"); blackNodes.clear(); computeTarjanOLCA(root, null, visited); } List tmpRef = lowestCommonAncestors; clear(); return tmpRef; } private void computeTarjanOLCA(V u, V p, Set visited) { visited.add(u); unionFind.addElement(u); ancestors.put(u, u); for (E edge : graph.outgoingEdgesOf(u)) { V v = Graphs.getOppositeVertex(graph, edge, u); if (!v.equals(p)) { computeTarjanOLCA(v, u, visited); unionFind.union(u, v); ancestors.put(unionFind.find(u), u); } } blackNodes.add(u); for (int index : queryOccurs.computeIfAbsent(u, x -> new HashSet<>())) { Pair query = queries.get(index); V v; if (query.getFirst().equals(u)) v = query.getSecond(); else v = query.getFirst(); if (blackNodes.contains(v)) { lowestCommonAncestors.set(index, ancestors.get(unionFind.find(v))); } } } /** * Note: This operation is not supported.
* * {@inheritDoc} * * @throws UnsupportedOperationException if the method is called */ @Override public Set getLCASet(V a, V b) { throw new UnsupportedOperationException(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/lca/package-info.java000066400000000000000000000001441402514743400305550ustar00rootroot00000000000000/** * Algorithms for computing lowest common ancestors in graphs. */ package org.jgrapht.alg.lca; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/000077500000000000000000000000001402514743400276465ustar00rootroot00000000000000AdamicAdarIndexLinkPrediction.java000066400000000000000000000052651402514743400362370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; import org.jgrapht.alg.util.Pair; /** * Predict links using the Adamic-Adar Index. * *

* This is a local method which computes $s_{uv} = \sum_{z \in * \Gamma(u)\cap\Gamma(v))}\frac{1}{\log(k(z))}$ where for a node $v$, $\Gamma(v)$ denotes the set * of neighbors of $v$ and $k(v) = |\Gamma(v)|$ denotes the degree of $v$. *

* * See the following two papers: *
    *
  • Liben‐Nowell, David, and Jon Kleinberg. "The link‐prediction problem for social networks." * Journal of the American society for information science and technology 58.7 (2007): * 1019-1031.
  • *
  • Zhou, Tao, Linyuan Lü, and Yi-Cheng Zhang. "Predicting missing links via local information." * The European Physical Journal B 71.4 (2009): 623-630.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class AdamicAdarIndexLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public AdamicAdarIndexLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { List gu = Graphs.successorListOf(graph, u); List gv = Graphs.successorListOf(graph, v); Set intersection = new HashSet<>(gu); intersection.retainAll(gv); double result = 0d; for (V z : intersection) { int dz = graph.outDegreeOf(z); if (dz < 2) { throw new LinkPredictionIndexNotWellDefinedException( "Vertex has less than 2 degree", Pair.of(u, v)); } result += 1d / Math.log(dz); } return result; } } CommonNeighborsLinkPrediction.java000066400000000000000000000044041402514743400363640ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; /** * Predict links using the number of common neighbors. * *

* This is a local method which computes $s_{xy} = |\Gamma(u)\cap\Gamma(v))|$ where for a node $v$, * $\Gamma(v)$ denotes the set of neighbors of $v$. *

* * See the following two papers: *
    *
  • Liben‐Nowell, David, and Jon Kleinberg. "The link‐prediction problem for social networks." * Journal of the American society for information science and technology 58.7 (2007): * 1019-1031.
  • *
  • Zhou, Tao, Linyuan Lü, and Yi-Cheng Zhang. "Predicting missing links via local information." * The European Physical Journal B 71.4 (2009): 623-630.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class CommonNeighborsLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public CommonNeighborsLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { List gu = Graphs.successorListOf(graph, u); List gv = Graphs.successorListOf(graph, v); Set intersection = new HashSet<>(gu); intersection.retainAll(gv); return intersection.size(); } } HubDepressedIndexLinkPrediction.java000066400000000000000000000046031402514743400366410ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; import org.jgrapht.alg.util.Pair; /** * Predict links using the Hub Depressed Index. * *

* This is a local method which computes $s_{xy} = * \frac{2|\Gamma(u)\cap\Gamma(v))|}{max(k(u),k(v))}$ where for a node $v$, $\Gamma(v)$ denotes the * set of neighbors of $v$ and $k(v) = |\Gamma(v)|$ denotes the degree of $v$. *

* * See the following paper: *
    *
  • E. Ravasz, A.L. Somera, D.A. Mongru, Z.N. Oltvai, A.-L. Barabási, Science 297, 1553 * (2002)
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class HubDepressedIndexLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public HubDepressedIndexLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { int du = graph.outDegreeOf(u); int dv = graph.outDegreeOf(v); if (du == 0 && dv == 0) { throw new LinkPredictionIndexNotWellDefinedException( "Both vertices have zero neighbors", Pair.of(u, v)); } List gu = Graphs.successorListOf(graph, u); List gv = Graphs.successorListOf(graph, v); Set intersection = new HashSet<>(gu); intersection.retainAll(gv); return (double) intersection.size() / Math.max(du, dv); } } HubPromotedIndexLinkPrediction.java000066400000000000000000000045771402514743400365260ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; import org.jgrapht.alg.util.Pair; /** * Predict links using the Hub Promoted Index. * *

* This is a local method which computes $s_{xy} = * \frac{2|\Gamma(u)\cap\Gamma(v))|}{min(k(u),k(v))}$ where for a node $v$, $\Gamma(v)$ denotes the * set of neighbors of $v$ and $k(v) = |\Gamma(v)|$ denotes the degree of $v$. *

* * See the following paper: *
    *
  • E. Ravasz, A.L. Somera, D.A. Mongru, Z.N. Oltvai, A.-L. Barabási, Science 297, 1553 * (2002)
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class HubPromotedIndexLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public HubPromotedIndexLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { int du = graph.outDegreeOf(u); int dv = graph.outDegreeOf(v); if (du == 0 || dv == 0) { throw new LinkPredictionIndexNotWellDefinedException( "Query vertex with zero neighbors", Pair.of(u, v)); } List gu = Graphs.successorListOf(graph, u); List gv = Graphs.successorListOf(graph, v); Set intersection = new HashSet<>(gu); intersection.retainAll(gv); return (double) intersection.size() / Math.min(du, dv); } } JaccardCoefficientLinkPrediction.java000066400000000000000000000052321402514743400367610ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; import org.jgrapht.alg.util.Pair; /** * Predict links using the Jaccard coefficient. * *

* This is a local method which computes $s_{xy} = * \frac{|\Gamma(u)\cap\Gamma(v))|}{|\Gamma(u)\cup\Gamma(v))|}$ where for a node $v$, $\Gamma(v)$ * denotes the set of neighbors of $v$. *

* * See the following two papers: *
    *
  • Liben‐Nowell, David, and Jon Kleinberg. "The link‐prediction problem for social networks." * Journal of the American society for information science and technology 58.7 (2007): * 1019-1031.
  • *
  • Zhou, Tao, Linyuan Lü, and Yi-Cheng Zhang. "Predicting missing links via local information." * The European Physical Journal B 71.4 (2009): 623-630.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class JaccardCoefficientLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public JaccardCoefficientLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { if (u.equals(v)) { return 1.0; } List gu = Graphs.successorListOf(graph, u); List gv = Graphs.successorListOf(graph, v); Set union = new HashSet<>(gu); union.addAll(gv); if (union.isEmpty()) { throw new LinkPredictionIndexNotWellDefinedException( "Query nodes have no neighbor in common", Pair.of(u, v)); } Set intersection = new HashSet<>(gu); intersection.retainAll(gv); return (double) intersection.size() / union.size(); } } LeichtHolmeNewmanIndexLinkPrediction.java000066400000000000000000000045571402514743400376370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; import org.jgrapht.alg.util.Pair; /** * Predict links using the Leicht-Holme-Newman Index. * *

* This is a local method which computes $s_{xy} = \frac{|\Gamma(u)\cap\Gamma(v))|}{k(u) \cdot * k(v)}$ where for a node $v$, $\Gamma(v)$ denotes the set of neighbors of $v$ and $k(v) = * |\Gamma(v)|$ denotes the degree of $v$. *

* * See the following paper: *
    *
  • E.A. Leicht, P. Holme, M.E.J. Newman, Phys. Rev. E 73, 026120 (2006)
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class LeichtHolmeNewmanIndexLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public LeichtHolmeNewmanIndexLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { int du = graph.outDegreeOf(u); int dv = graph.outDegreeOf(v); if (du == 0 || dv == 0) { throw new LinkPredictionIndexNotWellDefinedException( "Query vertex with zero neighbors", Pair.of(u, v)); } List gu = Graphs.successorListOf(graph, u); List gv = Graphs.successorListOf(graph, v); Set intersection = new HashSet<>(gu); intersection.retainAll(gv); return (double) intersection.size() / du * dv; } } LinkPredictionIndexNotWellDefinedException.java000066400000000000000000000055211402514743400410060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import org.jgrapht.alg.util.Pair; /** * An exception used to notify that a link prediction index is not well defined. * * @author Dimitrios Michail */ public class LinkPredictionIndexNotWellDefinedException extends RuntimeException { private static final long serialVersionUID = -8832535053621910719L; private Pair vertexPair; /** * Constructs a new exception with {@code null} as its detail message. The cause is not * initialized, and may subsequently be initialized by a call to {@link #initCause}. */ public LinkPredictionIndexNotWellDefinedException() { super(); } /** * Constructs a new exception with the specified detail message. The cause is not initialized, * and may subsequently be initialized by a call to {@link #initCause}. * * @param message the detail message. The detail message is saved for later retrieval by the * {@link #getMessage()} method. */ public LinkPredictionIndexNotWellDefinedException(String message) { super(message); } /** * Constructs a new exception with the specified detail message. The cause is not initialized, * and may subsequently be initialized by a call to {@link #initCause}. * * @param message the detail message. The detail message is saved for later retrieval by the * {@link #getMessage()} method. * @param vertexPair the vertex pair which caused the error. The pair is saved for later * retrieval by the {@link #getVertexPair()} method. */ public LinkPredictionIndexNotWellDefinedException(String message, Pair vertexPair) { super(message); this.vertexPair = vertexPair; } /** * Get the vertex pair which caused the error. May be null. * * @return the vertex pair which caused the error */ public Pair getVertexPair() { return vertexPair; } /** * Set the vertex pair which caused the error. May be null. * * @param vertexPair the vertex pair to set */ public void setVertexPair(Pair vertexPair) { this.vertexPair = vertexPair; } } PreferentialAttachmentLinkPrediction.java000066400000000000000000000041241402514743400377230ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.Objects; import org.jgrapht.Graph; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; /** * Predict links using Preferential Attachment. * *

* This is a local method which computes $s_{xy} = k(u) \times k(v)$ where for a node $v$, * $\Gamma(v)$ denotes the set of neighbors of $v$ and $k(v) = |\Gamma(v)|$ denotes the degree of * $v$. *

* * See the following two papers: *
    *
  • Liben‐Nowell, David, and Jon Kleinberg. "The link‐prediction problem for social networks." * Journal of the American society for information science and technology 58.7 (2007): * 1019-1031.
  • *
  • Zhou, Tao, Linyuan Lü, and Yi-Cheng Zhang. "Predicting missing links via local information." * The European Physical Journal B 71.4 (2009): 623-630.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class PreferentialAttachmentLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public PreferentialAttachmentLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { int du = graph.outDegreeOf(u); int dv = graph.outDegreeOf(v); return du * dv; } } ResourceAllocationIndexLinkPrediction.java000066400000000000000000000052671402514743400400700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; import org.jgrapht.alg.util.Pair; /** * Predict links using the Resource Allocation Index. * *

* This is a local method which computes $s_{uv} = \sum_{z \in * \Gamma(u)\cap\Gamma(v))}\frac{1}{k(z)}$ where for a node $v$, $\Gamma(v)$ denotes the set of * neighbors of $v$ and $k(v) = |\Gamma(v)|$ denotes the degree of $v$. *

* * See the following two papers: *
    *
  • Liben‐Nowell, David, and Jon Kleinberg. "The link‐prediction problem for social networks." * Journal of the American society for information science and technology 58.7 (2007): * 1019-1031.
  • *
  • Zhou, Tao, Linyuan Lü, and Yi-Cheng Zhang. "Predicting missing links via local information." * The European Physical Journal B 71.4 (2009): 623-630.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class ResourceAllocationIndexLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public ResourceAllocationIndexLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { List gu = Graphs.successorListOf(graph, u); List gv = Graphs.successorListOf(graph, v); Set intersection = new HashSet<>(gu); intersection.retainAll(gv); double result = 0d; for (V z : intersection) { int dz = graph.outDegreeOf(z); if (dz == 0) { throw new LinkPredictionIndexNotWellDefinedException( "Index not well defined", Pair.of(u, v)); } result += 1d / dz; } return result; } } SaltonIndexLinkPrediction.java000066400000000000000000000052511402514743400355240ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; import org.jgrapht.alg.util.Pair; /** * Predict links using the Salton Index, also called the cosine similarity. * *

* This is a local method which computes $s_{xy} = \frac{|\Gamma(u)\cap\Gamma(v))|}{\sqrt{k(u) * \times k(v)}}$ where for a node $v$, $\Gamma(v)$ denotes the set of neighbors of $v$ and $k(v) = * |\Gamma(v)|$ denotes the degree of $v$. *

* * See the following two papers: *
    *
  • Liben‐Nowell, David, and Jon Kleinberg. "The link‐prediction problem for social networks." * Journal of the American society for information science and technology 58.7 (2007): * 1019-1031.
  • *
  • Zhou, Tao, Linyuan Lü, and Yi-Cheng Zhang. "Predicting missing links via local information." * The European Physical Journal B 71.4 (2009): 623-630.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class SaltonIndexLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public SaltonIndexLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { int du = graph.outDegreeOf(u); int dv = graph.outDegreeOf(v); if (du == 0 || dv == 0) { throw new LinkPredictionIndexNotWellDefinedException( "Query vertex with zero neighbors", Pair.of(u, v)); } List gu = Graphs.successorListOf(graph, u); List gv = Graphs.successorListOf(graph, v); Set intersection = new HashSet<>(gu); intersection.retainAll(gv); return (double) intersection.size() / Math.sqrt(du * dv); } } SørensenIndexLinkPrediction.java000066400000000000000000000051611402514743400364740ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.LinkPredictionAlgorithm; import org.jgrapht.alg.util.Pair; /** * Predict links using the Sørensen Index. * *

* This is a local method which computes $s_{xy} = \frac{2|\Gamma(u)\cap\Gamma(v))|}{k(u) +k(v)}$ * where for a node $v$, $\Gamma(v)$ denotes the set of neighbors of $v$ and $k(v) = |\Gamma(v)|$ * denotes the degree of $v$. *

* * See the following two papers: *
    *
  • Liben‐Nowell, David, and Jon Kleinberg. "The link‐prediction problem for social networks." * Journal of the American society for information science and technology 58.7 (2007): * 1019-1031.
  • *
  • Zhou, Tao, Linyuan Lü, and Yi-Cheng Zhang. "Predicting missing links via local information." * The European Physical Journal B 71.4 (2009): 623-630.
  • *
* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class SørensenIndexLinkPrediction implements LinkPredictionAlgorithm { private Graph graph; /** * Create a new prediction * * @param graph the input graph */ public SørensenIndexLinkPrediction(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public double predict(V u, V v) { int du = graph.outDegreeOf(u); int dv = graph.outDegreeOf(v); if (du + dv == 0) { throw new LinkPredictionIndexNotWellDefinedException( "Both vertices have zero neighbors", Pair.of(u, v)); } List gu = Graphs.successorListOf(graph, u); List gv = Graphs.successorListOf(graph, v); Set intersection = new HashSet<>(gu); intersection.retainAll(gv); return 2d * intersection.size() / (du + dv); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/linkprediction/package-info.java000066400000000000000000000001221402514743400330300ustar00rootroot00000000000000/** * Algorithms for link prediction */ package org.jgrapht.alg.linkprediction; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/000077500000000000000000000000001402514743400264225ustar00rootroot00000000000000DenseEdmondsMaximumCardinalityMatching.java000066400000000000000000000650271402514743400367650ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; import java.util.stream.*; /** * This implementation of Edmonds' blossom algorithm computes maximum cardinality matchings in * undirected graphs. A matching in a graph $G(V,E)$ is a subset of edges $M$ such that no two edges * in $M$ have a vertex in common. A matching has at most $\frac{1}{2|V|}$ edges. A node $v$ in $G$ * is matched by matching $M$ if $M$ contains an edge incident to $v$. A matching is perfect if all * nodes are matched. By definition, a perfect matching consists of exactly $\frac{1}{2|V|}$ edges. * This algorithm will return a perfect matching if one exists. If no perfect matching exists, then * the largest (non-perfect) matching is returned instead. This algorithm does NOT compute a maximum * weight matching. In the special case that the input graph is bipartite, consider using * {@link HopcroftKarpMaximumCardinalityBipartiteMatching} instead. *

* To compute a maximum cardinality matching, at most $n$ augmenting path computations are * performed. Each augmenting path computation takes $O(m \alpha(m,n))$ time, where $\alpha(m,n)$ is * an inverse of the Ackerman function, $n$ is the number of vertices, and $m$ the number of edges. * This results in a total runtime complexity of O(nm alpha(m,n)). In practice, the number of * augmenting path computations performed is far smaller than $n$, since an efficient heuristic is * used to compute a near-optimal initial solution. This implementation is highly efficient: a * maximum matching in a graph of 2000 vertices and 1.5 million edges is calculated in a few * milliseconds on a desktop computer.
* The runtime complexity of this implementation could be improved to $O(nm)$ when the UnionFind * data structure used in this implementation is replaced by the linear-time set union data * structure proposed in: Gabow, H.N., Tarjan, R.E. A linear-time algorithm for a special case of * disjoint set union. Proc. Fifteenth Annual ACM Symposium on Theory of Computing, 1982, pp. * 246-251. *

* Edmonds' original algorithm first appeared in Edmonds, J. Paths, trees, and flowers. Canadian * Journal of Mathematics 17, 1965, pp. 449-467, and had a runtime complexity of $O(n^4)$. This * implementation however follows more closely the description provided in Tarjan, R.E. Data * Structures and Network Algorithms. Society for Industrial and Applied Mathematics, 1983, chapter * 9. In addition, the following sources were used for the implementation: * *

*

* For future reference - A more efficient algorithm than the one implemented in this class exists: * Micali, S., Vazirani, V. An $O(\sqrt{n}m)$ algorithm for finding maximum matching in general * graphs. Proc. 21st Ann. Symp. on Foundations of Computer Science, IEEE, 1980, pp. 17–27. This is * the most efficient algorithm known for computing maximum cardinality matchings in general graphs. * More details on this algorithm can be found in: *

* * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class DenseEdmondsMaximumCardinalityMatching implements MatchingAlgorithm { /* The graph we are matching on. */ private final Graph graph; /* (Heuristic) matching algorithm used to compute an initial feasible solution */ private final MatchingAlgorithm initializer; /* Ordered list of vertices */ private List vertices; /* Mapping of a vertex to their unique position in the ordered list of vertices */ private Map vertexIndexMap; /* A matching for the input graph (can be an empty set of edges) */ private SimpleMatching matching; /* Number of matched vertices. */ private int matchedVertices; /* -----Algorithm data structures below---------- */ /** Storage of the forest, even and odd levels */ private Levels levels; /** Special 'NIL' vertex. */ private static final int NIL = -1; /** Queue of 'even' (exposed) vertices */ private FixedSizeIntegerQueue queue; /** Union-Find to store blossoms. */ private UnionFind uf; /** * For each odd vertex condensed into a blossom, a bridge is defined. Suppose the examination of * edge $[v,w]$ causes a blossom to form containing odd vertex $x$. We define bridge(x) to be * $[v,w]$ if $x$ is an ancestor of $v$ before the blossom is formed, or $[w,v]$ if $x$ is an * ancestor of $w$. */ private final Map> bridges = new HashMap<>(); /** Pre-allocated array which stores augmenting paths. */ private int[] path; /* Pre-allocated bit sets to track paths in the trees. */ private BitSet vAncestors, wAncestors; /** * Constructs a new instance of the algorithm. {@link GreedyMaximumCardinalityMatching} is used * to quickly generate a near optimal initial solution. * * @param graph undirected graph (graph does not have to be simple) */ public DenseEdmondsMaximumCardinalityMatching(Graph graph) { this(graph, new GreedyMaximumCardinalityMatching<>(graph, false)); } /** * Constructs a new instance of the algorithm. * * @param graph undirected graph (graph does not have to be simple) * @param initializer heuristic matching algorithm used to quickly generate a (near optimal) * initial feasible solution. */ public DenseEdmondsMaximumCardinalityMatching( Graph graph, MatchingAlgorithm initializer) { this.graph = GraphTests.requireUndirected(graph); this.initializer = initializer; } /** * Prepares the data structures */ private void init() { vertices = new ArrayList<>(); vertices.addAll(graph.vertexSet()); vertexIndexMap = new HashMap<>(); for (int i = 0; i < vertices.size(); i++) vertexIndexMap.put(vertices.get(i), i); this.matching = new SimpleMatching(vertices.size()); this.matchedVertices = 0; this.levels = new Levels(vertices.size()); this.queue = new FixedSizeIntegerQueue(vertices.size()); this.uf = new UnionFind<>(new HashSet<>(vertexIndexMap.values())); // temp storage of paths in the algorithm path = new int[vertices.size()]; vAncestors = new BitSet(vertices.size()); wAncestors = new BitSet(vertices.size()); } /** * Calculates an initial feasible matching. * * @param initializer algorithm used to compute the initial matching */ private void warmStart(MatchingAlgorithm initializer) { Matching initialSolution = initializer.getMatching(); for (E e : initialSolution.getEdges()) { V u = graph.getEdgeSource(e); V v = graph.getEdgeTarget(e); this.matching.match(vertexIndexMap.get(u), vertexIndexMap.get(v)); } matchedVertices = initialSolution.getEdges().size() * 2; } /** * Search for an augmenting path. * * @return true if an augmenting path was found, false otherwise */ private boolean augment() { // reset data structures levels.reset(); uf.reset(); bridges.clear(); queue.clear(); Deque exposed = new ArrayDeque<>(matching.getExposed()); while (!exposed.isEmpty()) { int root = exposed.pop(); levels.setEven(root, root); queue.enqueue(root); // for each exposed vertex, start a bfs search while (!queue.isEmpty()) { int v = queue.poll(); // Even vertex for (V wOrig : Graphs.neighborListOf(graph, vertices.get(v))) { int w = vertexIndexMap.get(wOrig); // vertex w is even: we may have encountered a blossom. if (levels.isEven(uf.find(w))) { // w is an even vertex // if v and w belong to the same blossom, the edge has been shrunken away // and we can ignore it. if not, we found a new blossom. We do not need to // check whether v and w belong to the same tree since each tree is fully // grown before we continue growing a new tree. Consequently, vertex w // can only belong to the same tree as v. if (!uf.inSameSet(v, w)) blossom(v, w); // Create a new blossom using bridge edge (v,w) } // vertex w is either odd or unreached. If it is unreached, we have found an // augmenting path. If it is odd, we can grow the tree. else if (levels.isOddOrUnreached(w)) { // w is odd or unreached if (matching.isExposed(w)) { // w is unreached: we found an augmenting path augment(v); augment(w); matching.match(v, w); return true; } // w is an odd vertex: grow the tree levels.setOdd(w, v); int u = matching.opposite(w); // even vertex levels.setEven(u, w); queue.enqueue(u); // continue growing the tree from u } } } } // no augmenting paths, matching is maximum return false; } /** * Creates a new blossom using bridge $(v,w)$. The blossom is an odd cycle. Nodes $v$ and $w$ * are both even vertices. * * @param v endpoint of the bridge * @param w another endpoint the bridge */ private void blossom(int v, int w) { // Compute the base of the blossom. Let p_1, p_2 be the paths from the root of the tree to v // resp. w. The base vertex is the last vertex p_1 and p_2 have in common. In a blossom, the // base vertex is unique in the sense that it is the only vertex incident to 2 unmatched // edges. int base = nearestCommonAncestor(v, w); // Compute resp the left side (v to base) and right side (w to base) of the blossom. blossomSupports(v, w, base); blossomSupports(w, v, base); // To complete the blossom, combine the left and the right sides. uf.union(v, base); uf.union(w, base); // Blossoms are efficiently stored in a UnionFind data structure uf. Ideally, uf.find(x) for // some vertex x returns the base u of the blossom containing x. However, when uf uses rank // compression, it cannot be guaranteed that the vertex returned is indeed the base of the // blossom. In fact, it can be any vertex of the blossom containing x. We therefore have to // ensure that the predecessor of the blossom's representative is the predecessor of the // actual base vertex. levels.setEven(uf.find(base), levels.getEven(base)); } /** * This method creates one side of the blossom: the path from vertex $v$ to the base of the * blossom. The vertices encountered on this path are grouped together (union). The odd vertices * are added to the processing queue (odd vertices in a blossom become even) and a pointer to * the bridge $(v,w)$ is stored for each odd vertex. Notice the orientation of the bridge: the * first vertex of the bridge returned by bridge.get(x) is always on the same side of the * blossom as $x$. * * @param v an endpoint of the blossom bridge * @param w another endpoint of the blossom bridge * @param base the base of the blossom */ private void blossomSupports(int v, int w, int base) { Pair bridge = new Pair<>(v, w); v = uf.find(v); int u = v; while (v != base) { uf.union(v, u); u = levels.getEven(v); // odd vertex this.bridges.put(u, bridge); queue.enqueue(u); uf.union(v, u); v = uf.find(levels.getOdd(u)); // even vertex } } /** * Computes the base of the blossom formed by bridge edge $(v,w)$. The base vertex is the * nearest common ancestor of $v$ and $w$. * * @param v one side of the bridge * @param w other side of the bridge * @return base of the blossom */ private int nearestCommonAncestor(int v, int w) { vAncestors.clear(); vAncestors.set(uf.find(v)); wAncestors.clear(); wAncestors.set(uf.find(w)); // Walk back from $v$ and $w$ in the direction of the root of the tree, until their paths // intersect. while (true) { v = parent(v); vAncestors.set(v); w = parent(w); wAncestors.set(w); // vertex v is an ancestor of w, so v much be the base of the blossom if (wAncestors.get(v)) { return v; } // vertex w is an ancestor of v, so w much be the base of the blossom else if (vAncestors.get(w)) { return w; } } } /** * Compute the nearest even ancestor of even node $v$. If $v$ is the root of a tree, then this * method returns $v$ itself. * * @param v even vertex * @return the nearest even ancestor of $v$ */ private int parent(int v) { v = uf.find(v); // even vertex int parent = uf.find(levels.getEven(v)); // odd vertex, or v if v is the root of its tree if (parent == v) return v; // root of tree return uf.find(levels.getOdd(parent)); } /** * Construct a path from vertex $v$ to the root of its tree, and use the resulting path to * augment the matching. * * @param v starting vertex (leaf in the tree) */ private void augment(int v) { int n = buildPath(path, 0, v, NIL); for (int i = 2; i < n; i += 2) { matching.match(path[i], path[i - 1]); } } /** * Builds the path backwards from the specified start vertex to the end vertex. If the path * reaches a blossom then the path through the blossom is lifted to the original graph. * * @param path path storage * @param i offset (in path) * @param start start vertex * @param end end vertex * @return the total length of the path. */ private int buildPath(int[] path, int i, int start, int end) { while (true) { // Lift the path through the blossom. The buildPath method always starts from an even // vertex. Vertices which were originally odd become even // when they are contracted into a blossom. If we start constructing the path from such // an odd vertex, we must 'lift' the path through the blossom. // To lift the path through the blossom, we have to walk from odd node u in the // direction of the bridge, cross the bridge, and then // continue in the direction of the tree root. while (levels.isOdd(start)) { Pair bridge = bridges.get(start); // From the start vertex u, walk in the direction of the bridge (v,w). The first // edge encountered // on the path from u to v is always a matched edge. Notice that the path from u to // v leads away from the root of the tree. Since we only store // pointers in the direction of the root, we have to compute a path from v to u, and // reverse the resulting path. int j = buildPath(path, i, bridge.getFirst(), start); reverse(path, i, j - 1); i = j; // walk from the other side of the bridge up in the direction of the root. start = bridge.getSecond(); } path[i++] = start; // even vertex // root of the tree if (matching.isExposed(start)) return i; path[i++] = matching.opposite(start); // odd vertex // base case if (path[i - 1] == end) return i; start = levels.getOdd(path[i - 1]); // even vertex } } /** * Returns a matching of maximum cardinality. Each time this method is invoked, the matching is * computed from scratch. Consequently, it is possible to make changes to the graph and to * re-invoke this method on the altered graph. * * @return a matching of maximum cardinality. */ @Override public Matching getMatching() { this.init(); if (initializer != null) this.warmStart(initializer); // Continuously augment the matching until augmentation is no longer possible. while (matchedVertices < graph.vertexSet().size() - 1 && augment()) { matchedVertices += 2; } Set edges = new LinkedHashSet<>(); double cost = 0; for (int vx = 0; vx < vertices.size(); vx++) { if (matching.isExposed(vx)) continue; V v = vertices.get(vx); V w = vertices.get(matching.opposite(vx)); E edge = graph.getEdge(v, w); edges.add(edge); cost += 0.5 * graph.getEdgeWeight(edge); } return new MatchingImpl<>(graph, edges, cost); } /** * Checks whether the given matching is of maximum cardinality. A matching $m$ is maximum if * there does not exist a different matching $m'$ in the graph which is of larger cardinality. * This method is solely intended for verification purposes. Any matching returned by the * {@link #getMatching()} method in this class is guaranteed to be maximum. *

* To attest whether the matching is maximum, we use the Tutte-Berge Formula which provides a * tight bound on the cardinality of the matching. The Tutte-Berge Formula states: $m(G) = * \frac{1}{2} \min_{X \subseteq V} ( |X| - c_{\text{odd}}(G - X) + |V|), where $m(G)$ is the * size of the matching, $X$ a subset of vertices, $G-X$ the induced graph on vertex set $V(G) * \setminus X$, and $c_{\text{odd}}(G)$ the number of connected components of odd cardinality * in graph $G$.
* Note: to compute this bound, we do not iterate over all possible subsets $X$ (this would be * too expensive). Instead, $X$ is computed as a by-product of Edmonds' algorithm. Consequently, * the runtime of this method equals the time required to test for the existence of a single * augmenting path.
* This method does NOT check whether the matching is valid. * * @param matching matching * @return true if the matching is maximum, false otherwise. */ public boolean isMaximumMatching(Matching matching) { // The matching is maximum if it is perfect, or if it leaves only one node exposed in a // graph with an odd number of vertices if (matching.getEdges().size() * 2 >= graph.vertexSet().size() - 1) return true; this.init(); // Reset data structures and use the provided matching as a starting point for (E e : matching.getEdges()) { V u = graph.getEdgeSource(e); V v = graph.getEdgeTarget(e); Integer ux = vertexIndexMap.get(u); Integer vx = vertexIndexMap.get(v); this.matching.match(ux, vx); } // Search for an augmenting path. If one is found, then clearly the matching is not maximum if (augment()) return false; // A side effect of the Edmonds Blossom-Shrinking algorithm is that it computes what is // known as the // Edmonds-Gallai decomposition of a graph: it decomposes the graph into three disjoint sets // of vertices: odd, even, or free. // Let D(G) be the set of vertices such that for each v in D(G) there exists a maximum // matching missing v. Let A(G) be the set of vertices such that each v in A(G) // is a neighbor of D(G), but is not contained in D(G) itself. The set A(G) attains the // minimum in the Tutte-Berge Formula. It can be shown that // A(G)= {vertices labeled odd in the Edmonds Blossomg-Shrinking algorithm}. Note: we only // take odd vertices that are not consumed by blossoms (every blossom is even). Set oddVertices = vertexIndexMap .values().stream().filter(vx -> levels.isOdd(vx) && !bridges.containsKey(vx)) .map(vertices::get).collect(Collectors.toSet()); Set otherVertices = graph .vertexSet().stream().filter(v -> !oddVertices.contains(v)).collect(Collectors.toSet()); Graph subgraph = new AsSubgraph<>(graph, otherVertices, null); // Induced subgraph // defined on all // vertices which are // not odd. List> connectedComponents = new ConnectivityInspector<>(subgraph).connectedSets(); long nrOddCardinalityComponents = connectedComponents.stream().filter(s -> s.size() % 2 == 1).count(); return matching .getEdges() .size() == (graph.vertexSet().size() + oddVertices.size() - nrOddCardinalityComponents) / 2.0; } /** * Storage of the forest, even and odd levels. * * We explicitly maintain a dirty mark in order to be able to cleanup only the values that we * have changed. This is important when the graph is sparse to avoid performing an $O(n)$ * operation per augmentation. */ private static class Levels { private int[] even, odd; private List dirty; public Levels(int n) { this.even = new int[n]; this.odd = new int[n]; this.dirty = new ArrayList<>(); Arrays.fill(even, NIL); Arrays.fill(odd, NIL); } public int getEven(int v) { return even[v]; } public void setEven(int v, int value) { even[v] = value; if (value != NIL) { dirty.add(v); } } public int getOdd(int v) { return odd[v]; } public void setOdd(int v, int value) { odd[v] = value; if (value != NIL) { dirty.add(v); } } public boolean isEven(int v) { return even[v] != NIL; } public boolean isOddOrUnreached(int v) { return odd[v] == NIL; } public boolean isOdd(int v) { return odd[v] != NIL; } public void reset() { for (int v : dirty) { even[v] = NIL; odd[v] = NIL; } dirty.clear(); } } /** * Simple representation of a matching */ private static class SimpleMatching { private static final int UNMATCHED = -1; private final int[] match; private Set exposed; private SimpleMatching(int n) { this.match = new int[n]; this.exposed = CollectionUtil.newHashSetWithExpectedSize(n); Arrays.fill(match, UNMATCHED); IntStream.range(0, n).forEach(exposed::add); } /** * Test whether a vertex is matched (i.e. incident to a matched edge). */ boolean isMatched(int v) { return match[v] != UNMATCHED; } /** * Test whether a vertex is exposed (i.e. not incident to a matched edge). */ boolean isExposed(int v) { return match[v] == UNMATCHED; } /** * For a given vertex v and matched edge (v,w), this function returns vertex w. */ int opposite(int v) { assert isMatched(v); return match[v]; } /** * Add the edge $(u,v)$ to the matched edge set. */ void match(int u, int v) { match[u] = v; match[v] = u; exposed.remove(u); exposed.remove(v); } Set getExposed() { return exposed; } } /** Utility function to reverse part of an array */ private void reverse(int[] path, int i, int j) { while (i < j) { int tmp = path[i]; path[i] = path[j]; path[j] = tmp; i++; j--; } } } GreedyMaximumCardinalityMatching.java000066400000000000000000000104551402514743400356270ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * The greedy algorithm for computing a maximum cardinality matching. The algorithm can run in two * modes: sorted or unsorted. When unsorted, the matching is obtained by iterating through the edges * and adding an edge if it doesn't conflict with the edges already in the matching. When sorted, * the edges are first sorted by the sum of degrees of their endpoints. After that, the algorithm * proceeds in the same manner. Running this algorithm in sorted mode can sometimes produce better * results, albeit at the cost of some additional computational overhead. *

* Independent of the mode, the resulting matching is maximal, and is therefore guaranteed to * contain at least half of the edges that a maximum cardinality matching has ($\frac{1}{2}$ * approximation). Runtime complexity: $O(m)$ when the edges are not sorted, $O(m + m \log n)$ * otherwise, where $n$ is the number of vertices, and $m$ the number of edges. * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class GreedyMaximumCardinalityMatching implements MatchingAlgorithm { private final Graph graph; private final boolean sort; /** * Creates a new GreedyMaximumCardinalityMatching instance. * * @param graph graph * @param sort sort the edges prior to starting the greedy algorithm */ public GreedyMaximumCardinalityMatching(Graph graph, boolean sort) { this.graph = GraphTests.requireUndirected(graph); this.sort = sort; } /** * Get a matching that is a $\frac{1}{2}$-approximation of the maximum cardinality matching. * * @return a matching */ @Override public Matching getMatching() { Set matched = new HashSet<>(); Set edges = new LinkedHashSet<>(); double cost = 0; if (sort) { // sort edges in increasing order of the total degree of their endpoints List allEdges = new ArrayList<>(graph.edgeSet()); allEdges.sort(new EdgeDegreeComparator()); for (E e : allEdges) { V v = graph.getEdgeSource(e); V w = graph.getEdgeTarget(e); if (!v.equals(w) && !matched.contains(v) && !matched.contains(w)) { edges.add(e); matched.add(v); matched.add(w); cost += graph.getEdgeWeight(e); } } } else { for (V v : graph.vertexSet()) { if (matched.contains(v)) continue; for (E e : graph.edgesOf(v)) { V w = Graphs.getOppositeVertex(graph, e, v); if (!v.equals(w) && !matched.contains(w)) { edges.add(e); matched.add(v); matched.add(w); cost += graph.getEdgeWeight(e); break; } } } } return new MatchingImpl<>(graph, edges, cost); } private class EdgeDegreeComparator implements Comparator { @Override public int compare(E e1, E e2) { int degreeE1 = graph.degreeOf(graph.getEdgeSource(e1)) + graph.degreeOf(graph.getEdgeTarget(e1)); int degreeE2 = graph.degreeOf(graph.getEdgeSource(e2)) + graph.degreeOf(graph.getEdgeTarget(e2)); return Integer.compare(degreeE1, degreeE2); } } } GreedyWeightedMatching.java000066400000000000000000000146361402514743400335730ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; /** * The greedy algorithm for computing a maximum weight matching in an arbitrary graph. The algorithm * runs in $O(m + m \log n)$ where $n$ is the number of vertices and $m$ is the number of edges of * the graph. This implementation accepts directed and undirected graphs which may contain * self-loops and multiple (parallel) edges. There is no assumption on the edge weights, i.e. they * can also be negative or zero. * *

* This algorithm can be run in two modes: with and without edge cost normalization. Without * normalization, the algorithm first orders the edge set in non-increasing order of weights and * then greedily constructs a maximal cardinality matching out of the edges with positive weight. A * maximal cardinality matching (not to be confused with maximum cardinality) is a matching that * cannot be increased in cardinality without removing an edge first. The resulting matching is * guaranteed to be a $\frac{1}{2}$-Approximation.
* With normalization, the edges are sorted in non-increasing order of their normalized costs * $\frac{c(u,v)}{d(u)+d(v)}$ instead, after which the algorithm proceeds in the same manner. Here, * $c(u,v)$ is the cost of edge $(u,v)$, and $d(u)$ resp $d(v)$ are the degrees of vertices $u$ resp * $v$. Running this algorithm in normalized mode often (but not always!) produces a better result * than running the algorithm without normalization. Note however that the normalized version * does NOT produce a $\frac{1}{2}$-approximation. See this * proof for details. The runtime complexity remains the same, independent of whether * normalization is used. * *

* For more information about approximation algorithms for the maximum weight matching problem in * arbitrary graphs see: *

    *
  • R. Preis, Linear Time $\frac{1}{2}$-Approximation Algorithm for Maximum Weighted Matching in * General Graphs. Symposium on Theoretical Aspects of Computer Science, 259-269, 1999.
  • *
  • D.E. Drake, S. Hougardy, A Simple Approximation Algorithm for the Weighted Matching Problem, * Information Processing Letters 85, 211-213, 2003.
  • *
* * @see PathGrowingWeightedMatching * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class GreedyWeightedMatching implements MatchingAlgorithm { private final Graph graph; private final Comparator comparator; private final boolean normalizeEdgeCosts; /** * Create and execute a new instance of the greedy maximum weight matching algorithm. Floating * point values are compared using {@link #DEFAULT_EPSILON} tolerance. * * @param graph the input graph * @param normalizeEdgeCosts boolean indicating whether edge normalization has to be used. */ public GreedyWeightedMatching(Graph graph, boolean normalizeEdgeCosts) { this(graph, normalizeEdgeCosts, DEFAULT_EPSILON); } /** * Create and execute a new instance of the greedy maximum weight matching algorithm. * * @param graph the input graph * @param normalizeEdgeCosts boolean indicating whether edge normalization has to be used. * @param epsilon tolerance when comparing floating point values */ public GreedyWeightedMatching(Graph graph, boolean normalizeEdgeCosts, double epsilon) { if (graph == null) { throw new IllegalArgumentException("Input graph cannot be null"); } this.graph = graph; this.comparator = new ToleranceDoubleComparator(epsilon); this.normalizeEdgeCosts = normalizeEdgeCosts; } /** * Get a matching that is a $\frac{1}{2}$-approximation of the maximum weighted matching. * * @return a matching */ @Override public Matching getMatching() { // sort edges in non-decreasing order of weight // (the lambda uses e1 and e2 in the reverse order on purpose) List allEdges = new ArrayList<>(graph.edgeSet()); if (normalizeEdgeCosts) { allEdges.sort((e1, e2) -> { double degreeE1 = graph.degreeOf(graph.getEdgeSource(e1)) + graph.degreeOf(graph.getEdgeTarget(e1)); double degreeE2 = graph.degreeOf(graph.getEdgeSource(e2)) + graph.degreeOf(graph.getEdgeTarget(e2)); return comparator .compare( graph.getEdgeWeight(e2) / degreeE2, graph.getEdgeWeight(e1) / degreeE1); }); } else { allEdges .sort( (e1, e2) -> comparator .compare(graph.getEdgeWeight(e2), graph.getEdgeWeight(e1))); } double matchingWeight = 0d; Set matching = new HashSet<>(); Set matchedVertices = new HashSet<>(); // find maximal matching for (E e : allEdges) { double edgeWeight = graph.getEdgeWeight(e); V s = graph.getEdgeSource(e); V t = graph.getEdgeTarget(e); if (!s.equals(t) && comparator.compare(edgeWeight, 0d) > 0) { if (!matchedVertices.contains(s) && !matchedVertices.contains(t)) { matching.add(e); matchedVertices.add(s); matchedVertices.add(t); matchingWeight += edgeWeight; } } } // return matching return new MatchingImpl<>(graph, matching, matchingWeight); } } HopcroftKarpMaximumCardinalityBipartiteMatching.java000066400000000000000000000204061402514743400406530ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; /** * Implementation of the well-known Hopcroft Karp algorithm to compute a matching of maximum * cardinality in a bipartite graph. The algorithm runs in $O(|E| \cdot \sqrt{|V|})$ time. This * implementation accepts undirected graphs which may contain self-loops and multiple edges. To * compute a maximum cardinality matching in general (non-bipartite) graphs, use * {@link SparseEdmondsMaximumCardinalityMatching} instead. * *

* The Hopcroft Karp matching algorithm computes augmenting paths of increasing length, until no * augmenting path exists in the graph. At each iteration, the algorithm runs a Breadth First Search * from the exposed (free) vertices, until an augmenting path is found. Next, a Depth First Search * is performed to find all (vertex disjoint) augmenting paths of the same length. The matching is * augmented along all discovered augmenting paths simultaneously. * *

* The original algorithm is described in: Hopcroft, John E.; Karp, Richard M. (1973), "An n5/2 * algorithm for maximum matchings in bipartite graphs", SIAM Journal on Computing 2 (4): 225–231, * doi:10.1137/0202019 A coarse overview of the algorithm is given in: http://en.wikipedia.org/wiki/Hopcroft-Karp_algorithm * * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class HopcroftKarpMaximumCardinalityBipartiteMatching implements MatchingAlgorithm { private final Graph graph; private final Set partition1; private final Set partition2; /* Ordered list of vertices */ private List vertices; /* Mapping of a vertex to their unique position in the ordered list of vertices */ private Map vertexIndexMap; /* Number of matched vertices i partition 1. */ private int matchedVertices; /* Dummy vertex. All vertices are initially matched against this dummy vertex */ private final int DUMMY = 0; /* Infinity */ private final int INF = Integer.MAX_VALUE; /* Array keeping track of the matching. */ private int[] matching; /* Distance array. Used to compute shoretest augmenting paths */ private int[] dist; /* queue used for breadth first search */ private FixedSizeIntegerQueue queue; /** * Constructs a new instance of the Hopcroft Karp bipartite matching algorithm. The input graph * must be bipartite. For efficiency reasons, this class does not check whether the input graph * is bipartite. Invoking this class on a non-bipartite graph results in undefined behavior. To * test whether a graph is bipartite, use {@link GraphTests#isBipartite(Graph)}. * * @param graph bipartite graph * @param partition1 the first partition of vertices in the bipartite graph * @param partition2 the second partition of vertices in the bipartite graph */ public HopcroftKarpMaximumCardinalityBipartiteMatching( Graph graph, Set partition1, Set partition2) { this.graph = GraphTests.requireUndirected(graph); // Ensure that partition1 is smaller or equal in size compared to partition 2 if (partition1.size() <= partition2.size()) { this.partition1 = partition1; this.partition2 = partition2; } else { // else, swap this.partition1 = partition2; this.partition2 = partition1; } } /** * Initialize data structures */ private void init() { vertices = new ArrayList<>(); vertices.add(null); vertices.addAll(partition1); vertices.addAll(partition2); vertexIndexMap = new HashMap<>(); for (int i = 0; i < vertices.size(); i++) vertexIndexMap.put(vertices.get(i), i); matching = new int[vertices.size() + 1]; dist = new int[partition1.size() + 1]; queue = new FixedSizeIntegerQueue(vertices.size()); } /** * Greedily compute an initial feasible matching */ private void warmStart() { for (V uOrig : partition1) { int u = vertexIndexMap.get(uOrig); for (V vOrig : Graphs.neighborListOf(graph, uOrig)) { int v = vertexIndexMap.get(vOrig); if (matching[v] == DUMMY) { matching[v] = u; matching[u] = v; matchedVertices++; break; } } } } /** * BFS function which finds the shortest augmenting path. The length of the shortest augmenting * path is stored in dist[DUMMY]. * * @return true if an augmenting path was found, false otherwise */ private boolean bfs() { queue.clear(); for (int u = 1; u <= partition1.size(); u++) if (matching[u] == DUMMY) { // Add all unmatched vertices to the queue and set their // distance to 0 dist[u] = 0; queue.enqueue(u); } else // Set distance of all matched vertices to INF dist[u] = INF; dist[DUMMY] = INF; while (!queue.isEmpty()) { int u = queue.poll(); if (dist[u] < dist[DUMMY]) for (V vOrig : Graphs.neighborListOf(graph, vertices.get(u))) { int v = vertexIndexMap.get(vOrig); if (dist[matching[v]] == INF) { dist[matching[v]] = dist[u] + 1; queue.enqueue(matching[v]); } } } return dist[DUMMY] != INF; // Return true if an augmenting path is found } /** * Find all vertex disjoint augmenting paths of length dist[DUMMY]. To find paths of dist[DUMMY] * length, we simply follow nodes that are 1 distance increments away from each other. * * @param u vertex from which the DFS is started * @return true if an augmenting path from vertex u was found, false otherwise */ private boolean dfs(int u) { if (u != DUMMY) { for (V vOrig : Graphs.neighborListOf(graph, vertices.get(u))) { int v = vertexIndexMap.get(vOrig); if (dist[matching[v]] == dist[u] + 1) if (dfs(matching[v])) { matching[v] = u; matching[u] = v; return true; } } // No augmenting path has been found. Set distance of u to INF to ensure that u isn't // visited again. dist[u] = INF; return false; } return true; } @Override public Matching getMatching() { this.init(); this.warmStart(); while (matchedVertices < partition1.size() && bfs()) { // Greedily search for vertex disjoint augmenting paths for (int v = 1; v <= partition1.size() && matchedVertices < partition1.size(); v++) if (matching[v] == DUMMY) // v is unmatched if (dfs(v)) matchedVertices++; } assert matchedVertices <= partition1.size(); Set edges = new HashSet<>(); for (int i = 0; i < vertices.size(); i++) { if (matching[i] != DUMMY) { edges.add(graph.getEdge(vertices.get(i), vertices.get(matching[i]))); } } return new MatchingImpl<>(graph, edges, edges.size()); } } KuhnMunkresMinimalWeightBipartitePerfectMatching.java000066400000000000000000000516531402514743400410010ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/* * (C) Copyright 2013-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Kuhn-Munkres algorithm (named in honor of Harold Kuhn and James Munkres) solving assignment * problem also known as hungarian * algorithm (in the honor of hungarian mathematicians Dénes K?nig and Jen? Egerváry). It's * running time $O(V^3)$. * *

* Assignment problem could be set as follows: * *

* Given complete bipartite graph * $G = (S, T; E)$, such that $|S| = |T|$, and each edge has non-negative cost c(i, * j), find perfect matching of minimal cost. *

* * @param the graph vertex type * @param the graph edge type * * @author Alexey Kudinkin */ public class KuhnMunkresMinimalWeightBipartitePerfectMatching implements MatchingAlgorithm { private final Graph graph; private Set partition1; private Set partition2; /** * Construct a new instance of the algorithm. * * @param graph the input graph * @param partition1 the first partition of the vertex set * @param partition2 the second partition of the vertex set */ public KuhnMunkresMinimalWeightBipartitePerfectMatching( Graph graph, Set partition1, Set partition2) { if (graph == null) { throw new IllegalArgumentException("Input graph cannot be null"); } this.graph = graph; if (partition1 == null) { throw new IllegalArgumentException("Partition 1 cannot be null"); } this.partition1 = partition1; if (partition2 == null) { throw new IllegalArgumentException("Partition 2 cannot be null"); } this.partition2 = partition2; } /** * {@inheritDoc} */ @Override public Matching getMatching() { // Validate graph being complete bipartite with equally-sized partitions if (partition1.size() != partition2.size()) { throw new IllegalArgumentException( "Graph supplied isn't complete bipartite with equally sized partitions!"); } if (!GraphTests.isBipartitePartition(graph, partition1, partition2)) { throw new IllegalArgumentException("Invalid bipartite partition provided"); } int partition = partition1.size(); int edges = graph.edgeSet().size(); if (edges != (partition * partition)) { throw new IllegalArgumentException( "Graph supplied isn't complete bipartite with equally sized partitions!"); } if (!GraphTests.isSimple(graph)) { throw new IllegalArgumentException("Only simple graphs supported"); } List firstPartition = new ArrayList<>(partition1); List secondPartition = new ArrayList<>(partition2); // Expected behavior for an empty graph is to return an empty set, so // we check this last int[] matching; if (graph.vertexSet().isEmpty()) { matching = new int[] {}; } else { matching = new KuhnMunkresMatrixImplementation<>(graph, firstPartition, secondPartition) .buildMatching(); } Set edgeSet = new HashSet<>(); double weight = 0d; for (int i = 0; i < matching.length; ++i) { E e = graph.getEdge(firstPartition.get(i), secondPartition.get(matching[i])); weight += graph.getEdgeWeight(e); edgeSet.add(e); } return new MatchingImpl<>(graph, edgeSet, weight); } /** * The actual implementation. */ static class KuhnMunkresMatrixImplementation { /** * Cost matrix */ private double[][] costMatrix; /** * Excess matrix */ private double[][] excessMatrix; /** * Rows coverage bit-mask */ boolean[] rowsCovered; /** * Columns coverage bit-mask */ boolean[] columnsCovered; /** * ``columnMatched[i]'' is the column # of the ZERO matched at the $i$-th row */ private int[] columnMatched; /** * ``rowMatched[j]'' is the row # of the ZERO matched at the $j$-th column */ private int[] rowMatched; /** * Construct new instance * * @param G the input graph * @param S first partition of the vertex set * @param T second partition of the vertex set */ public KuhnMunkresMatrixImplementation( final Graph G, final List S, final List T) { int partition = S.size(); // Build an excess-matrix corresponding to the supplied weighted // complete bipartite graph costMatrix = new double[partition][]; for (int i = 0; i < S.size(); ++i) { V source = S.get(i); costMatrix[i] = new double[partition]; for (int j = 0; j < T.size(); ++j) { V target = T.get(j); if (source.equals(target)) { continue; } costMatrix[i][j] = G.getEdgeWeight(G.getEdge(source, target)); } } } /** * Gets costs-matrix as input and returns assignment of tasks (designated by the columns of * cost-matrix) to the workers (designated by the rows of the cost-matrix) so that to * MINIMIZE total tasks-tackling costs * * @return assignment of tasks */ protected int[] buildMatching() { int height = costMatrix.length, width = costMatrix[0].length; // Make an excess-matrix excessMatrix = makeExcessMatrix(); // Build row/column coverage rowsCovered = new boolean[height]; columnsCovered = new boolean[width]; // Cached values of zeroes' indices in particular columns/rows columnMatched = new int[height]; rowMatched = new int[width]; Arrays.fill(columnMatched, -1); Arrays.fill(rowMatched, -1); // Find perfect matching corresponding to the optimal assignment while (buildMaximalMatching() < width) { buildVertexCoverage(); extendEqualityGraph(); } // Almost done! return Arrays.copyOf(columnMatched, height); } ///////////////////////////////////////////////////////////////////////////////////////////////// /** * Composes excess-matrix corresponding to the given cost-matrix */ double[][] makeExcessMatrix() { double[][] excessMatrix = new double[costMatrix.length][]; for (int i = 0; i < excessMatrix.length; ++i) { excessMatrix[i] = Arrays.copyOf(costMatrix[i], costMatrix[i].length); } // Subtract minimal costs across the rows for (int i = 0; i < excessMatrix.length; ++i) { double cheapestTaskCost = Double.MAX_VALUE; for (int j = 0; j < excessMatrix[i].length; ++j) { if (cheapestTaskCost > excessMatrix[i][j]) { cheapestTaskCost = excessMatrix[i][j]; } } for (int j = 0; j < excessMatrix[i].length; ++j) { excessMatrix[i][j] -= cheapestTaskCost; } } // Subtract minimal costs across the columns // // NOTE: Makes nothing if there is any worker that can more // (cost-)effectively tackle this task than any other, i.e. there // is any row having zero in this column. However, if there is no // one, reduce the cost-demands of each worker to the size of the // min-cost (we can easily do this, since we have particular // interest of the relative values only), i.e. subtract the value // of the minimum cost-demands to highlight (with zero) the // worker that can tackle this task demanding lowest reward. for (int j = 0; j < excessMatrix[0].length; ++j) { double cheapestWorkerCost = Double.MAX_VALUE; for (int i = 0; i < excessMatrix.length; ++i) { if (cheapestWorkerCost > excessMatrix[i][j]) { cheapestWorkerCost = excessMatrix[i][j]; } } for (int i = 0; i < excessMatrix.length; ++i) { excessMatrix[i][j] -= cheapestWorkerCost; } } return excessMatrix; } /** * Builds maximal matching corresponding to the given excess-matrix * * @return size of a maximal matching built */ int buildMaximalMatching() { // Match all zeroes non-staying in the same column/row int matchingSizeLowerBound = 0; for (int i = 0; i < columnMatched.length; ++i) { if (columnMatched[i] != -1) { ++matchingSizeLowerBound; } } // Compose first-approximation by matching zeroes in a greed fashion for (int j = 0; j < excessMatrix[0].length; ++j) { if (rowMatched[j] == -1) { for (int i = 0; i < excessMatrix.length; ++i) { if ((excessMatrix[i][j] == 0) && (columnMatched[i] == -1)) { ++matchingSizeLowerBound; columnMatched[i] = j; rowMatched[j] = i; break; } } } } // Check whether perfect matching is found if (matchingSizeLowerBound == excessMatrix[0].length) { return matchingSizeLowerBound; } // // As to E. Burge: Matching is maximal iff bipartite graph doesn't // contain any matching-augmenting paths. // // Try to find any match-augmenting path boolean[] rowsVisited = new boolean[excessMatrix.length]; boolean[] colsVisited = new boolean[excessMatrix[0].length]; int matchingSize = 0; boolean extending = true; while (extending && (matchingSize < excessMatrix.length)) { Arrays.fill(rowsVisited, false); Arrays.fill(colsVisited, false); extending = false; for (int j = 0; j < excessMatrix.length; ++j) { if ((rowMatched[j] == -1) && !colsVisited[j]) { extending |= new MatchExtender(rowsVisited, colsVisited) .extend(j); /* Try to extend matching */ } } matchingSize = 0; for (int j = 0; j < rowMatched.length; ++j) { if (rowMatched[j] != -1) { ++matchingSize; } } } return matchingSize; } /** * Builds vertex-cover given built up matching */ void buildVertexCoverage() { Arrays.fill(columnsCovered, false); Arrays.fill(rowsCovered, false); boolean[] invertible = new boolean[rowsCovered.length]; for (int i = 0; i < excessMatrix.length; ++i) { if (columnMatched[i] != -1) { invertible[i] = true; continue; } for (int j = 0; j < excessMatrix[i].length; ++j) { if (Double.compare(excessMatrix[i][j], 0.) == 0) { rowsCovered[i] = invertible[i] = true; break; } } } boolean cont = true; while (cont) { for (int i = 0; i < excessMatrix.length; ++i) { if (rowsCovered[i]) { for (int j = 0; j < excessMatrix[i].length; ++j) { if ((Double.compare(excessMatrix[i][j], 0.) == 0) && !columnsCovered[j]) { columnsCovered[j] = true; } } } } // Shall we continue? cont = false; for (int j = 0; j < columnsCovered.length; ++j) { if (columnsCovered[j] && (rowMatched[j] != -1)) { if (!rowsCovered[rowMatched[j]]) { cont = true; rowsCovered[rowMatched[j]] = true; } } } } // Invert covered rows selection for (int i = 0; i < rowsCovered.length; ++i) { if (invertible[i]) { rowsCovered[i] ^= true; } } assert uncovered(excessMatrix, rowsCovered, columnsCovered) == 0; assert minimal(rowMatched, rowsCovered, columnsCovered); } /** * Extends equality-graph subtracting minimal excess from all the COLUMNS UNCOVERED and * adding it to the all ROWS COVERED */ void extendEqualityGraph() { double minExcess = Double.MAX_VALUE; for (int i = 0; i < excessMatrix.length; ++i) { if (rowsCovered[i]) { continue; } for (int j = 0; j < excessMatrix[i].length; ++j) { if (columnsCovered[j]) { continue; } if (minExcess > excessMatrix[i][j]) { minExcess = excessMatrix[i][j]; } } } // Add up to all elements of the COVERED ROWS for (int i = 0; i < excessMatrix.length; ++i) { if (!rowsCovered[i]) { continue; } for (int j = 0; j < excessMatrix[i].length; ++j) { excessMatrix[i][j] += minExcess; } } // Subtract from all elements of the UNCOVERED COLUMNS for (int j = 0; j < excessMatrix[0].length; ++j) { if (columnsCovered[j]) { continue; } for (int i = 0; i < excessMatrix.length; ++i) { excessMatrix[i][j] -= minExcess; } } } /** * Assures given column-n-rows-coverage/zero-matching to be minimal/maximal * * @param match zero-matching to check * @param rowsCovered rows coverage to check * @param colsCovered columns coverage to check * * @return true if given matching and coverage are maximal and minimal respectively */ private static boolean minimal( final int[] match, final boolean[] rowsCovered, final boolean[] colsCovered) { int matched = 0; for (int i = 0; i < match.length; ++i) { if (match[i] != -1) { ++matched; } } int covered = 0; for (int i = 0; i < rowsCovered.length; ++i) { if (rowsCovered[i]) { ++covered; } if (colsCovered[i]) { ++covered; } } return matched == covered; } /** * Accounts for zeroes being uncovered * * @param excessMatrix target excess-matrix * @param rowsCovered rows coverage to check * @param colsCovered columns coverage to check */ private static int uncovered( final double[][] excessMatrix, final boolean[] rowsCovered, final boolean[] colsCovered) { int uncoveredZero = 0; for (int i = 0; i < excessMatrix.length; ++i) { if (rowsCovered[i]) { continue; } for (int j = 0; j < excessMatrix[i].length; ++j) { if (colsCovered[j]) { continue; } if (Double.compare(excessMatrix[i][j], 0.) == 0) { ++uncoveredZero; } } } return uncoveredZero; } /** * Aggregates utilities to extend matching */ protected class MatchExtender { private final boolean[] rowsVisited; private final boolean[] colsVisited; private MatchExtender(final boolean[] rowsVisited, final boolean[] colsVisited) { this.rowsVisited = rowsVisited; this.colsVisited = colsVisited; } /** * Performs DFS to seek after matching-augmenting path starting at the initial-vertex * * @param initialCol column # of initial-vertex * @return true when some augmenting-path found, false otherwise */ public boolean extend(int initialCol) { return extendMatchingEL(initialCol); } /** * DFS helper #1 (applicable for ODD-LENGTH paths ONLY) * * @param pathTailRow row # of tail of the matching-augmenting path * @param pathTailCol column # of tail of the matching-augmenting path * * @return true if matching-augmenting path found, false otherwise */ private boolean extendMatchingOL(int pathTailRow, int pathTailCol) { // Seek after already matched zero // Check whether row occupied by the 'tail' vertex isn't matched if (columnMatched[pathTailRow] == -1) { // There is no way to continue: mark zero as matched, // trigger along-side flipping columnMatched[pathTailRow] = pathTailCol; rowMatched[pathTailCol] = pathTailRow; return true; } else { // Add next matched zero: mark current vertex as "visited" rowsVisited[pathTailRow] = true; // Check whether we can proceed if (colsVisited[columnMatched[pathTailRow]]) { return false; } boolean extending = extendMatchingEL(columnMatched[pathTailRow]); if (extending) { columnMatched[pathTailRow] = pathTailCol; rowMatched[pathTailCol] = pathTailRow; } return extending; } } /** * DFS helper #1 (applicable for ODD-LENGTH paths ONLY) * * @param pathTailCol column # of tail of the matching-augmenting path * * @return true if matching-augmenting path found, false otherwise */ private boolean extendMatchingEL(int pathTailCol) { colsVisited[pathTailCol] = true; for (int i = 0; i < excessMatrix.length; ++i) { if ((excessMatrix[i][pathTailCol] == 0) && !rowsVisited[i]) { boolean extending = extendMatchingOL( i, // New tail to continue pathTailCol // ); if (extending) { return true; } } } return false; } } } } MaximumWeightBipartiteMatching.java000066400000000000000000000307761402514743400353270ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jheaps.*; import org.jheaps.tree.*; import java.math.*; import java.util.*; import java.util.function.*; /** * Maximum weight matching in bipartite graphs. * *

* Running time is $O(n(m+n \log n))$ where n is the number of vertices and m the number of edges of * the input graph. Uses exact arithmetic and produces a certificate of optimality in the form of a * tight vertex potential function. * *

* This is the algorithm and implementation described in the * LEDA book. See the LEDA * Platform of Combinatorial and Geometric Computing, Cambridge University Press, 1999. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class MaximumWeightBipartiteMatching implements MatchingAlgorithm { private final Graph graph; private final Set partition1; private final Set partition2; private final Comparator comparator; private final Function, AddressableHeap> heapSupplier; // vertex potentials private Map pot; // the matched edge of a vertex, also used to check if a vertex is free private Map matchedEdge; // shortest path related data structures private AddressableHeap heap; private Map> nodeInHeap; private Map pred; private Map dist; // the actual result private Set matching; private BigDecimal matchingWeight; /** * Constructor. * * @param graph the input graph * @param partition1 the first partition of the vertex set * @param partition2 the second partition of the vertex set * @throws IllegalArgumentException if the graph is not undirected */ public MaximumWeightBipartiteMatching(Graph graph, Set partition1, Set partition2) { this(graph, partition1, partition2, (comparator) -> new FibonacciHeap<>(comparator)); } /** * Constructor. * * @param graph the input graph * @param partition1 the first partition of the vertex set * @param partition2 the second partition of the vertex set * @param heapSupplier a supplier for the addressable heap to use in the algorithm. * @throws IllegalArgumentException if the graph is not undirected */ public MaximumWeightBipartiteMatching( Graph graph, Set partition1, Set partition2, Function, AddressableHeap> heapSupplier) { this.graph = GraphTests.requireUndirected(graph); this.partition1 = Objects.requireNonNull(partition1, "Partition 1 cannot be null"); this.partition2 = Objects.requireNonNull(partition2, "Partition 2 cannot be null"); this.comparator = Comparator. naturalOrder(); this.heapSupplier = Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null"); } /** * {@inheritDoc} */ @Override public Matching getMatching() { /* * Test input instance */ if (!GraphTests.isSimple(graph)) { throw new IllegalArgumentException("Only simple graphs supported"); } if (!GraphTests.isBipartitePartition(graph, partition1, partition2)) { throw new IllegalArgumentException("Graph partition is not bipartite"); } // initialize result matching = new LinkedHashSet<>(); matchingWeight = BigDecimal.ZERO; // empty graph if (graph.edgeSet().isEmpty()) { return new MatchingImpl<>(graph, matching, matchingWeight.doubleValue()); } // initialize pot = new HashMap<>(); dist = new HashMap<>(); matchedEdge = new HashMap<>(); heap = heapSupplier.apply(comparator); nodeInHeap = new HashMap<>(); pred = new HashMap<>(); graph.vertexSet().forEach(v -> { pot.put(v, BigDecimal.ZERO); pred.put(v, null); dist.put(v, BigDecimal.ZERO); }); // run simple heuristic simpleHeuristic(); // augment to optimality for (V v : partition1) { if (!matchedEdge.containsKey(v)) { augment(v); } } return new MatchingImpl<>(graph, matching, matchingWeight.doubleValue()); } /** * Get the vertex potentials. * *

* This is a tight non-negative potential function which proves the optimality of the maximum * weight matching. See any standard textbook about linear programming duality. * * @return the vertex potentials */ public Map getPotentials() { if (pot == null) { return Collections.emptyMap(); } else { return Collections.unmodifiableMap(pot); } } /** * Get the weight of the matching. * * @return the weight of the matching */ public BigDecimal getMatchingWeight() { return matchingWeight; } /** * Augment from a particular node. The algorithm always looks for augmenting paths from nodes in * partition1. In the following code partition1 is $A$ and partition2 is $B$. * * @param a the node */ private void augment(V a) { dist.put(a, BigDecimal.ZERO); V bestInA = a; BigDecimal minA = pot.get(a); BigDecimal delta; Deque reachedA = new ArrayDeque<>(); reachedA.push(a); Deque reachedB = new ArrayDeque<>(); // relax all edges out of a1 V a1 = a; for (E e1 : graph.edgesOf(a1)) { if (!matching.contains(e1)) { V b1 = Graphs.getOppositeVertex(graph, e1, a1); BigDecimal db1 = dist .get(a1).add(pot.get(a1)).add(pot.get(b1)) .subtract(BigDecimal.valueOf(graph.getEdgeWeight(e1))); if (pred.get(b1) == null) { dist.put(b1, db1); pred.put(b1, e1); reachedB.push(b1); AddressableHeap.Handle node = heap.insert(db1, b1); nodeInHeap.put(b1, node); } else { if (comparator.compare(db1, dist.get(b1)) < 0) { dist.put(b1, db1); pred.put(b1, e1); nodeInHeap.get(b1).decreaseKey(db1); } } } } while (true) { /* * select from priority queue the node b with minimal distance db */ V b = null; BigDecimal db = BigDecimal.ZERO; if (!heap.isEmpty()) { b = heap.deleteMin().getValue(); nodeInHeap.remove(b); db = dist.get(b); } /* * three cases */ if (b == null || comparator.compare(db, minA) >= 0) { delta = minA; augmentPathTo(bestInA); break; } else { E e = matchedEdge.get(b); if (e == null) { delta = db; augmentPathTo(b); break; } else { a1 = Graphs.getOppositeVertex(graph, e, b); pred.put(a1, e); reachedA.push(a1); dist.put(a1, db); if (comparator.compare(db.add(pot.get(a1)), minA) < 0) { bestInA = a1; minA = db.add(pot.get(a1)); } // relax all edges out of a1 for (E e1 : graph.edgesOf(a1)) { if (!matching.contains(e1)) { V b1 = Graphs.getOppositeVertex(graph, e1, a1); BigDecimal db1 = dist .get(a1).add(pot.get(a1)).add(pot.get(b1)) .subtract(BigDecimal.valueOf(graph.getEdgeWeight(e1))); if (pred.get(b1) == null) { dist.put(b1, db1); pred.put(b1, e1); reachedB.push(b1); AddressableHeap.Handle node = heap.insert(db1, b1); nodeInHeap.put(b1, node); } else { if (comparator.compare(db1, dist.get(b1)) < 0) { dist.put(b1, db1); pred.put(b1, e1); nodeInHeap.get(b1).decreaseKey(db1); } } } } } } } // augment: potential update and re-initialization while (!reachedA.isEmpty()) { V v = reachedA.pop(); pred.put(v, null); BigDecimal potChange = delta.subtract(dist.get(v)); if (comparator.compare(potChange, BigDecimal.ZERO) <= 0) { continue; } pot.put(v, pot.get(v).subtract(potChange)); } while (!reachedB.isEmpty()) { V v = reachedB.pop(); pred.put(v, null); if (nodeInHeap.containsKey(v)) { nodeInHeap.remove(v).delete(); } BigDecimal potChange = delta.subtract(dist.get(v)); if (comparator.compare(potChange, BigDecimal.ZERO) <= 0) { continue; } pot.put(v, pot.get(v).add(potChange)); } } private void augmentPathTo(V v) { List matched = new ArrayList<>(); List free = new ArrayList<>(); E e1 = pred.get(v); while (e1 != null) { if (matching.contains(e1)) { matched.add(e1); } else { free.add(e1); } v = Graphs.getOppositeVertex(graph, e1, v); e1 = pred.get(v); } for (E e : matched) { BigDecimal w = BigDecimal.valueOf(graph.getEdgeWeight(e)); V s = graph.getEdgeSource(e); V t = graph.getEdgeTarget(e); matchedEdge.remove(s); matchedEdge.remove(t); matchingWeight = matchingWeight.subtract(w); matching.remove(e); } for (E e : free) { BigDecimal w = BigDecimal.valueOf(graph.getEdgeWeight(e)); V s = graph.getEdgeSource(e); V t = graph.getEdgeTarget(e); matchedEdge.put(s, e); matchedEdge.put(t, e); matchingWeight = matchingWeight.add(w); matching.add(e); } } private void simpleHeuristic() { for (V v : partition1) { E maxEdge = null; BigDecimal maxWeight = BigDecimal.ZERO; for (E e : graph.edgesOf(v)) { BigDecimal w = BigDecimal.valueOf(graph.getEdgeWeight(e)); if (comparator.compare(w, maxWeight) > 0) { maxWeight = w; maxEdge = e; } } pot.put(v, maxWeight); if (maxEdge != null) { V u = Graphs.getOppositeVertex(graph, maxEdge, v); if (!matchedEdge.containsKey(u)) { matching.add(maxEdge); matchingWeight = matchingWeight.add(maxWeight); matchedEdge.put(v, maxEdge); matchedEdge.put(u, maxEdge); } } } } } PathGrowingWeightedMatching.java000066400000000000000000000356761402514743400346140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; /** * A linear time $\frac{1}{2}$-approximation algorithm for finding a maximum weight matching in an * arbitrary graph. Linear time here means $O(m)$ where m is the cardinality of the edge set, even * if the graph contains isolated vertices. $\frac{1}{2}$-approximation means that for any graph * instance, the algorithm computes a matching whose weight is at least half of the weight of a * maximum weight matching. The implementation accepts directed and undirected graphs which may * contain self-loops and multiple edges. There is no assumption on the edge weights, i.e. they can * also be negative or zero. * *

* The algorithm is due to Drake and Hougardy, described in detail in the following paper: *

    *
  • D.E. Drake, S. Hougardy, A Simple Approximation Algorithm for the Weighted Matching Problem, * Information Processing Letters 85, 211-213, 2003.
  • *
* *

* This particular implementation uses by default two additional heuristics discussed by the authors * which also take linear time but improve the quality of the matchings. These heuristics can be * disabled by calling the constructor {@link #PathGrowingWeightedMatching(Graph, boolean)}. * Disabling the heuristics has the effect of fewer passes over the edge set of the input graph, * probably at the expense of the total weight of the matching. * *

* For a discussion on engineering approximate weighted matching algorithms see the following paper: *

    *
  • Jens Maue and Peter Sanders. Engineering algorithms for approximate weighted matching. * International Workshop on Experimental and Efficient Algorithms, Springer, 2007.
  • *
* * @see GreedyWeightedMatching * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class PathGrowingWeightedMatching implements MatchingAlgorithm { /** * Default value on whether to use extra heuristics to improve the result. */ public static final boolean DEFAULT_USE_HEURISTICS = true; private final Graph graph; private final Comparator comparator; private final boolean useHeuristics; /** * Construct a new instance of the path growing algorithm. Floating point values are compared * using {@link #DEFAULT_EPSILON} tolerance. By default two additional linear time heuristics * are used in order to improve the quality of the matchings. * * @param graph the input graph */ public PathGrowingWeightedMatching(Graph graph) { this(graph, DEFAULT_USE_HEURISTICS, DEFAULT_EPSILON); } /** * Construct a new instance of the path growing algorithm. Floating point values are compared * using {@link #DEFAULT_EPSILON} tolerance. * * @param graph the input graph * @param useHeuristics if true an improved version with additional heuristics is executed. The * running time remains linear but performs a few more passes over the input. While the * approximation factor remains $\frac{1}{2}$, in most cases the heuristics produce * matchings of higher quality. */ public PathGrowingWeightedMatching(Graph graph, boolean useHeuristics) { this(graph, useHeuristics, DEFAULT_EPSILON); } /** * Construct a new instance of the path growing algorithm. * * @param graph the input graph * @param useHeuristics if true an improved version with additional heuristics is executed. The * running time remains linear but performs a few more passes over the input. While the * approximation factor remains $\frac{1}{2}$, in most cases the heuristics produce * matchings of higher quality. * @param epsilon tolerance when comparing floating point values */ public PathGrowingWeightedMatching(Graph graph, boolean useHeuristics, double epsilon) { if (graph == null) { throw new IllegalArgumentException("Input graph cannot be null"); } this.graph = graph; this.comparator = new ToleranceDoubleComparator(epsilon); this.useHeuristics = useHeuristics; } /** * Get a matching that is a $\frac{1}{2}$-approximation of the maximum weighted matching. * * @return a matching */ @Override public Matching getMatching() { if (useHeuristics) { return runWithHeuristics(); } else { return run(); } } /** * Compute all vertices that have positive degree by iterating over the edges on purpose. This * keeps the complexity to $O(m)$ where $m$ is the number of edges. * * @return set of vertices with positive degree */ private Set initVisibleVertices() { Set visibleVertex = new HashSet<>(); for (E e : graph.edgeSet()) { V s = graph.getEdgeSource(e); V t = graph.getEdgeTarget(e); if (!s.equals(t)) { visibleVertex.add(s); visibleVertex.add(t); } } return visibleVertex; } // the algorithm (no heuristics) private Matching run() { // lookup all relevant vertices Set visibleVertex = initVisibleVertices(); // run algorithm Set m1 = new HashSet<>(); Set m2 = new HashSet<>(); double m1Weight = 0d, m2Weight = 0d; int i = 1; while (!visibleVertex.isEmpty()) { // find vertex arbitrarily V x = visibleVertex.stream().findAny().get(); // grow path from x while (x != null) { // first heaviest edge incident to vertex x (among visible neighbors) double maxWeight = 0d; E maxWeightedEdge = null; V maxWeightedNeighbor = null; for (E e : graph.edgesOf(x)) { V other = Graphs.getOppositeVertex(graph, e, x); if (visibleVertex.contains(other) && !other.equals(x)) { double curWeight = graph.getEdgeWeight(e); if (comparator.compare(curWeight, 0d) > 0 && (maxWeightedEdge == null || comparator.compare(curWeight, maxWeight) > 0)) { maxWeight = curWeight; maxWeightedEdge = e; maxWeightedNeighbor = other; } } } // add it to either m1 or m2, alternating between them if (maxWeightedEdge != null) { switch (i) { case 1: m1.add(maxWeightedEdge); m1Weight += maxWeight; break; case 2: m2.add(maxWeightedEdge); m2Weight += maxWeight; break; default: throw new RuntimeException( "Failed to figure out matching, seems to be a bug"); } i = 3 - i; } // remove x and incident edges visibleVertex.remove(x); // go to next vertex x = maxWeightedNeighbor; } } // return best matching if (comparator.compare(m1Weight, m2Weight) > 0) { return new MatchingImpl<>(graph, m1, m1Weight); } else { return new MatchingImpl<>(graph, m2, m2Weight); } } // the algorithm (improved with additional heuristics) private Matching runWithHeuristics() { // lookup all relevant vertices Set visibleVertex = initVisibleVertices(); // create solver for paths DynamicProgrammingPathSolver pathSolver = new DynamicProgrammingPathSolver(); Set matching = new HashSet<>(); double matchingWeight = 0d; Set matchedVertices = new HashSet<>(); // run algorithm while (!visibleVertex.isEmpty()) { // find vertex arbitrarily V x = visibleVertex.stream().findAny().get(); // grow path from x LinkedList path = new LinkedList<>(); while (x != null) { // first heaviest edge incident to vertex x (among visible neighbors) double maxWeight = 0d; E maxWeightedEdge = null; V maxWeightedNeighbor = null; for (E e : graph.edgesOf(x)) { V other = Graphs.getOppositeVertex(graph, e, x); if (visibleVertex.contains(other) && !other.equals(x)) { double curWeight = graph.getEdgeWeight(e); if (comparator.compare(curWeight, 0d) > 0 && (maxWeightedEdge == null || comparator.compare(curWeight, maxWeight) > 0)) { maxWeight = curWeight; maxWeightedEdge = e; maxWeightedNeighbor = other; } } } // add edge to path and remove x if (maxWeightedEdge != null) { path.add(maxWeightedEdge); } visibleVertex.remove(x); // go to next vertex x = maxWeightedNeighbor; } // find maximum weight matching of path using dynamic programming Pair> pathMatching = pathSolver.getMaximumWeightMatching(graph, path); // add it to result while keeping track of matched vertices matchingWeight += pathMatching.getFirst(); for (E e : pathMatching.getSecond()) { V s = graph.getEdgeSource(e); V t = graph.getEdgeTarget(e); if (!matchedVertices.add(s)) { throw new RuntimeException( "Set is not a valid matching, please submit a bug report"); } if (!matchedVertices.add(t)) { throw new RuntimeException( "Set is not a valid matching, please submit a bug report"); } matching.add(e); } } // extend matching to maximal cardinality (out of edges with positive weight) for (E e : graph.edgeSet()) { double edgeWeight = graph.getEdgeWeight(e); if (comparator.compare(edgeWeight, 0d) <= 0) { // ignore zero or negative weight continue; } V s = graph.getEdgeSource(e); if (matchedVertices.contains(s)) { // matched vertex, ignore continue; } V t = graph.getEdgeTarget(e); if (matchedVertices.contains(t)) { // matched vertex, ignore continue; } // add edge to matching matching.add(e); matchingWeight += edgeWeight; } // return extended matching return new MatchingImpl<>(graph, matching, matchingWeight); } /** * Helper class for repeatedly solving the maximum weight matching on paths. * * The work array used in the dynamic programming algorithm is reused between invocations. In * case its size is smaller than the path provided, its length is increased. This class is not * thread-safe. */ class DynamicProgrammingPathSolver { private static final int WORK_ARRAY_INITIAL_SIZE = 256; // work array private double[] a = new double[WORK_ARRAY_INITIAL_SIZE]; /** * Find the maximum weight matching of a path using dynamic programming. * * @param path a list of edges. The code assumes that the list of edges is a valid simple * path, and that is not a cycle. * @return a maximum weight matching of the path */ public Pair> getMaximumWeightMatching(Graph g, LinkedList path) { int pathLength = path.size(); // special cases switch (pathLength) { case 0: // special case, empty path return Pair.of(0d, Collections.emptySet()); case 1: // special case, one edge E e = path.getFirst(); double eWeight = g.getEdgeWeight(e); if (comparator.compare(eWeight, 0d) > 0) { return Pair.of(eWeight, Collections.singleton(e)); } else { return Pair.of(0d, Collections.emptySet()); } } // make sure work array has enough space if (a.length < pathLength + 1) { a = new double[pathLength + 1]; } // first pass to find solution Iterator it = path.iterator(); E e = it.next(); double eWeight = g.getEdgeWeight(e); a[0] = 0d; a[1] = (comparator.compare(eWeight, 0d) > 0) ? eWeight : 0d; for (int i = 2; i <= pathLength; i++) { e = it.next(); eWeight = g.getEdgeWeight(e); if (comparator.compare(a[i - 1], a[i - 2] + eWeight) > 0) { a[i] = a[i - 1]; } else { a[i] = a[i - 2] + eWeight; } } // reverse second pass to build solution Set matching = new HashSet<>(); it = path.descendingIterator(); int i = pathLength; while (i >= 1) { e = it.next(); if (comparator.compare(a[i], a[i - 1]) > 0) { matching.add(e); // skip next edge if (i > 1) { e = it.next(); } i--; } i--; } // return solution return Pair.of(a[pathLength], matching); } } } SparseEdmondsMaximumCardinalityMatching.java000066400000000000000000000533741402514743400371660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.util.*; import java.util.*; /** * Edmonds' blossom algorithm for maximum cardinality matching in general undirected graphs. * *

* A matching in a graph $G(V,E)$ is a subset of edges $M$ such that no two edges in $M$ have a * vertex in common. A matching has at most $\frac{1}{2}|V|$ edges. A node $v$ in $G$ is matched by * matching $M$ if $M$ contains an edge incident to $v$. A matching is perfect if all nodes are * matched. By definition, a perfect matching consists of exactly $\frac{1}{2}|V|$ edges. This * algorithm will return a perfect matching if one exists. If no perfect matching exists, then the * largest (non-perfect) matching is returned instead. In the special case that the input graph is * bipartite, consider using {@link HopcroftKarpMaximumCardinalityBipartiteMatching} instead. * *

* To compute a maximum cardinality matching, at most $n$ augmenting path computations are * performed. Each augmenting path computation takes $O(m \alpha(m,n))$ time, where $\alpha(m,n)$ is * the inverse of the Ackerman function, $n$ is the number of vertices, and $m$ the number of edges. * This results in a total runtime complexity of O(n m alpha(m,n)). In practice, the number of * augmenting path computations performed is far smaller than $n$, since an efficient heuristic is * used to compute a near-optimal initial solution. The heuristic by default is the * {@link GreedyMaximumCardinalityMatching} but can be changed using the appropriate constructor. * *

* The runtime complexity of this implementation could be improved to $O(n m)$ when the UnionFind * data structure used in this implementation is replaced by the linear-time set union data * structure proposed in: Gabow, H.N., Tarjan, R.E. A linear-time algorithm for a special case of * disjoint set union. Proc. Fifteenth Annual ACM Symposium on Theory of Computing, 1982, pp. * 246-251. *

* Edmonds' original algorithm first appeared in Edmonds, J. Paths, trees, and flowers. Canadian * Journal of Mathematics 17, 1965, pp. 449-467, and had a runtime complexity of $O(n^4)$. * *

* This is the algorithm and implementation described in the * LEDA book. See the LEDA * Platform of Combinatorial and Geometric Computing, Cambridge University Press, 1999. * *

* For future reference - A more efficient algorithm exists: S. Micali and V. Vazirani. An * $O(\sqrt{n}m)$ algorithm for finding maximum matching in general graphs. Proc. 21st Ann. Symp. on * Foundations of Computer Science, IEEE, 1980, pp. 17–27. This is the most efficient algorithm * known for computing maximum cardinality matchings in general graphs. More details on this * algorithm can be found in: *

* * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail * @author Joris Kinable */ public class SparseEdmondsMaximumCardinalityMatching implements MatchingAlgorithm { private final Graph graph; private MatchingAlgorithm initializer; private Matching result; private Map oddSetCover; /** * Constructs a new instance of the algorithm. {@link GreedyMaximumCardinalityMatching} is used * to quickly generate a near optimal initial solution. * * @param graph the input graph */ public SparseEdmondsMaximumCardinalityMatching(Graph graph) { this(graph, new GreedyMaximumCardinalityMatching<>(graph, false)); } /** * Constructs a new instance of the algorithm. * * @param graph undirected graph (graph does not have to be simple) * @param initializer heuristic matching algorithm used to quickly generate a (near optimal) * initial feasible solution */ public SparseEdmondsMaximumCardinalityMatching( Graph graph, MatchingAlgorithm initializer) { this.graph = GraphTests.requireUndirected(graph); this.initializer = initializer; } /** * The actual implementation as an inner class. We use this pattern in order to free the work * memory after computation. The outer class can cache the result but can also release all the * auxiliary memory. * * @param the vertex type * @param the edge type */ private static class Algorithm { private static final int NULL = -1; /** * Even, odd and unlabeled labels. */ private enum Label { EVEN, ODD, UNLABELED } private final Graph graph; private MatchingAlgorithm initializer; private int nodes; private Map vertexIndexMap; private V[] vertexMap; private int[] mate; private Label[] label; private int[] pred; double strue; private double[] path1; private double[] path2; private int[] sourceBridge; private int[] targetBridge; private VertexPartition base; private FixedSizeIntegerQueue queue; private List labeledNodes; public Algorithm(Graph graph, MatchingAlgorithm initializer) { this.graph = graph; this.initializer = initializer; } @SuppressWarnings("unchecked") private void initialize() { // index graph this.nodes = graph.vertexSet().size(); this.vertexIndexMap = CollectionUtil.newHashMapWithExpectedSize(nodes); this.vertexMap = (V[]) new Object[nodes]; int vIndex = 0; for (V vertex : graph.vertexSet()) { vertexIndexMap.put(vertex, vIndex); vertexMap[vIndex] = vertex; vIndex++; } this.mate = new int[nodes]; this.base = new VertexPartition(nodes); this.label = new Label[nodes]; this.pred = new int[nodes]; this.path1 = new double[nodes]; this.path2 = new double[nodes]; this.sourceBridge = new int[nodes]; this.targetBridge = new int[nodes]; for (int i = 0; i < nodes; i++) { this.mate[i] = NULL; this.label[i] = Label.EVEN; this.pred[i] = NULL; this.path1[i] = 0d; this.path2[i] = 0d; this.sourceBridge[i] = NULL; this.targetBridge[i] = NULL; } this.strue = 0d; this.queue = new FixedSizeIntegerQueue(nodes); this.labeledNodes = new ArrayList<>(); } private void runInitializer() { if (initializer == null) { return; } for (E e : initializer.getMatching()) { V u = graph.getEdgeSource(e); V v = graph.getEdgeTarget(e); int uIndex = vertexIndexMap.get(u); int vIndex = vertexIndexMap.get(v); mate[uIndex] = vIndex; label[uIndex] = Label.UNLABELED; mate[vIndex] = uIndex; label[vIndex] = Label.UNLABELED; } } private void findPath(Deque p, int x, int y) { if (x == y) { p.add(x); return; } if (label[x] == Label.EVEN) { p.add(x); p.add(mate[x]); findPath(p, pred[mate[x]], y); return; } // x is ODD p.add(x); Deque p2 = new ArrayDeque<>(); findPath(p2, sourceBridge[x], mate[x]); while (!p2.isEmpty()) { p.add(p2.removeLast()); } findPath(p, targetBridge[x], y); } private void shrinkPath(int b, int v, int w) { int x = base.find(v); while (x != b) { base.union(x, b); x = mate[x]; base.union(x, b); base.name(b); // make sure b is called the same queue.enqueue(x); sourceBridge[x] = v; targetBridge[x] = w; x = base.find(pred[x]); } } public Set computeMatching() { initialize(); runInitializer(); for (int i = 0; i < nodes; i++) { if (mate[i] != NULL) { continue; } queue.clear(); queue.enqueue(i); labeledNodes.clear(); labeledNodes.add(i); boolean breakThrough = false; while (!breakThrough && !queue.isEmpty()) { // grow tree int v = queue.poll(); V vAsVertex = vertexMap[v]; for (E e : graph.edgesOf(vAsVertex)) { V wAsVertex = Graphs.getOppositeVertex(graph, e, vAsVertex); int w = vertexIndexMap.get(wAsVertex); if (base.find(v) == base.find(w) || label[base.find(w)] == Label.ODD) { continue; } if (label[w] == Label.UNLABELED) { // grow tree label[w] = Label.ODD; labeledNodes.add(w); pred[w] = v; label[mate[w]] = Label.EVEN; labeledNodes.add(mate[w]); queue.enqueue(mate[w]); } else { // augment or shrink blossom int hv = base.find(v); int hw = base.find(w); strue += 1d; path1[hv] = strue; path2[hw] = strue; while ((path1[hw] != strue && path2[hv] != strue) && (mate[hv] != NULL || mate[hw] != NULL)) { if (mate[hv] != NULL) { hv = base.find(pred[mate[hv]]); path1[hv] = strue; } if (mate[hw] != NULL) { hw = base.find(pred[mate[hw]]); path2[hw] = strue; } } if (path1[hw] == strue || path2[hv] == strue) { // shrink blossom int b = (path1[hw] == strue) ? hw : hv; // base shrinkPath(b, v, w); shrinkPath(b, w, v); } else { // augment Deque p = new ArrayDeque<>(); findPath(p, v, hv); p.addFirst(w); while (!p.isEmpty()) { int a = p.pop(); int b = p.pop(); mate[a] = b; mate[b] = a; } labeledNodes.add(w); for (Integer k : labeledNodes) { label[k] = Label.UNLABELED; } base.split(labeledNodes); breakThrough = true; break; } } } } } // compute resulting matching Set matching = new HashSet<>(); for (E e : graph.edgeSet()) { V u = graph.getEdgeSource(e); V v = graph.getEdgeTarget(e); if (u.equals(v)) { continue; } int uIndex = vertexIndexMap.get(u); int vIndex = vertexIndexMap.get(v); if (uIndex != vIndex && mate[uIndex] == vIndex) { matching.add(e); // cleanup mate[uIndex] = uIndex; mate[vIndex] = vIndex; } } return matching; } public Map computeOddSetCover() { int[] osc = new int[nodes]; Arrays.fill(osc, -1); int numberOfUnlabeled = 0; int arbUNode = -1; for (int v = 0; v < nodes; v++) { if (label[v] == Label.UNLABELED) { numberOfUnlabeled++; arbUNode = v; } } if (numberOfUnlabeled > 0) { osc[arbUNode] = 1; int lambda = (numberOfUnlabeled == 2 ? 0 : 2); for (int v = 0; v < nodes; v++) { if (label[v] == Label.UNLABELED && v != arbUNode) { osc[v] = lambda; } } } int kappa = (numberOfUnlabeled <= 2 ? 2 : 3); for (int v = 0; v < nodes; v++) { if (base.find(v) != v && osc[base.find(v)] == -1) { osc[base.find(v)] = kappa++; } } for (int v = 0; v < nodes; v++) { if (base.find(v) == v && osc[v] == -1) { if (label[v] == Label.EVEN) { osc[v] = 0; } if (label[v] == Label.ODD) { osc[v] = 1; } } if (base.find(v) != v) { osc[v] = osc[base.find(v)]; } } Map oddSetCover = new HashMap<>(); for (int v = 0; v < nodes; v++) { oddSetCover.put(vertexMap[v], osc[v]); } return oddSetCover; } } @Override public Matching getMatching() { if (result == null) { Algorithm alg = new Algorithm<>(graph, initializer); Set matchingEdges = alg.computeMatching(); int cardinality = matchingEdges.size(); result = new MatchingImpl<>(graph, matchingEdges, cardinality); oddSetCover = alg.computeOddSetCover(); } return result; } /** * Get an odd set cover which proves the optimality of the computed matching. * *

* In order to check for optimality one needs to check that the odd-set-cover is a node labeling * that (a) covers the graph and (b) whose capacity is equal to the cardinality of the matching. * For (a) we check that every edge is either incident to a node with label 1 or connects two * nodes labeled $i$ for some $i \ge 2$. For (b) we count for each $i$ the number $n_i$ of nodes * with label $i$ and compute $S = n_1 + \sum_{i \ge 2} \floor{n_i/2}$. * *

* Method {{@link #isOptimalMatching(Graph, Set, Map)} performs this check given a matching and * an odd-set-cover. * * @return an odd set cover whose capacity is the same as the matching's cardinality */ public Map getOddSetCover() { getMatching(); return oddSetCover; } /** * Check whether a matching is optimal. * * The method first checks whether the matching is indeed a matching. Then it checks whether the * odd-set-cover provided is a node labeling that covers the graph and whose capacity is equal * to the cardinality of the matching. * * First, we count for each $i$ the number $n_i$ of nodes with label $i$, and then compute $S = * n_1 + \sum_{i \ge 2} \floor{n_i/2}$. $S$ should be equal to the size of the matching. Then, * we check that every edge is incident to a node label one or connects two nodes labeled $i$ * for some $i \ge 2$. * * This method runs in linear time. * * @param graph the graph * @param matching a matching * @param oddSetCover an odd set cover * @return true if the matching is optimal, false otherwise * * @param graph vertex type * @param graph edge type */ public static boolean isOptimalMatching( Graph graph, Set matching, Map oddSetCover) { // check matching Set matched = new HashSet<>(); for (E e : matching) { V s = graph.getEdgeSource(e); if (!matched.add(s)) { return false; } V t = graph.getEdgeTarget(e); if (!matched.add(t)) { return false; } } // check optimality int n = Math.max(2, graph.vertexSet().size()); int kappa = 1; int[] count = new int[n]; for (int i = 0; i < n; i++) { count[i] = 0; } for (V v : graph.vertexSet()) { Integer osc = oddSetCover.get(v); if (osc < 0 || osc >= n) { return false; } count[osc]++; if (osc > kappa) { kappa = osc; } } int s = count[1]; for (int i = 2; i <= kappa; i++) { s += count[i] / 2; } if (s != matching.size()) { return false; } for (E e : graph.edgeSet()) { V v = graph.getEdgeSource(e); V w = graph.getEdgeTarget(e); int oscv = oddSetCover.get(v); int oscw = oddSetCover.get(w); if (v.equals(w) || oscv == 1 || oscw == 1 || (oscv == oscw && oscv >= 2)) { continue; } return false; } return true; } /** * Special integer vertex union-find. * * @author Dimitrios Michail */ private static class VertexPartition { private Item[] items; public VertexPartition(int n) { this.items = new Item[n]; for (int i = 0; i < n; i++) { items[i] = new Item(i); } } public int find(int e) { return findItem(e).rep; } public void union(int a, int b) { assert a >= 0 && a < items.length; assert b >= 0 && b < items.length; Item ia = findItem(a); Item ib = findItem(b); // check if the elements are already in the same set if (ia == ib) { return; } // union by rank if (ia.rank > ib.rank) { ib.parent = ia; } else if (ia.rank < ib.rank) { ia.parent = ib; } else { ib.parent = ia; ia.rank += 1; } } /** * Name the representative of the group where e belongs as e. * * @param e a vertex */ public void name(int e) { Item ie = findItem(e); ie.rep = e; } /** * Split a partition. Assumes that it contains all members, otherwise bad things may happen. * * @param toSplit all members of a partition */ public void split(List toSplit) { for (int i : toSplit) { Item item = items[i]; item.parent = item; item.rep = i; item.rank = 0; } } private Item findItem(int e) { assert e >= 0 && e < items.length; // lookup Item current = items[e]; while (true) { Item parent = current.parent; if (parent.equals(current)) { break; } current = parent; } // path compression final Item root = current; current = items[e]; while (!current.equals(root)) { Item parent = current.parent; current.parent = root; current = parent; } return root; } // the item class private static class Item { public int rep; public int rank; Item parent; public Item(int rep) { this.rep = rep; this.rank = 0; this.parent = this; } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/000077500000000000000000000000001402514743400301005ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/000077500000000000000000000000001402514743400304325ustar00rootroot00000000000000BlossomVDualUpdater.java000066400000000000000000000472321402514743400351250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jheaps.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.DualUpdateStrategy.MULTIPLE_TREE_CONNECTED_COMPONENTS; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.DualUpdateStrategy.MULTIPLE_TREE_FIXED_DELTA; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.EPS; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.INFINITY; /** * This class is used by {@link KolmogorovWeightedPerfectMatching} to perform dual updates, thus * increasing the dual objective function value and creating new tight edges. *

* This class currently supports three types of dual updates: single tree, multiple trees fixed * delta, and multiple tree variable delta. The first one is used to updates duals of a single tree, * when at least one of the {@link BlossomVOptions#updateDualsBefore} or * {@link BlossomVOptions#updateDualsAfter} is true. The latter two are used to update the duals * globally and are defined by the {@link BlossomVOptions}. *

* There are two type of constraints on a dual change of a tree: in-tree and cross-tree. In-tree * constraints are imposed by the infinity edges, (+, +) in-tree edges and "-" blossoms. Cross-tree * constraints are imposed by (+, +), (+, -) and (-, +) cross-tree edges. With respect to this * classification of constraints the following strategies of changing the duals can be used: *

    *
  • Single tree strategy greedily increases the duals of the tree with respect to the in-tree and * cross-tree constraints. This can result in a zero-change update. If a tight (+, +) cross-tree * edge is encountered during this operation, an immediate augmentation is performed * afterwards.
  • * *
  • Multiple tree fixed delta approach considers only in-tree constraints and constraints imposed * by the (+, +) cross-tree edges. Since this approach increases the trees' epsilons by the same * amount, it doesn't need to consider other two dual constraints. If a tight (+, +) cross-tree edge * is encountered during this operation, an immediate augmentation is performed afterwards.
  • * *
  • Multiple tree variable delta approach considers all types of constraints. It determines a * connected components in the auxiliary graph, where only tight (-, +) and (+, -) cross-tree edges * are present. For these connected components it computes the same dual change, therefore the * constraints imposed by the (-, +) and (+, -) cross-tree edges can't be violated. If a tight (+, * +) cross-tree edge is encountered during this operation, an immediate augmentation is performed * afterwards.
  • *
* * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see BlossomVPrimalUpdater * @see KolmogorovWeightedPerfectMatching */ class BlossomVDualUpdater { /** * State information needed for the algorithm */ private BlossomVState state; /** * Instance of {@link BlossomVPrimalUpdater} for performing immediate augmentations after dual * updates when they are applicable. These speed up the overall algorithm. */ private BlossomVPrimalUpdater primalUpdater; /** * Creates a new instance of the BlossomVDualUpdater * * @param state the state common to {@link BlossomVPrimalUpdater}, {@link BlossomVDualUpdater} * and {@link KolmogorovWeightedPerfectMatching} * @param primalUpdater primal updater used by the algorithm */ public BlossomVDualUpdater(BlossomVState state, BlossomVPrimalUpdater primalUpdater) { this.state = state; this.primalUpdater = primalUpdater; } /** * Performs global dual update. Operates on the whole graph and updates duals according to the * strategy defined by {@link BlossomVOptions#dualUpdateStrategy}. * * @param type the strategy to use for updating the duals * @return the sum of all changes of dual variables of the trees */ public double updateDuals(BlossomVOptions.DualUpdateStrategy type) { long start = System.nanoTime(); BlossomVEdge augmentEdge = null; if (KolmogorovWeightedPerfectMatching.DEBUG) { System.out.println("Start updating duals"); } // go through all tree roots and determine the initial tree dual change wrt. in-tree // constraints // the cross-tree constraints are handles wrt. dual update strategy for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { BlossomVTree tree = root.tree; double eps = getEps(tree); tree.accumulatedEps = eps - tree.eps; } if (type == MULTIPLE_TREE_FIXED_DELTA) { augmentEdge = multipleTreeFixedDelta(); } else if (type == MULTIPLE_TREE_CONNECTED_COMPONENTS) { augmentEdge = updateDualsConnectedComponents(); } double dualChange = 0; // add tree.accumulatedEps to the tree.eps for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { if (root.tree.accumulatedEps > EPS) { dualChange += root.tree.accumulatedEps; root.tree.eps += root.tree.accumulatedEps; } } if (KolmogorovWeightedPerfectMatching.DEBUG) { for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { System.out .println("Updating duals: now eps of " + root.tree + " is " + (root.tree.eps)); } } state.statistics.dualUpdatesTime += System.nanoTime() - start; if (augmentEdge != null) { primalUpdater.augment(augmentEdge); } return dualChange; } /** * Computes and returns the value which can be assigned to the {@code tree.eps} so that it * doesn't violate in-tree constraints. In other words, {@code getEps(tree) - tree.eps} is the * resulting dual change wrt. in-tree constraints. The computed value is always greater than or * equal to the {@code tree.eps}, can violate the cross-tree constraints, and can be equal to * {@link KolmogorovWeightedPerfectMatching#INFINITY}. * * @param tree the tree to process * @return a value which can be safely assigned to tree.eps */ private double getEps(BlossomVTree tree) { double eps = KolmogorovWeightedPerfectMatching.INFINITY; // check minimum slack of the plus-infinity edges if (!tree.plusInfinityEdges.isEmpty()) { BlossomVEdge edge = tree.plusInfinityEdges.findMin().getValue(); if (edge.slack < eps) { eps = edge.slack; } } // check minimum dual variable of the "-" blossoms if (!tree.minusBlossoms.isEmpty()) { BlossomVNode node = tree.minusBlossoms.findMin().getValue(); if (node.dual < eps) { eps = node.dual; } } // check minimum slack of the (+, +) edges if (!tree.plusPlusEdges.isEmpty()) { BlossomVEdge edge = tree.plusPlusEdges.findMin().getValue(); if (2 * eps > edge.slack) { eps = edge.slack / 2; } } return eps; } /** * Updates the duals of the single tree. This method takes into account both in-tree and * cross-tree constraints. If possible, it also finds a cross-tree (+, +) edge of minimum slack * and performs an augmentation. * * @param tree the tree to update duals of * @return true iff some progress was made and there was no augmentation performed, false * otherwise */ public boolean updateDualsSingle(BlossomVTree tree) { long start = System.nanoTime(); double eps = getEps(tree); // include only constraints on (+,+) in-tree edges, (+, inf) // edges and "-' blossoms double epsAugment = KolmogorovWeightedPerfectMatching.INFINITY; // takes into account // constraints of the // cross-tree edges BlossomVEdge augmentEdge = null; // the (+, +) cross-tree edge of minimum slack double delta = 0; for (BlossomVTree.TreeEdgeIterator iterator = tree.treeEdgeIterator(); iterator.hasNext();) { BlossomVTreeEdge treeEdge = iterator.next(); BlossomVTree opposite = treeEdge.head[iterator.getCurrentDirection()]; if (!treeEdge.plusPlusEdges.isEmpty()) { BlossomVEdge plusPlusEdge = treeEdge.plusPlusEdges.findMin().getValue(); if (plusPlusEdge.slack - opposite.eps < epsAugment) { epsAugment = plusPlusEdge.slack - opposite.eps; augmentEdge = plusPlusEdge; } } MergeableAddressableHeap currentPlusMinusHeap = treeEdge.getCurrentPlusMinusHeap(opposite.currentDirection); if (!currentPlusMinusHeap.isEmpty()) { BlossomVEdge edge = currentPlusMinusHeap.findMin().getValue(); if (edge.slack + opposite.eps < eps) { eps = edge.slack + opposite.eps; } } } if (eps > epsAugment) { eps = epsAugment; } // now eps takes into account all the constraints if (eps > KolmogorovWeightedPerfectMatching.NO_PERFECT_MATCHING_THRESHOLD) { throw new IllegalArgumentException( KolmogorovWeightedPerfectMatching.NO_PERFECT_MATCHING); } if (eps > tree.eps) { delta = eps - tree.eps; tree.eps = eps; if (KolmogorovWeightedPerfectMatching.DEBUG) { System.out.println("Updating duals: now eps of " + tree + " is " + eps); } } state.statistics.dualUpdatesTime += System.nanoTime() - start; if (augmentEdge != null && epsAugment <= tree.eps) { primalUpdater.augment(augmentEdge); return false; // can't proceed with the same tree } else { return delta > EPS; } } /** * Updates the duals via connected components. The connected components are a set of trees which * are connected via tight (+, -) cross tree edges. For these components the same dual change is * chosen. As a result, the circular constraints are guaranteed to be avoided. This is the point * where the {@link BlossomVDualUpdater#updateDualsSingle} approach can fail. */ private BlossomVEdge updateDualsConnectedComponents() { BlossomVTree dummyTree = new BlossomVTree(); BlossomVEdge augmentEdge = null; double augmentEps = INFINITY; double oppositeEps; for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { root.tree.nextTree = null; } for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { BlossomVTree startTree = root.tree; if (startTree.nextTree != null) { // this tree is present in some connected component and has been processed already continue; } double eps = startTree.accumulatedEps; startTree.nextTree = startTree; BlossomVTree connectedComponentLast = startTree; BlossomVTree currentTree = startTree; while (true) { for (BlossomVTree.TreeEdgeIterator iterator = currentTree.treeEdgeIterator(); iterator.hasNext();) { BlossomVTreeEdge currentEdge = iterator.next(); int dir = iterator.getCurrentDirection(); BlossomVTree opposite = currentEdge.head[dir]; double plusPlusEps = KolmogorovWeightedPerfectMatching.INFINITY; int dirRev = 1 - dir; if (!currentEdge.plusPlusEdges.isEmpty()) { plusPlusEps = currentEdge.plusPlusEdges.findMin().getKey() - currentTree.eps - opposite.eps; if (augmentEps > plusPlusEps) { augmentEps = plusPlusEps; augmentEdge = currentEdge.plusPlusEdges.findMin().getValue(); } } if (opposite.nextTree != null && opposite.nextTree != dummyTree) { // opposite tree is in the same connected component // since the trees in the same connected component have the same dual change // we don't have to check (-, +) edges in this tree edge if (2 * eps > plusPlusEps) { eps = plusPlusEps / 2; } continue; } double[] plusMinusEps = new double[2]; plusMinusEps[dir] = KolmogorovWeightedPerfectMatching.INFINITY; if (!currentEdge.getCurrentPlusMinusHeap(dir).isEmpty()) { plusMinusEps[dir] = currentEdge.getCurrentPlusMinusHeap(dir).findMin().getKey() - currentTree.eps + opposite.eps; } plusMinusEps[dirRev] = KolmogorovWeightedPerfectMatching.INFINITY; if (!currentEdge.getCurrentPlusMinusHeap(dirRev).isEmpty()) { plusMinusEps[dirRev] = currentEdge.getCurrentPlusMinusHeap(dirRev).findMin().getKey() - opposite.eps + currentTree.eps; } if (opposite.nextTree == dummyTree) { // opposite tree is in another connected component and has valid accumulated // eps oppositeEps = opposite.accumulatedEps; } else if (plusMinusEps[0] > 0 && plusMinusEps[1] > 0) { // this tree edge doesn't contain any tight (-, +) cross-tree edge and // opposite tree // hasn't been processed yet. oppositeEps = 0; } else { // opposite hasn't been processed and there is a tight (-, +) cross-tree // edge between // current tree and opposite tree => we add opposite to the current // connected component connectedComponentLast.nextTree = opposite; connectedComponentLast = opposite.nextTree = opposite; if (eps > opposite.accumulatedEps) { // eps of the connected component can't be greater than the minimum // accumulated eps among trees in the connected component eps = opposite.accumulatedEps; } continue; } if (eps > plusPlusEps - oppositeEps) { // eps is bounded by the resulting slack of a (+, +) cross-tree edge eps = plusPlusEps - oppositeEps; } if (eps > plusMinusEps[dir] + oppositeEps) { // eps is bounded by the resulting slack of a (+, -) cross-tree edge in the // current direction eps = plusMinusEps[dir] + oppositeEps; } } if (currentTree.nextTree == currentTree) { // the end of the connected component break; } currentTree = currentTree.nextTree; } if (eps > KolmogorovWeightedPerfectMatching.NO_PERFECT_MATCHING_THRESHOLD) { throw new IllegalArgumentException( KolmogorovWeightedPerfectMatching.NO_PERFECT_MATCHING); } // apply dual change to all trees in the connected component BlossomVTree nextTree = startTree; do { currentTree = nextTree; nextTree = nextTree.nextTree; currentTree.nextTree = dummyTree; currentTree.accumulatedEps = eps; } while (currentTree != nextTree); } if (augmentEdge != null && augmentEps - augmentEdge.head[0].tree.accumulatedEps - augmentEdge.head[1].tree.accumulatedEps <= 0) { return augmentEdge; } return null; } /** * Updates duals by iterating through trees and greedily increasing their dual variables. */ private BlossomVEdge multipleTreeFixedDelta() { if (KolmogorovWeightedPerfectMatching.DEBUG) { System.out.println("Multiple tree fixed delta approach"); } BlossomVEdge augmentEdge = null; double eps = INFINITY; double augmentEps = INFINITY; for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { BlossomVTree tree = root.tree; double treeEps = tree.eps; eps = Math.min(eps, tree.accumulatedEps); // iterate only through outgoing tree edges so that every edge is considered only once for (BlossomVTreeEdge outgoingTreeEdge = tree.first[0]; outgoingTreeEdge != null; outgoingTreeEdge = outgoingTreeEdge.next[0]) { // since all epsilons are equal we don't have to check (+, -) cross tree edges if (!outgoingTreeEdge.plusPlusEdges.isEmpty()) { BlossomVEdge varEdge = outgoingTreeEdge.plusPlusEdges.findMin().getValue(); double slack = varEdge.slack - treeEps - outgoingTreeEdge.head[0].eps; eps = Math.min(eps, slack / 2); if (augmentEps > slack) { augmentEps = slack; augmentEdge = varEdge; } } } } if (eps > KolmogorovWeightedPerfectMatching.NO_PERFECT_MATCHING_THRESHOLD) { throw new IllegalArgumentException( KolmogorovWeightedPerfectMatching.NO_PERFECT_MATCHING); } for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { root.tree.accumulatedEps = eps; } if (augmentEps <= 2 * eps) { return augmentEdge; } return null; } } BlossomVEdge.java000066400000000000000000000307541402514743400335600ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jheaps.*; import java.util.*; /** * This class is a data structure for Kolmogorov's Blossom V algorithm. *

* It represents an edge between two nodes. Even though the weighted perfect matching problem is * formulated on an undirected graph, each edge has direction, i.e. it is an arc. According to this * direction it is present in two circular doubly linked lists of incident edges. The references to * the next and previous edges of this list are maintained via {@link BlossomVEdge#next} and * {@link BlossomVEdge#prev} references. The direction of an edge isn't stored in the edge, this * property is only reflected by the presence of an edge in the list of outgoing or incoming edges. *

* For example, let a $e = \{u, v\}$ be an edge in the graph $G = (V, E)$. Let's assume that after * initialization this edge has become directed from $u$ to $v$, i.e. now $e = (u, v)$. Then edge * $e$ belongs to the linked lists {@code u.first[0]} and {@code v.first[1]}. In other words, $e$ is * an outgoing edge of $u$ and an incoming edge of $v$. For convenience during computation, * {@code e.head[0] = v} and {@code e.head[1] = u}. Therefore, while iterating over incident edges * of a node {@code x} in the direction {@code dir}, we can easily access opposite node by * {@code x.head[dir]}. *

* An edge is called an infinity edge if it connects a "+" node with an infinity node. An * edge is called free if it connects two infinity nodes. An edge is called matched if * it belongs to the matching. During the shrink or expand operations an edge is called an * inner edge if it connects two nodes of the blossom. It is called a boundary edge if * it is incident to exactly one blossom node. An edge is called tight if its reduced cost * (reduced weight, slack, all three notions are equivalent) is zero. Note: in this algorithm * we use lazy delta spreading, so the {@link BlossomVEdge#slack} isn't necessarily equal to the * actual slack of an edge. * * @author Timofey Chudakov * @see KolmogorovWeightedPerfectMatching */ class BlossomVEdge { /** * Position of this edge in the array {@code state.edges}. This helps to determine generic * counterpart of this edge in constant time. */ final int pos; /** * A heap node from the heap this edge is stored in. *

* This variable doesn't need to be necessarily set to {@code null} after the edge is * removed from the heap it was stored in due to performance reasons. Therefore, no assumptions * should be made about whether this edge belongs to some heap or not based upon this variable * being {@code null} or not. */ AddressableHeap.Handle handle; /** * The slack of this edge. If an edge is an outer edge and doesn't connect 2 infinity nodes, * then its slack is subject to lazy delta spreading technique. Otherwise, this variable equals * the edge's true slack. *

* The true slack of the edge can be computed as following: for each of its two current * endpoints $\{u, v\}$ we subtract the endpoint.tree.eps if the endpoint is a "+" outer node or * add this value if it is a "-" outer node. After that we have valid slack for this edge. */ double slack; /** * A two-element array of original endpoints of this edge. They are used to quickly determine * original endpoints of an edge and compute the penultimate blossom. This is done while one of * the current endpoints of this edge is being shrunk or expanded. *

* These values stay unchanged throughout the course of the algorithm. */ BlossomVNode[] headOriginal; /** * A two-element array of current endpoints of this edge. These values change when previous * endpoints are contracted into blossoms or are expanded. For node head[0] this is an incoming * edge (direction 1) and for the node head[1] this is an outgoing edge (direction 0). This * feature is used to be able to access the opposite node via an edge by * {@code incidentEdgeIterator.next().head[incidentEdgeIterator.getDir()] }.git */ BlossomVNode[] head; /** * A two-element array of references to the previous elements in the circular doubly linked * lists of edges. Each list belongs to one of the current endpoints of this edge. */ BlossomVEdge[] prev; /** * A two-element array of references to the next elements in the circular doubly linked lists of * edges. Each list belongs to one of the current endpoints of this edge. */ BlossomVEdge[] next; /** * Constructs a new edge by initializing the arrays */ public BlossomVEdge(int pos) { headOriginal = new BlossomVNode[2]; head = new BlossomVNode[2]; next = new BlossomVEdge[2]; prev = new BlossomVEdge[2]; this.pos = pos; } /** * Returns the opposite edge with respect to the {@code endpoint}. Note: here we assume * that {@code endpoint} is one of the current endpoints. * * @param endpoint one of the current endpoints of this edge * @return node opposite to the {@code endpoint} */ public BlossomVNode getOpposite(BlossomVNode endpoint) { if (endpoint != head[0] && endpoint != head[1]) { // we need this check during finishing // phase return null; } return head[0] == endpoint ? head[1] : head[0]; } /** * Returns the original endpoint of this edge for some current {@code endpoint}. * * @param endpoint one of the current endpoints of this edge * @return the original endpoint of this edge which has the same direction as {@code endpoint} * with respect to this edge */ public BlossomVNode getCurrentOriginal(BlossomVNode endpoint) { if (endpoint != head[0] && endpoint != head[1]) { // we need this check during finishing // phase return null; } return head[0] == endpoint ? headOriginal[0] : headOriginal[1]; } /** * Returns the direction to the opposite node with respect to the {@code current}. * {@code current} must be one of the current endpoints of this edge. * * @param current one of the current endpoints of this edge. * @return the direction from the {@code current} */ public int getDirFrom(BlossomVNode current) { return head[0] == current ? 1 : 0; } @Override public String toString() { return "BlossomVEdge (" + head[0].pos + "," + head[1].pos + "), original: [" + headOriginal[0].pos + "," + headOriginal[1].pos + "], slack: " + slack + ", true slack: " + getTrueSlack() + (getTrueSlack() == 0 ? ", tight" : ""); } /** * Returns the true slack of this edge, i.e. the slack after applying lazy dual updates * * @return the true slack of this edge */ public double getTrueSlack() { double result = slack; if (head[0].tree != null) { if (head[0].isPlusNode()) { result -= head[0].tree.eps; } else { result += head[0].tree.eps; } } if (head[1].tree != null) { if (head[1].isPlusNode()) { result -= head[1].tree.eps; } else { result += head[1].tree.eps; } } return result; } /** * Moves the tail of the {@code edge} from the node {@code from} to the node {@code to} * * @param from the previous edge's tail * @param to the new edge's tail */ public void moveEdgeTail(BlossomVNode from, BlossomVNode to) { int dir = getDirFrom(from); from.removeEdge(this, dir); to.addEdge(this, dir); } /** * Returns a new instance of blossom nodes iterator * * @param root the root of the blossom * @return a new instance of blossom nodes iterator */ public BlossomVEdge.BlossomNodesIterator blossomNodesIterator(BlossomVNode root) { return new BlossomVEdge.BlossomNodesIterator(root, this); } /** * An iterator which traverses all nodes in the blossom. It starts from the endpoints of the * (+,+) edge and goes up to the blossom root. These two paths to the blossom root are called * branches. The branch of the blossomFormingEdge.head[0] has direction 0, the one has direction * 1. *

* Note: the nodes returned by this iterator aren't consecutive *

* Note: this iterator must return the blossom root in the first branch, i.e. when the * direction is 0. This feature is needed to setup the blossomSibling references correctly */ public static class BlossomNodesIterator implements Iterator { /** * Blossom's root */ private BlossomVNode root; /** * The node this iterator is currently on */ private BlossomVNode currentNode; /** * Helper variable, is used to determine whether currentNode has been returned or not */ private BlossomVNode current; /** * The current direction of this iterator */ private int currentDirection; /** * The (+, +) edge of the blossom */ private BlossomVEdge blossomFormingEdge; /** * Constructs a new BlossomNodeIterator for the {@code root} and {@code blossomFormingEdge} * * @param root the root of the blossom (the node which isn't matched to another node in the * blossom) * @param blossomFormingEdge a (+, +) edge in the blossom */ public BlossomNodesIterator(BlossomVNode root, BlossomVEdge blossomFormingEdge) { this.root = root; this.blossomFormingEdge = blossomFormingEdge; currentNode = current = blossomFormingEdge.head[0]; currentDirection = 0; } /** * {@inheritDoc} */ @Override public boolean hasNext() { if (current != null) { return true; } current = advance(); return current != null; } /** * @return the current direction of this iterator */ public int getCurrentDirection() { return currentDirection; } /** * {@inheritDoc} */ @Override public BlossomVNode next() { if (!hasNext()) { throw new NoSuchElementException(); } BlossomVNode result = current; current = null; return result; } /** * Advances this iterator to the next node in the blossom * * @return an unvisited node in the blossom */ private BlossomVNode advance() { if (currentNode == null) { return null; } if (currentNode == root && currentDirection == 0) { // we have just traversed blossom's root and now start to traverse the second branch currentDirection = 1; currentNode = blossomFormingEdge.head[1]; if (currentNode == root) { currentNode = null; } } else if (currentNode.getTreeParent() == root && currentDirection == 1) { // we have just finished traversing the blossom's nodes currentNode = null; } else { currentNode = currentNode.getTreeParent(); } return currentNode; } } } BlossomVInitializer.java000066400000000000000000001220251402514743400351700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.util.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVInitializer.Action.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVNode.Label.MINUS; import static org.jgrapht.alg.matching.blossom.v5.BlossomVNode.Label.PLUS; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.*; /** * Is used to start the Kolmogorov's Blossom V algorithm. Performs initialization of the algorithm's * internal data structures and finds an initial matching according to the strategy specified in * {@code options}. *

* The initialization process involves converting the graph into internal representation, allocating * trees for unmatched vertices, and creating an auxiliary graph whose nodes correspond to * alternating trees. The only part that varies is the strategy to find an initial matching to speed * up the main part of the algorithm. *

* The simple initialization (option {@link BlossomVOptions.InitializationType#NONE}) doesn't find * any matching and initializes the data structures by allocating $|V|$ single vertex trees. This is * the fastest initialization strategy; however, it slows the main algorithm down. *

* The greedy initialization (option {@link BlossomVOptions.InitializationType#GREEDY} runs in two * phases. First, for every node it determines an edge of minimum weight and assigns half of that * weight to the node's dual variable. This ensures that the slacks of all edges are non-negative. * After that it goes through all nodes again, greedily increases its dual variable and chooses an * incident matching edge if it is possible. After that every node is incident to at least one tight * edge. The resulting matching is an output of this initialization strategy. *

* The fractional matching initialization (option * {@link BlossomVOptions.InitializationType#FRACTIONAL}) is both the most complicated and the most * efficient type of initialization. The linear programming formulation of the fractional matching * problem is identical to the one used for bipartite graphs. More precisely: *

  • Minimize the $sum_{e\in E}x_e\times c_e$ subject to:
  • *
  • For all nodes: $\sum_{e is incident to v}x_e = 1$
  • *
  • For all edges: $x_e \ge 0$
  • Note: for an optimal solution in general graphs * we have to require the variables $x_e$ to be $0$ or $1$. For more information on this type of * initialization, see: David Applegate and William J. Cook. \Solving Large-Scale Matching * Problems". In: Network Flows And Matching. 1991. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see KolmogorovWeightedPerfectMatching */ class BlossomVInitializer { /** * The graph for which to find a matching */ private final Graph graph; /** * Number of nodes in the graph */ private int nodeNum; /** * Number of edges in the graph */ private int edgeNum = 0; /** * An array of nodes that will be passed to the resulting state object */ private BlossomVNode[] nodes; /** * An array of edges that will be passed to the resulting state object */ private BlossomVEdge[] edges; /** * Generic vertices of the {@code graph} in the same order as internal nodes in the array * {@code nodes}. Since for each node in the {@code nodes} we know its position in the * {@code nodes}, we can determine its generic counterpart in constant time */ private List graphVertices; /** * Generic edges of the {@code graph} in the same order as internal edges in the array * {@code edges}. Since for each edge in the {@code edges} we know its position in the * {@code edges}, we can determine its generic counterpart in constant time */ private List graphEdges; /** * Creates a new BlossomVInitializer instance * * @param graph the graph to search matching in */ public BlossomVInitializer(Graph graph) { this.graph = graph; nodeNum = graph.vertexSet().size(); } /** * Converts the generic graph representation into the data structure form convenient for the * algorithm, and initializes the matching according to the strategy specified in * {@code options}. * * @param options the options of the algorithm * @return the state object with all necessary information for the algorithm */ public BlossomVState initialize(BlossomVOptions options) { switch (options.initializationType) { case NONE: return simpleInitialization(options); case GREEDY: return greedyInitialization(options); case FRACTIONAL: return fractionalMatchingInitialization(options); default: return null; } } /** * Performs simple initialization of the matching by allocating $|V|$ trees. The result of this * type of initialization is an empty matching. That is why this is the most basic type of * initialization. * * @param options the options of the algorithm * @return the state object with all necessary information for the algorithm */ private BlossomVState simpleInitialization(BlossomVOptions options) { double minEdgeWeight = initGraph(); for (BlossomVNode node : nodes) { node.isOuter = true; } allocateTrees(); initAuxiliaryGraph(); return new BlossomVState<>( graph, nodes, edges, nodeNum, edgeNum, nodeNum, graphVertices, graphEdges, options, minEdgeWeight); } /** * Performs greedy initialization of the algorithm. For the description of this initialization * strategy see the class description. * * @param options the options of the algorithm * @return the state object with all necessary information for the algorithm */ private BlossomVState greedyInitialization(BlossomVOptions options) { double minEdgeWeight = initGraph(); int treeNum = initGreedy(); allocateTrees(); initAuxiliaryGraph(); return new BlossomVState<>( graph, nodes, edges, nodeNum, edgeNum, treeNum, graphVertices, graphEdges, options, minEdgeWeight); } /** * Performs fractional matching initialization, see {@link BlossomVInitializer#initFractional()} * for the description. * * @param options the options of the algorithm * @return the state object with all necessary information for the algorithm */ private BlossomVState fractionalMatchingInitialization(BlossomVOptions options) { double minEdgeWeight = initGraph(); initGreedy(); allocateTrees(); int treeNum = initFractional(); initAuxiliaryGraph(); return new BlossomVState<>( graph, nodes, edges, nodeNum, edgeNum, treeNum, graphVertices, graphEdges, options, minEdgeWeight); } /** * Converts the generic graph representation into the form convenient for the algorithm */ private double initGraph() { int expectedEdgeNum = graph.edgeSet().size(); nodes = new BlossomVNode[nodeNum + 1]; edges = new BlossomVEdge[expectedEdgeNum]; graphVertices = new ArrayList<>(nodeNum); graphEdges = new ArrayList<>(expectedEdgeNum); HashMap vertexMap = CollectionUtil.newHashMapWithExpectedSize(nodeNum); int i = 0; // maps nodes for (V vertex : graph.vertexSet()) { nodes[i] = new BlossomVNode(i); graphVertices.add(vertex); vertexMap.put(vertex, nodes[i]); i++; } nodes[nodeNum] = new BlossomVNode(nodeNum); // auxiliary node to keep track of the first // item in the linked list of tree roots i = 0; double minEdgeWeight = graph .edgeSet().stream().map(graph::getEdgeWeight).min(Comparator.naturalOrder()).orElse(0d); // maps edges for (E e : graph.edgeSet()) { BlossomVNode source = vertexMap.get(graph.getEdgeSource(e)); BlossomVNode target = vertexMap.get(graph.getEdgeTarget(e)); if (source != target) { // we avoid self-loops in order to support pseudographs edgeNum++; BlossomVEdge edge = addEdge(source, target, graph.getEdgeWeight(e) - minEdgeWeight, i); edges[i] = edge; graphEdges.add(e); i++; } } return minEdgeWeight; } /** * Adds a new edge between {@code from} and {@code to}. The resulting edge points from * {@code from} to {@code to} * * @param from the tail of this edge * @param to the head of this edge * @param slack the slack of the resulting edge * @param pos position of the resulting edge in the array {@code edges} * @return the newly added edge */ public BlossomVEdge addEdge(BlossomVNode from, BlossomVNode to, double slack, int pos) { BlossomVEdge edge = new BlossomVEdge(pos); edge.slack = slack; edge.headOriginal[0] = to; edge.headOriginal[1] = from; // the call to the BlossomVNode#addEdge implies setting head[dir] reference from.addEdge(edge, 0); to.addEdge(edge, 1); return edge; } /** * Performs greedy matching initialization. *

    * For every node we choose an incident edge of minimum slack and set its dual to half of this * slack. This maintains the nonnegativity of edge slacks. After that we go through all nodes * again, greedily increase their dual variables, and match them if it is possible. * * @return the number of unmatched nodes, which equals the number of trees */ private int initGreedy() { // set all dual variables to infinity for (int i = 0; i < nodeNum; i++) { nodes[i].dual = INFINITY; } // set dual variables to half of the minimum weight of the incident edges for (int i = 0; i < edgeNum; i++) { BlossomVEdge edge = edges[i]; if (edge.head[0].dual > edge.slack) { edge.head[0].dual = edge.slack; } if (edge.head[1].dual > edge.slack) { edge.head[1].dual = edge.slack; } } // divide dual variables by two; this ensures nonnegativity of all slacks; // decrease edge slacks accordingly for (int i = 0; i < edgeNum; i++) { BlossomVEdge edge = edges[i]; BlossomVNode source = edge.head[0]; BlossomVNode target = edge.head[1]; if (!source.isOuter) { source.isOuter = true; source.dual /= 2; } edge.slack -= source.dual; if (!target.isOuter) { target.isOuter = true; target.dual /= 2; } edge.slack -= target.dual; } // go through all vertices, greedily increase their dual variables to the minimum slack of // incident edges; // if there exists a tight unmatched edge in the neighborhood, match it int treeNum = nodeNum; for (int i = 0; i < nodeNum; i++) { BlossomVNode node = nodes[i]; if (!node.isInfinityNode()) { double minSlack = INFINITY; // find the minimum slack of incident edges for (BlossomVNode.IncidentEdgeIterator incidentEdgeIterator = node.incidentEdgesIterator(); incidentEdgeIterator.hasNext();) { BlossomVEdge edge = incidentEdgeIterator.next(); if (edge.slack < minSlack) { minSlack = edge.slack; } } node.dual += minSlack; double resultMinSlack = minSlack; // subtract minimum slack from the slacks of all incident edges for (BlossomVNode.IncidentEdgeIterator incidentEdgeIterator = node.incidentEdgesIterator(); incidentEdgeIterator.hasNext();) { BlossomVEdge edge = incidentEdgeIterator.next(); int dir = incidentEdgeIterator.getDir(); if (edge.slack <= resultMinSlack && node.isPlusNode() && edge.head[dir].isPlusNode()) { node.label = BlossomVNode.Label.INFINITY; edge.head[dir].label = BlossomVNode.Label.INFINITY; node.matched = edge; edge.head[dir].matched = edge; treeNum -= 2; } edge.slack -= resultMinSlack; } } } return treeNum; } /** * Initializes an auxiliary graph by adding tree edges between trees and adding (+, +) * cross-tree edges and (+, inf) edges to the appropriate heaps */ private void initAuxiliaryGraph() { // go through all tree roots and visit all incident edges of those roots. // if a (+, inf) edge is encountered => add it to the infinity heap // if a (+, +) edge is encountered and the opposite node hasn't been processed yet => // add this edge to the heap of (+, +) cross-tree edges for (BlossomVNode root = nodes[nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { BlossomVTree tree = root.tree; for (BlossomVNode.IncidentEdgeIterator edgeIterator = root.incidentEdgesIterator(); edgeIterator.hasNext();) { BlossomVEdge edge = edgeIterator.next(); BlossomVNode opposite = edge.head[edgeIterator.getDir()]; if (opposite.isInfinityNode()) { tree.addPlusInfinityEdge(edge); } else if (!opposite.isProcessed) { if (opposite.tree.currentEdge == null) { BlossomVTree.addTreeEdge(tree, opposite.tree); } opposite.tree.currentEdge.addPlusPlusEdge(edge); } } root.isProcessed = true; for (BlossomVTree.TreeEdgeIterator treeEdgeIterator = tree.treeEdgeIterator(); treeEdgeIterator.hasNext();) { BlossomVTreeEdge treeEdge = treeEdgeIterator.next(); treeEdge.head[treeEdgeIterator.getCurrentDirection()].currentEdge = null; } } // clear isProcessed flags for (BlossomVNode root = nodes[nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { root.isProcessed = false; } } /** * Allocates trees. Initializes the doubly linked list of tree roots via treeSiblingPrev and * treeSiblingNext. The same mechanism is used for keeping track of the children of a node in * the tree. The lookup {@code nodes[nodeNum] } is used to quickly find the first root in the * linked list */ private void allocateTrees() { BlossomVNode lastRoot = nodes[nodeNum]; for (int i = 0; i < nodeNum; i++) { BlossomVNode node = nodes[i]; if (node.isPlusNode()) { node.treeSiblingPrev = lastRoot; lastRoot.treeSiblingNext = node; lastRoot = node; new BlossomVTree(node); } } lastRoot.treeSiblingNext = null; } /** * Finishes the fractional matching initialization. Goes through all nodes and expands * half-loops. The total number or trees equals to the number of half-loops. Tree roots are * chosen arbitrarily. * * @return the number of trees in the resulting state object, which equals the number of * unmatched nodes */ private int finish() { if (DEBUG) { System.out.println("Finishing fractional matching initialization"); } BlossomVNode prevRoot = nodes[nodeNum]; int treeNum = 0; for (int i = 0; i < nodeNum; i++) { BlossomVNode node = nodes[i]; node.firstTreeChild = node.treeSiblingNext = node.treeSiblingPrev = null; if (!node.isOuter) { expandInit(node, null); // this node becomes unmatched node.parentEdge = null; node.label = PLUS; new BlossomVTree(node); prevRoot.treeSiblingNext = node; node.treeSiblingPrev = prevRoot; prevRoot = node; treeNum++; } } return treeNum; } /** * Performs lazy delta spreading during the fractional matching initialization. *

    * Goes through all nodes in the tree rooted at {@code root} and adds {@code eps} to the "+" * nodes and subtracts {@code eps} from "-" nodes. Updates incident edges respectively. * * @param heap the heap for storing best edges * @param root the root of the current tree * @param eps the accumulated dual change of the tree */ private void updateDuals( AddressableHeap heap, BlossomVNode root, double eps) { for (BlossomVTree.TreeNodeIterator treeNodeIterator = new BlossomVTree.TreeNodeIterator(root); treeNodeIterator.hasNext();) { BlossomVNode treeNode = treeNodeIterator.next(); if (treeNode.isProcessed) { treeNode.dual += eps; if (!treeNode.isTreeRoot) { BlossomVNode minusNode = treeNode.getOppositeMatched(); minusNode.dual -= eps; double delta = eps - treeNode.matched.slack; for (BlossomVNode.IncidentEdgeIterator iterator = minusNode.incidentEdgesIterator(); iterator.hasNext();) { iterator.next().slack += delta; } } for (BlossomVNode.IncidentEdgeIterator iterator = treeNode.incidentEdgesIterator(); iterator.hasNext();) { iterator.next().slack -= eps; } treeNode.isProcessed = false; } } // clear bestEdge after dual update while (!heap.isEmpty()) { BlossomVEdge edge = heap.findMin().getValue(); BlossomVNode node = edge.head[0].isInfinityNode() ? edge.head[0] : edge.head[1]; removeFromHeap(node); } } /** * Adds "best edges" to the {@code heap} * * @param heap the heap for storing best edges * @param node infinity node {@code bestEdge} is incident to * @param bestEdge current best edge of the {@code node} */ private void addToHead( AddressableHeap heap, BlossomVNode node, BlossomVEdge bestEdge) { bestEdge.handle = heap.insert(bestEdge.slack, bestEdge); node.bestEdge = bestEdge; } /** * Removes "best edge" from {@code heap} * * @param node the node which best edge should be removed from the heap it is stored in */ private void removeFromHeap(BlossomVNode node) { node.bestEdge.handle.delete(); node.bestEdge.handle = null; node.bestEdge = null; } /** * Finds blossom root during the fractional matching initialization * * @param blossomFormingEdge a tight (+, +) in-tree edge * @return the root of the blossom formed by the {@code blossomFormingEdge} */ private BlossomVNode findBlossomRootInit(BlossomVEdge blossomFormingEdge) { BlossomVNode[] branches = new BlossomVNode[] { blossomFormingEdge.head[0], blossomFormingEdge.head[1] }; BlossomVNode root, upperBound; // need to be scoped outside of the loop int dir = 0; while (true) { if (!branches[dir].isOuter) { root = branches[dir]; upperBound = branches[1 - dir]; break; } branches[dir].isOuter = false; if (branches[dir].isTreeRoot) { upperBound = branches[dir]; BlossomVNode jumpNode = branches[1 - dir]; while (jumpNode.isOuter) { jumpNode.isOuter = false; jumpNode = jumpNode.getTreeParent(); jumpNode.isOuter = false; jumpNode = jumpNode.getTreeParent(); } root = jumpNode; break; } BlossomVNode node = branches[dir].getTreeParent(); node.isOuter = false; branches[dir] = node.getTreeParent(); dir = 1 - dir; } BlossomVNode jumpNode = root; while (jumpNode != upperBound) { jumpNode = jumpNode.getTreeParent(); jumpNode.isOuter = true; jumpNode = jumpNode.getTreeParent(); jumpNode.isOuter = true; } return root; } /** * Handles encountered infinity edges incident to "+" nodes of the alternating tree. This method * determines whether the {@code infinityEdge} is tight. If so, it applies grow operation to it. * Otherwise, it determines whether it has smaller slack than {@code criticalEps}. If so, this * edge becomes the best edge of the "+" node in the tree. * * @param heap the heap of infinity edges incident to the currently processed tree * @param infinityEdge encountered infinity edge * @param dir direction of the infinityEdge to the infinity node * @param eps the eps of the current branch * @param criticalEps the value by which the epsilon of the current tree can be increased so * that the slacks of (+, +) cross-tree and in-tree edges don't become negative */ private void handleInfinityEdgeInit( AddressableHeap heap, BlossomVEdge infinityEdge, int dir, double eps, double criticalEps) { BlossomVNode inTreeNode = infinityEdge.head[1 - dir]; BlossomVNode oppositeNode = infinityEdge.head[dir]; if (infinityEdge.slack > eps) { // this edge isn't tight, but this edge can become a best // edge if (infinityEdge.slack < criticalEps) { // this edge can become a best edge if (oppositeNode.bestEdge == null) { // inTreeNode hadn't had any best edge before addToHead(heap, oppositeNode, infinityEdge); } else { if (infinityEdge.slack < oppositeNode.bestEdge.slack) { removeFromHeap(oppositeNode); addToHead(heap, oppositeNode, infinityEdge); } } } } else { if (DEBUG) { System.out.println("Growing an edge " + infinityEdge); } // this is a tight edge, can grow it if (oppositeNode.bestEdge != null) { removeFromHeap(oppositeNode); } oppositeNode.label = MINUS; inTreeNode.addChild(oppositeNode, infinityEdge, true); BlossomVNode plusNode = oppositeNode.matched.getOpposite(oppositeNode); if (plusNode.bestEdge != null) { removeFromHeap(plusNode); } plusNode.label = PLUS; oppositeNode.addChild(plusNode, plusNode.matched, true); } } /** * Augments the tree rooted at {@code treeRoot} via {@code augmentEdge}. The augmenting branch * starts at {@code branchStart} * * @param treeRoot the root of the tree to augment * @param branchStart the endpoint of the {@code augmentEdge} which belongs to the currentTree * @param augmentEdge a tight (+, +) cross-tree edge */ private void augmentBranchInit( BlossomVNode treeRoot, BlossomVNode branchStart, BlossomVEdge augmentEdge) { if (DEBUG) { System.out.println("Augmenting an edge " + augmentEdge); } for (BlossomVTree.TreeNodeIterator iterator = new BlossomVTree.TreeNodeIterator(treeRoot); iterator.hasNext();) { iterator.next().label = BlossomVNode.Label.INFINITY; } BlossomVNode plusNode = branchStart; BlossomVNode minusNode = branchStart.getTreeParent(); BlossomVEdge matchedEdge = augmentEdge; // alternate the matching from branch start up to the tree root while (minusNode != null) { plusNode.matched = matchedEdge; minusNode.matched = matchedEdge = minusNode.parentEdge; plusNode = minusNode.getTreeParent(); minusNode = plusNode.getTreeParent(); } treeRoot.matched = matchedEdge; treeRoot.removeFromChildList(); treeRoot.isTreeRoot = false; } /** * Forms a 1/2-valued odd circuit. Nodes from the odd circuit aren't actually contracted into a * single pseudonode. The blossomSibling references are set so that the nodes form a circular * linked list. The matching is updated respectively. *

    * Note: each node of the circuit can be expanded in the future and become a new tree * root. * * @param blossomFormingEdge a tight (+, +) in-tree edge that forms an odd circuit * @param treeRoot the root of the tree odd circuit belongs to */ private void shrinkInit(BlossomVEdge blossomFormingEdge, BlossomVNode treeRoot) { if (DEBUG) { System.out.println("Shrinking an edge " + blossomFormingEdge); } for (BlossomVTree.TreeNodeIterator iterator = new BlossomVTree.TreeNodeIterator(treeRoot); iterator.hasNext();) { iterator.next().label = BlossomVNode.Label.INFINITY; } BlossomVNode blossomRoot = findBlossomRootInit(blossomFormingEdge); // alternate the matching from blossom root up to the tree root if (!blossomRoot.isTreeRoot) { BlossomVNode minusNode = blossomRoot.getTreeParent(); BlossomVEdge prevEdge = minusNode.parentEdge; minusNode.matched = minusNode.parentEdge; BlossomVNode plusNode = minusNode.getTreeParent(); while (plusNode != treeRoot) { minusNode = plusNode.getTreeParent(); plusNode.matched = prevEdge; minusNode.matched = prevEdge = minusNode.parentEdge; plusNode = minusNode.getTreeParent(); } plusNode.matched = prevEdge; } // set the circular blossomSibling references BlossomVEdge prevEdge = blossomFormingEdge; for (BlossomVEdge.BlossomNodesIterator iterator = blossomFormingEdge.blossomNodesIterator(blossomRoot); iterator.hasNext();) { BlossomVNode current = iterator.next(); current.label = PLUS; if (iterator.getCurrentDirection() == 0) { current.blossomSibling = prevEdge; prevEdge = current.parentEdge; } else { current.blossomSibling = current.parentEdge; } } treeRoot.removeFromChildList(); treeRoot.isTreeRoot = false; } /** * Expands a 1/2-valued odd circuit. Essentially, changes the matching of the circuit so that * the {@code blossomNode} becomes matched to the {@code blossomNodeMatched} edge and all other * nodes become matched. Sets the labels of the matched nodes of the circuit to * {@link org.jgrapht.alg.matching.blossom.v5.BlossomVNode.Label#INFINITY} * * @param blossomNode some node that belongs to the "contracted" odd circuit * @param blossomNodeMatched a matched edge of the {@code blossomNode}, which doesn't belong to * the circuit. Note: this value can be {@code null} */ private void expandInit(BlossomVNode blossomNode, BlossomVEdge blossomNodeMatched) { if (DEBUG) { System.out.println("Expanding node " + blossomNode); } BlossomVNode currentNode = blossomNode.blossomSibling.getOpposite(blossomNode); blossomNode.isOuter = true; blossomNode.label = BlossomVNode.Label.INFINITY; blossomNode.matched = blossomNodeMatched; // change the matching in the blossom do { currentNode.matched = currentNode.blossomSibling; BlossomVEdge prevEdge = currentNode.blossomSibling; currentNode.isOuter = true; currentNode.label = BlossomVNode.Label.INFINITY; currentNode = currentNode.blossomSibling.getOpposite(currentNode); currentNode.matched = prevEdge; currentNode.isOuter = true; currentNode.label = BlossomVNode.Label.INFINITY; currentNode = currentNode.blossomSibling.getOpposite(currentNode); } while (currentNode != blossomNode); } /** * Solves the fractional matching problem formulated on the initial graph. See the class * description for more information about fractional matching initialization. * * @return the number of trees in the resulting state object, which equals to the number of * unmatched nodes. */ private int initFractional() { /* * For every free node u, which is adjacent to at least one "+" node in the current tree, we * keep track of an edge that has minimum slack and connects node u and some "+" node in the * current tree. This edge is called a "best edge". */ AddressableHeap heap = new PairingHeap<>(); for (BlossomVNode root = nodes[nodeNum].treeSiblingNext; root != null;) { BlossomVNode root2 = root.treeSiblingNext; BlossomVNode root3 = null; if (root2 != null) { root3 = root2.treeSiblingNext; } BlossomVNode currentNode = root; heap.clear(); double branchEps = 0; Action flag = NONE; BlossomVNode branchRoot = currentNode; BlossomVEdge criticalEdge = null; /* * Let's denote the minimum slack of (+, inf) edges incident to nodes of this tree as * infSlack. Critical eps is the minimum dual value which can be chosen as the branchEps * so that it doesn't violate the dual constraints on (+, +) in-tree and cross-tree * edges. It is always greater than or equal to the branchEps. If it is equal to the * branchEps, a shrink or augment operation can be applied immediately. If it is greater * than branchEps, we have to compare it with infSlack. If criticalEps is greater than * infSlack, we have to do a grow operation after we increase the branchEps by infSlack * - branchEps. Otherwise, we can apply shrink or augment operations after we increase * the branchEps by criticalEps - branchEps. */ double criticalEps = INFINITY; int criticalDir = -1; boolean primalOperation = false; /* * Grow a tree as much as possible. Main goal is to apply a primal operation. Therefore, * if we encounter a tight (+, +) cross-tree or in-tree edge => we won't be able to * increase dual objective function anymore (can't increase branchEps) => we go out of * the loop, apply lazy dual changes to the current branch and perform an augment or * shrink operation. * * A tree is grown in phases. Each phase starts with a new "branch"; the reason to start * a new branch is that the tree can't be grown any further without dual changes and * therefore no primal operation can be applied. That is why we choose an edge of * minimum slack from heap, and set the eps of the branch so that this edge becomes * tight */ while (true) { currentNode.isProcessed = true; currentNode.dual -= branchEps; // apply lazy delta spreading if (!currentNode.isTreeRoot) { // apply lazy delta spreading to the matched "-" node currentNode.getOppositeMatched().dual += branchEps; } // Process edges incident to the current node BlossomVNode.IncidentEdgeIterator iterator; for (iterator = currentNode.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge currentEdge = iterator.next(); int dir = iterator.getDir(); currentEdge.slack += branchEps; // apply lazy delta spreading BlossomVNode oppositeNode = currentEdge.head[dir]; if (oppositeNode.tree == root.tree) { // opposite node is in the same tree if (oppositeNode.isPlusNode()) { double slack = currentEdge.slack; if (!oppositeNode.isProcessed) { slack += branchEps; } if (2 * criticalEps > slack || criticalEdge == null) { flag = SHRINK; criticalEps = slack / 2; criticalEdge = currentEdge; criticalDir = dir; if (criticalEps <= branchEps) { // found a tight (+, +) in-tree edge to shrink => go out of the // loop primalOperation = true; break; } } } } else if (oppositeNode.isPlusNode()) { // current edge is a (+, +) cross-tree edge if (criticalEps >= currentEdge.slack || criticalEdge == null) { // flag = AUGMENT; criticalEps = currentEdge.slack; criticalEdge = currentEdge; criticalDir = dir; if (criticalEps <= branchEps) { // found a tight (+, +) cross-tree edge to augment primalOperation = true; break; } } } else { // opposite node is an infinity node since all other trees contain only one // "+" node handleInfinityEdgeInit(heap, currentEdge, dir, branchEps, criticalEps); } } if (primalOperation) { // finish processing incident edges while (iterator.hasNext()) { iterator.next().slack += branchEps; } // exit the loop since we can perform shrink or augment operation break; } else { /* * Move currentNode to the next unprocessed "+" node in the tree, growing the * tree if it is possible. Start a new branch if all nodes have been processed. * Exit the loop if the slack of fibHeap.min().getData() is >= than the slack of * critical edge (in this case we can perform primal operation after updating * the duals). */ if (currentNode.firstTreeChild != null) { // move to the next grandchild currentNode = currentNode.firstTreeChild.getOppositeMatched(); } else { // try to find another unprocessed node while (currentNode != branchRoot && currentNode.treeSiblingNext == null) { currentNode = currentNode.getTreeParent(); } if (currentNode.isMinusNode()) { // found an unprocessed node currentNode = currentNode.treeSiblingNext.getOppositeMatched(); } else if (currentNode == branchRoot) { // we've processed all nodes in the current branch BlossomVEdge minSlackEdge = heap.isEmpty() ? null : heap.findMin().getValue(); if (minSlackEdge == null || minSlackEdge.slack >= criticalEps) { // can perform primal operation after updating duals if (DEBUG) { System.out.println("Now current eps = " + criticalEps); } if (criticalEps > NO_PERFECT_MATCHING_THRESHOLD) { throw new IllegalArgumentException(NO_PERFECT_MATCHING); } branchEps = criticalEps; break; } else { // grow minimum slack edge if (DEBUG) { System.out.println("Growing an edge " + minSlackEdge); } int dirToFreeNode = minSlackEdge.head[0].isInfinityNode() ? 0 : 1; currentNode = minSlackEdge.head[1 - dirToFreeNode]; BlossomVNode minusNode = minSlackEdge.head[dirToFreeNode]; removeFromHeap(minusNode); minusNode.label = MINUS; currentNode.addChild(minusNode, minSlackEdge, true); branchEps = minSlackEdge.slack; // set new eps of the tree BlossomVNode plusNode = minusNode.getOppositeMatched(); if (plusNode.bestEdge != null) { removeFromHeap(plusNode); } plusNode.label = PLUS; minusNode.addChild(plusNode, minusNode.matched, true); if (DEBUG) { System.out .println( "New branch root is " + plusNode + ", eps = " + branchEps); } // Start a new branch currentNode = branchRoot = plusNode; } } } } } // update duals updateDuals(heap, root, branchEps); // apply primal operation BlossomVNode from = criticalEdge.head[1 - criticalDir]; BlossomVNode to = criticalEdge.head[criticalDir]; if (flag == SHRINK) { shrinkInit(criticalEdge, root); } else { augmentBranchInit(root, from, criticalEdge); if (to.isOuter) { // this node doesn't belong to a 1/2-values odd circuit augmentBranchInit(to, to, criticalEdge); // to is the root of the opposite tree } else { // this node belongs to a 1/2-values odd circuit expandInit(to, criticalEdge); } } root = root2; if (root != null && !root.isTreeRoot) { root = root3; } } return finish(); } /** * Enum for specifying the primal operation to perform with critical edge during fractional * matching initialization */ enum Action { NONE, SHRINK, AUGMENT, } } BlossomVNode.java000066400000000000000000000551661402514743400336050ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jheaps.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVNode.Label.*; /** * This class is a data structure for Kolmogorov's Blossom V algorithm. *

    * It represents a vertex of graph, and contains three major blocks of data needed for the * algorithm. *

      *
    • Node's state information, i.e. {@link BlossomVNode#label}, {@link BlossomVNode#isTreeRoot}, * etc. This information is maintained dynamically and is changed by * {@link BlossomVPrimalUpdater}
    • *
    • Information needed to maintain alternating tree structure. It is designed to be able to * quickly plant subtrees, split and concatenate child lists, traverse the tree up and down
    • *
    • information needed to maintain a "pyramid" of contracted nodes. The common use-cases are to * traverse the nodes of a blossom, to move from some node up to the outer blossom (or penultimate * blossom, if the outer one is being expanded)
    • *
    *

    * Each node has a dual variable. This is the only information that can be changed by the * {@link BlossomVDualUpdater}. This variable is updated lazily due to performance reasons. *

    * The edges incident to a node are stored in two linked lists. The first linked list is used for * outgoing edges; the other, for incoming edges. The notions of outgoing and incoming edges are * symmetric in the context of this algorithm since the initial graph is undirected. The first * element in the list of outgoing edges is {@code BlossomVNode#first[0]}, the first element in the * list of incoming edges is {@code BlossomVNode#first[1]}. *

    * A node is called a plus node if it belongs to the even layer of some alternating tree * (root has layer 0). Then its label is {@link Label#PLUS}. A node is called a minus node if * it belongs to the odd layer of some alternating tree. Then its label is {@link Label#MINUS}. A * node is called an infinity or free node if it doesn't belong to any alternating * tree. A node is called outer it belongs to the surface graph, i.e. it is not contracted. A * node is called a blossom or pseudonode if it emerged from contracting an odd * circuit. This implies that this node doesn't belong to the original graph. A node is called * matched, if it is matched to some other node. If a node is free, it means that it is * matched. If a node is not a free node, then it necessarily belongs to some tree. If a node isn't * matched, it necessarily is a tree root. * * @author Timofey Chudakov * @see KolmogorovWeightedPerfectMatching */ class BlossomVNode { /** * Node from the heap this node is stored in */ AddressableHeap.Handle handle; /** * True if this node is a tree root, implies that this node is outer and isn't matched */ boolean isTreeRoot; /** * True if this node is a blossom node (also called a "pseudonode", the notions are equivalent) */ boolean isBlossom; /** * True if this node is outer, i.e. it isn't contracted in some blossom and belongs to the * surface graph */ boolean isOuter; /** * Support variable to identify the nodes which have been "processed" in some sense by the * algorithm. Is used in the shrink and expand operations. *

    * For example, during the shrink operation we traverse the odd circuit and apply dual changes. * All nodes from this odd circuit are marked, i.e. {@code node.isMarked == true}. When a node * on this circuit is traversed, we set {@code node.isProcessed} to {@code true}. When a (+, +) * inner edge is encountered, we can determine whether the opposite endpoint has been processed * or not depending on the value of this variable. Without this variable inner (+, +) edges can * be processed twice (which is wrong). */ boolean isProcessed; /** * Support variable. In particular, it is used in shrink and expand operation to quickly * determine whether a node belongs to the current blossom or not. Is similar to the * {@link BlossomVNode#isProcessed} */ boolean isMarked; /** * Current label of this node. Is valid if this node is outer. */ Label label; /** * Two-element array of references of the first elements in the linked lists of edges that are * incident to this node. first[0] is the first outgoing edge, first[1] is the first incoming * edge, see {@link BlossomVEdge}. */ BlossomVEdge[] first; /** * Current dual variable of this node. If the node belongs to a tree and is an outer node, then * this value may not be valid. The true dual variable is $dual + tree.eps$ if this is a "+" * node and $dual - tree.eps$ if this is a "-" node. */ double dual; /** * An edge which is incident to this node and currently belongs to the matching */ BlossomVEdge matched; /** * A (+, inf) edge incident to this node. This variable is used during fractional matching * initialization and is assigned only to the infinity nodes. In fact, it is used to determine * for a particular infinity node the "cheapest" edge to connect it to the tree. The "cheapest" * means the edge with minimum slack. When the dual change is bounded by the dual constraints on * the (+, inf) edges, we choose the "cheapest" best edge, increase the duals of the tree if * needed, and grow this edge. */ BlossomVEdge bestEdge; /** * Reference to the tree this node belongs to */ BlossomVTree tree; /** * An edge to the parent node in the tree structure. */ BlossomVEdge parentEdge; /** * The first child in the linked list of children of this node. */ BlossomVNode firstTreeChild; /** * Reference of the next tree sibling in the doubly linked list of children of the node * parentEdge.getOpposite(this). Is null if this node is the last child of the parent node. *

    * If this node is a tree root, references the next tree root in the doubly linked list of tree * roots or is null if this is the last tree root. */ BlossomVNode treeSiblingNext; /** * Reference of the previous tree sibling in the doubly linked list of children of the node * parentEdge.getOpposite(this). If this node is the first child of the parent node (i.e. * parentEdge.getOpposite(this).firstTreeChild == this), references the last sibling. *

    * If this node is a tree root, references the previous tree root in the doubly linked list of * tree roots. The first element in the linked list of tree roots is a dummy node which is * stored in {@code nodes[nodeNum]}. This is done to quickly determine the first actual tree * root via {@code nodes[nodeNum].treeSiblingNext}. */ BlossomVNode treeSiblingPrev; /** * Reference of the blossom this node is contained in. The blossom parent is always one layer * higher than this node. */ BlossomVNode blossomParent; /** * Reference of some blossom that is higher than this node. This variable is used for the path * compression technique. It is used to quickly find the penultimate grandparent of this node, * i.e. a grandparent whose blossomParent is an outer node. */ BlossomVNode blossomGrandparent; /** * Reference of the next node in the blossom structure in the circular singly linked list of * blossom nodes. Is used to traverse the blossom nodes in a cyclic order. */ BlossomVEdge blossomSibling; /** * Position of this node in the array {@code state.nodes}. This helps to determine generic * counterpart of this node in constant time. */ int pos; /** * Constructs a new "+" node with a {@link Label#PLUS} label. */ public BlossomVNode(int pos) { this.first = new BlossomVEdge[2]; this.label = PLUS; this.pos = pos; } /** * Insert the {@code edge} into linked list of incident edges of this node in the specified * direction {@code dir} * * @param edge edge to insert in the linked list of incident edges * @param dir the direction of this edge with respect to this node */ public void addEdge(BlossomVEdge edge, int dir) { if (first[dir] == null) { // the list in the direction dir is empty first[dir] = edge.next[dir] = edge.prev[dir] = edge; } else { // the list in the direction dir isn't empty // append this edge to the end of the linked list edge.prev[dir] = first[dir].prev[dir]; edge.next[dir] = first[dir]; first[dir].prev[dir].next[dir] = edge; first[dir].prev[dir] = edge; } /* * this constraint is used to maintain the following feature: if an edge has direction dir * with respect to this node, then edge.head[dir] is the opposite node */ edge.head[1 - dir] = this; } /** * Removes the {@code edge} from the linked list of edges incident to this node. Updates the * first[dir] reference if needed. * * @param edge the edge to remove * @param dir the directions of the {@code edge} with respect to this node */ public void removeEdge(BlossomVEdge edge, int dir) { if (edge.prev[dir] == edge) { // it is the only edge of this node in the direction dir first[dir] = null; } else { // remove edge from the linked list edge.prev[dir].next[dir] = edge.next[dir]; edge.next[dir].prev[dir] = edge.prev[dir]; if (first[dir] == edge) { first[dir] = edge.next[dir]; } } } /** * Helper method, returns the tree grandparent of this node * * @return the tree grandparent of this node */ public BlossomVNode getTreeGrandparent() { BlossomVNode t = parentEdge.getOpposite(this); return t.parentEdge.getOpposite(t); } /** * Helper method, returns the tree parent of this node or null if this node has no tree parent * * @return node's tree parent or null if this node has no tree parent */ public BlossomVNode getTreeParent() { return parentEdge == null ? null : parentEdge.getOpposite(this); } /** * Appends the {@code child} to the end of the linked list of children of this node. The * {@code parentEdge} becomes the parent edge of the {@code child}. *

    * Variable {@code grow} is used to determine whether the {@code child} was an infinity node and * now is being added in tree structure. Then we have to set {@code child.firstTreeChild} to * {@code null} so that all its tree structure variables are changed. This allows us to avoid * overwriting the fields during tree destroying. * * @param child the new child of this node * @param parentEdge the edge between this node and {@code child} * @param grow true if {@code child} is being grown */ public void addChild(BlossomVNode child, BlossomVEdge parentEdge, boolean grow) { child.parentEdge = parentEdge; child.tree = tree; child.treeSiblingNext = firstTreeChild; if (grow) { // with this check we are able to avoid destroying the tree structure during the augment // operation child.firstTreeChild = null; } if (firstTreeChild == null) { child.treeSiblingPrev = child; } else { child.treeSiblingPrev = firstTreeChild.treeSiblingPrev; firstTreeChild.treeSiblingPrev = child; } firstTreeChild = child; } /** * Helper method, returns a node this node is matched to. * * @return a node this node is matched to. */ public BlossomVNode getOppositeMatched() { return matched.getOpposite(this); } /** * If this node is a tree root then this method removes this node from the tree root doubly * linked list. Otherwise, removes this vertex from the doubly linked list of tree children and * updates parent.firstTreeChild accordingly. */ public void removeFromChildList() { if (isTreeRoot) { treeSiblingPrev.treeSiblingNext = treeSiblingNext; if (treeSiblingNext != null) { treeSiblingNext.treeSiblingPrev = treeSiblingPrev; } } else { if (treeSiblingPrev.treeSiblingNext == null) { // this vertex is the first child => we have to update parent.firstTreeChild parentEdge.getOpposite(this).firstTreeChild = treeSiblingNext; } else { // this vertex isn't the first child treeSiblingPrev.treeSiblingNext = treeSiblingNext; } if (treeSiblingNext == null) { // this vertex is the last child => we have to set treeSiblingPrev of the firstChild if (parentEdge.getOpposite(this).firstTreeChild != null) { parentEdge.getOpposite(this).firstTreeChild.treeSiblingPrev = treeSiblingPrev; } } else { // this vertex isn't the last child treeSiblingNext.treeSiblingPrev = treeSiblingPrev; } } } /** * Appends the child list of this node to the beginning of the child list of the * {@code blossom}. * * @param blossom the node to which the children of the current node are moved */ public void moveChildrenTo(BlossomVNode blossom) { if (firstTreeChild != null) { if (blossom.firstTreeChild == null) { blossom.firstTreeChild = firstTreeChild; } else { BlossomVNode t = blossom.firstTreeChild.treeSiblingPrev; // concatenating child lists firstTreeChild.treeSiblingPrev.treeSiblingNext = blossom.firstTreeChild; blossom.firstTreeChild.treeSiblingPrev = firstTreeChild.treeSiblingPrev; // setting reference to the last child and updating firstTreeChild reference of the // blossom firstTreeChild.treeSiblingPrev = t; blossom.firstTreeChild = firstTreeChild; } firstTreeChild = null; // now this node has no children } } /** * Computes and returns the penultimate blossom of this node, i.e. the blossom which isn't outer * but whose blossomParent is outer. This method also applies path compression technique to the * blossomGrandparent references. More precisely, it finds the penultimate blossom of this node * and changes blossomGrandparent references of the previous nodes to point to the resulting * penultimate blossom. * * @return the penultimate blossom of this node */ public BlossomVNode getPenultimateBlossom() { BlossomVNode current = this; while (true) { if (!current.blossomGrandparent.isOuter) { current = current.blossomGrandparent; } else if (current.blossomGrandparent != current.blossomParent) { // this is the case when current.blossomGrandparent has been removed current.blossomGrandparent = current.blossomParent; } else { break; } } /* * Current references the penultimate blossom we were looking for. Now we change * blossomParent references to point to current */ BlossomVNode prev = this; BlossomVNode next; while (prev != current) { next = prev.blossomGrandparent; prev.blossomGrandparent = current; // apply path compression prev = next; } return current; } /** * Computes and returns the penultimate blossom of this node. The return value of this method * always equals to the value returned by {@link BlossomVNode#getPenultimateBlossom()}. However, * the main difference is that this method changes the blossomGrandparent references to point to * the node that is previous to the resulting penultimate blossom. This method is used during * the expand operation. * * @return the penultimate blossom of this node */ public BlossomVNode getPenultimateBlossomAndFixBlossomGrandparent() { BlossomVNode current = this; BlossomVNode prev = null; while (true) { if (!current.blossomGrandparent.isOuter) { prev = current; current = current.blossomGrandparent; } else if (current.blossomGrandparent != current.blossomParent) { // this is the case when current.blossomGrandparent has been removed current.blossomGrandparent = current.blossomParent; } else { break; } } /* * Now current node is the penultimate blossom, prev.blossomParent == current. All the * nodes, that are lower than prev, must have blossomGrandparent referencing a node, that is * not higher than prev */ if (prev != null) { BlossomVNode prevNode = this; BlossomVNode nextNode; while (prevNode != prev) { nextNode = prevNode.blossomGrandparent; prevNode.blossomGrandparent = prev; prevNode = nextNode; } } return current; } /** * Checks whether this node is a plus node * * @return true if the label of this node is {@link Label#PLUS}, false otherwise */ public boolean isPlusNode() { return label == PLUS; } /** * Checks whether this node is a minus node * * @return true if the label of this node is {@link Label#MINUS}, false otherwise */ public boolean isMinusNode() { return label == MINUS; } /** * Checks whether this node is an infinity node * * @return true if the label of this node is {@link Label#INFINITY}, false otherwise */ public boolean isInfinityNode() { return label == INFINITY; } /** * Returns the true dual variable of this node. If this node is outer and belongs to some tree * then it is subject to the lazy delta spreading technique. Otherwise, its dual is valid. * * @return the actual dual variable of this node */ public double getTrueDual() { if (isInfinityNode() || !isOuter) { return dual; } return isPlusNode() ? dual + tree.eps : dual - tree.eps; } /** * Returns an iterator over all incident edges of this node * * @return a new instance of IncidentEdgeIterator for this node */ public IncidentEdgeIterator incidentEdgesIterator() { return new IncidentEdgeIterator(); } @Override public String toString() { return "BlossomVNode pos = " + pos + ", dual: " + dual + ", true dual: " + getTrueDual() + ", label: " + label + (isMarked ? ", marked" : "") + (isProcessed ? ", processed" : "") + (blossomParent == null || isOuter ? "" : ", blossomParent = " + blossomParent.pos) + (matched == null ? "" : ", matched = " + matched); } /** * Represents nodes' labels */ public enum Label { /** * The node is on an even layer in the tree (root has layer 0) */ PLUS, /** * The node is on an odd layer in the tree (root has layer 0) */ MINUS, /** * This node doesn't belong to any tree and is matched to some other node */ INFINITY } /** * An iterator for traversing the edges incident to this node. *

    * This iterator has a feature that during every step it knows the next edge it'll return to the * caller. That's why it is safe to modify the current edge (move it to another node, for * example). */ public class IncidentEdgeIterator implements Iterator { /** * The direction of the current edge */ private int currentDir; /** * Direction of the {@code nextEdge} */ private int nextDir; /** * The edge that will be returned after the next call to * {@link IncidentEdgeIterator#next()}. Is null if all incident edges of the current node * have been traversed. */ private BlossomVEdge nextEdge; /** * Constructs a new instance of the IncidentEdgeIterator. */ public IncidentEdgeIterator() { nextDir = first[0] == null ? 1 : 0; nextEdge = first[nextDir]; } /** * Returns the direction of the edge returned by this iterator * * @return the direction of the edge returned by this iterator */ public int getDir() { return currentDir; } @Override public boolean hasNext() { return nextEdge != null; } @Override public BlossomVEdge next() { if (!hasNext()) { throw new NoSuchElementException(); } BlossomVEdge result = nextEdge; advance(); return result; } /** * Advances this iterator to the next incident edge. If previous edge was the last one with * direction 0, then the direction of this iterator changes. If previous edge was the last * incident edge, then {@code nextEdge} becomes null. */ private void advance() { currentDir = nextDir; nextEdge = nextEdge.next[nextDir]; if (nextEdge == first[0]) { nextEdge = first[1]; nextDir = 1; } else if (nextEdge == first[1]) { nextEdge = null; } } } } BlossomVOptions.java000066400000000000000000000212721402514743400343420ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.DualUpdateStrategy.MULTIPLE_TREE_CONNECTED_COMPONENTS; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.DualUpdateStrategy.MULTIPLE_TREE_FIXED_DELTA; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.*; /** * BlossomVOptions that define the strategies to use during the algorithm for updating duals and * initializing the matching *

    * According to the experimental results, the greedy initialization substantially speeds up the * algorithm. */ public class BlossomVOptions { /** * All possible options */ public static final BlossomVOptions[] ALL_OPTIONS = new BlossomVOptions[] { new BlossomVOptions(NONE, MULTIPLE_TREE_CONNECTED_COMPONENTS, true, true), // [0] new BlossomVOptions(NONE, MULTIPLE_TREE_CONNECTED_COMPONENTS, true, false), // [1] new BlossomVOptions(NONE, MULTIPLE_TREE_CONNECTED_COMPONENTS, false, true), // [2] new BlossomVOptions(NONE, MULTIPLE_TREE_CONNECTED_COMPONENTS, false, false), // [3] new BlossomVOptions(NONE, MULTIPLE_TREE_FIXED_DELTA, true, true), // [4] new BlossomVOptions(NONE, MULTIPLE_TREE_FIXED_DELTA, true, false), // [5] new BlossomVOptions(NONE, MULTIPLE_TREE_FIXED_DELTA, false, true), // [6] new BlossomVOptions(NONE, MULTIPLE_TREE_FIXED_DELTA, false, false), // [7] new BlossomVOptions(GREEDY, MULTIPLE_TREE_CONNECTED_COMPONENTS, true, true), // [8] new BlossomVOptions(GREEDY, MULTIPLE_TREE_CONNECTED_COMPONENTS, true, false), // [9] new BlossomVOptions(GREEDY, MULTIPLE_TREE_CONNECTED_COMPONENTS, false, true), // [10] new BlossomVOptions(GREEDY, MULTIPLE_TREE_CONNECTED_COMPONENTS, false, false), // [11] new BlossomVOptions(GREEDY, MULTIPLE_TREE_FIXED_DELTA, true, true), // [12] new BlossomVOptions(GREEDY, MULTIPLE_TREE_FIXED_DELTA, true, false), // [13] new BlossomVOptions(GREEDY, MULTIPLE_TREE_FIXED_DELTA, false, true), // [14] new BlossomVOptions(GREEDY, MULTIPLE_TREE_FIXED_DELTA, false, true), // [15] new BlossomVOptions(FRACTIONAL, MULTIPLE_TREE_CONNECTED_COMPONENTS, true, true), // [16] new BlossomVOptions(FRACTIONAL, MULTIPLE_TREE_CONNECTED_COMPONENTS, true, false), // [17] new BlossomVOptions(FRACTIONAL, MULTIPLE_TREE_CONNECTED_COMPONENTS, false, true), // [18] new BlossomVOptions(FRACTIONAL, MULTIPLE_TREE_CONNECTED_COMPONENTS, false, false), // [19] new BlossomVOptions(FRACTIONAL, MULTIPLE_TREE_FIXED_DELTA, true, true), // [20] new BlossomVOptions(FRACTIONAL, MULTIPLE_TREE_FIXED_DELTA, true, false), // [21] new BlossomVOptions(FRACTIONAL, MULTIPLE_TREE_FIXED_DELTA, false, true), // [22] new BlossomVOptions(FRACTIONAL, MULTIPLE_TREE_FIXED_DELTA, false, true), // [23] }; /** * Default algorithm initialization type */ private static final InitializationType DEFAULT_INITIALIZATION_TYPE = FRACTIONAL; /** * Default dual updates strategy */ private static final DualUpdateStrategy DEFAULT_DUAL_UPDATE_TYPE = MULTIPLE_TREE_FIXED_DELTA; /** * Default value for the flag {@link BlossomVOptions#updateDualsBefore} */ private static final boolean DEFAULT_UPDATE_DUALS_BEFORE = true; /** * Default value for the flag {@link BlossomVOptions#updateDualsAfter} */ private static final boolean DEFAULT_UPDATE_DUALS_AFTER = false; /** * What greedy strategy to use to perform a global dual update */ DualUpdateStrategy dualUpdateStrategy; /** * What strategy to choose to initialize the matching before the main phase of the algorithm */ InitializationType initializationType; /** * Whether to update duals of the tree before growth */ boolean updateDualsBefore; /** * Whether to update duals of the tree after growth */ boolean updateDualsAfter; /** * Constructs a custom set of options for the algorithm * * @param dualUpdateStrategy greedy strategy to update dual variables globally * @param initializationType strategy for initializing the matching * @param updateDualsBefore whether to update duals of the tree before growth * @param updateDualsAfter whether to update duals of the tree after growth */ public BlossomVOptions( InitializationType initializationType, DualUpdateStrategy dualUpdateStrategy, boolean updateDualsBefore, boolean updateDualsAfter) { this.dualUpdateStrategy = dualUpdateStrategy; this.initializationType = initializationType; this.updateDualsBefore = updateDualsBefore; this.updateDualsAfter = updateDualsAfter; } /** * Constructs a new options instance with a {@code initializationType} * * @param initializationType defines a strategy to use to initialize the matching */ public BlossomVOptions(InitializationType initializationType) { this( initializationType, DEFAULT_DUAL_UPDATE_TYPE, DEFAULT_UPDATE_DUALS_BEFORE, DEFAULT_UPDATE_DUALS_AFTER); } /** * Constructs a default set of options for the algorithm */ public BlossomVOptions() { this( DEFAULT_INITIALIZATION_TYPE, DEFAULT_DUAL_UPDATE_TYPE, DEFAULT_UPDATE_DUALS_BEFORE, DEFAULT_UPDATE_DUALS_AFTER); } @Override public String toString() { return "BlossomVOptions{initializationType=" + initializationType + ", dualUpdateStrategy=" + dualUpdateStrategy + ", updateDualsBefore=" + updateDualsBefore + ", updateDualsAfter=" + updateDualsAfter + '}'; } /** * Returns the {@link BlossomVOptions#updateDualsBefore} flag * * @return the flag {@link BlossomVOptions#updateDualsBefore} */ public boolean isUpdateDualsBefore() { return updateDualsBefore; } /** * Returns the {@link BlossomVOptions#updateDualsAfter} flag * * @return the flag {@link BlossomVOptions#updateDualsAfter} */ public boolean isUpdateDualsAfter() { return updateDualsAfter; } /** * Returns dual updates strategy * * @return dual updates strategy */ public DualUpdateStrategy getDualUpdateStrategy() { return dualUpdateStrategy; } /** * Returns initialization type * * @return initialization type */ public InitializationType getInitializationType() { return initializationType; } /** * Enum for choosing dual updates strategy */ public enum DualUpdateStrategy { MULTIPLE_TREE_FIXED_DELTA { @Override public String toString() { return "Multiple tree fixed delta"; } }, MULTIPLE_TREE_CONNECTED_COMPONENTS { @Override public String toString() { return "Multiple tree connected components"; } }; /** * Returns the name of the dual updates strategy * * @return the name of the dual updates strategy */ @Override public abstract String toString(); } /** * Enum for types of matching initialization */ public enum InitializationType { GREEDY { @Override public String toString() { return "Greedy initialization"; } }, NONE { @Override public String toString() { return "None"; } }, FRACTIONAL { @Override public String toString() { return "Fractional matching initializations"; } }; /** * Returns the name of the initialization type * * @return the name of the initialization type */ @Override public abstract String toString(); } } BlossomVPrimalUpdater.java000066400000000000000000001450241402514743400354620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import static org.jgrapht.alg.matching.blossom.v5.BlossomVNode.Label.*; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.DEBUG; /** * This class is used by {@link KolmogorovWeightedPerfectMatching} for performing primal operations: * grow, augment, shrink and expand. This class operates on alternating trees, blossom structures, * and node states. It changes them after applying any primal operation. Also, this class can add * and subtract some values from nodes' dual variables; it never changes their actual dual * variables. *

    * The augment operation is used to increase the cardinality of the matching. It is applied to a * tight (+, +) cross-tree edge. Its main purpose is to alter the matching on the simple path * between tree roots through the tight edge, destroy the previous tree structures, update the state * of the node, and change the presence of edges in the priority queues. This operation doesn't * destroy the tree structure; this technique is called lazy tree structure destroying. The * information of the nodes from the tree structure block is overwritten when a node is being added * to another tree. This operation doesn't change the matching in the contracted blossoms. *

    * The grow operation is used to add new nodes to a given tree. This operation is applied only to * tight infinity edges. It always adds even number of nodes. This operation can grow the tree * recursively in the depth-first order. If it encounters a tight (+, +) cross-tree edge, it stops * growing and performs immediate augmentation. *

    * The shrink operation contracts an odd node circuit and introduces a new pseudonode. It is applied * to tight (+, +) in-tree edges. It changes the state so than the contracted nodes don't appear in * the surface graph. If during the changing of the endpoints of boundary edge a tight (+, +) * cross-tree edge is encountered, an immediate augmentation is performed. *

    * The expand operation brings the contracted blossom nodes to the surface graph. It is applied only * to a "-" blossom with zero dual variable. The operation determines the two branches of a blossom: * an even and an odd one. The formercontains an even number of edges and can be empty, the latter * contains an odd number of edges and necessarily contains at least one edge. An even branch is * inserted into the tree. The state of the algorithm is changes respectively (node duals, tree * structure, etc.). If some boundary edge in a tight (+, +) cross-tree edge, an immediate * augmentation is performed. *

    * The immediate augmentations are used to speed up the algorithm. More detailed description of the * primal operations can be found in their Javadoc. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see KolmogorovWeightedPerfectMatching * @see BlossomVDualUpdater */ class BlossomVPrimalUpdater { /** * State information needed for the algorithm */ private BlossomVState state; /** * Constructs a new instance of BlossomVPrimalUpdater * * @param state contains the graph and associated information */ public BlossomVPrimalUpdater(BlossomVState state) { this.state = state; } /** * Performs grow operation. This is invoked on the plus-infinity {@code growEdge}, which * connects a "+" node in the tree and an infinity matched node. The {@code growEdge} and the * matched free edge are added to the tree structure. Two new nodes are added to the tree: minus * node and plus node. Let's call the node incident to the {@code growEdge} and opposite to the * minusNode the "tree node". *

    * As the result, following actions are performed: *

      *
    • Add new child to the children of tree node and minus node
    • *
    • Set parent edges of minus and plus nodes
    • *
    • If minus node is a blossom, add it to the heap of "-" blossoms
    • *
    • Remove growEdge from the heap of infinity edges
    • *
    • Remove former infinity edges and add new (+, +) in-tree and cross-tree edges, (+, -) * cross tree edges to the appropriate heaps (due to the changes of the labels of the minus and * plus nodes)
    • *
    • Add new infinity edge from the plus node
    • *
    • Add new tree edges is necessary
    • *
    • Subtract tree.eps from the slacks of all edges incident to the minus node
    • *
    • Add tree.eps to the slacks of all edges incident to the plus node
    • *
    *

    * If the {@code manyGrows} flag is true, performs recursive growing of the tree. * * @param growEdge the tight edge between node in the tree and minus node * @param recursiveGrow specifies whether to perform recursive growing * @param immediateAugment a flag that indicates whether to perform immediate augmentation if a * tight (+, +) cross-tree edge is encountered */ public void grow(BlossomVEdge growEdge, boolean recursiveGrow, boolean immediateAugment) { if (DEBUG) { System.out.println("Growing edge " + growEdge); } long start = System.nanoTime(); int initialTreeNum = state.treeNum; int dirToMinusNode = growEdge.head[0].isInfinityNode() ? 0 : 1; BlossomVNode nodeInTheTree = growEdge.head[1 - dirToMinusNode]; BlossomVNode minusNode = growEdge.head[dirToMinusNode]; BlossomVNode plusNode = minusNode.getOppositeMatched(); nodeInTheTree.addChild(minusNode, growEdge, true); minusNode.addChild(plusNode, minusNode.matched, true); BlossomVNode stop = plusNode; while (true) { minusNode.label = MINUS; plusNode.label = PLUS; minusNode.isMarked = plusNode.isMarked = false; processMinusNodeGrow(minusNode); processPlusNodeGrow(plusNode, recursiveGrow, immediateAugment); if (initialTreeNum != state.treeNum) { break; } if (plusNode.firstTreeChild != null) { minusNode = plusNode.firstTreeChild; plusNode = minusNode.getOppositeMatched(); } else { while (plusNode != stop && plusNode.treeSiblingNext == null) { plusNode = plusNode.getTreeParent(); } if (plusNode.isMinusNode()) { minusNode = plusNode.treeSiblingNext; plusNode = minusNode.getOppositeMatched(); } else { break; } } } state.statistics.growTime += System.nanoTime() - start; } /** * Performs augment operation. This is invoked on a tight (+, +) cross-tree edge. It increases * the matching by 1, converts the trees on both sides into the set of free matched edges, and * applies lazy delta spreading. *

    * For each tree the following actions are performed: *

      *
    • Labels of all nodes change to INFINITY
    • *
    • tree.eps is subtracted from "-" nodes' duals and added to the "+" nodes' duals
    • *
    • tree.eps is subtracted from all edges incident to "+" nodes and added to all edges * incident to "-" nodes. Consecutively, the slacks of the (+, -) in-tree edges stay * unchanged
    • *
    • Former (-, +) and (+, +) are substituted with the (+, inf) edges (removed and added to * appropriate heaps).
    • *
    • The cardinality of the matching is increased by 1
    • *
    • Tree structure references are set to null
    • *
    • Tree roots are removed from the linked list of tree roots
    • *
    *

    * These actions change only the surface graph. They don't change the nodes and edges in the * pseudonodes. * * @param augmentEdge the edge to augment */ public void augment(BlossomVEdge augmentEdge) { if (DEBUG) { System.out.println("Augmenting edge " + augmentEdge); } long start = System.nanoTime(); // augment trees on both sides for (int dir = 0; dir < 2; dir++) { BlossomVNode node = augmentEdge.head[dir]; augmentBranch(node, augmentEdge); node.matched = augmentEdge; } state.statistics.augmentTime += System.nanoTime() - start; } /** * Performs shrink operation. This is invoked on a tight (+, +) in-tree edge. The result of this * operation is the substitution of an odd circuit with a single node. This means that we * consider the set of nodes of odd cardinality as a single node. *

    * In the shrink operation the following main actions are performed: *

      *
    • Lazy dual updates are applied to all inner edges and nodes on the circuit. Thus, the * inner edges and nodes in the pseudonodes have valid slacks and dual variables
    • *
    • The endpoints of the boundary edges are moved to the new blossom node, which has label * {@link BlossomVNode.Label#PLUS} *
    • Lazy dual updates are applied to boundary edges and newly created blossom
    • *
    • Children of blossom nodes are moved to the blossom, their parent edges are changed * respectively
    • *
    • The blossomSibling references are set so that they form a circular linked list
    • *
    • If the blossom becomes a tree root, it substitutes the previous tree's root in the linked * list of tree roots
    • *
    • Since the newly created blossom with "+" label can change the classification of edges, * their presence in heaps is updated
    • *
    * * @param blossomFormingEdge the tight (+, +) in-tree edge * @param immediateAugment a flag that indicates whether to perform immediate augmentation if a * tight (+, +) cross-tree edge is encountered * @return the newly created blossom */ public BlossomVNode shrink(BlossomVEdge blossomFormingEdge, boolean immediateAugment) { long start = System.nanoTime(); if (DEBUG) { System.out.println("Shrinking edge " + blossomFormingEdge); } BlossomVNode blossomRoot = findBlossomRoot(blossomFormingEdge); BlossomVTree tree = blossomRoot.tree; /* * We don't actually need position of the blossom node since blossom nodes aren't stored in * the state.nodes array. We use blossom's position as its id for debug purposes. */ BlossomVNode blossom = new BlossomVNode(state.nodeNum + state.blossomNum); // initialize blossom node blossom.tree = tree; blossom.isBlossom = true; blossom.isOuter = true; blossom.isTreeRoot = blossomRoot.isTreeRoot; blossom.dual = -tree.eps; if (blossom.isTreeRoot) { tree.root = blossom; } else { blossom.matched = blossomRoot.matched; } // mark all blossom nodes for (BlossomVEdge.BlossomNodesIterator iterator = blossomFormingEdge.blossomNodesIterator(blossomRoot); iterator.hasNext();) { iterator.next().isMarked = true; } // move edges and children, change slacks if necessary BlossomVEdge augmentEdge = updateTreeStructure(blossomRoot, blossomFormingEdge, blossom); // create circular linked list of circuit nodes setBlossomSiblings(blossomRoot, blossomFormingEdge); // reset marks of blossom nodes blossomRoot.isMarked = false; blossomRoot.isProcessed = false; for (BlossomVNode current = blossomRoot.blossomSibling.getOpposite(blossomRoot); current != blossomRoot; current = current.blossomSibling.getOpposite(current)) { current.isMarked = false; current.isProcessed = false; } blossomRoot.matched = null; // now new blossom is matched (used when finishing the matching state.statistics.shrinkNum++; state.blossomNum++; state.statistics.shrinkTime += System.nanoTime() - start; if (augmentEdge != null && immediateAugment) { if (DEBUG) { System.out.println("Bingo shrink"); } augment(augmentEdge); } return blossom; } /** * Performs expand operation. This is invoked on a previously contracted pseudonode. The result * of this operation is bringing the nodes in the blossom to the surface graph. An even branch * of the blossom is inserted into the tree structure. Endpoints of the edges incident to the * blossom are moved one layer down. The slack of the inner and boundary edges are update * according to the lazy delta spreading technique. *

    * Note: only "-" blossoms can be expanded. At that moment their dual variables are * always zero. This is the reason why they don't need to be stored to compute the dual * solution. *

    * In the expand operation the following actions are performed: *

      *
    • Endpoints of the boundary edges are updated
    • *
    • The matching in the blossom is changed. Note: the resulting matching doesn't * depend on the previous matching
    • *
    • isOuter flags are updated
    • *
    • node.tree are updated
    • *
    • Tree structure is updated including parent edges and tree children of the nodes on the * even branch
    • *
    • The endpoints of some edges change their labels to "+" => their slacks are changed * according to the lazy delta spreading and their presence in heaps also changes
    • *
    * * @param blossom the blossom to expand * @param immediateAugment a flag that indicates whether to perform immediate augmentation if a * tight (+, +) cross-tree edge is encountered */ public void expand(BlossomVNode blossom, boolean immediateAugment) { if (DEBUG) { System.out.println("Expanding blossom " + blossom); } long start = System.nanoTime(); BlossomVTree tree = blossom.tree; double eps = tree.eps; blossom.dual -= eps; blossom.tree.removeMinusBlossom(blossom); // it doesn't belong to the tree no more BlossomVNode branchesEndpoint = blossom.parentEdge.getCurrentOriginal(blossom).getPenultimateBlossom(); if (DEBUG) { printBlossomNodes(branchesEndpoint); } // the node which is matched to the node from outside BlossomVNode blossomRoot = blossom.matched.getCurrentOriginal(blossom).getPenultimateBlossom(); // mark blossom nodes BlossomVNode current = blossomRoot; do { current.isMarked = true; current = current.blossomSibling.getOpposite(current); } while (current != blossomRoot); // move all edge from blossom to penultimate children blossom.removeFromChildList(); for (BlossomVNode.IncidentEdgeIterator iterator = blossom.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); BlossomVNode penultimateChild = edge.headOriginal[1 - iterator.getDir()] .getPenultimateBlossomAndFixBlossomGrandparent(); edge.moveEdgeTail(blossom, penultimateChild); } // reverse the circular blossomSibling references so that the first branch in even branch if (!forwardDirection(blossomRoot, branchesEndpoint)) { reverseBlossomSiblings(blossomRoot); } // change the matching, the labeling and the dual information on the odd branch expandOddBranch(blossomRoot, branchesEndpoint, tree); // change the matching, the labeling and dual information on the even branch BlossomVEdge augmentEdge = expandEvenBranch(blossomRoot, branchesEndpoint, blossom); // reset marks of blossom nodes current = blossomRoot; do { current.isMarked = false; current.isProcessed = false; current = current.blossomSibling.getOpposite(current); } while (current != blossomRoot); state.statistics.expandNum++; state.removedNum++; if (DEBUG) { tree.printTreeNodes(); } state.statistics.expandTime += System.nanoTime() - start; if (immediateAugment && augmentEdge != null) { if (DEBUG) { System.out.println("Bingo expand"); } augment(augmentEdge); } } /** * Processes a minus node in the grow operation. Applies lazy delta spreading, adds new (-,+) * cross-tree edges, removes former (+, inf) edges. * * @param minusNode a minus endpoint of the matched edge that is being appended to the tree */ private void processMinusNodeGrow(BlossomVNode minusNode) { double eps = minusNode.tree.eps; minusNode.dual += eps; // maintain heap of "-" blossoms if (minusNode.isBlossom) { minusNode.tree.addMinusBlossom(minusNode); } // maintain minus-plus edges in the minus-plus heaps in the tree edges for (BlossomVNode.IncidentEdgeIterator iterator = minusNode.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); BlossomVNode opposite = edge.head[iterator.getDir()]; edge.slack -= eps; if (opposite.isPlusNode()) { if (opposite.tree != minusNode.tree) { // encountered (-,+) cross-tree edge if (opposite.tree.currentEdge == null) { BlossomVTree.addTreeEdge(minusNode.tree, opposite.tree); } opposite.tree.removePlusInfinityEdge(edge); opposite.tree.currentEdge .addToCurrentMinusPlusHeap(edge, opposite.tree.currentDirection); } else if (opposite != minusNode.getOppositeMatched()) { // encountered a former (+, inf) edge minusNode.tree.removePlusInfinityEdge(edge); } } } } /** * Processes a plus node during the grow operation. Applies lazy delta spreading, removes former * (+, inf) edges, adds new (+, +) in-tree and cross-tree edges, new (+, -) cross-tree edges. * When the {@code manyGrows} flag is on, collects the tight (+, inf) edges on grows them as * well. *

    * Note: the recursive grows must be done ofter the grow operation on the current edge is * over. This ensures correct state of the heaps and the edges' slacks. * * @param node a plus endpoint of the matched edge that is being appended to the tree * @param recursiveGrow a flag that indicates whether to grow the tree recursively * @param immediateAugment a flag that indicates whether to perform immediate augmentation if a * tight (+, +) cross-tree edge is encountered */ private void processPlusNodeGrow( BlossomVNode node, boolean recursiveGrow, boolean immediateAugment) { double eps = node.tree.eps; node.dual -= eps; BlossomVEdge augmentEdge = null; for (BlossomVNode.IncidentEdgeIterator iterator = node.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); BlossomVNode opposite = edge.head[iterator.getDir()]; // maintain heap of plus-infinity edges edge.slack += eps; if (opposite.isPlusNode()) { // this is a (+,+) edge if (opposite.tree == node.tree) { // this is blossom-forming edge node.tree.removePlusInfinityEdge(edge); node.tree.addPlusPlusEdge(edge); } else { // this is plus-plus edge to another trees if (opposite.tree.currentEdge == null) { BlossomVTree.addTreeEdge(node.tree, opposite.tree); } opposite.tree.removePlusInfinityEdge(edge); opposite.tree.currentEdge.addPlusPlusEdge(edge); if (edge.slack <= node.tree.eps + opposite.tree.eps) { augmentEdge = edge; } } } else if (opposite.isMinusNode()) { // this is a (+,-) edge if (opposite.tree != node.tree) { // this is (+,-) edge to another trees if (opposite.tree.currentEdge == null) { BlossomVTree.addTreeEdge(node.tree, opposite.tree); } opposite.tree.currentEdge .addToCurrentPlusMinusHeap(edge, opposite.tree.currentDirection); } } else if (opposite.isInfinityNode()) { node.tree.addPlusInfinityEdge(edge); // this edge can be grown as well // it can be the case when this edge can't be grown because opposite vertex is // already added // to this tree via some other grow operation if (recursiveGrow && edge.slack <= eps && !edge.getOpposite(node).isMarked) { if (DEBUG) { System.out.println("Growing edge " + edge); } BlossomVNode minusNode = edge.getOpposite(node); BlossomVNode plusNode = minusNode.getOppositeMatched(); minusNode.isMarked = plusNode.isMarked = true; node.addChild(minusNode, edge, true); minusNode.addChild(plusNode, minusNode.matched, true); } } } if (immediateAugment && augmentEdge != null) { if (DEBUG) { System.out.println("Bingo grow"); } augment(augmentEdge); } state.statistics.growNum++; } /** * Expands an even branch of the blossom. Here it is assumed that the blossomSiblings are * directed in the way that the even branch goes from {@code blossomRoot} to * {@code branchesEndpoint}. *

    * The method traverses the nodes twice: first it changes the tree structure, updates the * labeling and flags, adds children, and changes the matching. After that it changes the slacks * of the edges according to the lazy delta spreading and their presence in heaps. This * operation is done in two steps because the later step requires correct labeling of the nodes * on the branch. *

    * Note: this branch may consist of only one node. In this case {@code blossomRoot} and * {@code branchesEndpoint} are the same nodes * * @param blossomRoot the node of the blossom which is matched from the outside * @param branchesEndpoint the common endpoint of the even and odd branches * @param blossom the node that is being expanded * @return a tight (+, +) cross-tree edge if it is encountered, null otherwise */ private BlossomVEdge expandEvenBranch( BlossomVNode blossomRoot, BlossomVNode branchesEndpoint, BlossomVNode blossom) { BlossomVEdge augmentEdge = null; BlossomVTree tree = blossom.tree; blossomRoot.matched = blossom.matched; blossomRoot.tree = tree; blossomRoot.addChild(blossom.matched.getOpposite(blossomRoot), blossomRoot.matched, false); BlossomVNode current = blossomRoot; BlossomVNode prevNode = current; current.label = MINUS; current.isOuter = true; current.parentEdge = blossom.parentEdge; // first traversal. It is done from blossomRoot to branchesEndpoint, i.e. from higher // layers of the tree to the lower while (current != branchesEndpoint) { // process "+" node current = current.blossomSibling.getOpposite(current); current.label = PLUS; current.isOuter = true; current.tree = tree; current.matched = current.blossomSibling; BlossomVEdge prevMatched = current.blossomSibling; current.addChild(prevNode, prevNode.blossomSibling, false); prevNode = current; // process "-" node current = current.blossomSibling.getOpposite(current); current.label = MINUS; current.isOuter = true; current.tree = tree; current.matched = prevMatched; current.addChild(prevNode, prevNode.blossomSibling, false); prevNode = current; } blossom.parentEdge .getOpposite(branchesEndpoint).addChild(branchesEndpoint, blossom.parentEdge, false); // second traversal, update edge slacks and their presence in heaps current = blossomRoot; expandMinusNode(current); while (current != branchesEndpoint) { current = current.blossomSibling.getOpposite(current); BlossomVEdge edge = expandPlusNode(current); if (edge != null) { augmentEdge = edge; } current.isProcessed = true; // this is needed for correct processing of (+, +) edges // connecting two node on the branch current = current.blossomSibling.getOpposite(current); expandMinusNode(current); } return augmentEdge; } /** * Expands the nodes on an odd branch. Here it is assumed that the blossomSiblings are directed * in the way the odd branch goes from {@code branchesEndpoint} to {@code blossomRoot}. *

    * The method traverses the nodes only once setting the labels, flags, updating the matching, * removing former (+, -) edges and creating new (+, inf) edges in the corresponding heaps. The * method doesn't process the {@code blossomRoot} and {@code branchesEndpoint} as they belong to * the even branch. * * @param blossomRoot the node that is matched from the outside * @param branchesEndpoint the common node of the even and odd branches * @param tree the tree the blossom was previously in */ private void expandOddBranch( BlossomVNode blossomRoot, BlossomVNode branchesEndpoint, BlossomVTree tree) { BlossomVNode current = branchesEndpoint.blossomSibling.getOpposite(branchesEndpoint); // the traversal is done from branchesEndpoint to blossomRoot, i.e. from // lower layers to higher while (current != blossomRoot) { current.label = BlossomVNode.Label.INFINITY; current.isOuter = true; current.tree = null; current.matched = current.blossomSibling; BlossomVEdge prevMatched = current.blossomSibling; expandInfinityNode(current, tree); current = current.blossomSibling.getOpposite(current); current.label = BlossomVNode.Label.INFINITY; current.isOuter = true; current.tree = null; current.matched = prevMatched; expandInfinityNode(current, tree); current = current.blossomSibling.getOpposite(current); } } /** * Changes dual information of the {@code plusNode} and edge incident to it. This method relies * on the labeling produced by the first traversal of the * {@link BlossomVPrimalUpdater#expandEvenBranch(BlossomVNode, BlossomVNode, BlossomVNode)} and * on the isProcessed flags of the nodes on the even branch that have been traversed already. It * also assumes that all blossom nodes are marked. *

    * Since one of endpoints of the edges previously incident to the blossom changes its label, we * have to update the slacks of the boundary edges incindent to the {@code plusNode}. * * @param plusNode the "+" node from the even branch * @return a tight (+, +) cross-tree edge if it is encountered, null otherwise */ private BlossomVEdge expandPlusNode(BlossomVNode plusNode) { BlossomVEdge augmentEdge = null; double eps = plusNode.tree.eps; // the plusNode.tree is assumed to be correct plusNode.dual -= eps; // apply lazy delta spreading for (BlossomVNode.IncidentEdgeIterator iterator = plusNode.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); BlossomVNode opposite = edge.head[iterator.getDir()]; // update slack of the edge if (opposite.isMarked && opposite.isPlusNode()) { // this is an inner (+, +) edge if (!opposite.isProcessed) { // we encounter this edge for the first time edge.slack += 2 * eps; } } else if (!opposite.isMarked) { // this is boundary edge edge.slack += 2 * eps; // the endpoint changes its label to "+" } else if (!opposite.isMinusNode()) { // this edge is inner edge between even and odd branches or it is an inner (+, +) // edge edge.slack += eps; } // update its presence in the heap of edges if (opposite.isPlusNode()) { if (opposite.tree == plusNode.tree) { // this edge becomes a (+, +) in-tree edge if (!opposite.isProcessed) { // if opposite.isProcessed = true => this is an inner (+, +) edge => its // slack has been // updated already and it has been added to the plus-plus edges heap already plusNode.tree.addPlusPlusEdge(edge); } } else { // opposite is from another tree since it's label is "+" opposite.tree.currentEdge.removeFromCurrentMinusPlusHeap(edge); opposite.tree.currentEdge.addPlusPlusEdge(edge); if (edge.slack <= eps + opposite.tree.eps) { augmentEdge = edge; } } } else if (opposite.isMinusNode()) { if (opposite.tree != plusNode.tree) { // this edge becomes a (+, -) cross-tree edge if (opposite.tree.currentEdge == null) { BlossomVTree.addTreeEdge(plusNode.tree, opposite.tree); } opposite.tree.currentEdge .addToCurrentPlusMinusHeap(edge, opposite.tree.currentDirection); } } else { // this is either an inner edge, that becomes a (+, inf) edge, or it is a former (-, // +) edge, // that also becomes a (+, inf) edge plusNode.tree.addPlusInfinityEdge(edge); // updating edge's key } } return augmentEdge; } /** * Expands a minus node from the odd branch. Changes the slacks of inner (-,-) and (-, inf) * edges. * * @param minusNode a "-" node from the even branch */ private void expandMinusNode(BlossomVNode minusNode) { double eps = minusNode.tree.eps; // the minusNode.tree is assumed to be correct minusNode.dual += eps; if (minusNode.isBlossom) { minusNode.tree.addMinusBlossom(minusNode); } for (BlossomVNode.IncidentEdgeIterator iterator = minusNode.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); BlossomVNode opposite = edge.head[iterator.getDir()]; if (opposite.isMarked && !opposite.isPlusNode()) { // this is a (-, inf) or (-, -) inner edge edge.slack -= eps; } } } /** * Expands an infinity node from the odd branch * * @param infinityNode a node from the odd branch * @param tree the tree the blossom was previously in */ private void expandInfinityNode(BlossomVNode infinityNode, BlossomVTree tree) { double eps = tree.eps; for (BlossomVNode.IncidentEdgeIterator iterator = infinityNode.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); BlossomVNode opposite = edge.head[iterator.getDir()]; if (!opposite.isMarked) { edge.slack += eps; // since edge's label changes to inf and this is a boundary edge if (opposite.isPlusNode()) { // if this node is marked => it's a blossom node => this edge has been processed // already if (opposite.tree != tree) { opposite.tree.currentEdge.removeFromCurrentMinusPlusHeap(edge); } opposite.tree.addPlusInfinityEdge(edge); } } } } /** * Converts a tree into a set of free matched edges. Changes the matching starting from * {@code firstNode} all the way up to the firstNode.tree.root. It changes the labeling of the * nodes, applies lazy delta spreading, updates edges' presence in the heaps. This method also * deletes unnecessary tree edges. *

    * This method doesn't change the nodes and edge contracted in the blossoms. * * @param firstNode an endpoint of the {@code augmentEdge} which belongs to the tree to augment * @param augmentEdge a tight (+, +) cross tree edge */ private void augmentBranch(BlossomVNode firstNode, BlossomVEdge augmentEdge) { BlossomVTree tree = firstNode.tree; double eps = tree.eps; BlossomVNode root = tree.root; // set currentEdge and currentDirection of all opposite trees connected via treeEdge tree.setCurrentEdges(); // apply tree.eps to all tree nodes and updating slacks of all incident edges for (BlossomVTree.TreeNodeIterator treeNodeIterator = tree.treeNodeIterator(); treeNodeIterator.hasNext();) { BlossomVNode node = treeNodeIterator.next(); if (!node.isMarked) { // apply lazy delta spreading if (node.isPlusNode()) { node.dual += eps; } else { node.dual -= eps; } for (BlossomVNode.IncidentEdgeIterator incidentEdgeIterator = node.incidentEdgesIterator(); incidentEdgeIterator.hasNext();) { BlossomVEdge edge = incidentEdgeIterator.next(); int dir = incidentEdgeIterator.getDir(); BlossomVNode opposite = edge.head[dir]; BlossomVTree oppositeTree = opposite.tree; if (node.isPlusNode()) { edge.slack -= eps; if (oppositeTree != null && oppositeTree != tree) { // if this edge is a cross-tree edge BlossomVTreeEdge treeEdge = oppositeTree.currentEdge; if (opposite.isPlusNode()) { // this is a (+,+) cross-tree edge treeEdge.removeFromPlusPlusHeap(edge); oppositeTree.addPlusInfinityEdge(edge); } else if (opposite.isMinusNode()) { // this is a (+,-) cross-tree edge treeEdge.removeFromCurrentPlusMinusHeap(edge); } } } else { // current node is a "-" node edge.slack += eps; if (oppositeTree != null && oppositeTree != tree && opposite.isPlusNode()) { // this is a (-,+) cross-tree edge BlossomVTreeEdge treeEdge = oppositeTree.currentEdge; treeEdge.removeFromCurrentMinusPlusHeap(edge); oppositeTree.addPlusInfinityEdge(edge); } } } node.label = INFINITY; } else { // this node was added to the tree by the grow operation, // but it hasn't been processed, so we don't need to process it here node.isMarked = false; } } // add all elements from the (-,+) and (+,+) heaps to (+, inf) heaps of the opposite trees // and // delete tree edges for (BlossomVTree.TreeEdgeIterator treeEdgeIterator = tree.treeEdgeIterator(); treeEdgeIterator.hasNext();) { BlossomVTreeEdge treeEdge = treeEdgeIterator.next(); int dir = treeEdgeIterator.getCurrentDirection(); BlossomVTree opposite = treeEdge.head[dir]; opposite.currentEdge = null; opposite.plusPlusEdges.meld(treeEdge.plusPlusEdges); opposite.plusPlusEdges.meld(treeEdge.getCurrentMinusPlusHeap(dir)); treeEdge.removeFromTreeEdgeList(); } // update the matching BlossomVEdge matchedEdge = augmentEdge; BlossomVNode plusNode = firstNode; BlossomVNode minusNode = plusNode.getTreeParent(); while (minusNode != null) { plusNode.matched = matchedEdge; matchedEdge = minusNode.parentEdge; minusNode.matched = matchedEdge; plusNode = minusNode.getTreeParent(); minusNode = plusNode.getTreeParent(); } root.matched = matchedEdge; // remove root from the linked list of roots; root.removeFromChildList(); root.isTreeRoot = false; state.treeNum--; } /** * Updates the tree structure in the shrink operation. Moves the endpoints of the boundary edges * to the {@code blossom}, moves the children of the nodes on the circuit to the blossom, * updates edges's slacks and presence in heaps accordingly. * * @param blossomRoot the node that is matched from the outside or is a tree root * @param blossomFormingEdge a tight (+, +) edge * @param blossom the node that is being inserted into the tree structure * @return a tight (+, +) cross-tree edge if it is encountered, null otherwise */ private BlossomVEdge updateTreeStructure( BlossomVNode blossomRoot, BlossomVEdge blossomFormingEdge, BlossomVNode blossom) { BlossomVEdge augmentEdge = null; BlossomVTree tree = blossomRoot.tree; /** * Go through every vertex in the blossom and move its child list to blossom child list. * Handle all blossom nodes except for the blossom root. The reason is that we can't move * root's correctly to the blossom until both children from the circuit are removed from the * its children list */ for (BlossomVEdge.BlossomNodesIterator iterator = blossomFormingEdge.blossomNodesIterator(blossomRoot); iterator.hasNext();) { BlossomVNode blossomNode = iterator.next(); if (blossomNode != blossomRoot) { if (blossomNode.isPlusNode()) { // substitute varNode with the blossom in the tree structure blossomNode.removeFromChildList(); blossomNode.moveChildrenTo(blossom); BlossomVEdge edge = shrinkPlusNode(blossomNode, blossom); if (edge != null) { augmentEdge = edge; } blossomNode.isProcessed = true; } else { if (blossomNode.isBlossom) { tree.removeMinusBlossom(blossomNode); } blossomNode.removeFromChildList(); // minus node have only one child and this // child belongs to the circuit shrinkMinusNode(blossomNode, blossom); } } blossomNode.blossomGrandparent = blossomNode.blossomParent = blossom; } // substitute varNode with the blossom in the tree structure blossomRoot.removeFromChildList(); if (!blossomRoot.isTreeRoot) { blossomRoot.getTreeParent().addChild(blossom, blossomRoot.parentEdge, false); } else { // substitute blossomRoot with blossom in the linked list of tree roots blossom.treeSiblingNext = blossomRoot.treeSiblingNext; blossom.treeSiblingPrev = blossomRoot.treeSiblingPrev; blossomRoot.treeSiblingPrev.treeSiblingNext = blossom; if (blossomRoot.treeSiblingNext != null) { blossomRoot.treeSiblingNext.treeSiblingPrev = blossom; } } // finally process blossomRoot blossomRoot.moveChildrenTo(blossom); BlossomVEdge edge = shrinkPlusNode(blossomRoot, blossom); if (edge != null) { augmentEdge = edge; } blossomRoot.isTreeRoot = false; return augmentEdge; } /** * Processes a plus node on an odd circuit in the shrink operation. Moves endpoints of the * boundary edges, updates slacks of incident edges. * * @param plusNode a plus node from an odd circuit * @param blossom a newly created pseudonode * @return a tight (+, +) cross-tree edge if it is encountered, null otherwise */ private BlossomVEdge shrinkPlusNode(BlossomVNode plusNode, BlossomVNode blossom) { BlossomVEdge augmentEdge = null; BlossomVTree tree = plusNode.tree; double eps = tree.eps; plusNode.dual += eps; for (BlossomVNode.IncidentEdgeIterator iterator = plusNode.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); BlossomVNode opposite = edge.head[iterator.getDir()]; if (!opposite.isMarked) { // opposite isn't a node inside the blossom edge.moveEdgeTail(plusNode, blossom); if (opposite.tree != tree && opposite.isPlusNode() && edge.slack <= eps + opposite.tree.eps) { augmentEdge = edge; } } else if (opposite.isPlusNode()) { // inner edge, subtract eps only in the case the opposite node is a "+" node if (!opposite.isProcessed) { // here we rely on the proper setting of the // isProcessed flag // remove this edge when it is encountered for the first time tree.removePlusPlusEdge(edge); } edge.slack -= eps; } } return augmentEdge; } /** * Processes a minus node from an odd circuit in the shrink operation. Moves the endpoints of * the boundary edges, updates their slacks * * @param minusNode a minus node from an odd circuit * @param blossom a newly create pseudonode */ private void shrinkMinusNode(BlossomVNode minusNode, BlossomVNode blossom) { BlossomVTree tree = minusNode.tree; double eps = tree.eps; minusNode.dual -= eps; for (BlossomVNode.IncidentEdgeIterator iterator = minusNode.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); BlossomVNode opposite = edge.head[iterator.getDir()]; BlossomVTree oppositeTree = opposite.tree; if (!opposite.isMarked) { // opposite isn't a node inside the blossom edge.moveEdgeTail(minusNode, blossom); edge.slack += 2 * eps; if (opposite.tree == tree) { // edge to the node from the same tree, need only to add it to "++" heap if // opposite is "+" node if (opposite.isPlusNode()) { tree.addPlusPlusEdge(edge); } } else { // cross-tree edge or infinity edge if (opposite.isPlusNode()) { oppositeTree.currentEdge.removeFromCurrentMinusPlusHeap(edge); oppositeTree.currentEdge.addPlusPlusEdge(edge); } else if (opposite.isMinusNode()) { if (oppositeTree.currentEdge == null) { BlossomVTree.addTreeEdge(tree, oppositeTree); } oppositeTree.currentEdge .addToCurrentPlusMinusHeap(edge, oppositeTree.currentDirection); } else { tree.addPlusInfinityEdge(edge); } } } else if (opposite.isMinusNode()) { // this is an inner edge edge.slack += eps; } } } /** * Creates a circular linked list of blossom nodes. *

    * Note: this method heavily relies on the property of the * {@link BlossomVEdge.BlossomNodesIterator} that it returns the blossomRoot while processing * the first branch (with direction 0). * * @param blossomRoot the common endpoint of two branches * @param blossomFormingEdge a tight (+, +) in-tree edge */ private void setBlossomSiblings(BlossomVNode blossomRoot, BlossomVEdge blossomFormingEdge) { // set blossom sibling nodes BlossomVEdge prevEdge = blossomFormingEdge; for (BlossomVEdge.BlossomNodesIterator iterator = blossomFormingEdge.blossomNodesIterator(blossomRoot); iterator.hasNext();) { BlossomVNode current = iterator.next(); if (iterator.getCurrentDirection() == 0) { current.blossomSibling = prevEdge; prevEdge = current.parentEdge; } else { current.blossomSibling = current.parentEdge; } } } /** * Finds a blossom root of the circuit created by the {@code edge}. More precisely, finds an lca * of edge.head[0] and edge.head[1]. * * @param blossomFormingEdge a tight (+, +) in-tree edge * @return the lca of edge.head[0] and edge.head[1] */ BlossomVNode findBlossomRoot(BlossomVEdge blossomFormingEdge) { BlossomVNode root, upperBound; // need to be scoped outside of the loop BlossomVNode[] endPoints = new BlossomVNode[2]; endPoints[0] = blossomFormingEdge.head[0]; endPoints[1] = blossomFormingEdge.head[1]; int branch = 0; while (true) { if (endPoints[branch].isMarked) { root = endPoints[branch]; upperBound = endPoints[1 - branch]; break; } endPoints[branch].isMarked = true; if (endPoints[branch].isTreeRoot) { upperBound = endPoints[branch]; BlossomVNode jumpNode = endPoints[1 - branch]; while (!jumpNode.isMarked) { jumpNode = jumpNode.getTreeGrandparent(); } root = jumpNode; break; } endPoints[branch] = endPoints[branch].getTreeGrandparent(); branch = 1 - branch; } BlossomVNode jumpNode = root; while (jumpNode != upperBound) { jumpNode = jumpNode.getTreeGrandparent(); jumpNode.isMarked = false; } clearIsMarkedAndSetIsOuter(root, blossomFormingEdge.head[0]); clearIsMarkedAndSetIsOuter(root, blossomFormingEdge.head[1]); return root; } /** * Traverses the nodes in the tree from {@code start} to {@code root} and sets isMarked and * isOuter to false */ private void clearIsMarkedAndSetIsOuter(BlossomVNode root, BlossomVNode start) { while (start != root) { start.isMarked = false; start.isOuter = false; start = start.getTreeParent(); start.isOuter = false; start = start.getTreeParent(); } root.isOuter = false; root.isMarked = false; } /** * Reverses the direction of blossomSibling references * * @param blossomNode some node on an odd circuit */ private void reverseBlossomSiblings(BlossomVNode blossomNode) { BlossomVEdge prevEdge = blossomNode.blossomSibling; BlossomVNode current = blossomNode; do { current = prevEdge.getOpposite(current); BlossomVEdge tmpEdge = prevEdge; prevEdge = current.blossomSibling; current.blossomSibling = tmpEdge; } while (current != blossomNode); } /** * Checks whether the direction of blossomSibling references is suitable for the expand * operation, i.e. an even branch goes from {@code blossomRoot} to {@code branchesEndpoint}. * * @param blossomRoot a node on an odd circuit that is matched from the outside * @param branchesEndpoint a node common to both branches * @return true if the condition described above holds, false otherwise */ private boolean forwardDirection(BlossomVNode blossomRoot, BlossomVNode branchesEndpoint) { int hops = 0; BlossomVNode current = blossomRoot; while (current != branchesEndpoint) { ++hops; current = current.blossomSibling.getOpposite(current); } return (hops & 1) == 0; } /** * Prints {@code blossomNode} and all its blossom siblings. This method is for debug purposes. * * @param blossomNode the node to start from */ public void printBlossomNodes(BlossomVNode blossomNode) { System.out.println("Printing blossom nodes"); BlossomVNode current = blossomNode; do { System.out.println(current); current = current.blossomSibling.getOpposite(current); } while (current != blossomNode); } } BlossomVState.java000066400000000000000000000075561402514743400340000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import java.util.*; /** * This class stores data needed for the Kolmogorov's Blossom V algorithm; it is used by * {@link KolmogorovWeightedPerfectMatching}, {@link BlossomVPrimalUpdater} and * {@link BlossomVDualUpdater} during the course of the algorithm. *

    * We refer to this object with all the data stored in nodes, edges, trees, and tree edges as the * state of the algorithm * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see KolmogorovWeightedPerfectMatching * @see BlossomVPrimalUpdater * @see BlossomVDualUpdater */ class BlossomVState { /** * Number of nodes in the graph */ final int nodeNum; /** * Number of edges in the graph */ final int edgeNum; /** * The graph for which to find a matching */ Graph graph; /** * An array of nodes of the graph. *

    * Note: the size of the array is nodeNum + 1. The node nodes[nodeNum] is an auxiliary * node that is used as the first element in the linked list of tree roots */ BlossomVNode[] nodes; /** * An array of edges of the graph */ BlossomVEdge[] edges; /** * Number of trees */ int treeNum; /** * Number of expanded blossoms */ int removedNum; /** * Number of blossoms */ int blossomNum; /** * Statistics of the algorithm performance */ KolmogorovWeightedPerfectMatching.Statistics statistics; /** * BlossomVOptions used to determine the strategies used in the algorithm */ BlossomVOptions options; /** * Initial generic vertices of the graph */ List graphVertices; /** * Initial edges of the graph */ List graphEdges; /** * Minimum edge weight in the graph */ double minEdgeWeight; /** * Constructs the algorithm's initial state * * @param graph the graph for which to find a matching * @param nodes nodes used in the algorithm * @param edges edges used in the algorithm * @param nodeNum number of nodes in the graph * @param edgeNum number of edges in the graph * @param treeNum number of trees in the graph * @param graphVertices generic vertices of the {@code graph} in the same order as nodes in * {@code nodes} * @param graphEdges generic edges of the {@code graph} in the same order as edges in * {@code edges} * @param options default or user defined options */ public BlossomVState( Graph graph, BlossomVNode[] nodes, BlossomVEdge[] edges, int nodeNum, int edgeNum, int treeNum, List graphVertices, List graphEdges, BlossomVOptions options, double minEdgeWeight) { this.graph = graph; this.nodes = nodes; this.edges = edges; this.nodeNum = nodeNum; this.edgeNum = edgeNum; this.treeNum = treeNum; this.graphVertices = graphVertices; this.graphEdges = graphEdges; this.options = options; this.statistics = new KolmogorovWeightedPerfectMatching.Statistics(); this.minEdgeWeight = minEdgeWeight; } } BlossomVTree.java000066400000000000000000000350151402514743400336060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; /** * This class is a data structure for Kolmogorov's Blossom V algorithm. *

    * Represents an alternating tree of tight edges which is used to find an augmenting path * of tight edges in order to perform an augmentation and increase the cardinality of the matching. * The nodes on odd layers are necessarily connected to their children via matched edges. Thus, * these nodes have always exactly one child. The nodes on even layers can have arbitrarily many * children. *

    * The tree structure information is contained in {@link BlossomVNode}, this class only contains the * reference to the root of the tree. It also contains three heaps: *

      *
    • A heap of (+, inf) edges. These edges are also called infinity edges. If there exists a tight * infinity edge, then it can be grown. Thus, this heap is used to determine an infinity edge of * minimum slack.
    • *
    • A heap of (+, +) in-tree edges. These are edges between "+" nodes from the same tree. If a * (+, +) in-tree edges is tight, it can be used to perform the shrink operation and introduce a new * blossom. Thus, this heap is used to determine a (+, +) in-tree edge of minimum slack in a given * tree.
    • *
    • A heap of "-" blossoms. If there exists a blossom with zero actual dual variable, it can be * expanded. Thus, this heap is used to determine a "-" blossom with minimum dual variable
    • *
    *

    * Each tree contains a variable which accumulates dual changes applied to it. The dual changes * aren't spread until a tree is destroyed by an augmentation. For every node in the tree its true * dual variable is equal to {@code node.dual + node.tree.eps} if it is a "+" node; otherwise it * equals {@code node.dual - node.tree.eps}. This applies only to the surface nodes that belong to * some tree. *

    * This class also contains implementations of two iterators: {@link TreeEdgeIterator} and * {@link TreeNodeIterator}. They are used to conveniently traverse the tree edges incident to a * particular tree, and to traverse the nodes of a tree in a depth-first order. * * @author Timofey Chudakov * @see BlossomVNode * @see BlossomVTreeEdge * @see KolmogorovWeightedPerfectMatching */ class BlossomVTree { /** * Variable for debug purposes */ private static int currentId = 1; /** * Two-element array of the first elements in the circular doubly linked lists of incident tree * edges in each direction. */ BlossomVTreeEdge[] first; /** * This variable is used to quickly determine the edge between two trees during primal * operations. *

    * Let $T$ be a tree that is being processed in the main loop. For every tree $T'$ that is * adjacent to $T$ this variable is set to the {@code BlossomVTreeEdge} that connects both * trees. This variable also helps to indicate whether a pair of trees is adjacent or not. This * variable is set to {@code null} when no primal operation can be applied to the tree $T$. */ BlossomVTreeEdge currentEdge; /** * Direction of the tree edge connecting this tree and the currently processed tree */ int currentDirection; /** * Dual change that hasn't been spread among the nodes in this tree. This technique is called * lazy delta spreading */ double eps; /** * Accumulated dual change. Is used during dual updates */ double accumulatedEps; /** * The root of this tree */ BlossomVNode root; /** * Next tree in the connected component, is used during updating the duals via connected * components */ BlossomVTree nextTree; /** * The heap of (+,+) edges of this tree */ MergeableAddressableHeap plusPlusEdges; /** * The heap of (+, inf) edges of this tree */ MergeableAddressableHeap plusInfinityEdges; /** * The heap of "-" blossoms of this tree */ MergeableAddressableHeap minusBlossoms; /** * Variable for debug purposes */ int id; /** * Empty constructor */ public BlossomVTree() { } /** * Constructs a new tree with the {@code root} * * @param root the root of this tree */ public BlossomVTree(BlossomVNode root) { this.root = root; root.tree = this; root.isTreeRoot = true; first = new BlossomVTreeEdge[2]; plusPlusEdges = new PairingHeap<>(); plusInfinityEdges = new PairingHeap<>(); minusBlossoms = new PairingHeap<>(); this.id = currentId++; } /** * Adds a new tree edge from {@code from} to {@code to}. Sets the to.currentEdge and * to.currentDirection with respect to the tree {@code from} * * @param from the tail of the directed tree edge * @param to the head of the directed tree edge */ public static BlossomVTreeEdge addTreeEdge(BlossomVTree from, BlossomVTree to) { BlossomVTreeEdge treeEdge = new BlossomVTreeEdge(); treeEdge.head[0] = to; treeEdge.head[1] = from; if (from.first[0] != null) { from.first[0].prev[0] = treeEdge; } if (to.first[1] != null) { to.first[1].prev[1] = treeEdge; } treeEdge.next[0] = from.first[0]; treeEdge.next[1] = to.first[1]; from.first[0] = treeEdge; to.first[1] = treeEdge; to.currentEdge = treeEdge; to.currentDirection = 0; return treeEdge; } /** * Sets the currentEdge and currentDirection variables for all trees adjacent to this tree */ public void setCurrentEdges() { BlossomVTreeEdge treeEdge; for (BlossomVTree.TreeEdgeIterator iterator = treeEdgeIterator(); iterator.hasNext();) { treeEdge = iterator.next(); BlossomVTree opposite = treeEdge.head[iterator.getCurrentDirection()]; opposite.currentEdge = treeEdge; opposite.currentDirection = iterator.getCurrentDirection(); } } /** * Clears the currentEdge variable of all adjacent to the {@code tree} trees */ public void clearCurrentEdges() { currentEdge = null; for (TreeEdgeIterator iterator = treeEdgeIterator(); iterator.hasNext();) { iterator.next().head[iterator.getCurrentDirection()].currentEdge = null; } } /** * Prints all the nodes of this tree */ public void printTreeNodes() { System.out.println("Printing tree nodes"); for (BlossomVTree.TreeNodeIterator iterator = treeNodeIterator(); iterator.hasNext();) { System.out.println(iterator.next()); } } @Override public String toString() { return "BlossomVTree pos=" + id + ", eps = " + eps + ", root = " + root; } /** * Ensures correct addition of an edge to the heap * * @param edge a (+, +) edge */ public void addPlusPlusEdge(BlossomVEdge edge) { edge.handle = plusPlusEdges.insert(edge.slack, edge); } /** * Ensures correct addition of an edge to the heap * * @param edge a (+, inf) edge */ public void addPlusInfinityEdge(BlossomVEdge edge) { edge.handle = plusInfinityEdges.insert(edge.slack, edge); } /** * Ensures correct addition of a blossom to the heap * * @param blossom a "-" blossom */ public void addMinusBlossom(BlossomVNode blossom) { blossom.handle = minusBlossoms.insert(blossom.dual, blossom); } /** * Removes the {@code edge} from the heap of (+, +) edges * * @param edge the edge to remove */ public void removePlusPlusEdge(BlossomVEdge edge) { edge.handle.delete(); } /** * Removes the {@code edge} from the heap of (+, inf) edges * * @param edge the edge to remove */ public void removePlusInfinityEdge(BlossomVEdge edge) { edge.handle.delete(); } /** * Removes the {@code blossom} from the heap of "-" blossoms * * @param blossom the blossom to remove */ public void removeMinusBlossom(BlossomVNode blossom) { blossom.handle.delete(); } /** * Returns a new instance of TreeNodeIterator for this tree * * @return new TreeNodeIterator for this tree */ public TreeNodeIterator treeNodeIterator() { return new TreeNodeIterator(root); } /** * Returns a new instance of TreeEdgeIterator for this tree * * @return new TreeEdgeIterators for this tree */ public TreeEdgeIterator treeEdgeIterator() { return new TreeEdgeIterator(); } /** * An iterator over tree nodes. This iterator traverses the nodes of the tree in a depth-first * order. Note: this iterator can also be used to iterate the nodes of some subtree of a * tree. */ public static class TreeNodeIterator implements Iterator { /** * The node this iterator is currently on */ private BlossomVNode currentNode; /** * Variable to determine whether {@code currentNode} has been returned or not */ private BlossomVNode current; /** * A root of the subtree of a tree */ private BlossomVNode treeRoot; /** * Constructs a new TreeNodeIterator for a {@code root}. *

    * Note: {@code root} doesn't need to be a root of some tree; this iterator also * works with subtrees. * * @param root node of a tree to start dfs traversal from. */ public TreeNodeIterator(BlossomVNode root) { this.currentNode = this.current = root; this.treeRoot = root; } /** * {@inheritDoc} */ @Override public boolean hasNext() { if (current != null) { return true; } current = advance(); return current != null; } /** * {@inheritDoc} */ @Override public BlossomVNode next() { if (!hasNext()) { throw new NoSuchElementException(); } BlossomVNode result = current; current = null; return result; } /** * Advances the iterator to the next tree node * * @return the next tree node */ private BlossomVNode advance() { if (currentNode == null) { return null; } else if (currentNode.firstTreeChild != null) { // advance deeper currentNode = currentNode.firstTreeChild; return currentNode; } else { // advance to the next unvisited sibling of the current node or // of some of its ancestors while (currentNode != treeRoot && currentNode.treeSiblingNext == null) { currentNode = currentNode.parentEdge.getOpposite(currentNode); } currentNode = currentNode.treeSiblingNext; if (currentNode == treeRoot.treeSiblingNext) { currentNode = null; } return currentNode; } } } /** * An iterator over tree edges incident to this tree. */ public class TreeEdgeIterator implements Iterator { /** * The direction of the {@code currentEdge} */ private int currentDirection; /** * The tree edge this iterator is currently on */ private BlossomVTreeEdge currentEdge; /** * Variable to determine whether currentEdge has been returned or not */ private BlossomVTreeEdge result; /** * Constructs a new TreeEdgeIterator */ public TreeEdgeIterator() { currentEdge = first[0]; currentDirection = 0; if (currentEdge == null) { currentEdge = first[1]; currentDirection = 1; } result = currentEdge; } /** * {@inheritDoc} */ @Override public boolean hasNext() { if (result != null) { return true; } result = advance(); return result != null; } /** * {@inheritDoc} */ @Override public BlossomVTreeEdge next() { if (!hasNext()) { throw new NoSuchElementException(); } BlossomVTreeEdge res = result; result = null; return res; } /** * Returns the direction of the current edge * * @return the direction of the current edge */ public int getCurrentDirection() { return currentDirection; } /** * Moves this iterator to the next tree edge. If the last outgoing edge has been traversed, * changes the current direction to 1. If the the last incoming edge has been traversed, * sets {@code currentEdge} to null. * * @return the next tree edge or null if all edges have been traversed already */ private BlossomVTreeEdge advance() { if (currentEdge == null) { return null; } currentEdge = currentEdge.next[currentDirection]; if (currentEdge == null && currentDirection == 0) { currentDirection = 1; currentEdge = first[1]; } return currentEdge; } } } BlossomVTreeEdge.java000066400000000000000000000204671402514743400344000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jheaps.*; import org.jheaps.tree.*; /** * This class is a data structure for Kolmogorov's Blossom V algorithm. *

    * Is used to maintain an auxiliary graph whose nodes correspond to alternating trees in the Blossom * V algorithm. Let's denote the current tree $T$ and some other tree $T'$. Every tree edge contains * three heaps:
    *

      *
    1. a heap of (+, +) cross-tree edges. This heap contains all edges between two "+" nodes where * one node belongs to tree $T$ and another to $T'$. The (+, +) cross-tree edges are used to augment * the matching.
    2. *
    3. a heap of (+, -) cross-tree edges
    4. *
    5. a heap of (-, +) cross-tree edges
    6. *
    * Note: from the tree edge perspective there is no difference between a heap of (+, -) and * (-, +) cross-tree edges. That's why we distinguish these heaps by the direction of the edge. Here * the direction is considered with respect to the trees $T$ and $T'$ based upon the notation * introduced above. *

    * Every tree edge is directed from one tree to another and every tree edge belongs to the two * doubly linked lists of tree edges. The presence of a tree edge in these lists in maintained by * the two-element arrays {@link BlossomVTreeEdge#prev} and {@link BlossomVTreeEdge#next}. For one * tree the edge is an outgoing tree edge; for the other, an incoming. In the first case it belongs * to the {@code tree.first[0]} linked list; in the second, to the {@code tree.first[1]} linked * list. *

    * Let {@code tree} be a tail of the edge, and {@code oppositeTree} a head of the edge. Then * {@code edge.head[0] == oppositeTree} and {@code edge.head[1] == tree}. * * @author Timofey Chudakov * @see KolmogorovWeightedPerfectMatching * @see BlossomVTree * @see BlossomVEdge */ class BlossomVTreeEdge { /** * Two-element array of trees this edge is incident to. */ BlossomVTree[] head; /** * A two-element array of references to the previous elements in the circular doubly linked * lists of tree edges. The lists are circular with one exception: the lastElement.next[dir] == * null. Each list belongs to one of the endpoints of this edge. */ BlossomVTreeEdge[] prev; /** * A two-element array of references to the next elements in the circular doubly linked lists of * tree edges. The lists are circular with one exception: the lastElementInTheList.next[dir] == * null. Each list belongs to one of the endpoints of this edge. */ BlossomVTreeEdge[] next; /** * A heap of (+, +) cross-tree edges */ MergeableAddressableHeap plusPlusEdges; /** * A heap of (-, +) cross-tree edges */ MergeableAddressableHeap plusMinusEdges0; /** * A heap of (+, -) cross-tree edges */ MergeableAddressableHeap plusMinusEdges1; /** * Constructs a new tree edge by initializing arrays and heaps */ public BlossomVTreeEdge() { this.head = new BlossomVTree[2]; this.prev = new BlossomVTreeEdge[2]; this.next = new BlossomVTreeEdge[2]; this.plusPlusEdges = new PairingHeap<>(); this.plusMinusEdges0 = new PairingHeap<>(); this.plusMinusEdges1 = new PairingHeap<>(); } /** * Removes this edge from both doubly linked lists of tree edges. */ public void removeFromTreeEdgeList() { for (int dir = 0; dir < 2; dir++) { if (prev[dir] != null) { prev[dir].next[dir] = next[dir]; } else { // this is the first edge in this direction head[1 - dir].first[dir] = next[dir]; } if (next[dir] != null) { next[dir].prev[dir] = prev[dir]; } } head[0] = head[1] = null; } /** * {@inheritDoc} */ @Override public String toString() { return "BlossomVTreeEdge (" + head[0].id + ":" + head[1].id + ")"; } /** * Adds {@code edge} to the heap of (-, +) cross-tree edges. As explained in the class * description, this method chooses {@link BlossomVTreeEdge#plusMinusEdges0} or * {@link BlossomVTreeEdge#plusMinusEdges1} based upon the {@code direction}. The key is * edge.slack * * @param edge an edge to add to the current heap of (-, +) cross-tree edges. * @param direction direction of this tree edge wrt. current tree and opposite tree */ public void addToCurrentMinusPlusHeap(BlossomVEdge edge, int direction) { edge.handle = getCurrentMinusPlusHeap(direction).insert(edge.slack, edge); } /** * Adds {@code edge} to the heap of (+, -) cross-tree edges. As explained in the class * description, this method chooses {@link BlossomVTreeEdge#plusMinusEdges0} or * {@link BlossomVTreeEdge#plusMinusEdges1} based upon the {@code direction}. The key is * edge.slack * * @param edge an edge to add to the current heap of (+, -) cross-tree edges. * @param direction direction of this tree edge wrt. current tree and opposite tree */ public void addToCurrentPlusMinusHeap(BlossomVEdge edge, int direction) { edge.handle = getCurrentPlusMinusHeap(direction).insert(edge.slack, edge); } /** * Adds {@code edge} to the heap of (+, +) cross-tree edges. The key is edge.slack * * @param edge an edge to add to the heap of (+, +) cross-tree edges */ public void addPlusPlusEdge(BlossomVEdge edge) { edge.handle = plusPlusEdges.insert(edge.slack, edge); } /** * Removes {@code edge} from the current heap of (-, +) cross-tree edges. As explained in the * class description, this method chooses {@link BlossomVTreeEdge#plusMinusEdges0} or * {@link BlossomVTreeEdge#plusMinusEdges1} based upon the {@code direction}. * * @param edge an edge to remove */ public void removeFromCurrentMinusPlusHeap(BlossomVEdge edge) { edge.handle.delete(); edge.handle = null; } /** * Removes {@code edge} from the current heap of (+, -) cross-tree edges. As explained in the * class description, this method chooses {@link BlossomVTreeEdge#plusMinusEdges0} or * {@link BlossomVTreeEdge#plusMinusEdges1} based upon the {@code direction}. * * @param edge an edge to remove */ public void removeFromCurrentPlusMinusHeap(BlossomVEdge edge) { edge.handle.delete(); edge.handle = null; } /** * Removes {@code edge} from the heap of (+, +) cross-tree edges. * * @param edge an edge to remove */ public void removeFromPlusPlusHeap(BlossomVEdge edge) { edge.handle.delete(); edge.handle = null; } /** * Returns the current heap of (-, +) cross-tree edges. Always returns a heap different from * {@code getCurrentPlusMinusHeap(currentDir)} * * @param currentDir the current direction of this edge * @return returns current heap of (-, +) cross-tree edges */ public MergeableAddressableHeap getCurrentMinusPlusHeap(int currentDir) { return currentDir == 0 ? plusMinusEdges0 : plusMinusEdges1; } /** * Returns the current heap of (+, -) cross-tree edges. Always returns a heap different from * {@code getCurrentMinusPlusHeap(currentDir)} * * @param currentDir the current direction of this edge * @return returns current heap of (+, -) cross-tree edges */ public MergeableAddressableHeap getCurrentPlusMinusHeap(int currentDir) { return currentDir == 0 ? plusMinusEdges1 : plusMinusEdges0; } } KolmogorovWeightedMatching.java000066400000000000000000000212601402514743400365110ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.DEFAULT_OPTIONS; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.EPS; import static org.jgrapht.alg.matching.blossom.v5.ObjectiveSense.MAXIMIZE; /** * This class computes weighted matchings in general graphs. Depending on the constructor parameter * the weight of the resulting matching is maximized or minimized. If maximum of minimum weight * perfect algorithm is needed, see {@link KolmogorovWeightedPerfectMatching}. *

    * This class reduces both maximum and minimum weight matching problems to the maximum and minimum * weight perfect matching problems correspondingly. See {@link KolmogorovWeightedPerfectMatching} * for relative definitions and algorithm description * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see KolmogorovWeightedPerfectMatching */ public class KolmogorovWeightedMatching implements MatchingAlgorithm { /** * The graph we are matching on */ private final Graph initialGraph; /** * The graph created during the reduction */ private Graph graph; /** * The computed matching of the {@code graph} */ private Matching matching; /** * The perfect matching algorithm used during for the problem reduction */ private KolmogorovWeightedPerfectMatching perfectMatching; /** * BlossomVOptions used by the algorithm to match the problem instance */ private BlossomVOptions options; /** * The objective sense of the algorithm, i.e. whether to maximize or minimize the weight of the * resulting matching */ private ObjectiveSense objectiveSense; /** * Constructs a new instance of the algorithm using the default options. The goal of the * constructed algorithm is to minimize the weight of the resulting matching. * * @param initialGraph the graph for which to find a weighted matching */ public KolmogorovWeightedMatching(Graph initialGraph) { this(initialGraph, DEFAULT_OPTIONS, MAXIMIZE); } /** * Constructs a new instance of the algorithm using the default options. The goal of the * constructed algorithm is to maximize or minimize the weight of the resulting matching * depending on the {@code maximize} parameter. * * @param initialGraph the graph for which to find a weighted matching * @param objectiveSense objective sense of the algorithm */ public KolmogorovWeightedMatching(Graph initialGraph, ObjectiveSense objectiveSense) { this(initialGraph, DEFAULT_OPTIONS, objectiveSense); } /** * Constructs a new instance of the algorithm with the specified {@code options}. The goal of * the constructed algorithm is to minimize the weight of the resulting matching. * * @param initialGraph the graph for which to find a weighted matching * @param options the options which define the strategies for the initialization and dual * updates */ public KolmogorovWeightedMatching(Graph initialGraph, BlossomVOptions options) { this(initialGraph, options, MAXIMIZE); } /** * Constructs a new instance of the algorithm with the specified {@code options}. The goal of * the constructed algorithm is to maximize or minimize the weight of the resulting matching * depending on the {@code maximize} parameter. * * @param initialGraph the graph for which to find a weighted matching * @param options the options which define the strategies for the initialization and dual * updates * @param objectiveSense objective sense of the algorithm */ public KolmogorovWeightedMatching( Graph initialGraph, BlossomVOptions options, ObjectiveSense objectiveSense) { this.initialGraph = Objects.requireNonNull(initialGraph); this.options = Objects.requireNonNull(options); this.objectiveSense = objectiveSense; } /** * Computes and returns a matching of maximum or minimum weight in the {@code initialGraph} * depending on the goal of the algorithm. * * @return weighted matching in the {@code initialGraph} */ @Override public Matching getMatching() { if (matching == null) { lazyComputeMaximumWeightMatching(); } return matching; } /** * Lazy computes optimal matching in the {@code initialGraph} by reducing the problem to the * optimal perfect matching problem. */ private void lazyComputeMaximumWeightMatching() { Map duplicatedVertices = new HashMap<>(); GraphType type = initialGraph.getType(); Graph graphCopy = GraphTypeBuilder .undirected().allowingMultipleEdges(type.isAllowingMultipleEdges()) .allowingSelfLoops(type.isAllowingSelfLoops()) .vertexSupplier(initialGraph.getVertexSupplier()) .edgeSupplier(initialGraph.getEdgeSupplier()).weighted(type.isWeighted()).buildGraph(); for (V v : initialGraph.vertexSet()) { duplicatedVertices.put(v, graphCopy.addVertex()); } for (E edge : initialGraph.edgeSet()) { Graphs .addEdgeWithVertices( graphCopy, duplicatedVertices.get(initialGraph.getEdgeSource(edge)), duplicatedVertices.get(initialGraph.getEdgeTarget(edge)), initialGraph.getEdgeWeight(edge)); } Map zeroWeightFunction = new HashMap<>(); for (Map.Entry entry : duplicatedVertices.entrySet()) { graphCopy.addVertex(entry.getKey()); zeroWeightFunction.put(graphCopy.addEdge(entry.getKey(), entry.getValue()), 0d); } this.graph = new AsGraphUnion<>(new AsWeightedGraph<>(graphCopy, zeroWeightFunction), initialGraph); this.perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options, objectiveSense); matching = perfectMatching.getMatching(); Set matchingEdges = matching.getEdges(); matchingEdges.removeIf(e -> !initialGraph.containsEdge(e)); this.matching = new MatchingImpl<>(initialGraph, matchingEdges, matching.getWeight() / 2); } /** * Performs an optimality test after the perfect matching is computed. This test is done via * {@link KolmogorovWeightedPerfectMatching#testOptimality()} *

    * More precisely, checks whether dual variables of all pseudonodes and resulting slacks of all * edges are non-negative and that slacks of all matched edges are exactly 0. Since the * algorithm uses floating point arithmetic, this check is done with precision of * {@link KolmogorovWeightedPerfectMatching#EPS} *

    * In general, this method should always return true unless the algorithm implementation has a * bug. * * @return true iff the assigned dual variables satisfy the dual linear program formulation AND * complementary slackness conditions are also satisfied. The total error must not * exceed EPS */ public boolean testOptimality() { return perfectMatching.getError() < EPS; } /** * Computes the error in the solution to the dual linear program. This computation is done via * {@link KolmogorovWeightedPerfectMatching#getError()}. More precisely, the total error equals * the sum of: *

      *
    • Absolute value of edge slack if negative or the edge is matched
    • *
    • Absolute value of pseudonode variable if negative
    • *
    * * @return the total numeric error */ public double getError() { lazyComputeMaximumWeightMatching(); return perfectMatching.getError(); } } KolmogorovWeightedPerfectMatching.java000066400000000000000000001205531402514743400400270ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.matching.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.DualUpdateStrategy.MULTIPLE_TREE_CONNECTED_COMPONENTS; import static org.jgrapht.alg.matching.blossom.v5.ObjectiveSense.MAXIMIZE; import static org.jgrapht.alg.matching.blossom.v5.ObjectiveSense.MINIMIZE; /** * This class computes weighted perfect matchings in general graphs using the Blossom V algorithm. * If maximum or minimum weight matching algorithms is needed, see * {@link KolmogorovWeightedMatching} *

    * Let $G = (V, E, c)$ be an undirected graph with a real-valued cost function defined on it. A * matching is an edge-disjoint subset of edges $M \subseteq E$. A matching is perfect if $2|M| = * |V|$. In the weighted perfect matching problem the goal is to maximize or minimize the weighted * sum of the edges in the matching. This class supports pseudographs, but a problem on a * pseudograph can be easily reduced to a problem on a simple graph. Moreover, this reduction can * heavily influence the running time since only an edge with a maximum or minimum weight between * two vertices can belong to the matching in the corresponding optimization problems. Currently, * users are responsible for doing this reduction themselves before invoking the algorithm. *

    * Note that if the graph is unweighted and dense, {@link SparseEdmondsMaximumCardinalityMatching} * may be a better choice. *

    * For more information about the algorithm see the following paper: Kolmogorov, V. Math. Prog. * Comp. (2009) 1: 43. https://doi.org/10.1007/s12532-009-0002-8, and the original * implementation: http://pub.ist.ac.at/~vnk/software/blossom5-v2.05.src.tar.gz *

    * The algorithm can be divided into two phases: initialization and the main algorithm. The * initialization phase is responsible for converting the specified graph into the form convenient * for the algorithm and for finding an initial matching to speed up the main part. Furthermore, the * main part of the algorithm can be further divided into primal and dual updates. The primal phases * are aimed at augmenting the matching so that the value of the objective function of the primal * linear program increases. Dual updates are aimed at increasing the objective function of the dual * linear program. The algorithm iteratively performs these primal and dual operations to build * alternating trees of tight edges and augment the matching. Thus, at any stage of the algorithm * the matching consists of tight edges. This means that the resulting perfect matching meets * complementary slackness conditions, and is therefore optimal. *

    * At construction time the set of options can be specified to define the strategies used by the * algorithm to perform initialization, dual updates, etc. This can be done with the * {@link BlossomVOptions}. During the construction time the objective sense of the optimization * problem can be specified, i.e. whether to maximize of minimize the weight of the resulting * perfect matching. Default objective sense of the algorithm is to minimize the weight of the * resulting perfect matching. If the objective sense of the algorithm is to maximize the weight of * the matching, the problem is reduced to minimum weight perfect matching problem by multiplying * all edge weights by $-1$. This class supports retrieving statistics for the algorithm * performance, see {@link KolmogorovWeightedPerfectMatching#getStatistics()}. It provides the time * elapsed during primal operations and dual updates, as well as the number of these primal * operations performed. *

    * The solution to a weighted perfect matching problem instance comes with a certificate of * optimality, which is represented by a solution to a dual linear program; see * {@link DualSolution}. This class encapsulates a mapping from the node sets of odd cardinality to * the corresponding dual variables. This mapping doesn't contain the sets whose dual variables are * $0$. The computation of the dual solution is performed lazily and doesn't affect the running time * of finding a weighted perfect matching. *

    * Here we describe the certificates of optimality more precisely. Let the graph $G = (V, E)$ be an * undirected graph with cost function $c: V \mapsto \mathbb{R}$ defined on it. Let $\mathcal{O}$ be * the set of all subsets of $V$ of odd cardinality containing at least 3 vertices, and $\delta(S), * S \subset V$ be the set of boundary edges of $V$. Then minimum weight perfect matching * problem has the following linear programming formulation: * * \[ \begin{align} \mbox{minimize} \qquad & \sum_{e\in E}c_e \cdot x_e &\\ s.t. \qquad * & \sum_{e\in \delta^(i)} x_e = 1 & \forall i\in V\\ & \sum_{e\in \delta(S)}x_e \ge 1 * & \forall S\in \mathcal{O} \\ & x_e \ge 0 & \forall e\in E \end{align}\] The * corresponding dual linear program has the following form: * * \[ \begin{align} \mbox{maximize} \qquad & \sum_{x \in V}y_e &\\ s.t. \qquad & y_u + * y_v + \sum_{S\in \mathcal{O}: e \in \delta(S)}y_S \le c_e & \forall\ e = \{u, v\}\in E\\ * & x_S \ge 0 & \forall S\in \mathcal{O} \end{align} \] Let's use the following notation: * $slack(e) = c_e - y_u - y_v - \sum_{S\in \mathcal{O}: e \in \delta(S)}y_S$. Complementary * slackness conditions have the following form: * * \[ \begin{align} slack(e) > 0 &\Rightarrow x_e = 0 \\ y_S > 0 &\Rightarrow * \sum_{e\in \delta(S)}x_e = 1 \end{align} \] Therefore, the slacks of all edges will be * non-negative and the slacks of matched edges will be $0$. *

    * The maximum weight perfect matching problem has the following linear programming * formulation: * * \[ \begin{align} \mbox{maximize} \qquad & \sum_{e\in E}c_e \cdot x_e &\\ s.t. \qquad * &\sum_{e\in \delta^(i)} x_e = 1 & \forall i\in V\\ & \sum_{e\in \delta(S)}x_e \ge 1 * & \forall S\in \mathcal{O} \\ & x_e \ge 0 & \forall e\in E \end{align} \] * * The corresponding dual linear program has the following form: * * \[ \begin{align} \mbox{minimize} \qquad & \sum_{x \in V}y_e &\\ s.t. \qquad & y_u + * y_v + \sum_{S\in \mathcal{O}: e \in \delta(S)}y_S \ge c_e & \forall\ e = \{u, v\}\in E\\ * & x_S \le 0 & \forall S\in \mathcal{O} \end{align} \] * * Complementary slackness conditions have the following form: * * \[ \begin{align} slack(e) < 0 &\Rightarrow x_e = 0 \\ y_S < 0 &\Rightarrow * \sum_{e\in \delta(S)}x_e = 1 \end{align} \] * * Therefore, the slacks of all edges will be non-positive and the slacks of matched edges will be * $0$. *

    * This class supports testing the optimality of the solution via * {@link KolmogorovWeightedPerfectMatching#testOptimality()}. It also supports retrieval of the * computation error when the edge weights are real values via * {@link KolmogorovWeightedPerfectMatching#getError()}. Both optimality test and error computation * are performed lazily and don't affect the running time of the main algorithm. If the problem * instance doesn't contain a perfect matching at all, the algorithm doesn't find a minimum weight * maximum matching; instead, it throws an exception. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see KolmogorovWeightedMatching * @see BlossomVPrimalUpdater * @see BlossomVDualUpdater */ public class KolmogorovWeightedPerfectMatching implements MatchingAlgorithm { /** * Default epsilon used in the algorithm */ public static final double EPS = MatchingAlgorithm.DEFAULT_EPSILON; /** * Default infinity value used in the algorithm */ public static final double INFINITY = 1e100; /** * Defines the threshold for throwing an exception about no perfect matching existence */ public static final double NO_PERFECT_MATCHING_THRESHOLD = 1e10; /** * Default options */ public static final BlossomVOptions DEFAULT_OPTIONS = new BlossomVOptions(); /** * When set to true, verbose debugging output will be produced */ static final boolean DEBUG = false; /** * Exception message if no perfect matching is possible */ static final String NO_PERFECT_MATCHING = "There is no perfect matching in the specified graph"; /** * Initial graph specified during the construction time */ private final Graph initialGraph; /** * The graph we are matching on */ private final Graph graph; /** * Current state of the algorithm */ BlossomVState state; /** * Performs primal operations (grow, augment, shrink and expand) */ private BlossomVPrimalUpdater primalUpdater; /** * Performs dual updates using the strategy defined by the {@code options} */ private BlossomVDualUpdater dualUpdater; /** * The computed matching of the {@code graph} */ private MatchingAlgorithm.Matching matching; /** * Defines solution to the dual linear program formulated on the {@code graph} */ private DualSolution dualSolution; /** * BlossomVOptions used by the algorithm to match the problem instance */ private BlossomVOptions options; /** * The objective sense of the algorithm, i.e. whether to maximize or minimize the weight of the * resulting perfect matching */ private ObjectiveSense objectiveSense; /** * Constructs a new instance of the algorithm using the default options. The goal of the * constructed algorithm is to minimize the weight of the resulting perfect matching. * * @param graph the graph for which to find a weighted perfect matching */ public KolmogorovWeightedPerfectMatching(Graph graph) { this(graph, DEFAULT_OPTIONS, MINIMIZE); } /** * Constructs a new instance of the algorithm using the default options. The goal of the * constructed algorithm is to maximize or minimize the weight of the resulting perfect matching * depending on the {@code maximize} parameter. * * @param graph the graph for which to find a weighted perfect matching * @param objectiveSense objective sense of the algorithm */ public KolmogorovWeightedPerfectMatching(Graph graph, ObjectiveSense objectiveSense) { this(graph, DEFAULT_OPTIONS, objectiveSense); } /** * Constructs a new instance of the algorithm with the specified {@code options}. The objective * sense of the constructed algorithm is to minimize the weight of the resulting matching * * @param graph the graph for which to find a weighted perfect matching * @param options the options which define the strategies for the initialization and dual * updates */ public KolmogorovWeightedPerfectMatching(Graph graph, BlossomVOptions options) { this(graph, options, MINIMIZE); } /** * Constructs a new instance of the algorithm with the specified {@code options}. The goal of * the constructed algorithm is to maximize or minimize the weight of the resulting perfect * matching depending on the {@code maximize} parameter. * * @param graph the graph for which to find a weighted perfect matching * @param options the options which define the strategies for the initialization and dual * updates * @param objectiveSense objective sense of the algorithm */ public KolmogorovWeightedPerfectMatching( Graph graph, BlossomVOptions options, ObjectiveSense objectiveSense) { Objects.requireNonNull(graph); this.objectiveSense = objectiveSense; if ((graph.vertexSet().size() & 1) == 1) { throw new IllegalArgumentException(NO_PERFECT_MATCHING); } else if (objectiveSense == MAXIMIZE) { this.graph = new AsWeightedGraph<>(graph, e -> -graph.getEdgeWeight(e), true, false); } else { this.graph = graph; } this.initialGraph = graph; this.options = Objects.requireNonNull(options); } /** * Computes and returns a weighted perfect matching in the {@code graph}. See the class * description for the relative definitions and algorithm description. * * @return a weighted perfect matching for the {@code graph} */ @Override public MatchingAlgorithm.Matching getMatching() { if (matching == null) { lazyComputeWeightedPerfectMatching(); } return matching; } /** * Returns the computed solution to the dual linear program with respect to the weighted perfect * matching linear program formulation. * * @return the solution to the dual linear program formulated on the {@code graph} */ public DualSolution getDualSolution() { dualSolution = lazyComputeDualSolution(); return dualSolution; } /** * Performs an optimality test after the perfect matching is computed. *

    * More precisely, checks whether dual variables of all pseudonodes and resulting slacks of all * edges are non-negative and that slacks of all matched edges are exactly 0. Since the * algorithm uses floating point arithmetic, this check is done with precision of * {@link KolmogorovWeightedPerfectMatching#EPS} *

    * In general, this method should always return true unless the algorithm implementation has a * bug. * * @return true iff the assigned dual variables satisfy the dual linear program formulation AND * complementary slackness conditions are also satisfied. The total error must not * exceed EPS */ public boolean testOptimality() { lazyComputeWeightedPerfectMatching(); return getError() < EPS; // getError() won't return -1 since matching != null } /** * Computes the error in the solution to the dual linear program. More precisely, the total * error equals the sum of: *

      *
    • Absolute value of edge slack if negative or the edge is matched
    • *
    • Absolute value of pseudonode variable if negative
    • *
    * * @return the total numeric error */ public double getError() { lazyComputeWeightedPerfectMatching(); double error = testNonNegativity(); Set matchedEdges = matching.getEdges(); for (int i = 0; i < state.graphEdges.size(); i++) { E graphEdge = state.graphEdges.get(i); BlossomVEdge edge = state.edges[i]; double slack = graph.getEdgeWeight(graphEdge); slack -= state.minEdgeWeight; BlossomVNode a = edge.headOriginal[0]; BlossomVNode b = edge.headOriginal[1]; Pair lca = lca(a, b); slack -= totalDual(a, lca.getFirst()); slack -= totalDual(b, lca.getSecond()); if (lca.getFirst() == lca.getSecond()) { // if a and b have a common ancestor, its dual is subtracted from edge's slack slack += 2 * lca.getFirst().getTrueDual(); } if (slack < 0 || matchedEdges.contains(graphEdge)) { error += Math.abs(slack); } } return error; } /** * Lazily runs the algorithm on the specified graph. */ private void lazyComputeWeightedPerfectMatching() { if (matching != null) { return; } BlossomVInitializer initializer = new BlossomVInitializer<>(graph); this.state = initializer.initialize(options); this.primalUpdater = new BlossomVPrimalUpdater<>(state); this.dualUpdater = new BlossomVDualUpdater<>(state, primalUpdater); if (DEBUG) { printMap(); } while (true) { int cycleTreeNum = state.treeNum; for (BlossomVNode currentRoot = state.nodes[state.nodeNum].treeSiblingNext; currentRoot != null;) { // initialize variables BlossomVNode nextRoot = currentRoot.treeSiblingNext; BlossomVNode nextNextRoot = null; if (nextRoot != null) { nextNextRoot = nextRoot.treeSiblingNext; } BlossomVTree tree = currentRoot.tree; int iterationTreeNum = state.treeNum; if (DEBUG) { printState(); } // first phase setCurrentEdgesAndTryToAugment(tree); if (iterationTreeNum == state.treeNum && options.updateDualsBefore) { dualUpdater.updateDualsSingle(tree); } // second phase // apply primal operations to the current tree while it is possible while (iterationTreeNum == state.treeNum) { if (DEBUG) { printState(); System.out .println( "Current tree is " + tree + ", current root is " + currentRoot); } if (!tree.plusInfinityEdges.isEmpty()) { // can grow tree BlossomVEdge edge = tree.plusInfinityEdges.findMin().getValue(); if (edge.slack <= tree.eps) { primalUpdater.grow(edge, true, true); continue; } } if (!tree.plusPlusEdges.isEmpty()) { // can shrink blossom BlossomVEdge edge = tree.plusPlusEdges.findMin().getValue(); if (edge.slack <= 2 * tree.eps) { primalUpdater.shrink(edge, true); continue; } } if (!tree.minusBlossoms.isEmpty()) { // can expand blossom BlossomVNode node = tree.minusBlossoms.findMin().getValue(); if (node.dual <= tree.eps) { primalUpdater.expand(node, true); continue; } } // can't do anything if (DEBUG) { System.out.println("Can't do anything"); } break; } if (DEBUG) { printState(); } // third phase if (state.treeNum == iterationTreeNum) { tree.currentEdge = null; if (options.updateDualsAfter && dualUpdater.updateDualsSingle(tree)) { // since some progress has been made, continue with the same trees continue; } // clear current edge pointers tree.clearCurrentEdges(); } currentRoot = nextRoot; if (nextRoot != null && nextRoot.isInfinityNode()) { currentRoot = nextNextRoot; } } if (DEBUG) { printTrees(); printState(); } if (state.treeNum == 0) { // we are done break; } if (cycleTreeNum == state.treeNum && dualUpdater.updateDuals(options.dualUpdateStrategy) <= 0) { dualUpdater.updateDuals(MULTIPLE_TREE_CONNECTED_COMPONENTS); } } finish(); } /** * Sets the currentEdge and currentDirection variables for all trees adjacent to the * {@code tree} * * @param tree the tree whose adjacent trees' variables are modified */ private void setCurrentEdgesAndTryToAugment(BlossomVTree tree) { for (BlossomVTree.TreeEdgeIterator iterator = tree.treeEdgeIterator(); iterator.hasNext();) { BlossomVTreeEdge treeEdge = iterator.next(); BlossomVTree opposite = treeEdge.head[iterator.getCurrentDirection()]; if (!treeEdge.plusPlusEdges.isEmpty()) { BlossomVEdge edge = treeEdge.plusPlusEdges.findMin().getValue(); if (edge.slack <= tree.eps + opposite.eps) { if (DEBUG) { System.out.println("Bingo traverse"); } primalUpdater.augment(edge); break; } } opposite.currentEdge = treeEdge; opposite.currentDirection = iterator.getCurrentDirection(); } } /** * Tests whether a non-negative dual variable is assigned to every blossom * * @return true iff the condition described above holds */ private double testNonNegativity() { BlossomVNode[] nodes = state.nodes; double error = 0; for (int i = 0; i < state.nodeNum; i++) { BlossomVNode node = nodes[i].blossomParent; while (node != null && !node.isMarked) { if (node.dual < 0) { error += Math.abs(node.dual); break; } node.isMarked = true; node = node.blossomParent; } } clearMarked(); return error; } /** * Computes the sum of all duals from {@code start} inclusive to {@code end} inclusive * * @param start the node to start from * @param end the node to end with * @return the sum = start.dual + start.blossomParent.dual + ... + end.dual */ private double totalDual(BlossomVNode start, BlossomVNode end) { if (end == start) { return start.getTrueDual(); } else { double result = 0; BlossomVNode current = start; do { result += current.getTrueDual(); current = current.blossomParent; } while (current != null && current != end); result += end.getTrueDual(); return result; } } /** * Returns $(b, b)$ in the case where the vertices {@code a} and {@code b} have a common * ancestor blossom $b$. Otherwise, returns the outermost parent blossoms of nodes {@code a} and * {@code b} * * @param a a vertex whose lca is to be found with respect to another vertex * @param b the other vertex whose lca is to be found * @return either an lca blossom of {@code a} and {@code b} or their outermost blossoms */ private Pair lca(BlossomVNode a, BlossomVNode b) { BlossomVNode[] branches = new BlossomVNode[] { a, b }; int dir = 0; Pair result; while (true) { if (branches[dir].isMarked) { result = new Pair<>(branches[dir], branches[dir]); break; } branches[dir].isMarked = true; if (branches[dir].isOuter) { BlossomVNode jumpNode = branches[1 - dir]; while (!jumpNode.isOuter && !jumpNode.isMarked) { jumpNode = jumpNode.blossomParent; } if (jumpNode.isMarked) { result = new Pair<>(jumpNode, jumpNode); } else { result = dir == 0 ? new Pair<>(branches[dir], jumpNode) : new Pair<>(jumpNode, branches[dir]); } break; } branches[dir] = branches[dir].blossomParent; dir = 1 - dir; } clearMarked(a); clearMarked(b); return result; } /** * Clears the marking of {@code node} and all its ancestors up until the first unmarked vertex * is encountered * * @param node the node to start from */ private void clearMarked(BlossomVNode node) { do { node.isMarked = false; node = node.blossomParent; } while (node != null && node.isMarked); } /** * Clears the marking of all nodes and pseudonodes */ private void clearMarked() { BlossomVNode[] nodes = state.nodes; for (int i = 0; i < state.nodeNum; i++) { BlossomVNode current = nodes[i]; do { current.isMarked = false; current = current.blossomParent; } while (current != null && current.isMarked); } } /** * Finishes the algorithm after all nodes are matched. The main problem it solves is that the * matching after the end of primal and dual operations may not be valid in the contracted * blossoms. *

    * Property: if a matching is changed in the parent blossom, the matching in all lower blossoms * can become invalid. Therefore, we traverse all nodes, find an unmatched node (it is * necessarily contracted), go up to the first blossom whose matching hasn't been fixed (we set * blossomGrandparent references to point to the previous nodes on the path). Then we start to * change the matching accordingly all the way down to the initial node. *

    * Let's call an edge that is matched to a blossom root a "blossom edge". To make the matching * valid we move the blossom edge one layer down at a time so that in the end its endpoints are * valid initial nodes of the graph. After this transformation we can't traverse the * blossomSibling references any more. That is why we initially compute a mapping of every * pseudonode to the set of nodes that are contracted in it. This map is needed to construct a * dual solution after the matching in the graph becomes valid. */ private void finish() { if (DEBUG) { System.out.println("Finishing matching"); } Set edges = new HashSet<>(); BlossomVNode[] nodes = state.nodes; List processed = new LinkedList<>(); for (int i = 0; i < state.nodeNum; i++) { if (nodes[i].matched == null) { BlossomVNode blossomPrev = null; BlossomVNode blossom = nodes[i]; // traverse the path from unmatched node to the first unprocessed pseudonode do { blossom.blossomGrandparent = blossomPrev; blossomPrev = blossom; blossom = blossomPrev.blossomParent; } while (!blossom.isOuter); // now node.blossomGrandparent points to the previous blossom in the hierarchy (not // counting the blossom node) while (true) { // find the root of the blossom. This can be a pseudonode BlossomVNode blossomRoot = blossom.matched.getCurrentOriginal(blossom); if (blossomRoot == null) { blossomRoot = blossom.matched.head[0].isProcessed ? blossom.matched.headOriginal[1] : blossom.matched.headOriginal[0]; } while (blossomRoot.blossomParent != blossom) { blossomRoot = blossomRoot.blossomParent; } blossomRoot.matched = blossom.matched; BlossomVNode node = blossom.getOppositeMatched(); if (node != null) { node.isProcessed = true; processed.add(node); } node = blossomRoot.blossomSibling.getOpposite(blossomRoot); // chang the matching in the blossom while (node != blossomRoot) { node.matched = node.blossomSibling; BlossomVNode nextNode = node.blossomSibling.getOpposite(node); nextNode.matched = node.matched; node = nextNode.blossomSibling.getOpposite(nextNode); } if (!blossomPrev.isBlossom) { break; } blossom = blossomPrev; blossomPrev = blossom.blossomGrandparent; } for (BlossomVNode processedNode : processed) { processedNode.isProcessed = false; } processed.clear(); } } // compute the final matching double weight = 0; for (int i = 0; i < state.nodeNum; i++) { E graphEdge = state.graphEdges.get(nodes[i].matched.pos); if (!edges.contains(graphEdge)) { edges.add(graphEdge); weight += state.graph.getEdgeWeight(graphEdge); } } if (objectiveSense == MAXIMIZE) { weight = -weight; } matching = new MatchingAlgorithm.MatchingImpl<>(state.graph, edges, weight); } /** * Sets the blossomGrandparent references so that from a pseudonode we can make one step down to * some node that belongs to that pseudonode */ private void prepareForDualSolution() { BlossomVNode[] nodes = state.nodes; for (int i = 0; i < state.nodeNum; i++) { BlossomVNode current = nodes[i]; BlossomVNode prev = null; do { current.blossomGrandparent = prev; current.isMarked = true; prev = current; current = current.blossomParent; } while (current != null && !current.isMarked); } clearMarked(); } /** * Computes the set of original contracted vertices in the {@code pseudonode} and puts computes * value into the {@code blossomNodes}. If {@code node} contains other pseudonodes which haven't * been processed already, recursively computes the same set for them. * * @param pseudonode the pseudonode whose contracted nodes are computed * @param blossomNodes the mapping from pseudonodes to the original nodes contained in them */ private Set getBlossomNodes(BlossomVNode pseudonode, Map> blossomNodes) { if (blossomNodes.containsKey(pseudonode)) { return blossomNodes.get(pseudonode); } Set result = new HashSet<>(); BlossomVNode endNode = pseudonode.blossomGrandparent; BlossomVNode current = endNode; do { if (current.isBlossom) { if (!blossomNodes.containsKey(current)) { result.addAll(getBlossomNodes(current, blossomNodes)); } else { result.addAll(blossomNodes.get(current)); } } else { result.add(state.graphVertices.get(current.pos)); } current = current.blossomSibling.getOpposite(current); } while (current != endNode); blossomNodes.put(pseudonode, result); return result; } /** * Computes a solution to a dual linear program formulated on the initial graph. * * @return the solution to the dual linear program */ private DualSolution lazyComputeDualSolution() { lazyComputeWeightedPerfectMatching(); if (dualSolution != null) { return dualSolution; } Map, Double> dualMap = new HashMap<>(); Map> nodesInBlossoms = new HashMap<>(); BlossomVNode[] nodes = state.nodes; prepareForDualSolution(); double dualShift = state.minEdgeWeight / 2; for (int i = 0; i < state.nodeNum; i++) { BlossomVNode current = nodes[i]; // jump up while the first already processed node is encountered do { double dual = current.getTrueDual(); if (!current.isBlossom) { dual += dualShift; } if (objectiveSense == MAXIMIZE) { dual = -dual; } if (Math.abs(dual) > EPS) { if (current.isBlossom) { dualMap.put(getBlossomNodes(current, nodesInBlossoms), dual); } else { dualMap .put(Collections.singleton(state.graphVertices.get(current.pos)), dual); } } current.isMarked = true; if (current.isOuter) { break; } current = current.blossomParent; } while (current != null && !current.isMarked); } clearMarked(); return new DualSolution<>(initialGraph, dualMap); } /** * Prints the state of the algorithm. This is a debug method. */ private void printState() { BlossomVNode[] nodes = state.nodes; BlossomVEdge[] edges = state.edges; System.out.println(); for (int i = 0; i < 20; i++) { System.out.print("-"); } System.out.println(); Set matched = new HashSet<>(); for (int i = 0; i < state.nodeNum; i++) { BlossomVNode node = nodes[i]; if (node.matched != null) { BlossomVEdge matchedEdge = node.matched; matched.add(node.matched); if (matchedEdge.head[0].matched == null || matchedEdge.head[1].matched == null) { System.out.println("Problem with edge " + matchedEdge); throw new RuntimeException(); } } System.out.println(nodes[i]); } for (int i = 0; i < 20; i++) { System.out.print("-"); } System.out.println(); for (int i = 0; i < state.edgeNum; i++) { System.out.println(edges[i] + (matched.contains(edges[i]) ? ", matched" : "")); } } /** * Debug method */ private void printTrees() { System.out.println("Printing trees"); for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { BlossomVTree tree = root.tree; System.out.println(tree); } } /** * Debug method */ private void printMap() { System.out.println(state.nodeNum + " " + state.edgeNum); for (int i = 0; i < state.nodeNum; i++) { System.out.println(state.graphVertices.get(i) + " -> " + state.nodes[i]); } } /** * Returns the statistics describing the performance characteristics of the algorithm. * * @return the statistics describing the algorithms characteristics */ public Statistics getStatistics() { return state.statistics; } /** * Describes the performance characteristics of the algorithm and numeric data about the number * of performed dual operations during the main phase of the algorithm */ public static class Statistics { /** * Number of shrink operations */ int shrinkNum = 0; /** * Number of expand operations */ int expandNum = 0; /** * Number of grow operations */ int growNum = 0; /** * Time spent during the augment operation in nanoseconds */ long augmentTime = 0; /** * Time spent during the expand operation in nanoseconds */ long expandTime = 0; /** * Time spent during the shrink operation in nanoseconds */ long shrinkTime = 0; /** * Time spent during the grow operation in nanoseconds */ long growTime = 0; /** * Time spent during the dual update phase (either single tree or global) in nanoseconds */ long dualUpdatesTime = 0; /** * @return the number of shrink operations */ public int getShrinkNum() { return shrinkNum; } /** * @return the number of expand operations */ public int getExpandNum() { return expandNum; } /** * @return the number of grow operations */ public int getGrowNum() { return growNum; } /** * @return the time spent during the augment operation in nanoseconds */ public long getAugmentTime() { return augmentTime; } /** * @return the time spent during the expand operation in nanoseconds */ public long getExpandTime() { return expandTime; } /** * @return the time spent during the shrink operation in nanoseconds */ public long getShrinkTime() { return shrinkTime; } /** * @return the time spent during the grow operation in nanoseconds */ public long getGrowTime() { return growTime; } /** * @return the time spent during the dual update phase (either single tree or global) in * nanoseconds */ public long getDualUpdatesTime() { return dualUpdatesTime; } @Override public String toString() { return "Statistics{shrinkNum=" + shrinkNum + ", expandNum=" + expandNum + ", growNum=" + growNum + ", augmentTime=" + augmentTime + ", expandTime=" + expandTime + ", shrinkTime=" + shrinkTime + ", growTime=" + growTime + '}'; } } /** * A solution to the dual linear program formulated on the {@code graph} * * @param the graph vertex type * @param the graph edge type */ public static class DualSolution { /** * The graph on which both primal and dual linear programs are formulated */ Graph graph; /** * Mapping from sets of vertices of odd cardinality to their dual variables. Represents a * solution to the dual linear program */ Map, Double> dualVariables; /** * Constructs a new solution for the dual linear program * * @param graph the graph on which the linear program is formulated * @param dualVariables the mapping from sets of vertices of odd cardinality to their dual * variables */ public DualSolution(Graph graph, Map, Double> dualVariables) { this.graph = graph; this.dualVariables = dualVariables; } /** * @return the graph on which the linear program is formulated */ public Graph getGraph() { return graph; } /** * The mapping from sets of vertices of odd cardinality to their dual variables, which * represents a solution to the dual linear program * * @return the mapping from sets of vertices of odd cardinality to their dual variables */ public Map, Double> getDualVariables() { return dualVariables; } @Override public String toString() { final StringBuilder sb = new StringBuilder("DualSolution{"); sb.append("graph=").append(graph); sb.append(", dualVariables=").append(dualVariables); sb.append('}'); return sb.toString(); } } } ObjectiveSense.java000066400000000000000000000021321402514743400341240ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; /** * Enum specifying the objective sense of the algorithm. {@link ObjectiveSense#MAXIMIZE} means the * goal is to maximize the linear programming objective value, {@link ObjectiveSense#MINIMIZE} - to * minimize the linear programming objective value. * * @author Timofey Chudakov * @see KolmogorovWeightedMatching * @see KolmogorovWeightedPerfectMatching */ public enum ObjectiveSense { MAXIMIZE, MINIMIZE } package-info.java000066400000000000000000000001451402514743400335420ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/blossom/v5/** * Package for Kolmogorov's Blossom V algorithm */ package org.jgrapht.alg.matching.blossom.v5; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/matching/package-info.java000066400000000000000000000001321402514743400316050ustar00rootroot00000000000000/** * Algorithms for the computation of matchings. */ package org.jgrapht.alg.matching; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/package-info.java000066400000000000000000000001151402514743400300140ustar00rootroot00000000000000/** * Algorithms provided with JGraphT. */ package org.jgrapht.alg; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/partition/000077500000000000000000000000001402514743400266415ustar00rootroot00000000000000BipartitePartitioning.java000066400000000000000000000115651402514743400337500ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/partition/* * (C) Copyright 2016-2021, by Dimitrios Michail, Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.partition; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; import static org.jgrapht.GraphTests.isEmpty; /** * Algorithm for computing bipartite partitions thus checking whether a graph is bipartite or not. * *

    * The algorithm runs in linear time in the number of vertices and edges. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail * @author Alexandru Valeanu */ public class BipartitePartitioning implements PartitioningAlgorithm { /* Input graph */ private Graph graph; /* Cached bipartite partitioning */ private boolean computed = false; private Partitioning cachedPartitioning; /** * Constructs a new bipartite partitioning. * * @param graph the input graph; */ public BipartitePartitioning(Graph graph) { this.graph = Objects.requireNonNull(graph, "graph cannot be null"); } /** * Test whether the input graph is bipartite. * * @return true if the input graph is bipartite, false otherwise */ public boolean isBipartite() { if (isEmpty(graph)) { return true; } try { // at most n^2/4 edges if (Math.multiplyExact(4, graph.edgeSet().size()) > Math .multiplyExact(graph.vertexSet().size(), graph.vertexSet().size())) { return false; } } catch (ArithmeticException e) { // ignore } return this.getPartitioning() != null; } /** * {@inheritDoc} */ @Override public Partitioning getPartitioning() { if (computed) { return cachedPartitioning; } Set unknown = new HashSet<>(graph.vertexSet()); Set odd = new HashSet<>(); Deque queue = new ArrayDeque<>(); while (!unknown.isEmpty()) { if (queue.isEmpty()) { queue.add(unknown.iterator().next()); } V v = queue.removeFirst(); unknown.remove(v); for (E e : graph.edgesOf(v)) { V n = Graphs.getOppositeVertex(graph, e, v); if (unknown.contains(n)) { queue.add(n); if (!odd.contains(v)) { odd.add(n); } } else if (odd.contains(v) == odd.contains(n)) { computed = true; cachedPartitioning = null; return null; } } } Set even = new HashSet<>(graph.vertexSet()); even.removeAll(odd); computed = true; cachedPartitioning = new PartitioningImpl<>(Arrays.asList(even, odd)); return cachedPartitioning; } /** * {@inheritDoc} */ @Override public boolean isValidPartitioning(Partitioning partitioning) { Objects.requireNonNull(partitioning, "Partition cannot be null"); if (partitioning.getNumberPartitions() != 2) return false; Set firstPartition = partitioning.getPartition(0); Set secondPartition = partitioning.getPartition(1); Objects.requireNonNull(firstPartition, "First partition class cannot be null"); Objects.requireNonNull(secondPartition, "Second partition class cannot be null"); if (graph.vertexSet().size() != firstPartition.size() + secondPartition.size()) { return false; } for (V v : graph.vertexSet()) { Collection otherPartition; if (firstPartition.contains(v)) { otherPartition = secondPartition; } else if (secondPartition.contains(v)) { otherPartition = firstPartition; } else { // v does not belong to any of the two partitions return false; } for (E e : graph.edgesOf(v)) { V other = Graphs.getOppositeVertex(graph, e, v); if (!otherPartition.contains(other)) { return false; } } } return true; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/partition/package-info.java000066400000000000000000000001221402514743400320230ustar00rootroot00000000000000/** * Algorithm for computing partitions. */ package org.jgrapht.alg.partition; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/planar/000077500000000000000000000000001402514743400261055ustar00rootroot00000000000000BoyerMyrvoldPlanarityInspector.java000066400000000000000000002723461402514743400351170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/planar/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.planar; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.lang.reflect.*; import java.util.*; import java.util.function.*; /** * An implementation of the Boyer-Myrvold planarity testing algorithm. This class determines whether * an input graph is planar or not. If the graph is planar, the algorithm provides a * combinatorial * embedding of the graph, which is represented as a clockwise ordering of the edges of the * graph. Otherwise, the algorithm provides a * Kuratowski * subgraph as a certificate. Both embedding of the graph and Kuratowski subdivision are * computed lazily, meaning that the call to the {@link BoyerMyrvoldPlanarityInspector#isPlanar()} * does spend time only on the planarity testing. All of the operations of this algorithm (testing, * embedding and Kuratowski subgraph extraction) run in linear time. *

    * A planar graph is a graph, which can be * drawn in two-dimensional space without any of its edges crossing. According to the * Kuratowski theorem, a graph is * planar if and only if it doesn't contain a subdivision of the $K_{3,3}$ or $K_{5}$ graphs. *

    * The Boyer-Myrvold planarity testing algorithm was originally described in: Boyer, John amp; * Myrvold, Wendy. (2004). On the Cutting Edge: Simplified O(n) Planarity by Edge Addition. J. Graph * Algorithms Appl.. 8. 241-273. 10.7155/jgaa.00091. . We refer to this paper for the complete * description of the Boyer-Myrvold algorithm * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov */ public class BoyerMyrvoldPlanarityInspector implements PlanarityTestingAlgorithm { /** * Whether to print debug messages */ private static final boolean DEBUG = false; /** * Whether to print Kuratowski case distinction messages */ private static final boolean PRINT_CASES = false; /** * The graph we're testing planarity of */ private Graph graph; /** * The number of vertices in the {@code graph} */ private int n; /** * The resulting combinatorial embedding. This value is computed only after the first call to * the {@link BoyerMyrvoldPlanarityInspector#getEmbedding()} */ private Embedding embedding; /** * The subgraph of the {@code graph}, which is a Kuratowski subdivision. This subgraph certifies * the nonplanarity of the graph. */ private Graph kuratowskiSubdivision; /** * List of the vertices of the {@code graph} in their internal representation. After the * orientation of the {@code graph} and edge sorting, nodes in this list are sorted according to * their dfs indexes */ private List nodes; /** * List of the dfs tree roots of the {@code graph}. This list has length more than 1 if the * input {@code graph} isn't connected */ private List dfsTreeRoots; /** * List of the virtual biconnected component roots. Initially, a virtual biconnected component * root is created for every node in the {@code graph}, except for the dfs roots. These * component roots don't belong to the {@code graph}. At each step of the algorithm, every * biconnected component has its own unique component root. */ private List componentRoots; /** * The stack containing merge information for every consecutive pair of biconnected components * on the path to the back edge source. After all the biconnected components are merged, this * stack is cleared */ private List stack; /** * The node $v$, which has an unembedded back edge incident to it. */ private Node failedV; /** * Whether the planarity of the {@code graph} has been tested already */ private boolean tested; /** * Whether the graph is planar or not. Is valid, if {@code tested} is {@code true} */ private boolean planar; /** * Creates new instance of the planarity testing algorithm for the {@code graph}. The input * graph can't be null. * * @param graph the graph to test the planarity of */ public BoyerMyrvoldPlanarityInspector(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph can't be null"); this.n = graph.vertexSet().size(); this.nodes = new ArrayList<>(n); this.dfsTreeRoots = new ArrayList<>(); this.componentRoots = new ArrayList<>(n); this.stack = new ArrayList<>(); } /** * Creates a new node by converting the {@code graphVertex} to the internal node representation. * * @param vertexMap the map from vertices of the {@code graph} to their internal representation * @param graphVertex the vertex of the {@code graph} we're processing * @param edge the parent edge of the {@code graphVertex}, is {@code null} for dfs tree roots * @param parent the parent node of the {@code graphVertex} * @param dfsIndex the dfs index of the {@code graphVertex} * @return the newly created node */ private Node createNewNode( Map vertexMap, V graphVertex, E edge, Node parent, int dfsIndex) { Node child; if (parent == null) { child = new Node(graphVertex, dfsIndex, 0, null, null); child.outerFaceNeighbors[0] = child.outerFaceNeighbors[1] = child; dfsTreeRoots.add(child); } else { Edge treeEdge = new Edge(edge, parent); Node componentRoot = new Node(parent.dfsIndex, treeEdge); child = new Node(graphVertex, dfsIndex, parent.height + 1, componentRoot, treeEdge); treeEdge.target = child; componentRoots.add(componentRoot); parent.treeEdges.add(treeEdge); child.outerFaceNeighbors[0] = child.outerFaceNeighbors[1] = componentRoot; componentRoot.outerFaceNeighbors[0] = componentRoot.outerFaceNeighbors[1] = child; } nodes.add(child); vertexMap.put(graphVertex, child); return child; } /** * Orients the input graph according to its dfs traversal by creating a dfs tree. Computes the * least ancestors and lowpoints of the nodes * * @param vertexMap the map from {@code graph} vertices to their internal representatives * @param startGraphVertex the node to start the traversal from (this is a dfs tree root). * @param currentDfsIndex the dfs index of the {@code startGraphVertex} * @return the {@code currentDfsIndex} + number of nodes in the traversed subtree */ private int orientDfs(Map vertexMap, V startGraphVertex, int currentDfsIndex) { List stack = new ArrayList<>(); stack.add(new OrientDfsStackInfo(startGraphVertex, null, null, false)); while (!stack.isEmpty()) { OrientDfsStackInfo info = stack.remove(stack.size() - 1); if (info.backtrack) { Node current = vertexMap.get(info.current); current.leastAncestor = current.lowpoint = current.dfsIndex; for (Edge backEdge : current.backEdges) { current.leastAncestor = Math.min(current.leastAncestor, backEdge.target.dfsIndex); } for (Edge treeEdge : current.treeEdges) { current.lowpoint = Math.min(current.lowpoint, treeEdge.target.lowpoint); } current.lowpoint = Math.min(current.lowpoint, current.leastAncestor); } else { if (vertexMap.containsKey(info.current)) { // other dfs branch has reached this vertex earlier continue; } stack.add(new OrientDfsStackInfo(info.current, info.parent, info.parentEdge, true)); Node current = createNewNode( vertexMap, info.current, info.parentEdge, vertexMap.get(info.parent), currentDfsIndex); ++currentDfsIndex; for (E e : graph.edgesOf(info.current)) { V opposite = Graphs.getOppositeVertex(graph, e, info.current); if (vertexMap.containsKey(opposite)) { // back edge or parent edge Node oppositeNode = vertexMap.get(opposite); if (opposite.equals(info.parent)) { continue; } Edge backEdge = new Edge(e, current, oppositeNode); oppositeNode.downEdges.add(backEdge); current.backEdges.add(backEdge); } else { // possible tree edge stack.add(new OrientDfsStackInfo(opposite, current.graphVertex, e, false)); } } } } return currentDfsIndex; } /** * Iteratively start an orienting dfs from every {@code graph} vertex that hasn't been visited * yet. After orienting the graph, sorts the nodes by their lowpoints and adds them to the * {@code separatedDfsChildList} */ private void orient() { Map visited = new HashMap<>(); int currentDfsIndex = 0; for (V vertex : graph.vertexSet()) { if (!visited.containsKey(vertex)) { currentDfsIndex = orientDfs(visited, vertex, currentDfsIndex); } } sortVertices(); } /** * Performs sorting of the vertices by their lowpoints and adding them to the * {@code separatedDfsChildList} */ private void sortVertices() { List> sorted = new ArrayList<>(Collections.nCopies(n, null)); for (Node node : nodes) { int lowpoint = node.lowpoint; if (sorted.get(lowpoint) == null) { sorted.set(lowpoint, new ArrayList<>()); } sorted.get(lowpoint).add(node); } int i = 0; for (List list : sorted) { if (i >= n) { break; } if (list != null) { for (Node node : list) { nodes.set(i++, node); if (node.parentEdge != null) { node.listNode = node.parentEdge.source.separatedDfsChildList.addElementLast(node); } } } } } /** * Lazily tests the planarity of the graph. The implementation below is close to the code * presented in the original paper * * @return true if the graph is planar, false otherwise */ private boolean lazyTestPlanarity() { if (!tested) { tested = true; orient(); if (DEBUG) { printState(); System.out.println("Start testing planarity"); } for (int currentNode = n - 1; currentNode >= 0; currentNode--) { Node current = nodes.get(currentNode); if (DEBUG) { System.out.printf("Current vertex is %s\n", current.toString(false)); } for (Edge downEdge : current.downEdges) { walkUp(downEdge.source, current, downEdge); } for (Edge treeEdge : current.treeEdges) { walkDown(treeEdge.target.initialComponentRoot); } for (Edge downEdge : current.downEdges) { if (!downEdge.embedded) { failedV = current; return planar = false; } } } planar = true; } return planar; } /** * Merges the last two biconnected components using the info stored on top of the stack. The key * goal of this method is to merge the outer faces of the two components and to merge the * embedded edges of the child component root with the embedded edges of the component parent * node. */ private void mergeBiconnectedComponent() { MergeInfo info = stack.get(stack.size() - 1); stack.remove(stack.size() - 1); if (DEBUG) { System.out.printf("\nMerging biconnected component, info: %s\n", info.toString()); } Node virtualRoot = info.child; if (info.isInverted()) { virtualRoot.swapNeighbors(); } Node root = info.parent; Node virtualRootChild = virtualRoot.parentEdge.target; root.pertinentRoots.removeNode(virtualRoot.listNode); root.separatedDfsChildList.removeNode(virtualRootChild.listNode); root .mergeChildEdges( virtualRoot.embedded, info.vIn, info.vOut, info.parentNext, virtualRoot.parentEdge); root.substituteAnother(info.parentNext, info.childPrev); info.childPrev.substitute(virtualRoot, root); virtualRoot.outerFaceNeighbors[0] = virtualRoot.outerFaceNeighbors[1] = null; } /** * Embeds the back edge {@code edge} into the list of embedded edges of the source and the * virtual target of the edge such that the {@code childPrev} belongs to the new inner face. * This method also takes care of modifying the boundary of the outer face accordingly * * @param root the component root * @param entryDir the component entry direction * @param edge the edge to embed * @param childPrev the neighbor of the source of the edge that should belong to the inner face * @return a circulator starting from the edge's source */ private OuterFaceCirculator embedBackEdge(Node root, int entryDir, Edge edge, Node childPrev) { if (DEBUG) { System.out.printf("Embedding back edge %s\n", edge.toString()); } assert !edge.embedded; if (entryDir == 0) { root.embedded.addLast(edge); } else { root.embedded.addFirst(edge); } Node child = edge.source; child.embedBackEdge(edge, childPrev); child.edgeToEmbed = null; child.backEdgeFlag = n; edge.embedded = true; child.substitute(childPrev, root); root.outerFaceNeighbors[entryDir] = child; Node next = child.nextOnOuterFace(root); return new OuterFaceCirculator(next, child); } /** * Embeds a short-circuit edge from the {@code componentRoot} to the current node of the * {@code circulator}. Changes the outer face accordingly * * @param componentRoot the component root * @param entryDir the direction used to enter the component * @param circulator a circulator to the source of the new edge */ private void embedShortCircuit(Node componentRoot, int entryDir, OuterFaceCirculator circulator) { Node current = circulator.getCurrent(), prev = circulator.getPrev(); Edge shortCircuit = new Edge(current, componentRoot.getParent()); if (entryDir == 0) { componentRoot.embedded.addLast(shortCircuit); componentRoot.outerFaceNeighbors[0] = current; } else { componentRoot.embedded.addFirst(shortCircuit); componentRoot.outerFaceNeighbors[1] = current; } current.embedBackEdge(shortCircuit, prev); current.substitute(prev, componentRoot); if (DEBUG) { System.out.printf("Embedding short circuit edge: %s\n", shortCircuit.toString()); printState(); } } /** * The walkdown procedure from the original paper. Either embeds all of the back edges in the * subtree rooted at the child of the {@code componentRoot} or identifies the back edges which * can be used to extract a Kuratowski subdivision. Iteratively traverses the tree of the * biconnected component and descends only to the pertinent components. This procedure is also * responsible for embedding short-circuit edges to make the algorithm run in linear time in the * worst case. * * @param componentRoot the root of the component to start the walkdown from */ private void walkDown(Node componentRoot) { if (DEBUG) { System.out.printf("\nStart walk down on node %s\n", componentRoot.toString(true)); } for (int componentEntryDir = 0; componentEntryDir < 2 && stack.isEmpty(); componentEntryDir++) { if (DEBUG) { System.out.println("\nNew traversal direction = " + componentEntryDir); } int currentComponentEntryDir = componentEntryDir; OuterFaceCirculator circulator = componentRoot.iterator(currentComponentEntryDir); for (Node current = circulator.next(); current != componentRoot;) { if (DEBUG) { System.out.printf("Current = %s\n", current.toString()); } if (current.hasBackEdgeWrtTo(componentRoot)) { Node childPrev = circulator.getPrev(); while (!stack.isEmpty()) { mergeBiconnectedComponent(); if (DEBUG) { printState(); } } circulator = embedBackEdge( componentRoot, componentEntryDir, current.edgeToEmbed, childPrev); if (DEBUG) { printState(); printBiconnectedComponent(current); } } if (!current.pertinentRoots.isEmpty()) { int parentComponentEntryDir = currentComponentEntryDir; Node root = current.pertinentRoots.getFirst(); if (DEBUG) { System.out.printf("Descending to the root = %s\n", root.toString()); } OuterFaceCirculator ccwCirculator = getActiveSuccessorOnOuterFace(root, componentRoot, 0); Node ccwActiveNode = ccwCirculator.getCurrent(); OuterFaceCirculator cwCirculator = getActiveSuccessorOnOuterFace(root, componentRoot, 1); Node cwActiveNode = cwCirculator.getCurrent(); if (ccwActiveNode.isInternallyActiveWrtTo(componentRoot)) { currentComponentEntryDir = 0; } else if (cwActiveNode.isInternallyActiveWrtTo(componentRoot)) { currentComponentEntryDir = 1; } else if (ccwActiveNode.isPertinentWrtTo(componentRoot)) { currentComponentEntryDir = 0; } else { currentComponentEntryDir = 1; } if (currentComponentEntryDir == 0) { stack .add( new MergeInfo( current, circulator.next(), root, root.outerFaceNeighbors[1], parentComponentEntryDir, currentComponentEntryDir)); current = ccwActiveNode; circulator = ccwCirculator; if (!cwActiveNode.hasRootNeighbor()) { embedShortCircuit(root, 1, cwCirculator); } } else { stack .add( new MergeInfo( current, circulator.next(), root, root.outerFaceNeighbors[0], parentComponentEntryDir, currentComponentEntryDir)); current = cwActiveNode; circulator = cwCirculator; if (!ccwActiveNode.hasRootNeighbor()) { embedShortCircuit(root, 0, ccwCirculator); } } } else if (current.isInactiveWrtTo(componentRoot)) { current = circulator.next(); } else { // current vertex is externally active if (DEBUG) { System.out.println("Current vertex is externally active, stop\n"); } if (!current.hasRootNeighbor() && stack.isEmpty()) { embedShortCircuit(componentRoot, componentEntryDir, circulator); } break; } } } } /** * The walkup procedure from the original paper. Identifies the pertinent subgraph of the graph * by going up the dfs tree from the {@code start} node to the {@code end} node using the edge * {@code edge} * * @param start the node to start the walkup from * @param end the node currently processed by the main loop of the algorithm * @param edge a back edge to embed */ private void walkUp(Node start, Node end, Edge edge) { if (DEBUG) { System.out.printf("\nStart walk up on edge = %s\n", edge.toString()); } int visited = end.dfsIndex; start.backEdgeFlag = visited; start.edgeToEmbed = edge; Node x = start.outerFaceNeighbors[0], y = start.outerFaceNeighbors[1], xPrev = start, yPrev = start; start.visited = visited; while (x != end && !x.isVisitedWrtTo(end) && !y.isVisitedWrtTo(end)) { if (DEBUG) { System.out.printf("Current x = %s\nCurrent y = %s\n", x.toString(), y.toString()); } x.visited = y.visited = visited; Node root = null; if (x.isRootVertex()) { root = x; } else if (y.isRootVertex()) { root = y; } if (root != null) { if (DEBUG) { System.out.printf("Found root = %s\n", root.toString()); } Node rootChild = root.parentEdge.target; Node newStart = root.parentEdge.source; if (newStart != end) { if (rootChild.lowpoint < end.dfsIndex) { // the component in externally active root.listNode = newStart.pertinentRoots.addElementLast(root); } else { // the component is internally active root.listNode = newStart.pertinentRoots.addElementFirst(root); } } else { break; } newStart.visited = visited; xPrev = yPrev = newStart; x = newStart.outerFaceNeighbors[0]; y = newStart.outerFaceNeighbors[1]; } else { Node t = x; x = x.nextOnOuterFace(xPrev); xPrev = t; t = y; y = y.nextOnOuterFace(yPrev); yPrev = t; } } } /** * Lazily computes a combinatorial embedding of the {@code graph} by removing all the * short-circuit edges and properly orienting the edges around the nodes. * * @return a combinatorial embedding of the {@code graph} */ private Embedding lazyComputeEmbedding() { lazyTestPlanarity(); if (!planar) { throw new IllegalArgumentException( "Input graph is not planar, can't compute graph embedding"); } if (embedding == null) { for (Node dfsTreeRoot : dfsTreeRoots) { cleanUpDfs(dfsTreeRoot); } Map> embeddingMap = new HashMap<>(); for (Node node : nodes) { for (Node child : node.separatedDfsChildList) { Node virtualRoot = child.initialComponentRoot; node.embedded.append(virtualRoot.embedded); } List embeddedEdges = new ArrayList<>(node.embedded.size()); for (Edge edge : node.embedded) { embeddedEdges.add(edge.graphEdge); } embeddingMap.put(node.graphVertex, embeddedEdges); } embedding = new EmbeddingImpl<>(graph, embeddingMap); } return embedding; } /** * Method for debug purposes, prints the boundary the {@code node} belongs to * * @param node a node on the outer face */ private void printBiconnectedComponent(Node node) { StringBuilder builder = new StringBuilder(node.toString(false)); OuterFaceCirculator circulator = node.iterator(0); Node current = circulator.next(); Node stop = current; do { builder.append(" -> ").append(current.toString(false)); current = circulator.next(); } while (current != stop); System.out.println("Biconnected component after merge: " + builder.toString()); } /** * Method for debug purposes, prints the state of the algorithm */ private void printState() { System.out.println("\nPrinting state:"); System.out.println("Dfs roots: " + dfsTreeRoots); System.out.println("Nodes:"); for (Node node : nodes) { System.out.println(node.toString(true)); } System.out.println("Virtual nodes:"); for (Node node : componentRoots) { System.out.println(node.toString(true)); } List inverted = new ArrayList<>(); for (Node node : nodes) { for (Edge edge : node.treeEdges) { if (edge.sign < 0) { inverted.add(edge); } } } System.out.println("Inverted edges = " + inverted); } /** * Either finds and returns a circulator to the node on the boundary of the component, which * satisfies the {@code predicate} or returns a circulator to the {@code stop} node. * * @param predicate the condition the desired node should satisfy * @param start the node to start the search from * @param stop the node to end the search with * @param dir the direction to start the traversal in * @return a circulator to the node satisfying the {@code predicate} or to the {@code stop} node */ private OuterFaceCirculator selectOnOuterFace( Predicate predicate, Node start, Node stop, int dir) { OuterFaceCirculator circulator = start.iterator(dir); Node current = circulator.next(); while (current != stop && !predicate.test(current)) { current = circulator.next(); } return circulator; } /** * Returns an active node on the outer face in the direction {@code dir} starting from the * {@code start} node * * @param start the node to start the search from * @param v an ancestor of the {@code start} * @param dir the direction of the search * @return a circulator to the found node */ private OuterFaceCirculator getActiveSuccessorOnOuterFace(Node start, Node v, int dir) { return selectOnOuterFace(n -> n.isActiveWrtTo(v), start, start, dir); } /** * Returns acirculator to the externally active node on the outer face between the {@code start} * and {@code end} nodes in the direction {@code dir}. * * @param start the node to start the search from * @param stop the node to end the search with * @param v an ancestor of the {@code start} and the {@code end} * @param dir the direction of the search * @return a circulator to the found node */ private OuterFaceCirculator getExternallyActiveSuccessorOnOuterFace( Node start, Node stop, Node v, int dir) { return selectOnOuterFace(n -> n.isExternallyActiveWrtTo(v), start, stop, dir); } /** * Returns a component root of component the {@code node} is contained in. * * @param node a node in the partially embedded graph * @return a component root of the component the {@code node} belongs to */ private Node getComponentRoot(Node node) { return selectOnOuterFace(Node::isRootVertex, node, node, 0).getCurrent(); } /** * Adds the edges on the path from the {@code startEdge} up to the node {@code stop} to the set * {@code edges} * * @param edges the set to add the path edges to * @param startEdge the edge to start from * @param stop the last node on the path */ private void addPathEdges(Set edges, Edge startEdge, Node stop) { edges.add(startEdge); Node current = startEdge.source; while (current != stop) { edges.add(current.parentEdge); current = current.getParent(); } } /** * Adds the edges between the {@code start} and the {@code end} to the set {@code edges} * * @param edges the set to add the path edges to to * @param start the node to start from * @param stop the node to end with */ private void addPathEdges(Set edges, Node start, Node stop) { if (start != stop) { addPathEdges(edges, start.parentEdge, stop); } } /** * Searches a back edge which target has a height smaller than {@code heightMax} * * @param current the node to start from * @param heightMax an upper bound on the height of the desired back edge * @return the desired back edge or null, if no such edge exist */ private Edge searchEdge(Node current, int heightMax) { return searchEdge(current, heightMax, null); } /** * Searches a back edge which target has a height smaller than {@code heightMax} * * @param current the node to start from * @param heightMax an upper bound on the height of the desired back edge * @param forbiddenEdge an edge the desired edge should not be equal to * @return the desired back edge or null, if no such edge exist */ private Edge searchEdge(Node current, int heightMax, Edge forbiddenEdge) { Predicate isNeeded = e -> { if (forbiddenEdge == e) { return false; } return e.target.height < heightMax; }; return searchEdge(current, isNeeded); } /** * Generically searches an edge in the subtree rooted at the {@code current}, which doesn't * include the children of the {@code current} that have beem merged to the parent biconnected * component. * * @param current the node to start the searh from * @param isNeeded the predicate which the desired edge should satisfy * @return an edge which satisfies the {@code predicate}, or null if such an edge doesn't exist */ private Edge searchEdge(Node current, Predicate isNeeded) { for (Node node : current.separatedDfsChildList) { Edge result = searchSubtreeDfs(node, isNeeded); if (result != null) { return result; } } for (Edge edge : current.backEdges) { if (isNeeded.test(edge)) { return edge; } } return null; } /** * Recursively searches all the subtree root at the node {@code start} to find an edge * satisfying the {@code predicate}. * * @param start the node to start the search from. * @param isNeeded a predicate, which the desired edge should satisfy * @return a desired edge, or null if no such edge exist. */ private Edge searchSubtreeDfs(Node start, Predicate isNeeded) { List stack = new ArrayList<>(); stack.add(start); while (!stack.isEmpty()) { Node current = stack.remove(stack.size() - 1); for (Edge edge : current.backEdges) { if (isNeeded.test(edge)) { return edge; } } for (Edge edge : current.treeEdges) { stack.add(edge.target); } } return null; } /** * Returns the highest of the two input node, i.e. the node with the greater height * * @param a a node in the dfs tree * @param b a node in the dfs tree * @return the highest of the two nodes */ private Node highest(Node a, Node b) { return a.height > b.height ? a : b; } /** * Returns the lowest of the two input node, i.e. the node with the least height. * * @param a a node in the dfs tree * @param b a node in the dfs tree * @return the lowest of the two nodes */ private Node lowest(Node a, Node b) { return a.height < b.height ? a : b; } /** * Iteratively sets a boundary height for the nodes on the outer face branch ending at the node * {@code w}. * * @param componentRoot the root of the component * @param w the end of the outer face branch * @param dir the direction to start the traversal in * @param delta a value in $\{+1, -1\}$ to set either positive or negative boundary height */ private void setBoundaryDepth(Node componentRoot, Node w, int dir, int delta) { OuterFaceCirculator circulator = componentRoot.iterator(dir); Node current = circulator.next(); int currentHeight = delta; while (current != w) { current.boundaryHeight = currentHeight; currentHeight += delta; current = circulator.next(); } } /** * Clears the visited variable of all the nodes and component roots */ private void clearVisited() { nodes.forEach(n -> n.visited = 0); componentRoots.forEach(n -> n.visited = 0); } /** * Generically searches a path from the {@code current} node to the first node satisfying the * {@code isFinish} predicate consisting of all the nodes satisfying the {@code canGo} * predicate. The key property of this method is that it searches the next edge on the path in * the clockwise order starting from the previous edge. The edges of the resulting path are * added to the {@code edges}. * * @param start the start node of the traversal * @param startPrev the previous edge of the start node * @param canGo specifies where the search can go * @param isFinish specifies what nodes are finish nodes * @param edges the list containing the resulting path * @return true if the search was successful, false otherwise */ private boolean findPathDfs( Node start, Edge startPrev, Predicate canGo, Predicate isFinish, List edges) { List stack = new ArrayList<>(); stack.add(new SearchInfo(start, startPrev, false)); while (!stack.isEmpty()) { SearchInfo info = stack.remove(stack.size() - 1); if (isFinish.test(info.current)) { edges.add(info.prevEdge); edges.remove(0); return true; } if (info.backtrack) { edges.remove(edges.size() - 1); } else { if (info.current.visited != 0) { continue; } info.current.visited = 1; stack.add(new SearchInfo(info.current, info.prevEdge, true)); edges.add(info.prevEdge); /* * The iteration is performed in the reverse order since the infos are pushed on the * stack and therefore will be processed in the again reverse order */ Iterator iterator = info.current.embedded.reverseCircularIterator(info.prevEdge); while (iterator.hasNext()) { Edge currentEdge = iterator.next(); Node opposite = currentEdge.getOpposite(info.current); if ((!canGo.test(opposite) || opposite.visited != 0) && !isFinish.test(opposite)) { continue; } stack.add(new SearchInfo(opposite, currentEdge, false)); } } } return false; } /** * Finds the highest obstructing path in the component rooted at {@code componentRoot}. See the * original paper for the definition of the obstructing path. This method heavily relies on the * fact that the method * {@link BoyerMyrvoldPlanarityInspector#findPathDfs(Node, Edge, Predicate, Predicate, List)} * chooses the edges in the clockwise order. * * @param componentRoot the root of the component * @param w the node called {@code w} in the Kuratowski subdivision extraction phase. * @return the edges of the desired path as a list */ private List findHighestObstructingPath(Node componentRoot, Node w) { clearVisited(); List result = new ArrayList<>(); OuterFaceCirculator circulator = componentRoot.iterator(0); Node current = circulator.next(); while (current != w) { if (findPathDfs( current, current.embedded.getFirst(), n -> !n.marked, n -> n.boundaryHeight < 0, result)) { return result; } current = circulator.next(); } return result; } /** * Finishes the Kuratowski subdivision extraction by constructing the desired subgraph * * @param subdivision the edges in the Kuratowski subdivision * @return the Kuratowski subgraph of the {@code graph} */ private Graph finish(Set subdivision) { Set edgeSubset = new HashSet<>(); Set vertexSubset = new HashSet<>(); subdivision.forEach(e -> { edgeSubset.add(e.graphEdge); vertexSubset.add(e.target.graphVertex); vertexSubset.add(e.source.graphVertex); }); kuratowskiSubdivision = new AsSubgraph<>(graph, vertexSubset, edgeSubset); return kuratowskiSubdivision; } /** * Adds the edges on the outer face of the component rooted at {@code componentRoot} to the set * {@code edges} * * @param edges the set to add the edges to * @param componentRoot the root of the biconnected component */ private void addBoundaryEdges(Set edges, Node componentRoot) { OuterFaceCirculator circulator = componentRoot.iterator(0); Node current; do { Edge edge = circulator.edgeToNext(); edge.source.marked = edge.target.marked = true; edges.add(edge); current = circulator.next(); } while (current != componentRoot); } /** * Cleans up the dfs trees before the Kuratowski subdivision extraction phase */ private void kuratowskiCleanUp() { for (Node dfsTreeRoot : dfsTreeRoots) { cleanUpDfs(dfsTreeRoot); } for (Node node : componentRoots) { if (node.outerFaceNeighbors[0] != null) { node.removeShortCircuitEdges(); fixBoundaryOrder(node); } } } /** * Recursively cleans up the dfs tree rooted at the {@code dfsTreeRoot} my removing all the * short-circuit edges and properly orienting other embedded edges * * @param dfsTreeRoot the root of the dfs tree to clean up */ private void cleanUpDfs(Node dfsTreeRoot) { List> stack = new ArrayList<>(); stack.add(Pair.of(dfsTreeRoot, 1)); while (!stack.isEmpty()) { Pair entry = stack.remove(stack.size() - 1); Node current = entry.getFirst(); int sign = entry.getSecond(); if (sign < 0) { current.embedded.invert(); } current.removeShortCircuitEdges(); for (Node node : current.separatedDfsChildList) { // all the components, that aren't merged, aren't inverted node.parentEdge.sign = sign; } for (Edge treeEdge : current.treeEdges) { stack.add(Pair.of(treeEdge.target, sign * treeEdge.sign)); } } } /** * Orient the connections on the outer face of the component rooted at {@code componentRoot} * such that they are ordered * * @param componentRoot the root of the component to process */ private void fixBoundaryOrder(Node componentRoot) { if (componentRoot.embedded.size() < 2) { return; } Node componentParent = componentRoot.getParent(); Edge edgeToNext = componentRoot.embedded.getLast(), edgeToPrev = componentRoot.embedded.getFirst(); Node next = edgeToNext.getOpposite(componentParent), prev = edgeToPrev.getOpposite(componentParent); componentRoot.outerFaceNeighbors[0] = next; componentRoot.outerFaceNeighbors[1] = prev; next.outerFaceNeighbors[1] = componentRoot; prev.outerFaceNeighbors[0] = componentRoot; Node current = componentRoot.outerFaceNeighbors[0]; do { edgeToNext = current.embedded.getLast(); edgeToPrev = current.embedded.getFirst(); next = edgeToNext.getOpposite(current); prev = edgeToPrev.getOpposite(current); if (prev != componentParent) { current.outerFaceNeighbors[1] = prev; } if (next != componentParent) { current.outerFaceNeighbors[0] = next; } current = next; } while (current != componentParent); } /** * Removes the edges from the outer face from the {@code start} node to the {@code end} node in * the direction {@code dir} from the set {@code edges} * * @param start the start of the boundary path * @param end the end of the boundary path * @param dir the direction to take from the {@code start} node * @param edges the set of edges to modify */ private void removeUp(Node start, Node end, int dir, Set edges) { if (start == end) { return; } OuterFaceCirculator circulator = start.iterator(dir); Node next; do { Edge edge = circulator.edgeToNext(); edges.remove(edge); next = circulator.next(); } while (next != end); } /** * Effectively is a method for finding node {@code z} in the notations of the original paper. * The search proceeds in the reverse order of the path from the {@code backEdge} to the node * {@code w} * * @param w the start of the path down * @param backEdge the last edge on the path * @return the desired node {@code z} or null if the source of the {@code backEdge} is equal to * {@code w} */ private Node getNextOnPath(Node w, Edge backEdge) { if (backEdge.source == w) { return null; } Node prev = backEdge.source, current = backEdge.source.getParent(); while (current != w) { prev = current; current = current.getParent(); } return prev; } /** * Finds a path from some intermediate nodes on the path represented by the list {@code path} to * the node {@code v}. The path to {@code v} certainly doesn't exist if the list {@code path} * has size 1, because we're looking for a path from some intermediate node * * @param path the path between left and right outer face branches * @param v the parent of the biconnected component * @return the path edges in a list, which can be empty */ private List findPathToV(List path, Node v) { clearVisited(); int i = 0; Edge currentEdge = path.get(i); Node current = currentEdge.source.boundaryHeight != 0 ? currentEdge.target : currentEdge.source; List result = new ArrayList<>(); while (i < path.size() - 1) { if (findPathDfs(current, currentEdge, n -> !n.marked, n -> n == v, result)) { return result; } ++i; currentEdge = path.get(i); current = currentEdge.getOpposite(current); } return result; } /** * Checks whether the first node {@code a} is strictly higher than nodes {@code b} and {@code c} * * @param a a node in the dfs tree * @param b a node in the dfs tree * @param c a node in the dfs tree * @return true if the first node in strictly higher that other node, false otherwise */ private boolean firstStrictlyHigher(Node a, Node b, Node c) { return a.height > b.height && a.height > c.height; } /** * Checks whether the biconnected component rooted at {@code componentRoot} can be used to * extract a Kuratowski subdivision. It can be used in the case there is one externally active * node on each branch of the outer face and there is a pertinent node on the lower part of the * outer face between these two externally active nodes. * * @param componentRoot the root of the biconnected component * @param v an ancestor of the nodes in the biconnected component * @return an unembedded back edge, which target is {@code v} and which can be used to extract a * Kuratowski subdivision, or {@code null} is no such edge exist for this biconnected * component */ private Edge checkComponentForFailedEdge(Node componentRoot, Node v) { OuterFaceCirculator firstDir = getExternallyActiveSuccessorOnOuterFace(componentRoot, componentRoot, v, 0); Node firstDirNode = firstDir.getCurrent(); OuterFaceCirculator secondDir = getExternallyActiveSuccessorOnOuterFace(componentRoot, componentRoot, v, 1); Node secondDirNode = secondDir.getCurrent(); if (firstDirNode != componentRoot && firstDirNode != secondDirNode) { Node current = firstDir.next(); while (current != secondDirNode) { if (current.isPertinentWrtTo(v)) { return searchEdge(current, e -> e.target == v && !e.embedded); } current = firstDir.next(); } } return null; } /** * Finds an unembedded back edge to {@code v}, which can be used to extract the Kuratowski * subdivision. If the merge stack isn't empty, the last biconnected component processed by the * walkdown can be used to find such an edge, because walkdown descended to that component * (which means that component is pertinent) and couldn't reach a pertinent node. This can only * happen by encountering externally active nodes on both branches of the traversal. Otherwise, * be have look in all the child biconnected components to find an unembedded back edge. We're * guided by the fact that an edge can not be embedded only in the case both traversals of the * walkdown could reach all off the pertinent nodes. This in turn can happen only if both * traversals get stuck on externally active nodes. *

    * Note: not every unembedded back edge can be used to extract a Kuratowski subdivision * * @param v the vertex which has an unembedded back edge incident to it * @return the found unembedded back edge which can be used to extract a Kuratowski subdivision */ private Edge findFailedEdge(Node v) { if (stack.isEmpty()) { for (Node child : v.separatedDfsChildList) { Node componentRoot = child.initialComponentRoot; Edge result = checkComponentForFailedEdge(componentRoot, v); if (result != null) { return result; } } return null; // should not happen in case node v has an incident unembedded back edge } else { MergeInfo info = stack.get(stack.size() - 1); return checkComponentForFailedEdge(info.child, v); } } /** * Lazily extracts a Kuratowski subdivision from the {@code graph}. The process of adding the * edges of the subdivision to the resulting set of edges had been made explicit for every case. * * @return a Kuratowski subgraph of the {@code graph} */ private Graph lazyExtractKuratowskiSubdivision() { if (kuratowskiSubdivision == null) { // remove short-circuit edges and orient all embedded lists clockwise kuratowskiCleanUp(); if (DEBUG) { printState(); } Set subdivision = new HashSet<>(); // find the needed unembedded back edge which can be used to find Kuratowski subgraph Edge failedEdge = findFailedEdge(failedV); assert failedEdge != null; /* * We're iteratively moving up traversing the outer faces of the biconnected components * to find externally active nodes x and y, which are on different branches of the outer * face. The way we're finding the nodes x and y helps us eliminate the case E_1 * described in the original paper. This can be done because we always find the closest * to the node w externally active nodes x and y. */ Node x, y, v = failedEdge.target, w = failedEdge.source, componentRoot; while (true) { componentRoot = getComponentRoot(w); x = getExternallyActiveSuccessorOnOuterFace(w, componentRoot, v, 1).getCurrent(); y = getExternallyActiveSuccessorOnOuterFace(w, componentRoot, v, 0).getCurrent(); if (x.isRootVertex()) { w = x.getParent(); } else if (y.isRootVertex()) { w = y.getParent(); } else { componentRoot = getComponentRoot(w); break; } } Edge xBackEdge = searchEdge(x, v.height); Edge yBackEdge = searchEdge(y, v.height); if (DEBUG) { System.out .printf( "Failed v = %s, failed edge = %s\n", failedV.toString(false), failedEdge.toString()); System.out.printf("x = %s, y = %s\n", x.toString(false), y.toString(false)); System.out .printf( "xBackEdge = %s, yBackEdge = %s\n", xBackEdge.toString(), yBackEdge.toString()); } Node backLower = lowest(xBackEdge.target, yBackEdge.target); Node backHigher = highest(xBackEdge.target, yBackEdge.target); addPathEdges(subdivision, xBackEdge, x); addPathEdges(subdivision, yBackEdge, y); addBoundaryEdges(subdivision, componentRoot); if (componentRoot.getParent() != v) { // case A, v isn't a parent of the component we've found if (PRINT_CASES) { System.out.println("Case A"); } addPathEdges(subdivision, componentRoot.getParent(), backLower); addPathEdges(subdivision, failedEdge, w); return finish(subdivision); } // node z will be null only if the tree path from w to failedEdge is failedEdge itself Node z = getNextOnPath(w, failedEdge); Edge backEdge = null; if (z != null) { backEdge = searchSubtreeDfs(z, e -> e.target.height < v.height && e != failedEdge); } if (backEdge != null) { // case B if (PRINT_CASES) { System.out.println("Case B"); } addPathEdges(subdivision, backEdge, w); addPathEdges(subdivision, failedEdge, w); Node highest = highest(xBackEdge.target, highest(yBackEdge.target, backEdge.target)); Node lowest = lowest(xBackEdge.target, lowest(yBackEdge.target, backEdge.target)); addPathEdges(subdivision, highest, lowest); return finish(subdivision); } /* * If we failed to either case A or B, we have to find a highest obstructing path and * then deal with cases C - E */ setBoundaryDepth(componentRoot, w, 0, 1); setBoundaryDepth(componentRoot, w, 1, -1); assert x.boundaryHeight > 0; List path = findHighestObstructingPath(componentRoot, w); assert !path.isEmpty(); if (DEBUG) { System.out.println("Path = " + path); } Edge firstEdge = path.get(0); Edge lastEdge = path.get(path.size() - 1); Node firstNode = firstEdge.source.boundaryHeight > 0 ? firstEdge.source : firstEdge.target; Node lastNode = lastEdge.source.boundaryHeight < 0 ? lastEdge.source : lastEdge.target; if (firstNode.boundaryHeight < x.boundaryHeight || lastNode.boundaryHeight > y.boundaryHeight) { // case C, either the first node or the last node of the path is higher than x or y // respectively if (PRINT_CASES) { System.out.println("Case C"); } Node removeStart; if (lastNode.boundaryHeight > y.boundaryHeight) { removeStart = firstNode.boundaryHeight < x.boundaryHeight ? firstNode : x; removeUp(removeStart, componentRoot, 1, subdivision); } else { removeUp(y, componentRoot, 0, subdivision); } addPathEdges(subdivision, failedEdge, w); subdivision.addAll(path); addPathEdges(subdivision, v, backLower); return finish(subdivision); } path.forEach(e -> e.source.marked = e.target.marked = true); List pathToV = findPathToV(path, v); if (!pathToV.isEmpty()) { // case D, we have a path to the node v if (PRINT_CASES) { System.out.println("Case D"); } removeUp(x, componentRoot, 1, subdivision); removeUp(y, componentRoot, 0, subdivision); subdivision.addAll(path); subdivision.addAll(pathToV); addPathEdges(subdivision, v, backLower); addPathEdges(subdivision, failedEdge, w); return finish(subdivision); } Edge externallyActive = searchEdge(w, v.height, failedEdge); assert externallyActive != null; if (DEBUG) { System.out.printf("Externally active edge = %s\n", externallyActive.toString()); } addPathEdges(subdivision, externallyActive, w); if (firstStrictlyHigher(externallyActive.target, xBackEdge.target, yBackEdge.target)) { // case E_2, equivalent to A if (PRINT_CASES) { System.out.println("Case E_2"); } addPathEdges(subdivision, componentRoot.getParent(), backLower); } else if (firstStrictlyHigher( xBackEdge.target, yBackEdge.target, externallyActive.target)) { // case E_2, u_x is higher if (PRINT_CASES) { System.out.println("Case E_2, u_x is higher"); } removeUp(componentRoot, x, 0, subdivision); removeUp(w, lastNode, 0, subdivision); subdivision.addAll(path); addPathEdges(subdivision, failedEdge, w); addPathEdges(subdivision, v, lowest(backLower, externallyActive.target)); } else if (firstStrictlyHigher( yBackEdge.target, xBackEdge.target, externallyActive.target)) { // case E_2, u_y is higher if (PRINT_CASES) { System.out.println("Case E_2, u_y is higher"); } removeUp(y, componentRoot, 0, subdivision); removeUp(firstNode, w, 0, subdivision); subdivision.addAll(path); addPathEdges(subdivision, failedEdge, w); addPathEdges(subdivision, v, lowest(backLower, externallyActive.target)); } else if (firstNode.boundaryHeight > x.boundaryHeight) { // case E_4, p_x is lower than x if (PRINT_CASES) { System.out.println("Case E_3, p_x is lower than x"); } removeUp(w, lastNode, 0, subdivision); subdivision.addAll(path); addPathEdges(subdivision, failedEdge, w); addPathEdges( subdivision, highest(backHigher, externallyActive.target), lowest(backLower, externallyActive.target)); } else if (lastNode.boundaryHeight < y.boundaryHeight) { // case E_4, p_y is lower than y if (PRINT_CASES) { System.out.println("Case E_3, p_y is lower than y"); } removeUp(firstNode, w, 0, subdivision); subdivision.addAll(path); addPathEdges(subdivision, failedEdge, w); addPathEdges( subdivision, highest(backHigher, externallyActive.target), lowest(backLower, externallyActive.target)); } else { // case E, extracting K_5 if (PRINT_CASES) { System.out.println("Case E, extracting K_5"); } subdivision.addAll(path); addPathEdges(subdivision, v, lowest(backLower, externallyActive.target)); addPathEdges(subdivision, failedEdge, w); } return finish(subdivision); } return kuratowskiSubdivision; } /** * {@inheritDoc} *

    * Only the first call to this method does the actual computation, all subsequent calls only * return the previously computed value. */ @Override public boolean isPlanar() { return lazyTestPlanarity(); } /** * {@inheritDoc} *

    * Only the first call to this method does the actual computation, all subsequent calls only * return the previously computed value. */ @Override public Embedding getEmbedding() { if (isPlanar()) { return lazyComputeEmbedding(); } else { throw new IllegalArgumentException("Graph is not planar"); } } /** * {@inheritDoc} *

    * Only the first call to this method does the actual computation, all subsequent calls only * return the previously computed value. */ @Override public Graph getKuratowskiSubdivision() { if (isPlanar()) { throw new IllegalArgumentException("Graph is planar"); } else { return lazyExtractKuratowskiSubdivision(); } } /** * Represents information needed to search a path within a biconnected component */ private class SearchInfo { /** * The current node of the dfs traversal */ Node current; /** * The edge used to go to the {@code current} vertex */ Edge prevEdge; /** * Whether dfs is in a forward or a backtracking phase */ boolean backtrack; /** * Creates a new search info * * @param current the current node of the traversal * @param prevEdge the edge used to go to the {@code current} vertex * @param backtrack whether dfs is in a forward or a backtracking phase */ SearchInfo(Node current, Edge prevEdge, boolean backtrack) { this.current = current; this.prevEdge = prevEdge; this.backtrack = backtrack; } } /** * Represents information needed to store in the stack during the input {@code graph} * orientation. */ private class OrientDfsStackInfo { /** * The current vertex of the dfs traversal */ V current; /** * The parent vertex of the {@code current} vertex, which is null for dfs tree roots */ V parent; /** * The edge connecting {@code parent} and {@code current} vertices */ E parentEdge; /** * Whether dfs is moving forward or backtracking on the {@code current} node */ boolean backtrack; /** * Creates new instance of the information stored on the stack during the orientation of the * {@code graph} * * @param current the vertex dfs is currently processing * @param parent the parent of the {@code current} vertex * @param parentEdge the edge between {@code current} and {@code parent} vertices * @param backtrack whether dfs is moving forward or backtracking on the {@code current} * vertex */ OrientDfsStackInfo(V current, V parent, E parentEdge, boolean backtrack) { this.current = current; this.parent = parent; this.parentEdge = parentEdge; this.backtrack = backtrack; } } /** * The information needed to merge two consecutive biconnected components */ private class MergeInfo { /** * The node current traversal descended from. This node belongs to the parent biconnected * component */ Node parent; /** * The next node along the traversal of the parent biconnected component */ Node parentNext; /** * The virtual root of the child biconnected component */ Node child; /** * The previous node along the traversal of the child biconnected component */ Node childPrev; /** * The direction used to enter the parent biconnected component. *

    * Note: this value doesn't specify the direction from {@code parent} node to the * {@code parentNext} node, i.e. {@code parent.outerFaceNeighbors[vIn]} may not be equal to * the {@code parentNext}. Instead, this value specifies the direction used to start the * traversal from the parent's biconnected component virtual root. */ int vIn; /** * The direction used to start the traversal of the child biconnected component. Since the * {@code child} is the component root, {@code child.outerFaceNeighbors[|1-vOut|]} is equal * to the {@code childPrev} */ int vOut; /** * Creates new instance of the infromation needed to merge to biconnected components * * @param parent the node current traversal descended from * @param parentNext the next node along the traversal of the parent component * @param child the virtual root of the child biconnected component * @param childPrev the previous node along the traversal of the child component * @param vIn the direction used to enter the parent biconnected component * @param vOut the direction used to enter the child biconnected component */ MergeInfo(Node parent, Node parentNext, Node child, Node childPrev, int vIn, int vOut) { this.parent = parent; this.parentNext = parentNext; this.child = child; this.childPrev = childPrev; this.vIn = vIn; this.vOut = vOut; } /** * Returns true if the traversal was inverted when descending to the child biconnected * component, false otherwise * * @return true if the traversal was inverted when descending to the child biconnected * component, false otherwise */ boolean isInverted() { return vIn != vOut; } /** * {@inheritDoc} */ @Override public String toString() { return String .format( "Parent dir = {%s -> %s}, child_dir = {%s -> %s}, inverted = %b, vIn = %d, vOut = %d", parent.toString(false), parentNext.toString(false), childPrev.toString(false), child.toString(false), isInverted(), vIn, vOut); } } /** * A circulator over the nodes on the boundary of the biconnected component. Traverses the nodes * in the cyclic manner, i.e. it doesn't stop when all the nodes are traversed */ private class OuterFaceCirculator implements Iterator { /** * The node this circulator will return next */ private Node current; /** * The previous node along the traversal of the component boundary. This node is needed * because the component boundary nodes aren't connected in an ordered way. */ private Node prev; /** * Creates a new instance of the circulator over the biconnected component boundary nodes. * The {@code prev} node is considered to be just traversed * * @param current the node this circulator will return next * @param prev the previous node along the traversal */ OuterFaceCirculator(Node current, Node prev) { this.current = current; this.prev = prev; } /** * {@inheritDoc} *

    * Always returns true since this is a circulator */ @Override public boolean hasNext() { return true; } /** * {@inheritDoc} */ @Override public Node next() { Node t = current; current = current.nextOnOuterFace(prev); prev = t; return prev; } /** * Returns an edge connecting previously returned node with node, which will be returned * next. If either of the mentioned nodes is virtual, the edge will be incident to its real * counterpart. * * @return an edge from the current node to the next node */ Edge edgeToNext() { Edge edge = prev.embedded.getFirst(); Node target = toExistingNode(current); Node source = toExistingNode(prev); if (edge.getOpposite(source) == target) { return edge; } else { return prev.embedded.getLast(); } } /** * Returns the node this circulator has just traversed * * @return the node this circulator has just traversed */ Node getCurrent() { return prev; } /** * Returns a node adjacent to the current node along the boundary, which is not equal to the * next node along the traversal. If the component consist of just one real node, returns * the only neighbor the the current node. * * @return node before the current node along the traversal */ Node getPrev() { return prev.nextOnOuterFace(current); } /** * Returns either {@code node} or its real counterpart in the case the {@code node} is a * component root. * * @param node the input argument * @return the real counterpath of the {@code node} */ private Node toExistingNode(Node node) { return node.isRootVertex() ? node.getParent() : node; } /** * {@inheritDoc} */ @Override public String toString() { return String.format("%s -> %s", prev.toString(false), current.toString(false)); } } /** * Internal representation of the edges of the input {@code graph}. */ private class Edge { /** * The counterpart of this edge in the {@code graph}. This value can be null if the edge was * created as a short-circuit edge. */ E graphEdge; /** * The source node of this edge. For tree edges the {@code source} is lower than the * {@code target}, for back edges the {@code target} is lower (having smaller height) */ Node source; /** * The target of this edge */ Node target; /** * Either $+1$ or $-1$ for regular and inverted edges respectively. This value is set to * $-1$ to flip a biconnected component in $\mathcal{O}(1)$ time. Note: this * operation doesn't flip any of the child biconnected components of this biconnected * component */ int sign; /** * Whether the edge is embedded or not. This value is important for */ boolean embedded; /** * Whether the edge is real or short-circuit. See the original paper for the definition of * the short-circuit edges. */ boolean shortCircuit; /** * Creates a new short-circuit edge with no counterpart in {@code graph}. The {@code source} * of this edge is always a real node on the boundary of some biconnected component, and the * {@code target} node is the parent node of the biconnected component the source node * belongs to, so the edge resembles a regular back edge except for that it doesn't have a * counterpart in the {@code graph} * * @param source the source of the short-circuit edge * @param target the target of the short-circuit edge */ Edge(Node source, Node target) { this(null, source, target); this.shortCircuit = true; this.embedded = true; } /** * Creates a new tree edge. * * @param graphEdge the counterpart of this edge in the {@code graph} * @param source the source node of this edge */ Edge(E graphEdge, Node source) { this(graphEdge, source, null); } /** * Creates a new edge. This constructor is used directly for the creation of the back edges * * @param graphEdge the counterpart of this edge in the {@code graph} * @param source the source node of this edge * @param target the target node of this edge */ Edge(E graphEdge, Node source, Node target) { this.graphEdge = graphEdge; this.source = source; this.target = target; this.sign = 1; } /** * True if this edge is incident to the node {@code node}, false otherwise * * @param node the node to test * @return true if this edge is incident to the node {@code node}, false otherwise */ boolean isIncidentTo(Node node) { return source == node || target == node; } /** * Returns the opposite node of the {@code node} * * @param node an endpoint of this edge * @return the other endpoint of this edge */ Node getOpposite(Node node) { assert isIncidentTo(node); // debug purpose assertion return source == node ? target : source; } /** * {@inheritDoc} */ @Override public String toString() { String formatString = "%s -> %s"; if (shortCircuit) { formatString = "%s ~ %s"; } return String.format(formatString, source.toString(false), target.toString(false)); } } /** * The internal representation of the vertices of the graph. Contains necessary information to * perform dfs traversals, biconnected component boundary traversals, to embed edges, etc. */ private class Node { /** * The counterpart of this node in the {@code graph}. For the component roots this value is * {@code null}. */ V graphVertex; /** * Whether this node is a root of the biconnected component or not. */ boolean rootVertex; /** * The dfs index of this node in the graph. Every node has a unique dfs index in the range * $[0,|V| - 1]$. This value is used to order the nodes for the embedding of the edges * incident to them. The index of the first dfs root is $1$ */ int dfsIndex; /** * The height of the node in the created dfs tree. The root of the tree has height $0$. The * smaller this value is, the lower the node is considered to be. */ int height; /** * The least dfs index of the nodes' least ancestors in the subtree rooted at this * node. If the subtree doesn't contain a node with a back edge, that ends lower that this * node, this value is equal to the dfs index of this node. */ int lowpoint; /** * The least dfs index of the nodes adjacent to this node. If the node doesn't have incident * back edges, this value is equal to the dfs index of the node itself. */ int leastAncestor; /** * Stores some value to indicate whether this node is visited in the current context or not. * During the testing phase, if this value if equal to the dfs index of the currently * processed node $v$, then this node is visited, otherwise not. During the Kuratowski * subdivision extraction phase, the value of $0$ indicates that the node isn't visited, * otherwise, the node is considered to be visited. */ int visited; /** * During the process of embedding of the down edges of the node $v$, this variable is set * to the dfs index of $v$ to indicate that this node has a back edge incident to $v$, which * needs to be embedded. */ int backEdgeFlag; /** * The height of this node on the boundary of the biconnected component, which is used to * extract the Kuratowski subdivision. The height of the component root is $0$, the heights * on the left side are positive, on the right side - negative. This value is used to * quickly determine for two nodes on the same boundary branch which one is higher (closer * to the component root). */ int boundaryHeight; /** * Used to mark the boundary nodes of the biconnected */ boolean marked; /** * Edge to the parent node of this node in the dfs tree. For tree roots this value is * {@code null} */ Edge parentEdge; /** * If this node has a back edge incident to the currently processed node $v$, then this * variable stores this edge */ Edge edgeToEmbed; /** * The component root the node is created in. For dfs tree roots this value is {@code null} */ Node initialComponentRoot; /** * Two neighbors on the outer face of the biconnected component. Their order is completely * irrelevant for the algorithm to traverse the outer face. */ Node[] outerFaceNeighbors; /** * The list containing the dfs children of this node, which are in the different child * biconnected component, i.e. their weren't merged in the parent component. */ DoublyLinkedList separatedDfsChildList; /** * The roots of the pertinent components during the processing of the node $v$. These are * the components that have pertinent descendant nodes, i.e. nodes incident to the node $v$ * via a back edge */ DoublyLinkedList pertinentRoots; /** * The list of tree edges incident to this node in the dfs tree. This list doesn't contain a * parent edge of this node */ List treeEdges; /** * The list containing the edges from descendants of this node in the dfs tree. For each * such descendant the corresponding edge is a back edge. */ List downEdges; /** * The list of back edges incident to this node. */ List backEdges; /** * Stores the list node from the {@code separatedDfsChildList} of the parent node this node * is stored in. This enable deleting of this node from the {@code separatedDfsChildList} * list in $\mathcal{O}(1)$ time */ DoublyLinkedList.ListNode listNode; /** * The list of the embedded edges incident to this node in a clockwise or a counterclockwise * order. The order is counterclockwise if the product of the signs of the parent edges * along the path from the root of the component this node is contained in to this node is * equal to $-1$. Otherwise, the order is clockwise. */ DoublyLinkedList embedded; /** * Creates a new real node with the specified parameters. * * @param graphVertex the counterpart of this node in the {@code graph} * @param dfsIndex the dfs index of this node * @param height the height of this node in the dfs tree * @param initialComponentRoot the component root of this node. * @param parentEdge the parent edge of this node */ Node(V graphVertex, int dfsIndex, int height, Node initialComponentRoot, Edge parentEdge) { this(graphVertex, dfsIndex, parentEdge, false); this.height = height; this.initialComponentRoot = initialComponentRoot; } /** * Creates a new component root. Dfs index of the component root is equal to dfs index of * its parent. * * @param dfsIndex the dfs index of this node * @param parentEdge the parent edge of this component root */ Node(int dfsIndex, Edge parentEdge) { this(null, dfsIndex, parentEdge, true); } /** * Creates a new node with the specified parameters * * @param graphVertex the vertex in the {@code graph} corresponding to this node * @param dfsIndex the dfs index of this node * @param parentEdge the parent edge of this node * @param rootVertex whether this is real or virtual node */ Node(V graphVertex, int dfsIndex, Edge parentEdge, boolean rootVertex) { this.graphVertex = graphVertex; this.dfsIndex = dfsIndex; this.parentEdge = parentEdge; this.rootVertex = rootVertex; this.outerFaceNeighbors = TypeUtil.uncheckedCast(Array.newInstance(Node.class, 2)); this.embedded = new DoublyLinkedList<>(); if (parentEdge != null) { embedded.add(parentEdge); } this.visited = this.backEdgeFlag = n; if (!rootVertex) { separatedDfsChildList = new DoublyLinkedList<>(); pertinentRoots = new DoublyLinkedList<>(); treeEdges = new ArrayList<>(); downEdges = new ArrayList<>(); backEdges = new ArrayList<>(); } } /** * Checks whether this node is visited in the context of processing the node {@code node} * * @param node the node that is currently been processed * @return true if this node is visited, false otherwise */ boolean isVisitedWrtTo(Node node) { return node.dfsIndex == visited; } /** * Checks whether this node is pertinent in the context of processing the node {@code node}. * During the processing of the node {@code node}, a node is pertinent if it has a back edge * to {@code node} or it has a child biconnected component, which has a pertinent node. * * @param node the node that is currently been processed * @return true if this node is pertinent, false otherwise */ boolean isPertinentWrtTo(Node node) { return backEdgeFlag == node.dfsIndex || !pertinentRoots.isEmpty(); } /** * Checks whether this node has a back edge to the {@code node}. * * @param node the other endpoint of the edge we're looking for * @return true if the edge between this node and {@code node} exists, false otherwise */ boolean hasBackEdgeWrtTo(Node node) { return backEdgeFlag == node.dfsIndex; } /** * Checks whether this node is externally active with respect to the {@code node}. A node is * externally active, if it is incident to the edge ending higher than {@code node}, or it * has a child biconnected component with a pertinent node * * @param node an ancestor of this node * @return true if this node is externally active with respect to the {@code node}, false * otherwise */ boolean isExternallyActiveWrtTo(Node node) { return leastAncestor < node.dfsIndex || (!separatedDfsChildList.isEmpty() && separatedDfsChildList.getFirst().lowpoint < node.dfsIndex); } /** * Returns true if the node is a component root, false otherwise * * @return true if the node is a component root, false otherwise */ boolean isRootVertex() { return rootVertex; } /** * Check whether this node is internally active. A node is internally active if it's * pertinent and not externally active * * @param node an ancestor of this node * @return true if this node is internally active, false otherwise */ boolean isInternallyActiveWrtTo(Node node) { return isPertinentWrtTo(node) && !isExternallyActiveWrtTo(node); } /** * Check whether this node is inactive. A node is inactive it is neither pertinent nor * externally active * * @param node an ancestor of this node * @return true if this node is inactive, false otherwise */ boolean isInactiveWrtTo(Node node) { return !isExternallyActiveWrtTo(node) && !isPertinentWrtTo(node); } /** * Check whether this node is active. A node is active it is either pertinent or externally * active * * @param node an ancestor of this node * @return true if this node is active, false otherwise */ boolean isActiveWrtTo(Node node) { return !isInactiveWrtTo(node); } /** * Returns a circulator, that moves in the direction {@code direction}. The next node along * the traversal will be {@code this.outerFaceNeighbor[direction]}. * * @param direction the direction to move in * @return an iterator over the boundary nodes in the direction {@code direction} */ OuterFaceCirculator iterator(int direction) { return new OuterFaceCirculator(outerFaceNeighbors[direction], this); } void removeShortCircuitEdges() { embedded.removeIf(e -> e.shortCircuit); } /** * Returns the parent of this node in the dfs tree. Tree parent of the dfs root node is * {@code null} * * @return the parent of this node in the dfs tree */ Node getParent() { return parentEdge == null ? null : parentEdge.source; } /** * Checks whether this node has a neighbor {@code node} on the boundary of the biconnected * component * * @param node a possible neighbor of this node */ void checkIsAdjacent(Node node) { assert node == outerFaceNeighbors[0] || node == outerFaceNeighbors[1]; } /** * Swaps the outer face neighbors of this node */ void swapNeighbors() { Node t = outerFaceNeighbors[0]; outerFaceNeighbors[0] = outerFaceNeighbors[1]; outerFaceNeighbors[1] = t; } /** * Substitutes the neighbor {@code node} with a {@code newNeighbor} * * @param node an old neighbor * @param newNeighbor a new neighbor */ void substitute(Node node, Node newNeighbor) { checkIsAdjacent(node); if (outerFaceNeighbors[0] == node) { outerFaceNeighbors[0] = newNeighbor; } else { outerFaceNeighbors[1] = newNeighbor; } } /** * Substitutes a neighbor of this node, which is not equal to the {@code node}, with the * {@code newNeighbor} * * @param node the remaining neighbor * @param newNeighbor a new neighbor */ void substituteAnother(Node node, Node newNeighbor) { checkIsAdjacent(node); if (outerFaceNeighbors[0] == node) { outerFaceNeighbors[1] = newNeighbor; } else { outerFaceNeighbors[0] = newNeighbor; } } /** * Checks whether this node has a neighbor, which is a root of a biconnected component * * @return true, if this node has a root node neighbor, false otherwise */ boolean hasRootNeighbor() { return outerFaceNeighbors[0].isRootVertex() || outerFaceNeighbors[1].isRootVertex(); } /** * Returns a neighbor of this node which is not equal to the {@code prev} * * @param prev a neighbor of this node * @return a neightbor, which is not equal to the {@code prev} */ Node nextOnOuterFace(Node prev) { checkIsAdjacent(prev); if (outerFaceNeighbors[0] == prev) { return outerFaceNeighbors[1]; } else { return outerFaceNeighbors[0]; } } /** * Adds {@code edge} to the list of the embedded edges such that the {@code prev} node * becomes an inner node. * * @param edge an edge to embed * @param prev the node which should be on the new inner face */ void embedBackEdge(Edge edge, Node prev) { assert !embedded.isEmpty(); if (prev.isRootVertex()) { prev = prev.getParent(); } Edge firstEdge = embedded.getFirst(); if (firstEdge.getOpposite(this) == prev) { // edge on the new inner face is at the beginning of the list embedded.addFirst(edge); } else { embedded.addLast(edge); } } /** * Merges the embedded edges of the child component root into this node's embedded edges. * Note, that the edges in the {@code edges} list are always in the clockwise order. There * are 3 parameters which determine how the {@code edges} list is merged: the {@code vIn} * direction, the {@code vOut} direction and the orientation of the edges around this node * (clockwise or counterclockwise). The edges in the {@code edges} list should have the same * orientation. If this list is inverted, the sign of the {@code parentEdge} is set to $-1$. * * @param edges the edges from the child component root * @param vIn the direction used to enter the parent component * @param vOut the direction used to enter the child component * @param parentNext the next node along the traversal of the parent biconnected component * @param parentEdge the parent edge if the child component */ void mergeChildEdges( DoublyLinkedList edges, int vIn, int vOut, Node parentNext, Edge parentEdge) { assert !embedded.isEmpty(); Node firstOpposite = embedded.getFirst().getOpposite(this); boolean alongParentTraversal = firstOpposite != parentNext; boolean actionAppend = false, invert = false; if (vIn == 0) { if (vOut == 0) { if (!alongParentTraversal) { invert = actionAppend = true; } } else { if (alongParentTraversal) { invert = true; } else { actionAppend = true; } } } else { if (vOut == 0) { if (!alongParentTraversal) { invert = actionAppend = true; } } else { if (alongParentTraversal) { invert = true; } else { actionAppend = true; } } } if (invert) { parentEdge.sign = -1; edges.invert(); } if (actionAppend) { embedded.append(edges); } else { embedded.prepend(edges); } } /** * {@inheritDoc} */ @Override public String toString() { String neighbor1 = outerFaceNeighbors[0] == null ? "null" : outerFaceNeighbors[0].toString(false); String neighbor2 = outerFaceNeighbors[1] == null ? "null" : outerFaceNeighbors[1].toString(false); String childListString = "null"; if (separatedDfsChildList != null) { StringBuilder builder = new StringBuilder("{"); separatedDfsChildList.forEach(n -> builder.append(n.toString(false)).append(", ")); childListString = builder.append("}").toString(); } if (rootVertex) { return String .format( "R {%s}: neighbors = [%s, %s], embedded = %s, visited = %d, back_edge_flag = %d, dfs_index = %d", toString(false), neighbor1, neighbor2, embedded.toString(), visited, backEdgeFlag, dfsIndex); } else { return String .format( "{%s}: neighbors = [%s, %s], embedded = %s, visited = %d, back_edge_flag = %d, dfs_index = %d, separated = %s, tree_edges = %s, down_edges = %s, back_edges = %s, parent = %s, lowpoint = %d, least_ancestor = %d", toString(false), neighbor1, neighbor2, embedded.toString(), visited, backEdgeFlag, dfsIndex, childListString, treeEdges.toString(), downEdges.toString(), backEdges.toString(), parentEdge == null ? "null" : parentEdge.source.toString(false), lowpoint, leastAncestor); } } /** * Returns a full or a partial string representation of this node * * @param full whether to return full or partial string representation of this node * @return either full or partial string representation of this node */ public String toString(boolean full) { if (!full) { if (rootVertex) { return String .format( "%s^%s", parentEdge.source.graphVertex.toString(), parentEdge.target.graphVertex.toString()); } else { return graphVertex.toString(); } } else { return toString(); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/planar/package-info.java000066400000000000000000000001321402514743400312700ustar00rootroot00000000000000/** * Algorithms for testing planarity of the graphs */ package org.jgrapht.alg.planar; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/000077500000000000000000000000001402514743400262745ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/AlphaCentrality.java000066400000000000000000000213611402514743400322260ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.function.ToDoubleFunction; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.VertexScoringAlgorithm; /** * Deprecated implementation of Katz centrality. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail * @author Pratik Tibrewal * @deprecated Please use {@link KatzCentrality} instead. */ @Deprecated public final class AlphaCentrality implements VertexScoringAlgorithm { /** * Default number of maximum iterations. */ public static final int MAX_ITERATIONS_DEFAULT = 100; /** * Default value for the tolerance. The calculation will stop if the difference of * AlphaCentrality values between iterations change less than this value. */ public static final double TOLERANCE_DEFAULT = 0.0001; /** * Damping factor default value. */ public static final double DAMPING_FACTOR_DEFAULT = 0.01d; /** * Exogenous factor default value. */ public static final double EXOGENOUS_FACTOR_DEFAULT = 1.0d; private final Graph g; private Map scores; /** * Create and execute an instance of AlphaCentrality. * * @param g the input graph */ public AlphaCentrality(final Graph g) { this( g, DAMPING_FACTOR_DEFAULT, EXOGENOUS_FACTOR_DEFAULT, MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of AlphaCentrality. * * @param g the input graph * @param dampingFactor the damping factor */ public AlphaCentrality(final Graph g, final double dampingFactor) { this(g, dampingFactor, EXOGENOUS_FACTOR_DEFAULT, MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of AlphaCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param exogenousFactor the exogenous factor */ public AlphaCentrality( final Graph g, final double dampingFactor, final double exogenousFactor) { this(g, dampingFactor, exogenousFactor, MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of AlphaCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param exogenousFactorFunction ToDoubleFunction a provider of exogenous factors per vertex */ public AlphaCentrality( final Graph g, final double dampingFactor, final ToDoubleFunction exogenousFactorFunction) { this(g, dampingFactor, exogenousFactorFunction, MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of AlphaCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param exogenousFactor the exogenous factor * @param maxIterations the maximum number of iterations to perform */ public AlphaCentrality( final Graph g, final double dampingFactor, final double exogenousFactor, final int maxIterations) { this(g, dampingFactor, exogenousFactor, maxIterations, TOLERANCE_DEFAULT); } /** * Create and execute an instance of AlphaCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param exogenousFactorFunction ToDoubleFunction a provider of exogenous factors per vertex * @param maxIterations the maximum number of iterations to perform */ public AlphaCentrality( final Graph g, final double dampingFactor, final ToDoubleFunction exogenousFactorFunction, final int maxIterations) { this(g, dampingFactor, exogenousFactorFunction, maxIterations, TOLERANCE_DEFAULT); } /** * Create and execute an instance of AlphaCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param exogenousFactor the exogenous factor * @param maxIterations the maximum number of iterations to perform * @param tolerance the calculation will stop if the difference of AlphaCentrality values * between iterations change less than this value */ public AlphaCentrality( final Graph g, final double dampingFactor, final double exogenousFactor, final int maxIterations, final double tolerance) { this.g = g; this.scores = new HashMap<>(); validate(dampingFactor, maxIterations, tolerance); final ToDoubleFunction exofactorFunction = (v) -> exogenousFactor; run(dampingFactor, exofactorFunction, maxIterations, tolerance); } /** * Create and execute an instance of AlphaCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param exogenousFactorFunction ToDoubleFunction a provider of exogenous factors per vertex * @param maxIterations the maximum number of iterations to perform * @param tolerance the calculation will stop if the difference of AlphaCentrality values * between iterations change less than this value */ public AlphaCentrality( final Graph g, final double dampingFactor, final ToDoubleFunction exogenousFactorFunction, final int maxIterations, final double tolerance) { this.g = g; this.scores = new HashMap<>(); validate(dampingFactor, maxIterations, tolerance); run(dampingFactor, exogenousFactorFunction, maxIterations, tolerance); } /** * {@inheritDoc} */ @Override public Map getScores() { return Collections.unmodifiableMap(scores); } /** * {@inheritDoc} */ @Override public Double getVertexScore(final V v) { if (!g.containsVertex(v)) { throw new IllegalArgumentException("Cannot return score of unknown vertex"); } return scores.get(v); } /* Checks for the valid values of the parameters */ private void validate( final double dampingFactor, final int maxIterations, final double tolerance) { if (maxIterations <= 0) { throw new IllegalArgumentException("Maximum iterations must be positive"); } if (dampingFactor < 0.0 || dampingFactor > 1.0) { throw new IllegalArgumentException("Damping factor not valid"); } if (tolerance <= 0.0) { throw new IllegalArgumentException("Tolerance not valid, must be positive"); } } private void run( final double dampingFactor, final ToDoubleFunction exofactorFunction, int maxIterations, final double tolerance) { // initialization final int totalVertices = g.vertexSet().size(); final double initScore = 1.0d / totalVertices; for (final V v : g.vertexSet()) { scores.put(v, initScore); } // run AlphaCentrality Map nextScores = new HashMap<>(); double maxChange = tolerance; while (maxIterations > 0 && maxChange >= tolerance) { // compute next iteration scores maxChange = 0d; for (final V v : g.vertexSet()) { double contribution = 0d; for (final E e : g.incomingEdgesOf(v)) { final V w = Graphs.getOppositeVertex(g, e, v); contribution += dampingFactor * scores.get(w) * g.getEdgeWeight(e); } final double vOldValue = scores.get(v); final double vNewValue = contribution + exofactorFunction.applyAsDouble(v); maxChange = Math.max(maxChange, Math.abs(vNewValue - vOldValue)); nextScores.put(v, vNewValue); } // swap scores final Map tmp = scores; scores = nextScores; nextScores = tmp; // progress maxIterations--; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/BetweennessCentrality.java000066400000000000000000000255551402514743400334740ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Queue; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.VertexScoringAlgorithm; import org.jheaps.AddressableHeap; import org.jheaps.tree.PairingHeap; /** * Betweenness centrality. * *

    * Computes the betweenness centrality of each vertex of a graph. The betweenness centrality of a * node $v$ is given by the expression: $g(v)= \sum_{s \neq v \neq * t}\frac{\sigma_{st}(v)}{\sigma_{st}}$ where $\sigma_{st}$ is the total number of shortest paths * from node $s$ to node $t$ and $\sigma_{st}(v)$ is the number of those paths that pass through * $v$. For more details see * wikipedia. * * The algorithm is based on *

      *
    • Brandes, Ulrik (2001). "A faster algorithm for betweenness centrality". Journal of * Mathematical Sociology. 25 (2): 163–177.
    • *
    * * The running time is $O(nm)$ and $O(nm +n^2 \log n)$ for unweighted and weighted graph * respectively, where $n$ is the number of vertices and $m$ the number of edges of the graph. The * space complexity is $O(n + m)$. * * Note that this running time assumes that arithmetic is performed between numbers whose * representation needs a number of bits which is logarithmic in the instance size. There are * instances where this is not true and path counters might grow super exponential. This class * allows the user to adjust whether an exception is thrown in case overflow occurs. Default * behavior is to ignore overflow issues. * * * @param the graph vertex type * @param the graph edge type * * @author Assaf Mizrachi */ public class BetweennessCentrality implements VertexScoringAlgorithm { /** * Underlying graph */ private final Graph graph; /** * Whether to normalize scores */ private final boolean normalize; /** * The actual scores */ private Map scores; /** * Strategy for overflow when counting paths. */ private OverflowStrategy overflowStrategy; /** * Strategy followed when counting paths. */ public enum OverflowStrategy { /** * Do not check for overflow in counters. This means that on certain instances the results * might be wrong due to counters being too large to fit in a long. */ IGNORE_OVERFLOW, /** * An exception is thrown if an overflow in counters is detected. */ THROW_EXCEPTION_ON_OVERFLOW, } /** * Construct a new instance. * * @param graph the input graph */ public BetweennessCentrality(Graph graph) { this(graph, false); } /** * Construct a new instance. * * @param graph the input graph * @param normalize whether to normalize by dividing the closeness by $(n-1) \cdot (n-2)$, where * $n$ is the number of vertices of the graph */ public BetweennessCentrality(Graph graph, boolean normalize) { this(graph, normalize, OverflowStrategy.IGNORE_OVERFLOW); } /** * Construct a new instance. * * @param graph the input graph * @param normalize whether to normalize by dividing the closeness by $(n-1) \cdot (n-2)$, where * $n$ is the number of vertices of the graph * @param overflowStrategy strategy to use if overflow is detected */ public BetweennessCentrality( Graph graph, boolean normalize, OverflowStrategy overflowStrategy) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); this.scores = null; this.normalize = normalize; this.overflowStrategy = overflowStrategy; } /** * {@inheritDoc} */ @Override public Map getScores() { if (scores == null) { compute(); } return Collections.unmodifiableMap(scores); } /** * {@inheritDoc} */ @Override public Double getVertexScore(V v) { if (!graph.containsVertex(v)) { throw new IllegalArgumentException("Cannot return score of unknown vertex"); } if (scores == null) { compute(); } return scores.get(v); } /** * Compute the centrality index */ private void compute() { // initialize result container scores = new HashMap<>(); graph.vertexSet().forEach(v -> scores.put(v, 0.0)); // compute for each source graph.vertexSet().forEach(this::compute); // For undirected graph, divide scores by two as each shortest path // considered twice. if (!graph.getType().isDirected()) { scores.forEach((v, score) -> scores.put(v, score / 2)); } if (normalize) { int n = graph.vertexSet().size(); int normalizationFactor = (n - 1) * (n - 2); if (normalizationFactor != 0) { scores.forEach((v, score) -> scores.put(v, score / normalizationFactor)); } } } private void compute(V s) { // initialize ArrayDeque stack = new ArrayDeque<>(); Map> predecessors = new HashMap<>(); graph.vertexSet().forEach(w -> predecessors.put(w, new ArrayList<>())); // Number of shortest paths from s to v Map sigma = new HashMap<>(); graph.vertexSet().forEach(t -> sigma.put(t, 0l)); sigma.put(s, 1l); // Distance (Weight) of the shortest path from s to v Map distance = new HashMap<>(); graph.vertexSet().forEach(t -> distance.put(t, Double.POSITIVE_INFINITY)); distance.put(s, 0.0); MyQueue queue = graph.getType().isWeighted() ? new WeightedQueue() : new UnweightedQueue(); queue.insert(s, 0.0); // 1. compute the length and the number of shortest paths between all s to v while (!queue.isEmpty()) { V v = queue.remove(); stack.push(v); for (E e : graph.outgoingEdgesOf(v)) { V w = Graphs.getOppositeVertex(graph, e, v); double eWeight = graph.getEdgeWeight(e); if (eWeight < 0.0) { throw new IllegalArgumentException("Negative edge weight not allowed"); } double d = distance.get(v) + eWeight; // w found for the first time? if (distance.get(w) == Double.POSITIVE_INFINITY) { queue.insert(w, d); distance.put(w, d); sigma.put(w, sigma.get(v)); predecessors.get(w).add(v); } // shortest path to w via v? else if (distance.get(w) == d) { // queue.update(w, d); long wCounter = sigma.get(w); long vCounter = sigma.get(v); long sum = wCounter + vCounter; if (overflowStrategy.equals(OverflowStrategy.THROW_EXCEPTION_ON_OVERFLOW) && sum < 0) { throw new ArithmeticException("long overflow"); } sigma.put(w, sum); predecessors.get(w).add(v); } else if (distance.get(w) > d) { queue.update(w, d); distance.put(w, d); sigma.put(w, sigma.get(v)); predecessors.get(w).clear(); predecessors.get(w).add(v); } } } // 2. sum all pair dependencies. // The pair-dependency of s and v in w Map dependency = new HashMap<>(); graph.vertexSet().forEach(v -> dependency.put(v, 0.0)); // S returns vertices in order of non-increasing distance from s while (!stack.isEmpty()) { V w = stack.pop(); for (V v : predecessors.get(w)) { dependency .put( v, dependency.get(v) + (sigma.get(v).doubleValue() / sigma.get(w).doubleValue()) * (1 + dependency.get(w))); } if (!w.equals(s)) { scores.put(w, scores.get(w) + dependency.get(w)); } } } private interface MyQueue { void insert(T t, D d); void update(T t, D d); T remove(); boolean isEmpty(); } private class WeightedQueue implements MyQueue { AddressableHeap delegate = new PairingHeap<>(); Map> seen = new HashMap<>(); @Override public void insert(V t, Double d) { AddressableHeap.Handle node = delegate.insert(d, t); seen.put(t, node); } @Override public void update(V t, Double d) { if (!seen.containsKey(t)) { throw new IllegalArgumentException("Element " + t + " does not exist in queue"); } seen.get(t).decreaseKey(d); } @Override public V remove() { return delegate.deleteMin().getValue(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } } private class UnweightedQueue implements MyQueue { Queue delegate = new ArrayDeque<>(); @Override public void insert(V t, Double d) { delegate.add(t); } @Override public void update(V t, Double d) { // do nothing } @Override public V remove() { return delegate.remove(); } @Override public boolean isEmpty() { return delegate.isEmpty(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ClosenessCentrality.java000066400000000000000000000137461402514743400331470ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.graph.*; import java.util.*; /** * Closeness centrality. * *

    * Computes the closeness centrality of each vertex of a graph. The closeness of a vertex $x$ is * defined as the reciprocal of the farness, that is $H(x)= 1 / \sum_{y \neq x} d(x,y)$, where * $d(x,y)$ is the shortest path distance from $x$ to $y$. When normalization is used, the score is * multiplied by $n-1$ where $n$ is the total number of vertices in the graph. For more details see * wikipedia and *

      *
    • Alex Bavelas. Communication patterns in task-oriented groups. J. Acoust. Soc. Am, * 22(6):725–730, 1950.
    • *
    * *

    * This implementation computes by default the closeness centrality using outgoing paths and * normalizes the scores. This behavior can be adjusted by the constructor arguments. * *

    * When the graph is disconnected, the closeness centrality score equals $0$ for all vertices. In * the case of weakly connected digraphs, the closeness centrality of several vertices might be 0. * See {@link HarmonicCentrality} for a different approach in case of disconnected graphs. * *

    * Shortest paths are computed either by using Dijkstra's algorithm or Floyd-Warshall depending on * whether the graph has edges with negative edge weights. Thus, the running time is either $O(n (m * +n \log n))$ or $O(n^3)$ respectively, where $n$ is the number of vertices and $m$ the number of * edges of the graph. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class ClosenessCentrality implements VertexScoringAlgorithm { /** * Underlying graph */ protected final Graph graph; /** * Whether to use incoming or outgoing paths */ protected final boolean incoming; /** * Whether to normalize scores */ protected final boolean normalize; /** * The actual scores */ protected Map scores; /** * Construct a new instance. By default the centrality is normalized and computed using outgoing * paths. * * @param graph the input graph */ public ClosenessCentrality(Graph graph) { this(graph, false, true); } /** * Construct a new instance. * * @param graph the input graph * @param incoming if true incoming paths are used, otherwise outgoing paths * @param normalize whether to normalize by multiplying the closeness by $n-1$, where $n$ is the * number of vertices of the graph */ public ClosenessCentrality(Graph graph, boolean incoming, boolean normalize) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); this.incoming = incoming; this.normalize = normalize; this.scores = null; } /** * {@inheritDoc} */ @Override public Map getScores() { if (scores == null) { compute(); } return Collections.unmodifiableMap(scores); } /** * {@inheritDoc} */ @Override public Double getVertexScore(V v) { if (!graph.containsVertex(v)) { throw new IllegalArgumentException("Cannot return score of unknown vertex"); } if (scores == null) { compute(); } return scores.get(v); } /** * Get the shortest path algorithm for the paths computation. * * @return the shortest path algorithm */ protected ShortestPathAlgorithm getShortestPathAlgorithm() { // setup graph Graph g; if (incoming && graph.getType().isDirected()) { g = new EdgeReversedGraph<>(graph); } else { g = graph; } // test if we can use Dijkstra boolean noNegativeWeights = true; for (E e : g.edgeSet()) { double w = g.getEdgeWeight(e); if (w < 0.0) { noNegativeWeights = false; break; } } // initialize shortest path algorithm ShortestPathAlgorithm alg; if (noNegativeWeights) { alg = new DijkstraShortestPath<>(g); } else { alg = new FloydWarshallShortestPaths<>(g); } return alg; } /** * Compute the centrality index */ protected void compute() { // create result container this.scores = new HashMap<>(); // initialize shortest path algorithm ShortestPathAlgorithm alg = getShortestPathAlgorithm(); // compute shortest paths int n = graph.vertexSet().size(); for (V v : graph.vertexSet()) { double sum = 0d; SingleSourcePaths paths = alg.getPaths(v); for (V u : graph.vertexSet()) { if (!u.equals(v)) { sum += paths.getWeight(u); } } if (normalize) { this.scores.put(v, (n - 1) / sum); } else { this.scores.put(v, 1 / sum); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/ClusteringCoefficient.java000066400000000000000000000163441402514743400334250ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; /** * Clustering coefficient. This implementation computes the global, the local and the average * clustering coefficient in an undirected or a directed network. * *

    * The * local * clustering coefficient of a vertex in a graph quantifies how close its neighbors are to being * a clique. For a vertex $v$ it counts how many of its direct neighbors are connected by an edge * over the total number of neighbor pairs. In the case of undirected graphs the total number of * possible neighbor pairs is only half compared to directed graphs. * *

    * The local clustering coefficient of a graph was introduced in D. J. Watts and Steven Strogatz * (June 1998). "Collective dynamics of 'small-world' networks". Nature. 393 (6684): 440–442. * doi:10.1038/30918. It is simply the average of the local clustering coefficients of all the * vertices of the graph. * *

    * The global clustering coefficient of a graph is based on triplets of nodes. A triplet is three * graph nodes which are connected either by two edges or by three edges. A triplet which is * connected by two edges, is called an open triplet. A triplet which is connected with three edges * is called a closed triplet. The global clustering coefficient is defined as the number of closed * triplets over the total number of triplets (open and closed). It was introduced in R. D. Luce * and A. D. Perry (1949). "A method of matrix analysis of group structure". Psychometrika. 14 (1): * 95–116. doi:10.1007/BF02289146. * *

    * The running time is $O(|V| + \Delta(G)^2)$ where $|V|$ is the number of vertices and $\Delta(G)$ * is the maximum degree of a vertex. The space complexity is $O(|V|)$. * * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public class ClusteringCoefficient implements VertexScoringAlgorithm { /** * Underlying graph */ private final Graph graph; /** * The actual scores */ private Map scores; private boolean fullyComputedMap = false; /** * Global Clustering Coefficient */ private boolean computed = false; private double globalClusteringCoefficient; /** * Average Clustering Coefficient */ private boolean computedAverage = false; private double averageClusteringCoefficient; /** * Construct a new instance * * @param graph the input graph * @throws NullPointerException if {@code graph} is {@code null} */ public ClusteringCoefficient(Graph graph) { this.graph = Objects.requireNonNull(graph); this.scores = new HashMap<>(); } /** * Computes the global clustering coefficient. The global clustering coefficient $C$ is defined * as $C = 3 \times number\_of\_triangles / number\_of\_triplets$. * *

    * A triplet is three nodes that are connected by either two (open triplet) or three (closed * triplet) undirected ties. *

    * * @return the global clustering coefficient */ public double getGlobalClusteringCoefficient() { if (!computed) { computeGlobalClusteringCoefficient(); } return globalClusteringCoefficient; } /** * Computes the average clustering coefficient. The average clustering coefficient $\={C}$ is * defined as $\={C} = \frac{\sum_{i=1}^{n} C_i}{n}$ where $n$ is the number of vertices. * * Note: the average is $0$ if the graph is empty * * @return the average clustering coefficient */ public double getAverageClusteringCoefficient() { if (graph.vertexSet().isEmpty()) return 0; if (!computedAverage) { computeFullScoreMap(); computedAverage = true; averageClusteringCoefficient = 0; for (double value : scores.values()) averageClusteringCoefficient += value; averageClusteringCoefficient /= graph.vertexSet().size(); } return averageClusteringCoefficient; } private void computeGlobalClusteringCoefficient() { NeighborCache neighborCache = new NeighborCache<>(graph); computed = true; double numberTriplets = 0; for (V v : graph.vertexSet()) { if (graph.getType().isUndirected()) { numberTriplets += 1.0 * graph.degreeOf(v) * (graph.degreeOf(v) - 1) / 2; } else { numberTriplets += 1.0 * neighborCache.predecessorsOf(v).size() * neighborCache.successorsOf(v).size(); } } globalClusteringCoefficient = 3 * GraphMetrics.getNumberOfTriangles(graph) / numberTriplets; } private double computeLocalClusteringCoefficient(V v) { if (scores.containsKey(v)) { return scores.get(v); } NeighborCache neighborCache = new NeighborCache<>(graph); Set neighbourhood = neighborCache.neighborsOf(v); final double k = neighbourhood.size(); double numberTriplets = 0; for (V p : neighbourhood) for (V q : neighbourhood) if (graph.containsEdge(p, q)) numberTriplets++; if (k <= 1) return 0.0; else return numberTriplets / (k * (k - 1)); } private void computeFullScoreMap() { if (fullyComputedMap) { return; } fullyComputedMap = true; for (V v : graph.vertexSet()) { if (scores.containsKey(v)) { continue; } scores.put(v, computeLocalClusteringCoefficient(v)); } } /** * Get a map with the local clustering coefficients of all vertices * * @return a map with all local clustering coefficients */ @Override public Map getScores() { computeFullScoreMap(); return Collections.unmodifiableMap(scores); } /** * Get a vertex's local clustering coefficient * * @param v the vertex * @return the local clustering coefficient */ @Override public Double getVertexScore(V v) { if (!graph.containsVertex(v)) { throw new IllegalArgumentException("Cannot return score of unknown vertex"); } return computeLocalClusteringCoefficient(v); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/Coreness.java000066400000000000000000000120401402514743400307150ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.lang.reflect.*; import java.util.*; /** * Computes the coreness of each vertex in an undirected graph. * *

    * A $k$-core of a graph $G$ is a maximal connected subgraph of $G$ in which all vertices have * degree at least $k$. Equivalently, it is one of the connected components of the subgraph of $G$ * formed by repeatedly deleting all vertices of degree less than $k$. A vertex $u$ has coreness $c$ * if it belongs to a $c$-core but not to any $(c+1)$-core. * *

    * If a non-empty k-core exists, then, clearly, $G$ has * degeneracy at least $k$, * and the degeneracy of $G$ is the largest $k$ for which $G$ has a $k$-core. * *

    * As described in the following paper *

      *
    • D. W. Matula and L. L. Beck. Smallest-last ordering and clustering and graph coloring * algorithms. Journal of the ACM, 30(3):417--427, 1983.
    • *
    * it is possible to find a vertex ordering of a finite graph $G$ that optimizes the coloring number * of the ordering, in linear time, by using a bucket queue to repeatedly find and remove the vertex * of smallest degree. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public final class Coreness implements VertexScoringAlgorithm { private final Graph g; private Map scores; private int degeneracy; /** * Constructor * * @param g the input graph */ public Coreness(Graph g) { this.g = GraphTests.requireUndirected(g); } /** * {@inheritDoc} */ @Override public Map getScores() { lazyRun(); return Collections.unmodifiableMap(scores); } /** * {@inheritDoc} */ @Override public Integer getVertexScore(V v) { if (!g.containsVertex(v)) { throw new IllegalArgumentException("Cannot return score of unknown vertex"); } lazyRun(); return scores.get(v); } /** * Compute the degeneracy of a graph. * *

    * The degeneracy of a graph is the smallest value of $k$ for which it is $k$-degenerate. In * graph theory, a $k$-degenerate graph is an undirected graph in which every subgraph has a * vertex of degree at most $k$: that is, some vertex in the subgraph touches $k$ or fewer of * the subgraph's edges. * * @return the degeneracy of a graph */ public int getDegeneracy() { lazyRun(); return degeneracy; } @SuppressWarnings("unchecked") private void lazyRun() { if (scores != null) { return; } if (!GraphTests.isSimple(g)) { throw new IllegalArgumentException("Graph must be simple"); } scores = new HashMap<>(); degeneracy = 0; /* * Initialize buckets */ int n = g.vertexSet().size(); int maxDegree = n - 1; Set[] buckets = (Set[]) Array.newInstance(Set.class, maxDegree + 1); for (int i = 0; i < buckets.length; i++) { buckets[i] = new HashSet<>(); } int minDegree = n; Map degrees = new HashMap<>(); for (V v : g.vertexSet()) { int d = g.degreeOf(v); buckets[d].add(v); degrees.put(v, d); minDegree = Math.min(minDegree, d); } /* * Extract from buckets */ while (minDegree < n) { Set b = buckets[minDegree]; if (b.isEmpty()) { minDegree++; continue; } V v = b.iterator().next(); b.remove(v); scores.put(v, minDegree); degeneracy = Math.max(degeneracy, minDegree); for (E e : g.edgesOf(v)) { V u = Graphs.getOppositeVertex(g, e, v); int uDegree = degrees.get(u); if (uDegree > minDegree && !scores.containsKey(u)) { buckets[uDegree].remove(u); uDegree--; degrees.put(u, uDegree); buckets[uDegree].add(u); minDegree = Math.min(minDegree, uDegree); } } } } } EdgeBetweennessCentrality.java000066400000000000000000000270701402514743400341740ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import org.jgrapht.Graph; import org.jgrapht.GraphTests; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.EdgeScoringAlgorithm; import org.jheaps.AddressableHeap; import org.jheaps.AddressableHeap.Handle; import org.jheaps.tree.PairingHeap; /** * Edge betweenness centrality. * *

    * A natural extension of betweenness to edges by counting the total shortest paths that pass * through an edge. See the paper: Ulrik Brandes: On Variants of Shortest-Path Betweenness * Centrality and their Generic Computation. Social Networks 30(2):136-145, 2008, for a nice * discussion of different variants of betweenness centrality. Note that this implementation does * not work for graphs which have multiple edges. Self-loops do not influence the result and are * thus ignored. * *

    * This implementation allows the user to compute centrality contributions only from a subset of the * graph vertices, i.e. to start shortest path computations only from a subset of the vertices. This * allows centrality approximations in big graphs. Note that in this case, the user is responsible * for any normalization necessary due to duplicate shortest paths that might occur in undirected * graphs. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class EdgeBetweennessCentrality implements EdgeScoringAlgorithm { private final Graph graph; private final Iterable startVertices; private final boolean divideByTwo; private Map scores; private final OverflowStrategy overflowStrategy; /** * Strategy followed when counting paths. */ public enum OverflowStrategy { /** * Do not check for overflow in counters. This means that on certain instances the results * might be wrong due to counters being too large to fit in a long. */ IGNORE_OVERFLOW, /** * An exception is thrown if an overflow in counters is detected. */ THROW_EXCEPTION_ON_OVERFLOW, } /** * Construct a new instance. * * @param graph the input graph */ public EdgeBetweennessCentrality(Graph graph) { this(graph, OverflowStrategy.IGNORE_OVERFLOW, null); } /** * Construct a new instance. * * @param graph the input graph * @param overflowStrategy strategy to use if overflow is detected */ public EdgeBetweennessCentrality(Graph graph, OverflowStrategy overflowStrategy) { this(graph, overflowStrategy, null); } /** * Construct a new instance. * * @param graph the input graph * @param overflowStrategy strategy to use if overflow is detected * @param startVertices vertices from which to start shortest path computations. This parameter * allows the user to compute edge centrality contributions only from a subset of the * vertices of the graph. If null the whole graph vertex set is used. */ public EdgeBetweennessCentrality( Graph graph, OverflowStrategy overflowStrategy, Iterable startVertices) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); if (GraphTests.hasMultipleEdges(graph)) { throw new IllegalArgumentException("Graphs with multiple edges not supported"); } this.scores = null; this.overflowStrategy = overflowStrategy; if (startVertices == null) { this.startVertices = graph.vertexSet(); // divide by two only if all pairs are used this.divideByTwo = graph.getType().isUndirected(); } else { this.startVertices = startVertices; // the user is responsible for duplicate shortest paths this.divideByTwo = false; } } @Override public Map getScores() { if (scores == null) { scores = graph.getType().isWeighted() ? new WeightedAlgorithm().getScores() : new Algorithm().getScores(); } return Collections.unmodifiableMap(scores); } @Override public Double getEdgeScore(E e) { if (!graph.containsEdge(e)) { throw new IllegalArgumentException("Cannot return score of unknown edge"); } if (scores == null) { scores = graph.getType().isWeighted() ? new WeightedAlgorithm().getScores() : new Algorithm().getScores(); } return scores.get(e); } /* * The basic algorithm */ private class Algorithm { protected Map scores = new HashMap<>(); protected Deque stack = new ArrayDeque<>(); public Map getScores() { for (E e : graph.iterables().edges()) { scores.put(e, 0d); } for (V v : startVertices) { singleVertexUpdate(v); } if (divideByTwo) { scores.forEach((e, score) -> scores.put(e, score / 2d)); } return scores; } protected void singleVertexUpdate(V source) { // initialization Map> pred = new HashMap<>(); Map dist = new HashMap<>(); Map sigma = new HashMap<>(); Deque queue = new ArrayDeque<>(); for (V v : graph.vertexSet()) { sigma.put(v, 0l); } sigma.put(source, 1l); dist.put(source, 0d); queue.add(source); // main loop while (!queue.isEmpty()) { V v = queue.remove(); stack.push(v); double vDistance = dist.get(v); for (E e : graph.outgoingEdgesOf(v)) { V w = Graphs.getOppositeVertex(graph, e, v); if (w.equals(v)) { // ignore self-loops continue; } // path discovery if (!dist.containsKey(w)) { dist.put(w, vDistance + 1d); queue.add(w); } // path counting double wDistance = dist.get(w); if (Double.compare(wDistance, vDistance + 1d) == 0) { long wCounter = sigma.get(w); long vCounter = sigma.get(v); long sum = wCounter + vCounter; if (overflowStrategy.equals(OverflowStrategy.THROW_EXCEPTION_ON_OVERFLOW) && sum < 0) { throw new ArithmeticException("long overflow"); } sigma.put(w, sum); pred.computeIfAbsent(w, k -> new ArrayList<>()).add(e); } } } // accumulation accumulate(pred, sigma); } protected void accumulate(Map> pred, Map sigma) { Map delta = new HashMap<>(); for (V v : graph.iterables().vertices()) { delta.put(v, 0d); } while (!stack.isEmpty()) { V w = stack.pop(); List wPred = pred.get(w); if (wPred != null) { for (E e : wPred) { V v = Graphs.getOppositeVertex(graph, e, w); double c = (sigma.get(v).doubleValue() / sigma.get(w).doubleValue()) * (1 + delta.get(w)); scores.put(e, scores.get(e) + c); delta.put(v, delta.get(v) + c); } } } } } /* * Weighted variant where shortest paths are computed using edge weights. */ private class WeightedAlgorithm extends Algorithm { @Override protected void singleVertexUpdate(V source) { // initialization Map> pred = new HashMap<>(); Map> dist = new HashMap<>(); Map sigma = new HashMap<>(); AddressableHeap heap = new PairingHeap<>(); for (V v : graph.vertexSet()) { sigma.put(v, 0l); } sigma.put(source, 1l); dist.put(source, heap.insert(0d, source)); // main loop while (!heap.isEmpty()) { Handle vHandle = heap.deleteMin(); V v = vHandle.getValue(); double vDistance = vHandle.getKey(); stack.push(v); for (E e : graph.outgoingEdgesOf(v)) { V w = Graphs.getOppositeVertex(graph, e, v); if (w.equals(v)) { // ignore self-loops continue; } double eWeight = graph.getEdgeWeight(e); if (eWeight < 0d) { throw new IllegalArgumentException("Negative edge weights are not allowed"); } double newDistance = vDistance + eWeight; // path discovery Handle wHandle = dist.get(w); if (wHandle == null) { wHandle = heap.insert(newDistance, w); dist.put(w, wHandle); sigma.put(w, 0l); pred.put(w, new ArrayList<>()); } else if (Double.compare(wHandle.getKey(), newDistance) > 0) { wHandle.decreaseKey(newDistance); sigma.put(w, 0l); pred.put(w, new ArrayList<>()); } // path counting if (Double.compare(wHandle.getKey(), newDistance) == 0) { long wCounter = sigma.get(w); long vCounter = sigma.get(v); long sum = wCounter + vCounter; if (overflowStrategy.equals(OverflowStrategy.THROW_EXCEPTION_ON_OVERFLOW) && sum < 0) { throw new ArithmeticException("long overflow"); } sigma.put(w, sum); pred.computeIfAbsent(w, k -> new ArrayList<>()).add(e); } } } // accumulation accumulate(pred, sigma); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/EigenvectorCentrality.java000066400000000000000000000147271402514743400334630ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.jgrapht.Graph; import org.jgrapht.GraphIterables; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.VertexScoringAlgorithm; /** * Eigenvector-centrality implementation. * *

    * Eigenvector centrality, introduced in 1895 by Edmund Landau for chess tournaments, associates * with a (weighted) graph the left dominant eigenvector of its adjacency matrix. More information * can be found on wikipedia. *

    * *

    * This is a simple iterative implementation of the * power method which stops after a * given number of iterations or if centrality values between two iterations do not change more than * a predefined value (technically, we stop when the ℓ2 norm of the difference * between the current estimate and the next one drops below a given threshold). Correspondingly, * the result will be ℓ2-normalized. *

    * *

    * Each iteration of the algorithm runs in linear time O(n+m) when n is the number of nodes and m * the number of edges of the graph. The maximum number of iterations can be adjusted by the caller. * The default value is {@link EigenvectorCentrality#MAX_ITERATIONS_DEFAULT}. Also in case of * weighted graphs, negative weights are not expected. *

    * * @param the graph vertex type * @param the graph edge type * * @author Sebastiano Vigna */ public final class EigenvectorCentrality implements VertexScoringAlgorithm { /** * Default number of maximum iterations. */ public static final int MAX_ITERATIONS_DEFAULT = 100; /** * Default value for the tolerance. The calculation will stop if the ℓ2 norm * of the difference of centrality values between iterations changes less than this value. */ public static final double TOLERANCE_DEFAULT = 0.0001; private final Graph g; private Map scores; /** * Create and execute an instance of EigenvectorCentrality * * @param g the input graph */ public EigenvectorCentrality(final Graph g) { this(g, MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of EigenvectorCentrality * * @param g the input graph * @param maxIterations the maximum number of iterations to perform */ public EigenvectorCentrality(final Graph g, final int maxIterations) { this(g, maxIterations, TOLERANCE_DEFAULT); } /** * Create and execute an instance of EigenvectorCentrality. * * @param g the input graph * @param maxIterations the maximum number of iterations to perform * @param tolerance calculation will stop if the ℓ2 norm of the difference of * centrality values between iterations changes less than this value */ public EigenvectorCentrality( final Graph g, final int maxIterations, final double tolerance) { this.g = g; this.scores = new HashMap<>(); validate(maxIterations, tolerance); run(maxIterations, tolerance); } /** * {@inheritDoc} */ @Override public Map getScores() { return Collections.unmodifiableMap(scores); } /** * {@inheritDoc} */ @Override public Double getVertexScore(final V v) { if (!g.containsVertex(v)) { throw new IllegalArgumentException("Cannot return score of unknown vertex"); } return scores.get(v); } /* Checks for the valid values of the parameters */ private void validate(final int maxIterations, final double tolerance) { if (maxIterations <= 0) { throw new IllegalArgumentException("Maximum iterations must be positive"); } if (tolerance <= 0.0) { throw new IllegalArgumentException("Tolerance not valid, must be positive"); } } private void run(int maxIterations, final double tolerance) { // initialization final int totalVertices = g.vertexSet().size(); final GraphIterables iterables = g.iterables(); final double initScore = Math.sqrt(1.0d / totalVertices); for (final V v : iterables.vertices()) { scores.put(v, initScore); } // run the power method Map nextScores = new HashMap<>(); double l2Norm = tolerance; while (maxIterations > 0 && l2Norm >= tolerance) { // compute next iteration scores double sumOfSquares = 0d; for (final V v : iterables.vertices()) { double vNewValue = 0d; for (final E e : iterables.incomingEdgesOf(v)) { final V w = Graphs.getOppositeVertex(g, e, v); vNewValue += scores.get(w) * g.getEdgeWeight(e); } sumOfSquares += vNewValue * vNewValue; nextScores.put(v, vNewValue); } final double l2NormFactor = 1 / Math.sqrt(sumOfSquares); double sumOfDiffs2 = 0; // Normalize and evaluate norm for (final V v : iterables.vertices()) { final double score = nextScores.get(v) * l2NormFactor; nextScores.put(v, score); final double d = scores.get(v) - score; sumOfDiffs2 += d * d; } // swap scores final Map tmp = scores; scores = nextScores; nextScores = tmp; l2Norm = Math.sqrt(sumOfDiffs2); // progress maxIterations--; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/HarmonicCentrality.java000066400000000000000000000074311402514743400327430ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import java.util.*; /** * Harmonic centrality. * *

    * The harmonic centrality of a vertex $x$ is defined as $H(x)=\sum_{y \neq x} 1/d(x,y)$, where * $d(x,y)$ is the shortest path distance from $x$ to $y$. In case a distance $d(x,y)=\infinity$, * then $1/d(x,y)=0$. When normalization is used the score is divided by $n-1$ where $n$ is the * total number of vertices in the graph. * * For details see the following papers: *

      *
    • Yannick Rochat. Closeness centrality extended to unconnected graphs: The harmonic centrality * index. Applications of Social Network Analysis, 2009.
    • *
    • Newman, Mark. 2003. The Structure and Function of Complex Networks. SIAM Review, 45(mars), * 167–256.
    • *
    * and the wikipedia article. * *

    * This implementation computes by default the centrality using outgoing paths and normalizes the * scores. This behavior can be adjusted by the constructor arguments. * *

    * Shortest paths are computed either by using Dijkstra's algorithm or Floyd-Warshall depending on * whether the graph has edges with negative edge weights. Thus, the running time is either $O(n (m * + n \log n))$ or $O(n^3)$ respectively, where $n$ is the number of vertices and $m$ the number of * edges of the graph. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public final class HarmonicCentrality extends ClosenessCentrality { /** * Construct a new instance. By default the centrality is normalized and computed using outgoing * paths. * * @param graph the input graph */ public HarmonicCentrality(Graph graph) { this(graph, false, true); } /** * Construct a new instance. * * @param graph the input graph * @param incoming if true incoming paths are used, otherwise outgoing paths * @param normalize whether to normalize by dividing the closeness by $n-1$, where $n$ is the * number of vertices of the graph */ public HarmonicCentrality(Graph graph, boolean incoming, boolean normalize) { super(graph, incoming, normalize); } @Override protected void compute() { // create result container this.scores = new HashMap<>(); // initialize shortest path algorithm ShortestPathAlgorithm alg = getShortestPathAlgorithm(); // compute shortest paths int n = graph.vertexSet().size(); for (V v : graph.vertexSet()) { double sum = 0d; SingleSourcePaths paths = alg.getPaths(v); for (V u : graph.vertexSet()) { if (!u.equals(v)) { sum += 1.0 / paths.getWeight(u); } } if (normalize && n > 1) { this.scores.put(v, sum / (n - 1)); } else { this.scores.put(v, sum); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/KatzCentrality.java000066400000000000000000000230541402514743400321130ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.function.ToDoubleFunction; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.VertexScoringAlgorithm; /** * Katz centrality implementation. * *

    * The wikipedia article contains a nice * description of Katz centrality. Every path coming into a node contributes to its Katz centrality * by αk, where α is the damping factor and k * is the length of the path. *

    * *

    * This is a simple iterative implementation of Katz centrality which stops after a given number of * iterations or if the Katz centrality values between two iterations do not change more than a * predefined value. Each iteration increases the length of the paths contributing to the centrality * value. Note that unless the damping factor is smaller than the reciprocal of the * spectral radius of the adjacency * matrix, the computation will not converge. *

    * *

    * This implementation makes it possible to provide an exogenous factor in the form of a * {@link ToDoubleFunction} mapping each vertex to its exogenous score. Each path is then multiplied * by the exogenous score of its starting vertex. The {@linkplain #exogenousFactorDefaultFunction() * default exogenous function} maps all vertices to one, as in standard Katz centrality. *

    * *

    * Each iteration of the algorithm runs in linear time O(n+m) when n is the number of nodes and m * the number of edges of the graph. The maximum number of iterations can be adjusted by the caller. * The default value is {@link KatzCentrality#MAX_ITERATIONS_DEFAULT}. Also in case of weighted * graphs, negative weights are not expected. *

    * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail * @author Pratik Tibrewal * @author Sebastiano Vigna */ public final class KatzCentrality implements VertexScoringAlgorithm { /** * Default number of maximum iterations. */ public static final int MAX_ITERATIONS_DEFAULT = 100; /** * Default value for the tolerance. The calculation will stop if the difference of Katz * centrality values between iterations change less than this value. */ public static final double TOLERANCE_DEFAULT = 0.0001; /** * Damping factor default value. */ public static final double DAMPING_FACTOR_DEFAULT = 0.01d; /** * Exogenous factor default function (the constant function returning 1). * * @return always 1. * @param the input type of the function. */ public static final ToDoubleFunction exogenousFactorDefaultFunction() { return x -> 1; } private final Graph g; private Map scores; /** * Create and execute an instance of KatzCentrality. * * @param g the input graph */ public KatzCentrality(final Graph g) { this( g, DAMPING_FACTOR_DEFAULT, exogenousFactorDefaultFunction(), MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of KatzCentrality. * * @param g the input graph * @param dampingFactor the damping factor */ public KatzCentrality(final Graph g, final double dampingFactor) { this( g, dampingFactor, exogenousFactorDefaultFunction(), MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of KatzCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param maxIterations the maximum number of iterations to perform */ public KatzCentrality(final Graph g, final double dampingFactor, final int maxIterations) { this(g, dampingFactor, exogenousFactorDefaultFunction(), maxIterations, TOLERANCE_DEFAULT); } /** * Create and execute an instance of KatzCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param maxIterations the maximum number of iterations to perform * @param tolerance the calculation will stop if the difference of Katz centrality values * between iterations change less than this value */ public KatzCentrality( final Graph g, final double dampingFactor, final int maxIterations, final double tolerance) { this(g, dampingFactor, exogenousFactorDefaultFunction(), maxIterations, tolerance); } /** * Create and execute an instance of KatzCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param exogenousFactorFunction a provider of exogenous factor per vertex */ public KatzCentrality( final Graph g, final double dampingFactor, final ToDoubleFunction exogenousFactorFunction) { this(g, dampingFactor, exogenousFactorFunction, MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of KatzCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param exogenousFactorFunction a provider of exogenous factor per vertex * @param maxIterations the maximum number of iterations to perform */ public KatzCentrality( final Graph g, final double dampingFactor, final ToDoubleFunction exogenousFactorFunction, final int maxIterations) { this(g, dampingFactor, exogenousFactorFunction, maxIterations, TOLERANCE_DEFAULT); } /** * Create and execute an instance of KatzCentrality. * * @param g the input graph * @param dampingFactor the damping factor * @param exogenousFactorFunction a provider of exogenous factor per vertex * @param maxIterations the maximum number of iterations to perform * @param tolerance the calculation will stop if the difference of Katz centrality values * between iterations change less than this value */ public KatzCentrality( final Graph g, final double dampingFactor, final ToDoubleFunction exogenousFactorFunction, final int maxIterations, final double tolerance) { this.g = g; this.scores = new HashMap<>(); validate(dampingFactor, maxIterations, tolerance); run(dampingFactor, exogenousFactorFunction, maxIterations, tolerance); } /** * {@inheritDoc} */ @Override public Map getScores() { return Collections.unmodifiableMap(scores); } /** * {@inheritDoc} */ @Override public Double getVertexScore(final V v) { if (!g.containsVertex(v)) { throw new IllegalArgumentException("Cannot return score of unknown vertex"); } return scores.get(v); } /* Checks for the valid values of the parameters */ private void validate( final double dampingFactor, final int maxIterations, final double tolerance) { if (maxIterations <= 0) { throw new IllegalArgumentException("Maximum iterations must be positive"); } if (dampingFactor < 0.0) { throw new IllegalArgumentException("Damping factor not valid"); } if (tolerance <= 0.0) { throw new IllegalArgumentException("Tolerance not valid, must be positive"); } } private void run( final double dampingFactor, final ToDoubleFunction exofactorFunction, int maxIterations, final double tolerance) { for (final V v : g.vertexSet()) { scores.put(v, exofactorFunction.applyAsDouble(v)); } // run KatzCentrality Map nextScores = new HashMap<>(); double maxChange = tolerance; while (maxIterations > 0 && maxChange >= tolerance) { // compute next iteration scores maxChange = 0d; for (final V v : g.vertexSet()) { double contribution = 0d; for (final E e : g.incomingEdgesOf(v)) { final V w = Graphs.getOppositeVertex(g, e, v); contribution += dampingFactor * scores.get(w) * g.getEdgeWeight(e); } final double vOldValue = scores.get(v); final double vNewValue = contribution + exofactorFunction.applyAsDouble(v); maxChange = Math.max(maxChange, Math.abs(vNewValue - vOldValue)); nextScores.put(v, vNewValue); } // swap scores final Map tmp = scores; scores = nextScores; nextScores = tmp; // progress maxIterations--; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/PageRank.java000066400000000000000000000276631402514743400306450ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * PageRank implementation. * *

    * The wikipedia article contains a nice * description of PageRank. The method can be found on the article: Sergey Brin and Larry Page: The * Anatomy of a Large-Scale Hypertextual Web Search Engine. Proceedings of the 7th World-Wide Web * Conference, Brisbane, Australia, April 1998. See also the following * page. *

    * *

    * This is a simple iterative implementation of PageRank which stops after a given number of * iterations or if the PageRank values between two iterations do not change more than a predefined * value. The implementation uses the variant which divides by the number of nodes, thus forming a * probability distribution over graph nodes. *

    * *

    * Each iteration of the algorithm runs in linear time $O(n+m)$ when $n$ is the number of nodes and * $m$ the number of edges of the graph. The maximum number of iterations can be adjusted by the * caller. The default value is {@link PageRank#MAX_ITERATIONS_DEFAULT}. *

    * *

    * If the graph is a weighted graph, a weighted variant is used where the probability of following * an edge e out of node $v$ is equal to the weight of $e$ over the sum of weights of all outgoing * edges of $v$. *

    * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public final class PageRank implements VertexScoringAlgorithm { /** * Default number of maximum iterations. */ public static final int MAX_ITERATIONS_DEFAULT = 100; /** * Default value for the tolerance. The calculation will stop if the difference of PageRank * values between iterations change less than this value. */ public static final double TOLERANCE_DEFAULT = 0.0001; /** * Damping factor default value. */ public static final double DAMPING_FACTOR_DEFAULT = 0.85d; /** * The input graph */ private final Graph graph; /** * The damping factor */ private final double dampingFactor; /** * Maximum iterations to run */ private final int maxIterations; /** * The calculation will stop if the difference of PageRank values between iterations change less * than this value */ private final double tolerance; /** * The result */ private Map scores; /** * Create and execute an instance of PageRank. * * @param graph the input graph */ public PageRank(Graph graph) { this(graph, DAMPING_FACTOR_DEFAULT, MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of PageRank. * * @param graph the input graph * @param dampingFactor the damping factor */ public PageRank(Graph graph, double dampingFactor) { this(graph, dampingFactor, MAX_ITERATIONS_DEFAULT, TOLERANCE_DEFAULT); } /** * Create and execute an instance of PageRank. * * @param graph the input graph * @param dampingFactor the damping factor * @param maxIterations the maximum number of iterations to perform */ public PageRank(Graph graph, double dampingFactor, int maxIterations) { this(graph, dampingFactor, maxIterations, TOLERANCE_DEFAULT); } /** * Create and execute an instance of PageRank. * * @param graph the input graph * @param dampingFactor the damping factor * @param maxIterations the maximum number of iterations to perform * @param tolerance the calculation will stop if the difference of PageRank values between * iterations change less than this value */ public PageRank(Graph graph, double dampingFactor, int maxIterations, double tolerance) { this.graph = graph; if (maxIterations <= 0) { throw new IllegalArgumentException("Maximum iterations must be positive"); } this.maxIterations = maxIterations; if (dampingFactor < 0.0 || dampingFactor > 1.0) { throw new IllegalArgumentException("Damping factor not valid"); } this.dampingFactor = dampingFactor; if (tolerance <= 0.0) { throw new IllegalArgumentException("Tolerance not valid, must be positive"); } this.tolerance = tolerance; } /** * {@inheritDoc} */ @Override public Map getScores() { if (scores == null) { scores = Collections.unmodifiableMap(new Algorithm().getScores()); } return scores; } /** * {@inheritDoc} */ @Override public Double getVertexScore(V v) { if (!graph.containsVertex(v)) { throw new IllegalArgumentException("Cannot return score of unknown vertex"); } return getScores().get(v); } /** * The actual implementation. * *

    * We use this pattern with the inner class in order to be able to cache the result but also * allow the garbage collector to acquire all auxiliary memory used during the execution of the * algorithm. * * @author Dimitrios Michail * * @param the graph type * @param the edge type */ private class Algorithm { private int totalVertices; private boolean isWeighted; private Map vertexIndexMap; private V[] vertexMap; private double[] weightSum; private double[] curScore; private double[] nextScore; private int[] outDegree; private ArrayList adjList; private ArrayList weightsList; @SuppressWarnings("unchecked") public Algorithm() { this.totalVertices = graph.vertexSet().size(); this.isWeighted = graph.getType().isWeighted(); /* * Initialize score, map vertices to [0,n) and pre-compute degrees and adjacency lists */ this.curScore = new double[totalVertices]; this.nextScore = new double[totalVertices]; this.vertexIndexMap = new HashMap<>(); this.vertexMap = (V[]) new Object[totalVertices]; this.outDegree = new int[totalVertices]; this.adjList = new ArrayList<>(totalVertices); double initScore = 1.0d / totalVertices; int i = 0; for (V v : graph.vertexSet()) { vertexIndexMap.put(v, i); vertexMap[i] = v; outDegree[i] = graph.outDegreeOf(v); curScore[i] = initScore; i++; } if (isWeighted) { this.weightSum = new double[totalVertices]; this.weightsList = new ArrayList<>(totalVertices); for (i = 0; i < totalVertices; i++) { V v = vertexMap[i]; int[] inNeighbors = new int[graph.inDegreeOf(v)]; double[] edgeWeights = new double[graph.inDegreeOf(v)]; int j = 0; for (E e : graph.incomingEdgesOf(v)) { V w = Graphs.getOppositeVertex(graph, e, v); Integer mappedVertexId = vertexIndexMap.get(w); inNeighbors[j] = mappedVertexId; double edgeWeight = graph.getEdgeWeight(e); edgeWeights[j] += edgeWeight; weightSum[mappedVertexId] += edgeWeight; j++; } weightsList.add(edgeWeights); adjList.add(inNeighbors); } } else { for (i = 0; i < totalVertices; i++) { V v = vertexMap[i]; int[] inNeighbors = new int[graph.inDegreeOf(v)]; int j = 0; for (E e : graph.incomingEdgesOf(v)) { V w = Graphs.getOppositeVertex(graph, e, v); inNeighbors[j++] = vertexIndexMap.get(w); } adjList.add(inNeighbors); } } } public Map getScores() { // compute if (isWeighted) { runWeighted(); } else { run(); } // make results user friendly Map scores = new HashMap<>(); for (int i = 0; i < totalVertices; i++) { V v = vertexMap[i]; scores.put(v, curScore[i]); } return scores; } private void run() { double maxChange = tolerance; int iterations = maxIterations; while (iterations > 0 && maxChange >= tolerance) { double r = teleProp(); maxChange = 0d; for (int i = 0; i < totalVertices; i++) { double contribution = 0d; for (int w : adjList.get(i)) { contribution += dampingFactor * curScore[w] / outDegree[w]; } double vOldValue = curScore[i]; double vNewValue = r + contribution; maxChange = Math.max(maxChange, Math.abs(vNewValue - vOldValue)); nextScore[i] = vNewValue; } // progress swapScores(); iterations--; } } private void runWeighted() { double maxChange = tolerance; int iterations = maxIterations; while (iterations > 0 && maxChange >= tolerance) { double r = teleProp(); maxChange = 0d; for (int i = 0; i < totalVertices; i++) { double contribution = 0d; int[] neighbors = adjList.get(i); double[] weights = weightsList.get(i); for (int j = 0, getLength = neighbors.length; j < getLength; j++) { int w = neighbors[j]; contribution += dampingFactor * curScore[w] * weights[j] / weightSum[w]; } double vOldValue = curScore[i]; double vNewValue = r + contribution; maxChange = Math.max(maxChange, Math.abs(vNewValue - vOldValue)); nextScore[i] = vNewValue; } // progress swapScores(); iterations--; } } private double teleProp() { double r = 0d; for (int i = 0; i < totalVertices; i++) { if (outDegree[i] > 0) { r += (1d - dampingFactor) * curScore[i]; } else { r += curScore[i]; } } r /= totalVertices; return r; } private void swapScores() { double[] tmp = curScore; curScore = nextScore; nextScore = tmp; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/scoring/package-info.java000066400000000000000000000001231402514743400314570ustar00rootroot00000000000000/** * Vertex and/or edge scoring algorithms. */ package org.jgrapht.alg.scoring; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/000077500000000000000000000000001402514743400273605ustar00rootroot00000000000000ALTAdmissibleHeuristic.java000066400000000000000000000172131402514743400344450ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.util.*; /** * An admissible heuristic for the A* algorithm using a set of landmarks and the triangle * inequality. Assumes that the graph contains non-negative edge weights. * *

    * The heuristic requires a set of input nodes from the graph, which are used as landmarks. During a * pre-processing phase, which requires two shortest path computations per landmark using Dijkstra's * algorithm, all distances to and from these landmark nodes are computed and stored. Afterwards, * the heuristic estimates the distance from a vertex to another vertex using the already computed * distances to and from the landmarks and the fact that shortest path distances obey the * triangle-inequality. The heuristic's space requirement is $O(n)$ per landmark where n is the * number of vertices of the graph. In case of undirected graphs only one Dijkstra's algorithm * execution is performed per landmark. * *

    * The method generally abbreviated as ALT (from A*, Landmarks and Triangle inequality) is described * in detail in the following * paper which also contains a discussion on landmark selection strategies. *

      *
    • Andrew Goldberg and Chris Harrelson. Computing the shortest path: A* Search Meets Graph * Theory. In Proceedings of the sixteenth annual ACM-SIAM symposium on Discrete algorithms (SODA' * 05), 156--165, 2005.
    • *
    * *

    * Note that using this heuristic does not require the edge weights to satisfy the * triangle-inequality. The method depends on the triangle inequality with respect to the shortest * path distances in the graph, not an embedding in Euclidean space or some other metric, which need * not be present. * *

    * In general more landmarks will speed up A* but will need more space. Given an A* query with * vertices source and target, a good landmark appears "before" source or "after" target where * before and after are relative to the "direction" from source to target. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class ALTAdmissibleHeuristic implements AStarAdmissibleHeuristic { private final Graph graph; private final Comparator comparator; private final Map> fromLandmark; private final Map> toLandmark; private final boolean directed; /** * Constructs a new {@link AStarAdmissibleHeuristic} using a set of landmarks. * * @param graph the graph * @param landmarks a set of vertices of the graph which will be used as landmarks * * @throws IllegalArgumentException if no landmarks are provided * @throws IllegalArgumentException if the graph contains edges with negative weights */ public ALTAdmissibleHeuristic(Graph graph, Set landmarks) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); Objects.requireNonNull(landmarks, "Landmarks cannot be null"); if (landmarks.isEmpty()) { throw new IllegalArgumentException("At least one landmark must be provided"); } this.fromLandmark = new HashMap<>(); if (graph.getType().isDirected()) { this.directed = true; this.toLandmark = new HashMap<>(); } else if (graph.getType().isUndirected()) { this.directed = false; this.toLandmark = this.fromLandmark; } else { throw new IllegalArgumentException("Graph must be directed or undirected"); } this.comparator = new ToleranceDoubleComparator(); // precomputation and validation for (V v : landmarks) { for (E e : graph.edgesOf(v)) { if (comparator.compare(graph.getEdgeWeight(e), 0d) < 0) { throw new IllegalArgumentException("Graph edge weights cannot be negative"); } } precomputeToFromLandmark(v); } } /** * An admissible heuristic estimate from a source vertex to a target vertex. The estimate is * always non-negative and never overestimates the true distance. * * @param u the source vertex * @param t the target vertex * * @return an admissible heuristic estimate */ @Override public double getCostEstimate(V u, V t) { double maxEstimate = 0d; /* * Special case, source equals target */ if (u.equals(t)) { return maxEstimate; } /* * Special case, source is landmark */ if (fromLandmark.containsKey(u)) { return fromLandmark.get(u).get(t); } /* * Special case, target is landmark */ if (toLandmark.containsKey(t)) { return toLandmark.get(t).get(u); } /* * Compute from landmarks */ for (V l : fromLandmark.keySet()) { double estimate; Map from = fromLandmark.get(l); if (directed) { Map to = toLandmark.get(l); estimate = Math.max(to.get(u) - to.get(t), from.get(t) - from.get(u)); } else { estimate = Math.abs(from.get(u) - from.get(t)); } // max over all landmarks if (Double.isFinite(estimate)) { maxEstimate = Math.max(maxEstimate, estimate); } } return maxEstimate; } /** * Compute all distances to and from a landmark * * @param landmark the landmark */ private void precomputeToFromLandmark(V landmark) { // compute distances from landmark SingleSourcePaths fromLandmarkPaths = new DijkstraShortestPath<>(graph).getPaths(landmark); Map fromLandMarkDistances = new HashMap<>(); for (V v : graph.vertexSet()) { fromLandMarkDistances.put(v, fromLandmarkPaths.getWeight(v)); } fromLandmark.put(landmark, fromLandMarkDistances); // compute distances to landmark (using reverse graph) if (directed) { Graph reverseGraph = new EdgeReversedGraph<>(graph); SingleSourcePaths toLandmarkPaths = new DijkstraShortestPath<>(reverseGraph).getPaths(landmark); Map toLandMarkDistances = new HashMap<>(); for (V v : graph.vertexSet()) { toLandMarkDistances.put(v, toLandmarkPaths.getWeight(v)); } toLandmark.put(landmark, toLandMarkDistances); } } /** * {@inheritDoc} */ @Override public boolean isConsistent(Graph graph) { return true; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/AStarShortestPath.java000066400000000000000000000245531402514743400336170ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Joris Kinable, Jon Robison, Thomas Breitbart and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; import java.util.function.*; /** * A* shortest path. *

    * An implementation of A* shortest path * algorithm. This class works for directed and undirected graphs, as well as multi-graphs and * mixed-graphs. The graph can also change between invocations of the * {@link #getPath(Object, Object)} method; no new instance of this class has to be created. The * heuristic is implemented using a PairingHeap data structure by default to maintain the set of * open nodes. However, there still exist several approaches in literature to improve the * performance of this heuristic which one could consider to implement. Custom heap implementation * can be specified during the construction time. Another issue to take into consideration is the * following: given two candidate nodes, $i$, $j$ to expand, where $f(i)=f(j)$, $g(i)$ > $g(j)$, * $h(i)$ < $g(j)$, $f(i)=g(i)+h(i)$, $g(i)$ is the actual distance from the source node to $i$, * $h(i)$ is the estimated distance from $i$ to the target node. Usually a depth-first search is * desired, so ideally we would expand node $i$ first. Using the PairingHeap, this is not * necessarily the case though. This could be improved in a later version. * *

    * Note: This implementation works with both consistent and inconsistent admissible heuristics. For * details on consistency, refer to the description of the method * {@link AStarAdmissibleHeuristic#isConsistent(Graph)}. However, this class is not optimized * for inconsistent heuristics. Several opportunities to improve both worst case and average runtime * complexities for A* with inconsistent heuristics described in literature can be used to improve * this implementation! * * @param the graph vertex type * @param the graph edge type * @author Joris Kinable * @author Jon Robison * @author Thomas Breitbart */ public class AStarShortestPath extends BaseShortestPathAlgorithm { // Supplier of the preferable heap implementation protected final Supplier> heapSupplier; // List of open nodes protected AddressableHeap openList; protected Map> vertexToHeapNodeMap; // List of closed nodes protected Set closedList; // Mapping of nodes to their g-scores (g(x)). protected Map gScoreMap; // Predecessor map: mapping of a node to an edge that leads to its // predecessor on its shortest path towards the targetVertex protected Map cameFrom; // Reference to the admissible heuristic protected AStarAdmissibleHeuristic admissibleHeuristic; // Counter which keeps track of the number of expanded nodes protected int numberOfExpandedNodes; // Comparator for comparing doubles with tolerance protected Comparator comparator; /** * Create a new instance of the A* shortest path algorithm. * * @param graph the input graph * @param admissibleHeuristic admissible heuristic which estimates the distance from a node to * the target node. The heuristic must never overestimate the distance. */ public AStarShortestPath(Graph graph, AStarAdmissibleHeuristic admissibleHeuristic) { this(graph, admissibleHeuristic, PairingHeap::new); } /** * Create a new instance of the A* shortest path algorithm. * * @param graph the input graph * @param admissibleHeuristic admissible heuristic which estimates the distance from a node to * the target node. The heuristic must never overestimate the distance. * @param heapSupplier supplier of the preferable heap implementation */ public AStarShortestPath( Graph graph, AStarAdmissibleHeuristic admissibleHeuristic, Supplier> heapSupplier) { super(graph); this.admissibleHeuristic = Objects.requireNonNull(admissibleHeuristic, "Heuristic function cannot be null!"); this.comparator = new ToleranceDoubleComparator(); this.heapSupplier = Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null!"); } /** * Initializes the data structures. * * @param admissibleHeuristic admissible heuristic */ private void initialize(AStarAdmissibleHeuristic admissibleHeuristic) { this.admissibleHeuristic = admissibleHeuristic; openList = heapSupplier.get(); vertexToHeapNodeMap = new HashMap<>(); closedList = new HashSet<>(); gScoreMap = new HashMap<>(); cameFrom = new HashMap<>(); numberOfExpandedNodes = 0; } /** * Calculates (and returns) the shortest path from the sourceVertex to the targetVertex. Note: * each time you invoke this method, the path gets recomputed. * * @param sourceVertex source vertex * @param targetVertex target vertex * @return the shortest path from sourceVertex to targetVertex */ @Override public GraphPath getPath(V sourceVertex, V targetVertex) { if (!graph.containsVertex(sourceVertex) || !graph.containsVertex(targetVertex)) { throw new IllegalArgumentException( "Source or target vertex not contained in the graph!"); } if (sourceVertex.equals(targetVertex)) { return createEmptyPath(sourceVertex, targetVertex); } this.initialize(admissibleHeuristic); gScoreMap.put(sourceVertex, 0.0); AddressableHeap.Handle heapNode = openList.insert(0.0, sourceVertex); vertexToHeapNodeMap.put(sourceVertex, heapNode); do { AddressableHeap.Handle currentNode = openList.deleteMin(); // Check whether we reached the target vertex if (currentNode.getValue().equals(targetVertex)) { // Build the path return this.buildGraphPath(sourceVertex, targetVertex, currentNode.getKey()); } // We haven't reached the target vertex yet; expand the node expandNode(currentNode, targetVertex); closedList.add(currentNode.getValue()); } while (!openList.isEmpty()); // No path exists from sourceVertex to TargetVertex return createEmptyPath(sourceVertex, targetVertex); } /** * Returns how many nodes have been expanded in the A* search procedure in its last invocation. * A node is expanded if it is removed from the open list. * * @return number of expanded nodes */ public int getNumberOfExpandedNodes() { return numberOfExpandedNodes; } private void expandNode(AddressableHeap.Handle currentNode, V endVertex) { numberOfExpandedNodes++; Set outgoingEdges = graph.outgoingEdgesOf(currentNode.getValue()); for (E edge : outgoingEdges) { V successor = Graphs.getOppositeVertex(graph, edge, currentNode.getValue()); if (successor.equals(currentNode.getValue())) { // Ignore self-loop continue; } double gScore_current = gScoreMap.get(currentNode.getValue()); double tentativeGScore = gScore_current + graph.getEdgeWeight(edge); double fScore = tentativeGScore + admissibleHeuristic.getCostEstimate(successor, endVertex); if (vertexToHeapNodeMap.containsKey(successor)) { // We re-encountered a vertex. It's // either in the open or closed list. if (tentativeGScore >= gScoreMap.get(successor)) // Ignore path since it is // non-improving continue; cameFrom.put(successor, edge); gScoreMap.put(successor, tentativeGScore); if (closedList.contains(successor)) { // it's in the closed list. Move node back to // open list, since we discovered a shorter // path to this node closedList.remove(successor); openList.insert(fScore, vertexToHeapNodeMap.get(successor).getValue()); } else { // It's in the open list vertexToHeapNodeMap.get(successor).decreaseKey(fScore); } } else { // We've encountered a new vertex. cameFrom.put(successor, edge); gScoreMap.put(successor, tentativeGScore); AddressableHeap.Handle heapNode = openList.insert(fScore, successor); vertexToHeapNodeMap.put(successor, heapNode); } } } /** * Builds the graph path * * @param startVertex starting vertex of the path * @param targetVertex ending vertex of the path * @param pathLength length of the path * @return the shortest path from startVertex to endVertex */ private GraphPath buildGraphPath(V startVertex, V targetVertex, double pathLength) { List edgeList = new ArrayList<>(); List vertexList = new ArrayList<>(); vertexList.add(targetVertex); V v = targetVertex; while (!v.equals(startVertex)) { edgeList.add(cameFrom.get(v)); v = Graphs.getOppositeVertex(graph, cameFrom.get(v), v); vertexList.add(v); } Collections.reverse(edgeList); Collections.reverse(vertexList); return new GraphWalk<>(graph, startVertex, targetVertex, vertexList, edgeList, pathLength); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/AllDirectedPaths.java000066400000000000000000000303741402514743400334060ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Vera-Licona Research Group and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; /** * A Dijkstra-like algorithm to find all paths between two sets of nodes in a directed graph, with * options to search only simple paths and to limit the path length. * * @param the graph vertex type * @param the graph edge type * * @author Andrew Gainer-Dewar, Google LLC */ public class AllDirectedPaths { private final Graph graph; /** * Create a new instance. * * @param graph the input graph * @throws IllegalArgumentException if the graph is not directed */ public AllDirectedPaths(Graph graph) { this.graph = GraphTests.requireDirected(graph); } /** * Calculate (and return) all paths from the source vertex to the target vertex. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @param simplePathsOnly if true, only search simple (non-self-intersecting) paths * @param maxPathLength maximum number of edges to allow in a path (if null, all paths are * considered, which may be very slow due to potentially huge output) * @return all paths from the source vertex to the target vertex */ public List> getAllPaths( V sourceVertex, V targetVertex, boolean simplePathsOnly, Integer maxPathLength) { return getAllPaths( Collections.singleton(sourceVertex), Collections.singleton(targetVertex), simplePathsOnly, maxPathLength); } /** * Calculate (and return) all paths from the source vertices to the target vertices. * * @param sourceVertices the source vertices * @param targetVertices the target vertices * @param simplePathsOnly if true, only search simple (non-self-intersecting) paths * @param maxPathLength maximum number of edges to allow in a path (if null, all paths are * considered, which may be very slow due to potentially huge output) * * @return list of all paths from the sources to the targets containing no more than * maxPathLength edges */ public List> getAllPaths( Set sourceVertices, Set targetVertices, boolean simplePathsOnly, Integer maxPathLength) { if ((maxPathLength != null) && (maxPathLength < 0)) { throw new IllegalArgumentException("maxPathLength must be non-negative if defined"); } if (!simplePathsOnly && (maxPathLength == null)) { throw new IllegalArgumentException( "If search is not restricted to simple paths, a maximum path length must be set to avoid infinite cycles"); } if ((sourceVertices.isEmpty()) || (targetVertices.isEmpty())) { return Collections.emptyList(); } // Decorate the edges with the minimum path lengths through them Map edgeMinDistancesFromTargets = edgeMinDistancesBackwards(targetVertices, maxPathLength); // Generate all the paths return generatePaths( sourceVertices, targetVertices, simplePathsOnly, maxPathLength, edgeMinDistancesFromTargets); } /** * Compute the minimum number of edges in a path to the targets through each edge, so long as it * is not greater than a bound. * * @param targetVertices the target vertices * @param maxPathLength maximum number of edges to allow in a path (if null, all edges will be * considered, which may be expensive) * * @return the minimum number of edges in a path from each edge to the targets, encoded in a Map */ private Map edgeMinDistancesBackwards(Set targetVertices, Integer maxPathLength) { /* * We walk backwards through the network from the target vertices, marking edges and * vertices with their minimum distances as we go. */ Map edgeMinDistances = new HashMap<>(); Map vertexMinDistances = new HashMap<>(); Queue verticesToProcess = new ArrayDeque<>(); // Input sanity checking if (maxPathLength != null) { if (maxPathLength < 0) { throw new IllegalArgumentException("maxPathLength must be non-negative if defined"); } if (maxPathLength == 0) { return edgeMinDistances; } } // Bootstrap the process with the target vertices for (V target : targetVertices) { vertexMinDistances.put(target, 0); verticesToProcess.add(target); } // Work through the node queue. When it's empty, we're done! for (V vertex; (vertex = verticesToProcess.poll()) != null;) { assert vertexMinDistances.containsKey(vertex); Integer childDistance = vertexMinDistances.get(vertex) + 1; // Check whether the incoming edges of this node are correctly // decorated for (E edge : graph.incomingEdgesOf(vertex)) { // Mark the edge if needed if (!edgeMinDistances.containsKey(edge) || (edgeMinDistances.get(edge) > childDistance)) { edgeMinDistances.put(edge, childDistance); } // Mark the edge's source vertex if needed V edgeSource = graph.getEdgeSource(edge); if (!vertexMinDistances.containsKey(edgeSource) || (vertexMinDistances.get(edgeSource) > childDistance)) { vertexMinDistances.put(edgeSource, childDistance); if ((maxPathLength == null) || (childDistance < maxPathLength)) { verticesToProcess.add(edgeSource); } } } } assert verticesToProcess.isEmpty(); return edgeMinDistances; } /** * Generate all paths from the sources to the targets, using pre-computed minimum distances. * * @param sourceVertices the source vertices * @param targetVertices the target vertices * @param maxPathLength maximum number of edges to allow in a path * @param simplePathsOnly if true, only search simple (non-self-intersecting) paths (if null, * all edges will be considered, which may be expensive) * @param edgeMinDistancesFromTargets the minimum number of edges in a path to a target through * each edge, as computed by {@code * edgeMinDistancesBackwards}. * * @return a List of all GraphPaths from the sources to the targets satisfying the given * constraints */ private List> generatePaths( Set sourceVertices, Set targetVertices, boolean simplePathsOnly, Integer maxPathLength, Map edgeMinDistancesFromTargets) { /* * We walk forwards through the network from the source vertices, exploring all outgoing * edges whose minimum distances is small enough. */ List> completePaths = new ArrayList<>(); Deque> incompletePaths = new LinkedList<>(); // Input sanity checking if (maxPathLength != null && maxPathLength < 0) { throw new IllegalArgumentException("maxPathLength must be non-negative if defined"); } // Bootstrap the search with the source vertices for (V source : sourceVertices) { if (targetVertices.contains(source)) { completePaths.add(GraphWalk.singletonWalk(graph, source, 0d)); } if (maxPathLength != null && maxPathLength == 0) { continue; } for (E edge : graph.outgoingEdgesOf(source)) { assert graph.getEdgeSource(edge).equals(source); if (targetVertices.contains(graph.getEdgeTarget(edge))) { completePaths.add(makePath(Collections.singletonList(edge))); } if (edgeMinDistancesFromTargets.containsKey(edge) && (maxPathLength == null || maxPathLength > 1)) { List path = Collections.singletonList(edge); incompletePaths.add(path); } } } if (maxPathLength != null && maxPathLength == 0) { return completePaths; } // Walk through the queue of incomplete paths for (List incompletePath; (incompletePath = incompletePaths.poll()) != null;) { Integer lengthSoFar = incompletePath.size(); assert (maxPathLength == null) || (lengthSoFar < maxPathLength); E leafEdge = incompletePath.get(lengthSoFar - 1); V leafNode = graph.getEdgeTarget(leafEdge); Set pathVertices = new HashSet<>(); for (E pathEdge : incompletePath) { pathVertices.add(graph.getEdgeSource(pathEdge)); pathVertices.add(graph.getEdgeTarget(pathEdge)); } for (E outEdge : graph.outgoingEdgesOf(leafNode)) { // Proceed if the outgoing edge is marked and the mark // is sufficiently small if (edgeMinDistancesFromTargets.containsKey(outEdge) && ((maxPathLength == null) || ((edgeMinDistancesFromTargets.get(outEdge) + lengthSoFar) <= maxPathLength))) { List newPath = new ArrayList<>(incompletePath); newPath.add(outEdge); // If requested, make sure this path isn't self-intersecting if (simplePathsOnly && pathVertices.contains(graph.getEdgeTarget(outEdge))) { continue; } // If this path reaches a target, add it to completePaths if (targetVertices.contains(graph.getEdgeTarget(outEdge))) { GraphPath completePath = makePath(newPath); assert sourceVertices.contains(completePath.getStartVertex()); assert targetVertices.contains(completePath.getEndVertex()); assert (maxPathLength == null) || (completePath.getLength() <= maxPathLength); completePaths.add(completePath); } // If this path is short enough, consider further // extensions of it if ((maxPathLength == null) || (newPath.size() < maxPathLength)) { incompletePaths.addFirst(newPath); // We use // incompletePaths in // FIFO mode to avoid // memory blowup } } } } assert incompletePaths.isEmpty(); return completePaths; } /** * Transform an ordered list of edges into a GraphPath. * * The weight of the generated GraphPath is set to the sum of the weights of the edges. * * @param edges the edges * * @return the corresponding GraphPath */ private GraphPath makePath(List edges) { V source = graph.getEdgeSource(edges.get(0)); V target = graph.getEdgeTarget(edges.get(edges.size() - 1)); double weight = edges.stream().mapToDouble(edge -> graph.getEdgeWeight(edge)).sum(); return new GraphWalk<>(graph, source, target, edges, weight); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/BFSShortestPath.java000066400000000000000000000071541402514743400332150ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Karri Sai Satish Kumar Reddy and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import java.util.*; /** * The BFS Shortest Path algorithm. * *

    * An implementation of BFS shortest * path algorithm to compute shortest paths from a single source vertex to all other vertices in * an unweighted graph. * *

    * The running time is $O(|V|+|E|)$. * * @param the graph vertex type * @param the graph edge type * * @author Karri Sai Satish Kumar Reddy */ public class BFSShortestPath extends BaseShortestPathAlgorithm { /** * Construct a new instance. * * @param graph the input graph */ public BFSShortestPath(Graph graph) { super(graph); } /** * {@inheritDoc} */ @Override public SingleSourcePaths getPaths(V source) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } /* * Initialize distanceAndPredecessorMap */ Map> distanceAndPredecessorMap = new HashMap<>(); distanceAndPredecessorMap.put(source, Pair.of(0d, null)); /* * Declaring queue */ Deque queue = new ArrayDeque<>(); queue.add(source); /* * Take the top most vertex from the queue, relax its outgoing edges, update the distance of * the neighbouring vertices and push them into the queue */ while (!queue.isEmpty()) { V v = queue.poll(); for (E e : graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (!distanceAndPredecessorMap.containsKey(u)) { queue.add(u); double newDist = distanceAndPredecessorMap.get(v).getFirst() + 1.0; distanceAndPredecessorMap.put(u, Pair.of(newDist, e)); } } } return new TreeSingleSourcePathsImpl<>(graph, source, distanceAndPredecessorMap); } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V sink) { if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } return getPaths(source).getPath(sink); } /** * Find a path between two vertices. * * @param graph the graph to be searched * @param source the vertex at which the path should start * @param sink the vertex at which the path should end * * @param the graph vertex type * @param the graph edge type * * @return a shortest path, or null if no path exists */ public static GraphPath findPathBetween(Graph graph, V source, V sink) { return new BFSShortestPath<>(graph).getPath(source, sink); } } BaseBidirectionalShortestPathAlgorithm.java000066400000000000000000000077461402514743400377450ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; /** * Base class for the bidirectional shortest path algorithms. Currently known extensions are * {@link BidirectionalDijkstraShortestPath} and {@link BidirectionalAStarShortestPath}. * * @param vertices type * @param edges type * @author Dimitrios Michail */ public abstract class BaseBidirectionalShortestPathAlgorithm extends BaseShortestPathAlgorithm { /** * Constructs a new instance of the algorithm for a given graph. * * @param graph the graph */ public BaseBidirectionalShortestPathAlgorithm(Graph graph) { super(graph); } /** * Builds shortest path between {@code source} and {@code sink} based on the information * provided by search frontiers and common vertex. * * @param forwardFrontier forward direction frontier * @param backwardFrontier backward direction frontier * @param weight weight of the shortest path * @param source path source * @param commonVertex path common vertex * @param sink path sink * @return shortest path between source and sink */ protected GraphPath createPath( BaseSearchFrontier forwardFrontier, BaseSearchFrontier backwardFrontier, double weight, V source, V commonVertex, V sink) { LinkedList edgeList = new LinkedList<>(); LinkedList vertexList = new LinkedList<>(); // add common vertex vertexList.add(commonVertex); // traverse forward path V v = commonVertex; while (true) { E e = forwardFrontier.getTreeEdge(v); if (e == null) { break; } edgeList.addFirst(e); v = Graphs.getOppositeVertex(forwardFrontier.graph, e, v); vertexList.addFirst(v); } // traverse reverse path v = commonVertex; while (true) { E e = backwardFrontier.getTreeEdge(v); if (e == null) { break; } edgeList.addLast(e); v = Graphs.getOppositeVertex(backwardFrontier.graph, e, v); vertexList.addLast(v); } return new GraphWalk<>(graph, source, sink, vertexList, edgeList, weight); } /** * Base class of the search frontier used by bidirectional shortest path algorithms. * * @param vertices type * @param edges type */ abstract static class BaseSearchFrontier { /** * Frontier`s graph. */ final Graph graph; /** * Constructs instance for a given {@code graph}. * * @param graph graph */ BaseSearchFrontier(Graph graph) { this.graph = graph; } /** * Returns distance to vertex {@code v} computed so far. * * @param v vertex * @return distance to {@code v} */ abstract double getDistance(V v); /** * Returns edge which connects {@code v} to its predecessor in the shortest paths tree of * this frontier. * * @param v vertex * @return edge in shortest paths tree */ abstract E getTreeEdge(V v); } } BaseKDisjointShortestPathsAlgorithm.java000077500000000000000000000244231402514743400372500ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2018-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.util.*; import java.util.stream.*; /** * A base implementation of a $k$ disjoint shortest paths algorithm based on the strategy used in * Suurballe and Bhandari algorithms. The algorithm procedure goes as follow: *

      *
    1. Using some known shortest path algorithm (e.g. Dijkstra) to find the shortest path $P_1$ from * source to target. *
    2. For i = 2,...,$k$ *
    3.  Perform some graph transformations based on the previously found path *
    4.  Find the shortest path $P_i$ from source to target *
    5. Remove all overlapping edges to get $k$ disjoint paths. *
    * The class implements the above procedure and resolves final paths (step 5) from the intermediate * path results found in step 4. An extending class has to implement two methods: *
      *
    • {@link #transformGraph} - to be used in step 3. *
    • {@link #calculateShortestPath} - to be used in step 4. *
    * Currently known extensions are {@link SuurballeKDisjointShortestPaths} and * {@link BhandariKDisjointShortestPaths}. * * @param the graph vertex type * @param the graph edge type * * @author Assaf Mizrachi * @author Benjamin Krogh */ abstract class BaseKDisjointShortestPathsAlgorithm implements KShortestPathAlgorithm { /** * Graph on which shortest paths are searched. */ protected Graph workingGraph; protected List> pathList; protected Graph originalGraph; private Set validEdges; /** * Creates a new instance of the algorithm * * @param graph graph on which shortest paths are searched. * * @throws IllegalArgumentException if the graph is null. * @throws IllegalArgumentException if the graph is undirected. * @throws IllegalArgumentException if the graph is not simple. */ public BaseKDisjointShortestPathsAlgorithm(Graph graph) { this.originalGraph = graph; GraphTests.requireDirected(graph); if (!GraphTests.isSimple(graph)) { throw new IllegalArgumentException("Graph must be simple"); } } /** * Returns the $k$ shortest simple paths in increasing order of weight. * * @param startVertex source vertex of the calculated paths. * @param endVertex target vertex of the calculated paths. * * @return list of disjoint paths between the start vertex and the end vertex * * @throws IllegalArgumentException if the graph does not contain the startVertex or the * endVertex * @throws IllegalArgumentException if the startVertex and the endVertex are the same vertices * @throws IllegalArgumentException if the startVertex or the endVertex is null */ @Override public List> getPaths(V startVertex, V endVertex, int k) { if (k <= 0) { throw new IllegalArgumentException("Number of paths must be positive"); } Objects.requireNonNull(startVertex, "startVertex is null"); Objects.requireNonNull(endVertex, "endVertex is null"); if (endVertex.equals(startVertex)) { throw new IllegalArgumentException("The end vertex is the same as the start vertex!"); } if (!originalGraph.containsVertex(startVertex)) { throw new IllegalArgumentException("graph must contain the start vertex!"); } if (!originalGraph.containsVertex(endVertex)) { throw new IllegalArgumentException("graph must contain the end vertex!"); } // Create a working graph copy to avoid modifying the underlying graph. This gets // reinitialized for every call to getPaths since previous calls may have modified it. Since // the original graph may be using intrusive edges, we have to use an AsWeightedGraph view // (even when the graph copy is already weighted) to avoid writing weight changes through to // the underlying graph. this.workingGraph = new AsWeightedGraph<>( new DefaultDirectedWeightedGraph<>( this.originalGraph.getVertexSupplier(), this.originalGraph.getEdgeSupplier()), new HashMap<>(), false); Graphs.addGraph(workingGraph, this.originalGraph); this.pathList = new ArrayList<>(); GraphPath currentPath = calculateShortestPath(startVertex, endVertex); if (currentPath != null) { pathList.add(currentPath.getEdgeList()); for (int i = 0; i < k - 1; i++) { transformGraph(this.pathList.get(i)); currentPath = calculateShortestPath(startVertex, endVertex); if (currentPath != null) { pathList.add(currentPath.getEdgeList()); } else { break; } } } return pathList.size() > 0 ? resolvePaths(startVertex, endVertex) : Collections.emptyList(); } /** * At the end of the search we have list of intermediate paths - not necessarily disjoint and * may contain reversed edges. Here we go over all, removing overlapping edges and merging them * to valid paths (from start to end). Finally, we sort them according to their weight. * * @param startVertex the start vertex * @param endVertex the end vertex * * @return sorted list of disjoint paths from start vertex to end vertex. */ private List> resolvePaths(V startVertex, V endVertex) { // first we need to remove overlapping edges. findValidEdges(); // now we might be left with path fragments (not necessarily leading from start to end). // We need to merge them to valid paths. List> paths = buildPaths(startVertex, endVertex); // sort paths by overall weight (ascending) Collections.sort(paths, Comparator.comparingDouble(GraphPath::getWeight)); return paths; } /** * After removing overlapping edges, each path is not necessarily connecting start to end * vertex. Here we connect the path fragments to valid paths (from start to end). * * @param startVertex the start vertex * @param endVertex the end vertex * * @return list of disjoint paths from start to end. */ private List> buildPaths(V startVertex, V endVertex) { Map> sourceVertexToEdge = this.validEdges .stream().collect( Collectors .groupingBy(this::getEdgeSource, Collectors.toCollection(ArrayDeque::new))); ArrayDeque startEdges = sourceVertexToEdge.get(startVertex); List> result = new ArrayList<>(); for (E edge : startEdges) { final List resultPath = new ArrayList<>(); resultPath.add(edge); while (true) { final V edgeTarget = getEdgeTarget(edge); if (edgeTarget.equals(endVertex)) { break; } ArrayDeque outgoingEdges = sourceVertexToEdge.get(edgeTarget); edge = outgoingEdges.poll(); resultPath.add(edge); } GraphPath graphPath = createGraphPath(resultPath, startVertex, endVertex); result.add(graphPath); } return result; } /** * Iterate over all paths and remove all edges used an even number of times. The remaining edges * forms the valid edge set, which is used in the buildPaths method to construct the k-shortest * paths */ private void findValidEdges() { Map, E> validEdges = new LinkedHashMap<>(); for (List path : pathList) { for (E e : path) { V v = this.getEdgeSource(e); V u = this.getEdgeTarget(e); UnorderedPair edgePair = new UnorderedPair<>(v, u); validEdges.compute(edgePair, (unused, edge) -> edge == null ? e : null); } } this.validEdges = new LinkedHashSet<>(validEdges.values()); } private GraphPath createGraphPath(List edgeList, V startVertex, V endVertex) { double weight = 0; for (E edge : edgeList) { weight += originalGraph.getEdgeWeight(edge); } return new GraphWalk<>(originalGraph, startVertex, endVertex, edgeList, weight); } private V getEdgeSource(E e) { return this.workingGraph.containsEdge(e) ? this.workingGraph.getEdgeSource(e) : this.originalGraph.getEdgeSource(e); } private V getEdgeTarget(E e) { return this.workingGraph.containsEdge(e) ? this.workingGraph.getEdgeTarget(e) : this.originalGraph.getEdgeTarget(e); } /** * Calculates the shortest paths for the current iteration. Path is not final; rather, it is * intended to be used in a "post-production" phase (see resolvePaths method). * * @param startVertex the start vertex * @param endVertex the end vertex * * @return the shortest path between start and end vertices. */ protected abstract GraphPath calculateShortestPath(V startVertex, V endVertex); /** * Prepares the working graph for next iteration. To be called from the second iteration and on * so implementation may assume a preceding {@link #calculateShortestPath} call. * * @param previousPath the path found at the previous iteration. */ protected abstract void transformGraph(List previousPath); } BaseManyToManyShortestPaths.java000066400000000000000000000066731402514743400355430ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Base class for many-to-many shortest paths algorithms. Currently extended by * {@link CHManyToManyShortestPaths} and {@link DijkstraManyToManyShortestPaths}. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @author Dimitrios Michail */ abstract class BaseManyToManyShortestPaths implements ManyToManyShortestPathsAlgorithm { protected final Graph graph; /** * Constructs a new instance of the algorithm for a given graph. * * @param graph the graph */ public BaseManyToManyShortestPaths(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph is null"); } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V sink) { return getManyToManyPaths(Collections.singleton(source), Collections.singleton(sink)) .getPath(source, sink); } /** * {@inheritDoc} */ @Override public double getPathWeight(V source, V sink) { GraphPath p = getPath(source, sink); if (p == null) { return Double.POSITIVE_INFINITY; } else { return p.getWeight(); } } /** * {@inheritDoc} */ @Override public SingleSourcePaths getPaths(V source) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException("graph must contain the source vertex"); } Map> paths = new HashMap<>(); for (V v : graph.vertexSet()) { paths.put(v, getPath(source, v)); } return new ListSingleSourcePathsImpl<>(graph, source, paths); } /** * Computes shortest paths tree starting at {@code source} and stopping as soon as all of the * {@code targets} are reached. Here the {@link DijkstraClosestFirstIterator} is used. * * @param graph a graph * @param source source vertex * @param targets target vertices * @param the graph vertex type * @param the graph edge type * @return shortest paths starting from {@code source} and reaching all {@code targets} */ protected static ShortestPathAlgorithm.SingleSourcePaths getShortestPathsTree( Graph graph, V source, Set targets) { DijkstraClosestFirstIterator iterator = new DijkstraClosestFirstIterator<>(graph, source); int reachedTargets = 0; while (iterator.hasNext() && reachedTargets < targets.size()) { if (targets.contains(iterator.next())) { ++reachedTargets; } } return iterator.getPaths(); } } BaseMultiObjectiveShortestPathAlgorithm.java000066400000000000000000000054451402514743400401140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * A base implementation of the multi-objective shortest path interface. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ abstract class BaseMultiObjectiveShortestPathAlgorithm implements MultiObjectiveShortestPathAlgorithm { /** * Error message for reporting that a source vertex is missing. */ static final String GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX = "Graph must contain the source vertex!"; /** * Error message for reporting that a sink vertex is missing. */ static final String GRAPH_MUST_CONTAIN_THE_SINK_VERTEX = "Graph must contain the sink vertex!"; /** * The underlying graph. */ protected final Graph graph; /** * Constructs a new instance of the algorithm for a given graph * * @param graph the graph */ public BaseMultiObjectiveShortestPathAlgorithm(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph is null"); } @Override public MultiObjectiveSingleSourcePaths getPaths(V source) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } Map>> paths = new HashMap<>(); for (V v : graph.vertexSet()) { paths.put(v, getPaths(source, v)); } return new ListMultiObjectiveSingleSourcePathsImpl<>(graph, source, paths); } /** * Create an empty path. Returns null if the source vertex is different than the target vertex. * * @param source the source vertex * @param sink the sink vertex * @return an empty path or null null if the source vertex is different than the target vertex */ protected final GraphPath createEmptyPath(V source, V sink) { if (source.equals(sink)) { return GraphWalk.singletonWalk(graph, source, 0d); } else { return null; } } } BaseShortestPathAlgorithm.java000066400000000000000000000064161402514743400352450ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * A base implementation of the shortest path interface. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ abstract class BaseShortestPathAlgorithm implements ShortestPathAlgorithm { /** * Error message for reporting the existence of a negative-weight cycle. */ protected static final String GRAPH_CONTAINS_A_NEGATIVE_WEIGHT_CYCLE = "Graph contains a negative-weight cycle"; /** * Error message for reporting that a source vertex is missing. */ protected static final String GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX = "Graph must contain the source vertex!"; /** * Error message for reporting that a sink vertex is missing. */ protected static final String GRAPH_MUST_CONTAIN_THE_SINK_VERTEX = "Graph must contain the sink vertex!"; /** * The underlying graph. */ protected final Graph graph; /** * Constructs a new instance of the algorithm for a given graph. * * @param graph the graph */ public BaseShortestPathAlgorithm(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph is null"); } /** * {@inheritDoc} */ @Override public SingleSourcePaths getPaths(V source) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException("graph must contain the source vertex"); } Map> paths = new HashMap<>(); for (V v : graph.vertexSet()) { paths.put(v, getPath(source, v)); } return new ListSingleSourcePathsImpl<>(graph, source, paths); } /** * {@inheritDoc} */ @Override public double getPathWeight(V source, V sink) { GraphPath p = getPath(source, sink); if (p == null) { return Double.POSITIVE_INFINITY; } else { return p.getWeight(); } } /** * Create an empty path. Returns null if the source vertex is different than the target vertex. * * @param source the source vertex * @param sink the sink vertex * @return an empty path or null null if the source vertex is different than the target vertex */ protected final GraphPath createEmptyPath(V source, V sink) { if (source.equals(sink)) { return GraphWalk.singletonWalk(graph, source, 0d); } else { return null; } } } BellmanFordShortestPath.java000066400000000000000000000207641402514743400347130ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.lang.reflect.*; import java.util.*; /** * The Bellman-Ford algorithm. * *

    * Computes shortest paths from a single source vertex to all other vertices in a weighted graph. * The Bellman-Ford algorithm supports negative edge weights. * *

    * Negative weight cycles are not allowed and will be reported by the algorithm. This implies that * negative edge weights are not allowed in undirected graphs. In such cases the code will throw an * exception of type {@link NegativeCycleDetectedException} which will contain the detected negative * weight cycle. Note that the algorithm will not report or find negative weight cycles which are * not reachable from the source vertex. * *

    * The running time is $O(|E||V|)$. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class BellmanFordShortestPath extends BaseShortestPathAlgorithm { protected final Comparator comparator; protected final int maxHops; /** * Construct a new instance. * * @param graph the input graph */ public BellmanFordShortestPath(Graph graph) { this(graph, ToleranceDoubleComparator.DEFAULT_EPSILON); } /** * Construct a new instance. * * @param graph the input graph * @param epsilon tolerance when comparing floating point values */ public BellmanFordShortestPath(Graph graph, double epsilon) { this(graph, ToleranceDoubleComparator.DEFAULT_EPSILON, Integer.MAX_VALUE); } /** * Construct a new instance. * * @param graph the input graph * @param epsilon tolerance when comparing floating point values * @param maxHops execute the algorithm for at most this many iterations. If this is smaller * than the number of vertices, then the negative cycle detection feature is disabled. * @throws IllegalArgumentException if the number of maxHops is not positive */ public BellmanFordShortestPath(Graph graph, double epsilon, int maxHops) { super(graph); this.comparator = new ToleranceDoubleComparator(epsilon); if (maxHops < 1) { throw new IllegalArgumentException("Number of hops must be positive"); } this.maxHops = maxHops; } /** * {@inheritDoc} * * @throws NegativeCycleDetectedException in case a negative weight cycle is detected */ @Override public GraphPath getPath(V source, V sink) { if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } return getPaths(source).getPath(sink); } /** * {@inheritDoc} * * @throws NegativeCycleDetectedException in case a negative weight cycle is detected */ @Override @SuppressWarnings("unchecked") public SingleSourcePaths getPaths(V source) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } /* * Initialize distance and predecessor. */ int n = graph.vertexSet().size(); Map distance = new HashMap<>(); Map pred = new HashMap<>(); for (V v : graph.vertexSet()) { distance.put(v, Double.POSITIVE_INFINITY); } distance.put(source, 0d); /* * Maintain two sets of vertices whose edges need relaxation. The first set is the current * set of vertices while the second is the set for the subsequent iteration. */ Set[] updated = (Set[]) Array.newInstance(Set.class, 2); updated[0] = new LinkedHashSet<>(); updated[1] = new LinkedHashSet<>(); int curUpdated = 0; updated[curUpdated].add(source); /* * Relax edges. */ for (int i = 0; i < Math.min(n - 1, maxHops); i++) { Set curVertexSet = updated[curUpdated]; Set nextVertexSet = updated[(curUpdated + 1) % 2]; for (V v : curVertexSet) { for (E e : graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); double newDist = distance.get(v) + graph.getEdgeWeight(e); if (comparator.compare(newDist, distance.get(u)) < 0) { distance.put(u, newDist); pred.put(u, e); nextVertexSet.add(u); } } } // swap next with current curVertexSet.clear(); curUpdated = (curUpdated + 1) % 2; // stop if no relaxation if (nextVertexSet.isEmpty()) { break; } } /* * Check for negative cycles. The user can disable this by providing a maxHops parameter * smaller than the number of vertices. */ if (maxHops >= n) { for (V v : updated[curUpdated]) { for (E e : graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); double newDist = distance.get(v) + graph.getEdgeWeight(e); if (comparator.compare(newDist, distance.get(u)) < 0) { // record update for negative cycle computation pred.put(u, e); throw new NegativeCycleDetectedException( GRAPH_CONTAINS_A_NEGATIVE_WEIGHT_CYCLE, computeNegativeCycle(e, pred)); } } } } /* * Transform result */ Map> distanceAndPredecessorMap = new HashMap<>(); for (V v : graph.vertexSet()) { distanceAndPredecessorMap.put(v, Pair.of(distance.get(v), pred.get(v))); } return new TreeSingleSourcePathsImpl<>(graph, source, distanceAndPredecessorMap); } /** * Find a path between two vertices. * * @param graph the graph to be searched * @param source the vertex at which the path should start * @param sink the vertex at which the path should end * * @param the graph vertex type * @param the graph edge type * * @return a shortest path, or null if no path exists */ public static GraphPath findPathBetween(Graph graph, V source, V sink) { return new BellmanFordShortestPath<>(graph).getPath(source, sink); } /** * Computes a negative weight cycle assuming that the algorithm has already determined that it * exists. * * @param edge an edge which we know that belongs to the negative weight cycle * @param pred the predecessor array * * @return the negative weight cycle */ private GraphPath computeNegativeCycle(E edge, Map pred) { // find a vertex of the cycle Set visited = new HashSet<>(); V start = graph.getEdgeTarget(edge); visited.add(start); V cur = Graphs.getOppositeVertex(graph, edge, start); while (!visited.contains(cur)) { visited.add(cur); E e = pred.get(cur); cur = Graphs.getOppositeVertex(graph, e, cur); } // now build the actual cycle List cycle = new ArrayList<>(); double weight = 0d; start = cur; do { E e = pred.get(cur); cycle.add(e); weight += graph.getEdgeWeight(e); cur = Graphs.getOppositeVertex(graph, e, cur); } while (cur != start); Collections.reverse(cycle); return new GraphWalk<>(graph, start, start, cycle, weight); } } BhandariKDisjointShortestPaths.java000066400000000000000000000061341402514743400362330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2018-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import java.util.*; /** * An implementation of Bhandari algorithm for finding $K$ edge-disjoint shortest paths. * The algorithm determines the $k$ edge-disjoint shortest simple paths in increasing order of * weight. Weights can be negative (but no negative cycle is allowed). Only directed simple graphs * are allowed. * *

    * The algorithm is running $k$ sequential Bellman-Ford iterations to find the shortest path at each * step. Hence, yielding a complexity of $k$*O(Bellman-Ford). * *

      *
    • Bhandari, Ramesh 1999. Survivable networks: algorithms for diverse routing. 477. Springer. p. * 46. ISBN 0-7923-8381-8. *
    • Iqbal, F. and Kuipers, F. A. 2015. * Disjoint Paths in * Networks . Wiley Encyclopedia of Electrical and Electronics Engineering. 1–11. *
    * * @param the graph vertex type * @param the graph edge type * * @author Assaf Mizrachi */ public class BhandariKDisjointShortestPaths extends BaseKDisjointShortestPathsAlgorithm { /** * Creates a new instance of the algorithm. * * @param graph graph on which shortest paths are searched. * * @throws IllegalArgumentException if the graph is null. * @throws IllegalArgumentException if the graph is undirected. * @throws IllegalArgumentException if the graph is not simple. */ public BhandariKDisjointShortestPaths(Graph graph) { super(graph); } @Override protected void transformGraph(List previousPath) { V source, target; E reversedEdge; // replace previous path edges with reversed edges with negative weight for (E originalEdge : previousPath) { source = workingGraph.getEdgeSource(originalEdge); target = workingGraph.getEdgeTarget(originalEdge); double originalEdgeWeight = workingGraph.getEdgeWeight(originalEdge); workingGraph.removeEdge(originalEdge); workingGraph.addEdge(target, source); reversedEdge = workingGraph.getEdge(target, source); workingGraph.setEdgeWeight(reversedEdge, -originalEdgeWeight); } } @Override protected GraphPath calculateShortestPath(V startVertex, V endVertex) { return new BellmanFordShortestPath<>(this.workingGraph).getPath(startVertex, endVertex); } } BidirectionalAStarShortestPath.java000066400000000000000000000323621402514743400362260ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; import java.util.function.*; /** * A bidirectional version of A* algorithm. * *

    * See the Wikipedia article for details and references about * bidirectional search. This * technique does not change the worst-case behavior of the algorithm but reduces, in some cases, * the number of visited vertices in practice. *

    * The algorithm was first introduced in Ira Sheldon Pohl. 1969. Bi-Directional and Heuristic Search * in Path Problems. Ph.D. Dissertation. Stanford University, Stanford, CA, USA. AAI7001588. *

    * The implementation uses two termination criteria depending on if the provided heuristic is * consistent or not. Both criteria are based on the shortest path distance $\mu$ seen thus far in * the search. Initially, in both cases the algorithm sets $\mu=\infty$. Whenever the search updates * the information about the vertex $v$, it sets $\mu = min\{\mu; g_f(v) + g_b(v)\}$, where $g_f(v)$ * is the current best-known path cost from $source$ to $sink$ and $g_b(v)$ is the current * best-known path cost from $sink$ to $source$. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @author Dimitrios Michail * @author Joris Kinable * @author Jon Robison * @author Thomas Breitbart * @see AStarShortestPath */ public class BidirectionalAStarShortestPath extends BaseBidirectionalShortestPathAlgorithm { /** * Heuristic used for forward search. */ private AStarAdmissibleHeuristic forwardHeuristic; /** * Heuristic used for backward search. In general $d(u,v)\neq d(v,u)$, e.g. in the directed * graphs. */ private AStarAdmissibleHeuristic backwardHeuristic; private final Supplier> heapSupplier; /** * Constructs a new instance of the algorithm for a given graph and heuristic. * * @param graph the graph * @param heuristic heuristic that estimates distances between nodes */ public BidirectionalAStarShortestPath(Graph graph, AStarAdmissibleHeuristic heuristic) { this(graph, heuristic, PairingHeap::new); } /** * Constructs a new instance of the algorithm for a given graph, heuristic and heap supplier. * * @param graph the graph * @param heuristic heuristic that estimates distances between nodes * @param heapSupplier supplier of the preferable heap implementation */ public BidirectionalAStarShortestPath( Graph graph, AStarAdmissibleHeuristic heuristic, Supplier> heapSupplier) { super(graph); this.forwardHeuristic = Objects.requireNonNull(heuristic, "Heuristic function cannot be null!"); if (graph.getType().isDirected()) { backwardHeuristic = new ReversedGraphHeuristic( Objects.requireNonNull(heuristic, "Heuristic function cannot be null!")); } else { this.backwardHeuristic = Objects.requireNonNull(heuristic, "Heuristic function cannot be null!"); } this.heapSupplier = Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null!"); } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V sink) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } // handle special case if source equals target if (source.equals(sink)) { return createEmptyPath(source, sink); } // create frontiers AStarSearchFrontier forwardFrontier = new AStarSearchFrontier(graph, sink, forwardHeuristic); AStarSearchFrontier backwardFrontier; if (graph.getType().isDirected()) { backwardFrontier = new AStarSearchFrontier(new EdgeReversedGraph<>(graph), source, backwardHeuristic); } else { backwardFrontier = new AStarSearchFrontier(graph, source, backwardHeuristic); } forwardFrontier.updateDistance(source, null, 0.0, 0.0); backwardFrontier.updateDistance(sink, null, 0.0, 0.0); // initialize best path double bestPath = Double.POSITIVE_INFINITY; V bestPathCommonVertex = null; AStarSearchFrontier frontier = forwardFrontier; AStarSearchFrontier otherFrontier = backwardFrontier; TerminationCriterion condition; if (forwardHeuristic.isConsistent(graph)) { double sourceTargetEstimate = forwardFrontier.heuristic.getCostEstimate(source, sink); condition = new ConsistentTerminationCriterion( forwardFrontier, backwardFrontier, sourceTargetEstimate); } else { condition = new InconsistentTerminationCriterion(forwardFrontier, backwardFrontier); } while (true) { // stopping condition if (condition.stop(bestPath)) { break; } // frontier scan AddressableHeap.Handle node = frontier.openList.deleteMin(); V v = node.getValue(); for (E edge : frontier.graph.outgoingEdgesOf(v)) { V successor = Graphs.getOppositeVertex(frontier.graph, edge, v); if (successor.equals(v)) { // Ignore self-loop continue; } double edgeWeight = frontier.graph.getEdgeWeight(edge); double gScore_current = frontier.getDistance(v); double tentativeGScore = gScore_current + edgeWeight; double fScore = tentativeGScore + frontier.heuristic.getCostEstimate(successor, frontier.endVertex); frontier.updateDistance(successor, edge, tentativeGScore, fScore); // check if best path can be updated double pathDistance = gScore_current + edgeWeight + otherFrontier.getDistance(successor); if (pathDistance < bestPath) { bestPath = pathDistance; bestPathCommonVertex = successor; } } // close current vertex frontier.closedList.add(v); // swap frontiers if (frontier.openList.size() > otherFrontier.openList.size()) { AStarSearchFrontier tmpFrontier = frontier; frontier = otherFrontier; otherFrontier = tmpFrontier; } } // create path if found if (Double.isFinite(bestPath)) { return createPath( forwardFrontier, backwardFrontier, bestPath, source, bestPathCommonVertex, sink); } else { return createEmptyPath(source, sink); } } /** * Maintains search frontier during shortest path computation. */ class AStarSearchFrontier extends BaseSearchFrontier { /** * End vertex of the frontier. */ final V endVertex; /** * Heuristic used in this frontier. */ final AStarAdmissibleHeuristic heuristic; /** * Open nodes of the frontier. */ final AddressableHeap openList; final Map> vertexToHeapNodeMap; /** * Closed nodes of the frontier. */ final Set closedList; /** * Tentative distance to the vertices in tha graph computed so far. */ final Map gScoreMap; /** * Predecessor map. */ final Map cameFrom; AStarSearchFrontier(Graph graph, V endVertex, AStarAdmissibleHeuristic heuristic) { super(graph); this.endVertex = endVertex; this.heuristic = heuristic; openList = heapSupplier.get(); vertexToHeapNodeMap = new HashMap<>(); closedList = new HashSet<>(); gScoreMap = new HashMap<>(); cameFrom = new HashMap<>(); } void updateDistance(V v, E e, double tentativeGScore, double fScore) { AddressableHeap.Handle node = vertexToHeapNodeMap.get(v); if (vertexToHeapNodeMap.containsKey(v)) { // We re-encountered a vertex. It's // either in the open or closed list. if (tentativeGScore >= gScoreMap.get(v)) {// Ignore path since it is non-improving return; } cameFrom.put(v, e); gScoreMap.put(v, tentativeGScore); if (closedList.contains(v)) { // it's in the closed list. Move node back to // open list, since we discovered a shorter path to this node closedList.remove(v); openList.insert(fScore, v); } else { // It's in the open list node.decreaseKey(fScore); } } else { // We've encountered a new vertex. cameFrom.put(v, e); gScoreMap.put(v, tentativeGScore); node = openList.insert(fScore, v); vertexToHeapNodeMap.put(v, node); } } @Override double getDistance(V v) { Double distance = gScoreMap.get(v); if (distance == null) { return Double.POSITIVE_INFINITY; } else { return distance; } } @Override E getTreeEdge(V v) { return cameFrom.get(v); } } /** * Helper class for backward search, since it should operate on the reversed graph. */ class ReversedGraphHeuristic implements AStarAdmissibleHeuristic { private final AStarAdmissibleHeuristic heuristic; ReversedGraphHeuristic(AStarAdmissibleHeuristic heuristic) { this.heuristic = heuristic; } @Override public double getCostEstimate(V sourceVertex, V targetVertex) { return heuristic.getCostEstimate(targetVertex, sourceVertex); } } /** * Termination criterion for the heuristic search. */ abstract class TerminationCriterion { final AStarSearchFrontier forward; final AStarSearchFrontier backward; TerminationCriterion(AStarSearchFrontier forward, AStarSearchFrontier backward) { this.forward = forward; this.backward = backward; } /** * Determines if the search should be terminated. * * @param bestPath length of the shortest path seen so far * @return true iff the search should be terminated */ abstract boolean stop(double bestPath); } /** * Termination criterion for the consistent heuristics. */ class ConsistentTerminationCriterion extends TerminationCriterion { final double sourceTargetEstimate; ConsistentTerminationCriterion( AStarSearchFrontier forward, AStarSearchFrontier backward, double sourceTargetEstimate) { super(forward, backward); this.sourceTargetEstimate = sourceTargetEstimate; } @Override boolean stop(double bestPath) { return forward.openList.isEmpty() || backward.openList.isEmpty() || forward.openList.findMin().getKey() + backward.openList.findMin().getKey() >= bestPath + sourceTargetEstimate; } } /** * Termination criterion for the inconsistent heuristics. */ class InconsistentTerminationCriterion extends TerminationCriterion { InconsistentTerminationCriterion(AStarSearchFrontier forward, AStarSearchFrontier backward) { super(forward, backward); } @Override boolean stop(double bestPath) { return forward.openList.isEmpty() || backward.openList.isEmpty() || Math .max( forward.openList.findMin().getKey(), backward.openList.findMin().getKey()) >= bestPath; } } } BidirectionalDijkstraShortestPath.java000066400000000000000000000217331402514743400367670ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; import java.util.function.*; /** * A bidirectional version of Dijkstra's algorithm. * *

    * See the Wikipedia article for details and references about * bidirectional search. This * technique does not change the worst-case behavior of the algorithm but reduces, in some cases, * the number of visited vertices in practice. This implementation alternatively constructs forward * and reverse paths from the source and target vertices respectively. *

    * This iterator can use a custom heap implementation, which can specified during the construction * time. Pairing heap is used by default * * @param the graph vertex type * @param the graph edge type * @author Dimitrios Michail * @see DijkstraShortestPath */ public final class BidirectionalDijkstraShortestPath extends BaseBidirectionalShortestPathAlgorithm { private double radius; private final Supplier>> heapSupplier; /** * Constructs a new instance for a specified graph. * * @param graph the input graph */ public BidirectionalDijkstraShortestPath(Graph graph) { this(graph, Double.POSITIVE_INFINITY, PairingHeap::new); } /** * Constructs a new instance for a specified graph. The constructed algorithm will use the heap * supplied by the {@code heapSupplier}. * * @param graph the input graph * @param heapSupplier supplier of the preferable heap implementation */ public BidirectionalDijkstraShortestPath( Graph graph, Supplier>> heapSupplier) { this(graph, Double.POSITIVE_INFINITY, heapSupplier); } /** * Constructs a new instance for a specified graph. * * @param graph the input graph * @param radius limit on path length, or Double.POSITIVE_INFINITY for unbounded search */ public BidirectionalDijkstraShortestPath(Graph graph, double radius) { this(graph, radius, PairingHeap::new); } /** * Constructs a new instance for a specified graph. The constructed algorithm will use the heap * supplied by the {@code heapSupplier}. * * @param graph the input graph * @param radius limit on path length, or Double.POSITIVE_INFINITY for unbounded search * @param heapSupplier supplier of the preferable heap implementation */ public BidirectionalDijkstraShortestPath( Graph graph, double radius, Supplier>> heapSupplier) { super(graph); if (radius < 0.0) { throw new IllegalArgumentException("Radius must be non-negative"); } this.heapSupplier = Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null"); this.radius = radius; } /** * Find a path between two vertices. For a more advanced search (e.g. limited by radius), use * the constructor instead. * * @param graph the graph to be searched * @param source the vertex at which the path should start * @param sink the vertex at which the path should end * @param the graph vertex type * @param the graph edge type * @return a shortest path, or null if no path exists */ public static GraphPath findPathBetween(Graph graph, V source, V sink) { return new BidirectionalDijkstraShortestPath<>(graph).getPath(source, sink); } @Override public GraphPath getPath(V source, V sink) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } // handle special case if source equals target if (source.equals(sink)) { return createEmptyPath(source, sink); } // create frontiers DijkstraSearchFrontier forwardFrontier = new DijkstraSearchFrontier<>(graph, heapSupplier); DijkstraSearchFrontier backwardFrontier; if (graph.getType().isDirected()) { backwardFrontier = new DijkstraSearchFrontier<>(new EdgeReversedGraph<>(graph), heapSupplier); } else { backwardFrontier = new DijkstraSearchFrontier<>(graph, heapSupplier); } assert !source.equals(sink); // initialize both frontiers forwardFrontier.updateDistance(source, null, 0d); backwardFrontier.updateDistance(sink, null, 0d); // initialize best path double bestPath = Double.POSITIVE_INFINITY; V bestPathCommonVertex = null; DijkstraSearchFrontier frontier = forwardFrontier; DijkstraSearchFrontier otherFrontier = backwardFrontier; while (true) { // stopping condition if (frontier.heap.isEmpty() || otherFrontier.heap.isEmpty() || frontier.heap.findMin().getKey() + otherFrontier.heap.findMin().getKey() >= bestPath) { break; } // frontier scan AddressableHeap.Handle> node = frontier.heap.deleteMin(); V v = node.getValue().getFirst(); double vDistance = node.getKey(); for (E e : frontier.graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(frontier.graph, e, v); double eWeight = frontier.graph.getEdgeWeight(e); frontier.updateDistance(u, e, vDistance + eWeight); // check path with u's distance from the other frontier double pathDistance = vDistance + eWeight + otherFrontier.getDistance(u); if (pathDistance < bestPath) { bestPath = pathDistance; bestPathCommonVertex = u; } } // swap frontiers DijkstraSearchFrontier tmpFrontier = frontier; frontier = otherFrontier; otherFrontier = tmpFrontier; } // create path if found if (Double.isFinite(bestPath) && bestPath <= radius) { return createPath( forwardFrontier, backwardFrontier, bestPath, source, bestPathCommonVertex, sink); } else { return createEmptyPath(source, sink); } } /** * Maintains search frontier during shortest path computation. * * @param vertices type * @param edges type */ static class DijkstraSearchFrontier extends BaseSearchFrontier { final AddressableHeap> heap; final Map>> seen; DijkstraSearchFrontier( Graph graph, Supplier>> heapSupplier) { super(graph); this.heap = heapSupplier.get(); this.seen = new HashMap<>(); } void updateDistance(V v, E e, double distance) { AddressableHeap.Handle> node = seen.get(v); if (node == null) { node = heap.insert(distance, new Pair<>(v, e)); seen.put(v, node); } else { if (distance < node.getKey()) { node.decreaseKey(distance); node.setValue(Pair.of(v, e)); } } } @Override public double getDistance(V v) { AddressableHeap.Handle> node = seen.get(v); if (node == null) { return Double.POSITIVE_INFINITY; } else { return node.getKey(); } } @Override public E getTreeEdge(V v) { AddressableHeap.Handle> node = seen.get(v); if (node == null) { return null; } else { return node.getValue().getSecond(); } } } } CHManyToManyShortestPaths.java000066400000000000000000000520751402514743400351600ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.util.ConcurrencyUtil; import java.util.*; import java.util.concurrent.ThreadPoolExecutor; import java.util.stream.*; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.*; /** * Efficient algorithm for the many-to-many shortest paths problem based on contraction hierarchy. * *

    * The algorithm is originally described in the article: Sebastian Knopp, Peter Sanders, Dominik * Schultes, Frank Schulz, and Dorothea Wagner. 2007. Computing many-to-many shortest paths using * highway hierarchies. In Proceedings of the Meeting on Algorithm Engineering & Expermiments. * Society for Industrial and Applied Mathematics, Philadelphia, PA, USA, 36-45. * *

    * First contraction hierarchy is constructed. Then for each target vertex a backward single source * shortest paths search is performed on the contracted graph. During the searches a bucket $b(v)$ * is associated with each vertex $v$ in the graph. A bucket stores a set of pairs $(t,d)$, where * $t$ is a target vertex current search is performed from and $d$ is the computed distance from $v$ * to this target. Then a forward single source shortest paths search is performed from every source * vertex. When a search settles a vertex $v$ with distance $d(s,v)$, where $s$ is current source * vertex, its bucket is scanned. For each entry $(t,d)$ if $d(s,t) > d(s,v) + d$ values of paths * weight between $s$ and $t$ and its middle vertex is updated. The middle vertices are then used to * restored actual path from the information in the shortest paths trees. * *

    * Additionally if $|S| > |T|$ the algorithm is executed on the reversed graph. This allows to * reduce the number of buckets and optimize memory usage of the algorithm. * *

    * The efficiency of this algorithm is derived from the fact that contraction hierarchy produces * fairly small shortest paths trees. This allows to both speedup the computations and decrease * memory usage to store the paths. The bottleneck of the algorithm is the contraction hierarchy * computation, which can lead to significant overhead for dense graphs both in terms of running * time and space complexity. Therefore the ideal use cases for this algorithm are sparse graphs of * any size with low average out-degree of vertices. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @see DefaultManyToManyShortestPaths * @see DijkstraManyToManyShortestPaths */ public class CHManyToManyShortestPaths extends BaseManyToManyShortestPaths { /** * Contraction hierarchy of {@code graph}. */ private ContractionHierarchy contractionHierarchy; /** * Contracted version of {@code graph}. */ private Graph, ContractionEdge> contractionGraph; /** * Mapping from vertices in the original {@code graph} to vertices in the * {@code contractionGraph}. */ private Map> contractionMapping; /** * Constructs an instance of the algorithm for a given {@code graph}. * * @param graph a graph * @deprecated replaced with {@link #CHManyToManyShortestPaths(Graph, ThreadPoolExecutor)} */ @Deprecated public CHManyToManyShortestPaths(Graph graph) { this(new ContractionHierarchyPrecomputation<>(graph).computeContractionHierarchy()); } /** * Constructs an instance of the algorithm for a given {@code graph} and {@code executor}. It is * up to a user of this algorithm to handle the creation and termination of the provided * {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see * {@link ConcurrencyUtil}. * * @param graph a graph * @param executor executor which will be used to compute {@link ContractionHierarchy} */ public CHManyToManyShortestPaths(Graph graph, ThreadPoolExecutor executor) { this( new ContractionHierarchyPrecomputation<>(graph, executor) .computeContractionHierarchy()); } /** * Constructs an instance of the algorithm for a given {@code contractionHierarchy}. * * @param contractionHierarchy contraction of the {@code graph} */ public CHManyToManyShortestPaths(ContractionHierarchy contractionHierarchy) { super(contractionHierarchy.getGraph()); this.contractionHierarchy = contractionHierarchy; this.contractionGraph = contractionHierarchy.getContractionGraph(); this.contractionMapping = contractionHierarchy.getContractionMapping(); } /** * {@inheritDoc} */ @Override public ManyToManyShortestPaths getManyToManyPaths(Set sources, Set targets) { Objects.requireNonNull(sources, "sources cannot be null!"); Objects.requireNonNull(targets, "targets cannot be null!"); Graph, ContractionEdge> searchContractionGraph; boolean reversed; if (sources.size() <= targets.size()) { searchContractionGraph = contractionGraph; reversed = false; } else { searchContractionGraph = new EdgeReversedGraph<>(contractionGraph); reversed = true; Set tmp = targets; targets = sources; sources = tmp; } Map, Map, Pair>>> forwardSearchSpaces = new HashMap<>(); Map, Map, Pair>>> backwardSearchSpaces = new HashMap<>(); Map, ContractionVertex>, Pair>> middleVertices = new HashMap<>(); Set> contractedSources = sources .stream().map(contractionMapping::get).collect(Collectors.toCollection(HashSet::new)); Set> contractedTargets = targets .stream().map(contractionMapping::get).collect(Collectors.toCollection(HashSet::new)); Map, List> bucketsMap = new HashMap<>(); for (ContractionVertex vertex : searchContractionGraph.vertexSet()) { bucketsMap.put(vertex, new ArrayList<>()); } for (ContractionVertex contractedTarget : contractedTargets) { backwardSearch( searchContractionGraph, contractedTarget, contractedSources, bucketsMap, backwardSearchSpaces, reversed); } for (ContractionVertex contractedSource : contractedSources) { forwardSearch( searchContractionGraph, contractedSource, contractedTargets, bucketsMap, forwardSearchSpaces, middleVertices, reversed); } if (reversed) { return new CHManyToManyShortestPathsImpl( graph, contractionHierarchy, targets, sources, backwardSearchSpaces, forwardSearchSpaces, middleVertices); } else { return new CHManyToManyShortestPathsImpl( graph, contractionHierarchy, sources, targets, forwardSearchSpaces, backwardSearchSpaces, middleVertices); } } /** * Performs backward single source shortest paths search in {@code contractionGraph} starting * from {@code target} to {@code sources}. For each vertex $v$ in {@code contractionGraph} a * bucket is created that records entries $(t,d)$, where $t$ is a current {@code target} and $d$ * is a distance computed during current search. A constructed shortest paths tree is then put * in {@code backwardSearchSpaces}. If {@code reversed} flag is set to $true$ the specified * {@code target} belongs to the original source vertices and therefore downward edges should be * masked in the contraction graph instead of upward. * * @param contractionGraph graph to perform search in * @param target search start vertex * @param contractedSources vertices to end search at * @param bucketsMap map from vertices to their buckets * @param backwardSearchSpaces map from vertices to their search spaces * @param reversed indicates if current search is reversed */ private void backwardSearch( Graph, ContractionEdge> contractionGraph, ContractionVertex target, Set> contractedSources, Map, List> bucketsMap, Map, Map, Pair>>> backwardSearchSpaces, boolean reversed) { Graph, ContractionEdge> maskSubgraph; if (reversed) { maskSubgraph = new MaskSubgraph<>( new EdgeReversedGraph<>(contractionGraph), v -> false, e -> !e.isUpward); } else { maskSubgraph = new MaskSubgraph<>( new EdgeReversedGraph<>(contractionGraph), v -> false, e -> e.isUpward); } Map, Pair>> distanceAndPredecessorMap = getDistanceAndPredecessorMap(maskSubgraph, target, contractedSources); backwardSearchSpaces.put(target, distanceAndPredecessorMap); for (Map.Entry, Pair>> entry : distanceAndPredecessorMap.entrySet()) { bucketsMap .get(entry.getKey()).add(new BucketEntry(target, entry.getValue().getFirst())); } } /** * Performs forward search from the given {@code source} to {@code targets}. A constructed * shortest paths tree is then put in {@code forwardSearchSpaces}. If {@code reversed} flag is * set to $true$ the specified {@code source} belongs to the original target vertices and * therefore upward edges should be masked in the contraction graph instead of the downward. * * @param contractionGraph graph to perform search in * @param source start vertex of the search * @param contractedTargets vertices to end search at * @param bucketsMap map from vertices to their buckets * @param forwardSearchSpaces map from vertices to their search spaces * @param middleVerticesMap map from source-target pairs to theirs distances and middle nodes * @param reversed indicates if current search is reversed */ private void forwardSearch( Graph, ContractionEdge> contractionGraph, ContractionVertex source, Set> contractedTargets, Map, List> bucketsMap, Map, Map, Pair>>> forwardSearchSpaces, Map, ContractionVertex>, Pair>> middleVerticesMap, boolean reversed) { Graph, ContractionEdge> maskSubgraph; if (reversed) { maskSubgraph = new MaskSubgraph<>(contractionGraph, v -> false, e -> e.isUpward); } else { maskSubgraph = new MaskSubgraph<>(contractionGraph, v -> false, e -> !e.isUpward); } Map, Pair>> distanceAndPredecessorMap = getDistanceAndPredecessorMap(maskSubgraph, source, contractedTargets); forwardSearchSpaces.put(source, distanceAndPredecessorMap); for (Map.Entry, Pair>> entry : distanceAndPredecessorMap.entrySet()) { ContractionVertex middleVertex = entry.getKey(); double forwardDistance = entry.getValue().getFirst(); for (BucketEntry bucketEntry : bucketsMap.get(middleVertex)) { double pathDistance = forwardDistance + bucketEntry.distance; Pair, ContractionVertex> pair; if (reversed) { pair = Pair.of(bucketEntry.target, source); } else { pair = Pair.of(source, bucketEntry.target); } middleVerticesMap.compute(pair, (p, distanceAndMiddleNode) -> { if (distanceAndMiddleNode == null || distanceAndMiddleNode.getFirst() > pathDistance) { return Pair.of(pathDistance, middleVertex); } return distanceAndMiddleNode; }); } } } /** * Computes distance and predecessor map for a single source shortest paths search starting at * source and finishing the search as soon as all {@code targets} are reached. * * @param contractionGraph a graph * @param source search start vertex * @param targets search end vertices * @return distance and predecessor map */ private Map, Pair>> getDistanceAndPredecessorMap( Graph, ContractionEdge> contractionGraph, ContractionVertex source, Set> targets) { return ((TreeSingleSourcePathsImpl, ContractionEdge>) getShortestPathsTree(contractionGraph, source, targets)).map; } /** * Stores data computed during the backward searches. */ private class BucketEntry { /** * Start vertex of the backward search during which this entry is created. */ ContractionVertex target; /** * Distance from a vertex this entry is created for to {@code target}. */ double distance; /** * Constrcuts an instance of an entry for the given {@code target} and {@code distance}. * * @param target backward search start vertex * @param distance distance to {@code target} */ public BucketEntry(ContractionVertex target, double distance) { this.target = target; this.distance = distance; } } /** * Implementation of * {@link org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths} * for many-to-many shortest paths algorithm based on contraction hierarchy. Paths are stored in * form of bidirectional single source shortest paths trees. When a path weight is queried a * value that is stored in {@code distanceAndMiddleVertexMap} is returned. When an actual paths * is required it is constructed by recursively unpacking edges stored in the shortest paths * trees corresponding to source and target vertices. */ private class CHManyToManyShortestPathsImpl extends BaseManyToManyShortestPathsImpl { /** * The underlying graph. */ private final Graph graph; /** * Contraction hierarchy for {@code graph}. */ private final Graph, ContractionEdge> contractionGraph; /** * Mapping from original to contracted vertices. */ private final Map> contractionMapping; /** * Stores forward search space for each start vertex. */ private Map, Map, Pair>>> forwardSearchSpaces; /** * Stores backward search space for each target vertex. */ private Map, Map, Pair>>> backwardSearchSpaces; /** * Stores pair of path weight and middle vertex for each source-target pair. */ private Map, ContractionVertex>, Pair>> distanceAndMiddleVertexMap; /** * Constructs a new instance for the given {@code graph}, {@code contractionGraph}, * {@code contractionMapping}, {@code forwardSearchSpaces}, {@code backwardSearchSpaces} and * {@code distanceAndMiddleVertexMap}. * * @param graph underlying graph. * @param hierarchy contraction hierarchy * @param forwardSearchSpaces search spaces of source vertices * @param backwardSearchSpaces search spaces of target vertices * @param distanceAndMiddleVertexMap weights and middle vertices of paths */ public CHManyToManyShortestPathsImpl( Graph graph, ContractionHierarchy hierarchy, Set sources, Set targets, Map, Map, Pair>>> forwardSearchSpaces, Map, Map, Pair>>> backwardSearchSpaces, Map, ContractionVertex>, Pair>> distanceAndMiddleVertexMap) { super(sources, targets); this.graph = graph; this.contractionGraph = hierarchy.getContractionGraph(); this.contractionMapping = hierarchy.getContractionMapping(); this.forwardSearchSpaces = forwardSearchSpaces; this.backwardSearchSpaces = backwardSearchSpaces; this.distanceAndMiddleVertexMap = distanceAndMiddleVertexMap; } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V target) { assertCorrectSourceAndTarget(source, target); LinkedList edgeList = new LinkedList<>(); LinkedList vertexList = new LinkedList<>(); ContractionVertex contractedSource = contractionMapping.get(source); ContractionVertex contractedTarget = contractionMapping.get(target); Pair, ContractionVertex> contractedVertices = Pair.of(contractedSource, contractedTarget); Map, Pair>> forwardTree = forwardSearchSpaces.get(contractedSource); Map, Pair>> backwardTree = backwardSearchSpaces.get(contractedTarget); Pair> distanceAndCommonVertex = distanceAndMiddleVertexMap.get(contractedVertices); if (distanceAndCommonVertex == null) { return null; } ContractionVertex commonVertex = distanceAndCommonVertex.getSecond(); // add common vertex vertexList.add(commonVertex.vertex); // traverse forward path ContractionVertex v = commonVertex; while (true) { ContractionEdge e = forwardTree.get(v).getSecond(); if (e == null) { break; } contractionHierarchy.unpackBackward(e, vertexList, edgeList); v = contractionGraph.getEdgeSource(e); } // traverse reverse path v = commonVertex; while (true) { ContractionEdge e = backwardTree.get(v).getSecond(); if (e == null) { break; } contractionHierarchy.unpackForward(e, vertexList, edgeList); v = contractionGraph.getEdgeTarget(e); } return new GraphWalk<>( graph, source, target, vertexList, edgeList, distanceAndCommonVertex.getFirst()); } /** * {@inheritDoc} */ @Override public double getWeight(V source, V target) { assertCorrectSourceAndTarget(source, target); Pair, ContractionVertex> contractedVertices = Pair.of(contractionMapping.get(source), contractionMapping.get(target)); if (distanceAndMiddleVertexMap.containsKey(contractedVertices)) { return distanceAndMiddleVertexMap.get(contractedVertices).getFirst(); } else { return Double.POSITIVE_INFINITY; } } } } ContractionHierarchyBidirectionalDijkstra.java000066400000000000000000000336001402514743400404550ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.util.ConcurrencyUtil; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; import java.util.concurrent.ThreadPoolExecutor; import java.util.function.*; import static org.jgrapht.alg.shortestpath.BidirectionalDijkstraShortestPath.DijkstraSearchFrontier; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.*; /** * Implementation of the hierarchical query algorithm based on the bidirectional Dijkstra search. * This algorithm is designed to contracted graphs. The best speedup is achieved on sparse graphs * with low average outdegree. * *

    * The query algorithm is originally described the article: Robert Geisberger, Peter Sanders, * Dominik Schultes, and Daniel Delling. 2008. Contraction hierarchies: faster and simpler * hierarchical routing in road networks. In Proceedings of the 7th international conference on * Experimental algorithms (WEA'08), Catherine C. McGeoch (Ed.). Springer-Verlag, Berlin, * Heidelberg, 319-333. * *

    * During contraction graph is divided into 2 parts which are called upward and downward graphs. * Both parts have all vertices of the original graph. The upward graph ($G_{\uparrow}$) * contains only those edges which source has lower level than the target and vice versa for the * downward graph ($G_{\downarrow}$). * *

    * For the shortest path query from $s$ to $t$, a modified bidirectional Dijkstra shortest path * search is performed. The forward search from $s$ operates on $G_{\uparrow}$ and the backward * search from $t$ - on the $G_{\downarrow}$. In each direction only the edges of the corresponding * part of the graph are considered. Both searches eventually meet at the vertex $v$, which has the * highest level in the shortest path from $s$ to $t$. Whenever a search in one direction reaches a * vertex that has already been processed in other direction, a new candidate for a shortest path is * found. Search is aborted in one direction if the smallest element in the corresponding priority * queue is at least as large as the best candidate path found so far. * *

    * After computing a contracted path, the algorithm unpacks it recursively into the actual shortest * path using the bypassed edges stored in the contraction hierarchy graph. * *

    * There is a possibility to provide an already computed contraction for the graph. For now there is * no means to ensure that the specified contraction is correct, nor to fail-fast. If algorithm uses * an incorrect contraction, the results of the search are unpredictable. * *

    * Comparing to usual shortest path algorithm, as {@link DijkstraShortestPath}, * {@link AStarShortestPath}, etc., this algorithm spends time for computing contraction hierarchy * but offers significant speedup in shortest path query performance. Therefore it is efficient to * use it in order to compute many shortest path on a single graph. Furthermore, on small graphs * (i.e with less than 1.000 vertices) the overhead of precomputation is higher than the speed at * the stage of computing shortest paths. Typically this algorithm is used to gain speedup for * shortest path queries on graphs of middle and large size (i.e. starting at 1.000 vertices). If a * further query performance improvement is needed take a look at * {@link TransitNodeRoutingShortestPath}. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @see ContractionHierarchyPrecomputation * @since July 2019 */ public class ContractionHierarchyBidirectionalDijkstra extends BaseShortestPathAlgorithm { /** * Contraction hierarchy which is used to compute shortest paths. */ private ContractionHierarchy contractionHierarchy; /** * Contracted graph, which is used during the queries. */ private Graph, ContractionEdge> contractionGraph; /** * Mapping from original to contracted vertices. */ private Map> contractionMapping; /** * Supplier for preferable heap implementation. */ private Supplier< AddressableHeap, ContractionEdge>>> heapSupplier; /** * Radius of the search. */ private double radius; /** * Constructs a new instance of the algorithm for a given {@code graph}. * * @param graph the graph * @deprecated replaced with * {@link #ContractionHierarchyBidirectionalDijkstra(Graph, ThreadPoolExecutor)} */ @Deprecated public ContractionHierarchyBidirectionalDijkstra(Graph graph) { this(new ContractionHierarchyPrecomputation<>(graph).computeContractionHierarchy()); } /** * Constructs a new instance of the algorithm for a given {@code graph} and {@code executor}. It * is up to a user of this algorithm to handle the creation and termination of the provided * {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see * {@link ConcurrencyUtil}. * * @param graph the graph * @param executor executor which is used for computing the {@link ContractionHierarchy} */ public ContractionHierarchyBidirectionalDijkstra(Graph graph, ThreadPoolExecutor executor) { this( new ContractionHierarchyPrecomputation<>(graph, executor) .computeContractionHierarchy()); } /** * Constructs a new instance of the algorithm for a given {@code hierarchy}. * * @param hierarchy contraction of the {@code graph} */ public ContractionHierarchyBidirectionalDijkstra(ContractionHierarchy hierarchy) { this(hierarchy, Double.POSITIVE_INFINITY, PairingHeap::new); } /** * Constructs a new instance of the algorithm for the given {@code hierarchy}, {@code radius} * and {@code heapSupplier}. * * @param hierarchy contraction of the {@code graph} * @param radius search radius * @param heapSupplier supplier of the preferable heap implementation */ public ContractionHierarchyBidirectionalDijkstra( ContractionHierarchy hierarchy, double radius, Supplier< AddressableHeap, ContractionEdge>>> heapSupplier) { super(hierarchy.getGraph()); this.contractionHierarchy = hierarchy; this.contractionGraph = hierarchy.getContractionGraph(); this.contractionMapping = hierarchy.getContractionMapping(); this.radius = radius; this.heapSupplier = heapSupplier; } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V sink) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } // handle special case if source equals target if (source.equals(sink)) { return createEmptyPath(source, sink); } ContractionVertex contractedSource = contractionMapping.get(source); ContractionVertex contractedSink = contractionMapping.get(sink); // create frontiers ContractionSearchFrontier, ContractionEdge> forwardFrontier = new ContractionSearchFrontier<>( new MaskSubgraph<>(contractionGraph, v -> false, e -> !e.isUpward), heapSupplier); ContractionSearchFrontier, ContractionEdge> backwardFrontier = new ContractionSearchFrontier<>( new MaskSubgraph<>( new EdgeReversedGraph<>(contractionGraph), v -> false, e -> e.isUpward), heapSupplier); // initialize both frontiers forwardFrontier.updateDistance(contractedSource, null, 0d); backwardFrontier.updateDistance(contractedSink, null, 0d); // initialize best path double bestPath = Double.POSITIVE_INFINITY; ContractionVertex bestPathCommonVertex = null; ContractionSearchFrontier, ContractionEdge> frontier = forwardFrontier; ContractionSearchFrontier, ContractionEdge> otherFrontier = backwardFrontier; while (true) { if (frontier.heap.isEmpty()) { frontier.isFinished = true; } if (otherFrontier.heap.isEmpty()) { otherFrontier.isFinished = true; } // stopping condition for search if (frontier.isFinished && otherFrontier.isFinished) { break; } // stopping condition for current frontier if (frontier.heap.findMin().getKey() >= bestPath) { frontier.isFinished = true; } else { // frontier scan AddressableHeap.Handle, ContractionEdge>> node = frontier.heap.deleteMin(); ContractionVertex v = node.getValue().getFirst(); double vDistance = node.getKey(); for (ContractionEdge e : frontier.graph.outgoingEdgesOf(v)) { ContractionVertex u = frontier.graph.getEdgeTarget(e); double eWeight = frontier.graph.getEdgeWeight(e); frontier.updateDistance(u, e, vDistance + eWeight); // check path with u's distance from the other frontier double pathDistance = vDistance + eWeight + otherFrontier.getDistance(u); if (pathDistance < bestPath) { bestPath = pathDistance; bestPathCommonVertex = u; } } } // swap frontiers only if the other frontier is not yet finished if (!otherFrontier.isFinished) { ContractionSearchFrontier, ContractionEdge> tmpFrontier = frontier; frontier = otherFrontier; otherFrontier = tmpFrontier; } } // create path if found if (Double.isFinite(bestPath) && bestPath <= radius) { return createPath( forwardFrontier, backwardFrontier, bestPath, contractedSource, bestPathCommonVertex, contractedSink); } else { return createEmptyPath(source, sink); } } /** * Builds shortest unpacked path between {@code source} and {@code sink} based on the * information provided by search frontiers and common vertex. * * @param forwardFrontier forward direction frontier * @param backwardFrontier backward direction frontier * @param weight weight of the shortest path * @param source path source * @param commonVertex path common vertex * @param sink path sink * @return unpacked shortest path between source and sink */ private GraphPath createPath( ContractionSearchFrontier, ContractionEdge> forwardFrontier, ContractionSearchFrontier, ContractionEdge> backwardFrontier, double weight, ContractionVertex source, ContractionVertex commonVertex, ContractionVertex sink) { LinkedList edgeList = new LinkedList<>(); LinkedList vertexList = new LinkedList<>(); // add common vertex vertexList.add(commonVertex.vertex); // traverse forward path ContractionVertex v = commonVertex; while (true) { ContractionEdge e = forwardFrontier.getTreeEdge(v); if (e == null) { break; } contractionHierarchy.unpackBackward(e, vertexList, edgeList); v = contractionGraph.getEdgeSource(e); } // traverse reverse path v = commonVertex; while (true) { ContractionEdge e = backwardFrontier.getTreeEdge(v); if (e == null) { break; } contractionHierarchy.unpackForward(e, vertexList, edgeList); v = contractionGraph.getEdgeTarget(e); } return new GraphWalk<>(graph, source.vertex, sink.vertex, vertexList, edgeList, weight); } /** * Maintains search frontier during shortest path computation. * * @param vertices type * @param edges type */ static class ContractionSearchFrontier extends DijkstraSearchFrontier { boolean isFinished; /** * Constructs an instance of a search frontier for the given graph, heap supplier and * {@code isDownwardEdge} function. * * @param graph the graph * @param heapSupplier supplier for the preferable heap implementation */ ContractionSearchFrontier( Graph graph, Supplier>> heapSupplier) { super(graph, heapSupplier); } } } ContractionHierarchyPrecomputation.java000066400000000000000000001501011402514743400372160ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.util.Pair; import org.jgrapht.graph.MaskSubgraph; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.ConcurrencyUtil; import org.jheaps.AddressableHeap; import org.jheaps.tree.PairingHeap; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Random; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; /** * Parallel implementation of the * contraction hierarchy route planning precomputation technique. * *

    * The original algorithm is described the article: Robert Geisberger, Peter Sanders, Dominik * Schultes, and Daniel Delling. 2008. Contraction hierarchies: faster and simpler hierarchical * routing in road networks. In Proceedings of the 7th international conference on Experimental * algorithms (WEA'08), Catherine C. McGeoch (Ed.). Springer-Verlag, Berlin, Heidelberg, 319-333. * *

    * Parallel version of the algorithm is described in the article: Vetter, Christian. "Parallel * Time-Dependent Contraction Hierarchies." (2009). * *

    * This algorithm speeds up shortest paths computation by contracting graph vertices. To contract a * vertex means to remove it from the graph in such a way that shortest paths in the remaining * overlay graph are preserved. This property is achieved by replacing paths of the form $\langle u, * v, w\rangle$ by a shortcut edge $(u, w)$. Note that the shortcut $(u, w)$ is only required if * $\langle u, v, w\rangle$ is the only shortest path from $u$ to $w$. * *

    * Contraction is performed as follows. First a priority is computed for each vertex in the graph. * This implementation uses edge quotient, complexity quotient and hierarchical depth metrics for * computing priority. A hierarchy is then generated by iteratively contracting independent sets of * vertices. A vertex is independent iff it`s priority is less than the priority of every vertex in * its 2-neighbourhood. A 2-neighbourhood of a vertex $v$ is defined as a set of vertices that are * reachable from $v$ using at most 2 hops. After contraction each vertex gets its unique * contraction level - its position in the computed hierarchy. Finally, after all vertices are * contracted each edge is set to be either upward if its source has lower level that its target, or * downward if vice versa. * *

    * Computing initial priorities, independent sets and shortcuts, updating neighbours priorities and * marking upward edges is performed in parallel what gives this implementation performance speedup * comparing to the sequential approach. * *

    * For parallelization, this implementation relies on the {@link ThreadPoolExecutor} which is * supplied to this algorithm from outside. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov */ public class ContractionHierarchyPrecomputation { /** * The underlying graph. */ private Graph graph; /** * Graph that stores the computed contraction hierarchy. */ private Graph, ContractionEdge> contractionGraph; /** * Mapping of the vertices in the original graph to the vertices in the contraction hierarchy * graph. */ private Map> contractionMapping; /** * The immutable view of the {@code contractionGraph} which masks already contracted vertices. * It is used to determine overlay graph during the computations. */ private Graph, ContractionEdge> maskedContractionGraph; /** * Vertices of the {@code contractionGraph}. */ private List> vertices; /** * Lists of shortcuts that correspond to vertices in the {@code contractionGraph}. The id of a * vertex is the position in this array, where corresponding shortcuts are stored. */ private List, ContractionEdge>>> shortcutEdges; /** * Data of each vertex int the {@code contractionGraph}. The id of a vertex is the position in * this list, where corresponding data is stored. */ private List verticesData; /** * Counter of contraction level that should be assigned to vertex that is being contracted. * Variable is made atomic due to the concurrent updates in the computations. */ private AtomicInteger contractionLevelCounter; /** * Supplier for the preferable heap implementation. */ private Supplier>> shortcutsSearchHeapSupplier; /** * Decorator for {@link ThreadPoolExecutor} supplied to this algorithm that enables to keep * track of when all submitted tasks are finished. */ private ExecutorCompletionService completionService; /** * Maximum number of threads used in the computations. */ private int parallelism; /** * Tasks that are submitted to the {@code executor}. */ private List tasks; /** * Consumers that perform computation of initial priorities for vertices in * {@code contractionGraph}. Each consumer holds an instance of the {@link Random} class to * avoid concurrent calls to single instance. */ private List>> computeInitialPrioritiesConsumers; /** * Computes independent set during contraction. */ private Consumer> computeIndependentSetConsumer; /** * Computes shortcuts for a vertex. */ private Consumer> computeShortcutsConsumer; /** * Updates neighbours priorities of a vertex. */ private Consumer> updateNeighboursConsumer; /** * Sets value of {@code isUpward} for the outgoing edges of a vertex. */ private Consumer> markUpwardEdgesConsumer; /** * Constructs a new instance of the algorithm for a given {@code graph}. * * @param graph graph * @deprecated replaced with * {@link #ContractionHierarchyPrecomputation(Graph, ThreadPoolExecutor)} */ @Deprecated public ContractionHierarchyPrecomputation(Graph graph) { this(graph, Runtime.getRuntime().availableProcessors()); } /** * Constructs a new instance of the algorithm for a given {@code graph} and {@code executor}. It * is up to a user of this algorithm to handle the creation and termination of the provided * {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see * {@link ConcurrencyUtil}. * * @param graph graph * @param executor executor which will be used for parallelization */ public ContractionHierarchyPrecomputation(Graph graph, ThreadPoolExecutor executor) { this(graph, Random::new, executor); } /** * Constructs a new instance of the algorithm for a given {@code graph} and {@code parallelism}. * * @param graph graph * @param parallelism maximum number of threads used in the computations * @deprecated replaced with * {@link #ContractionHierarchyPrecomputation(Graph, ThreadPoolExecutor)} */ @Deprecated public ContractionHierarchyPrecomputation(Graph graph, int parallelism) { this(graph, parallelism, Random::new, PairingHeap::new); } /** * Constructs a new instance of the algorithm for a given {@code graph} and * {@code randomSupplier}. Provided {@code randomSupplier} should return different random * generators instances, because they are used by different threads. * * @param graph graph * @param randomSupplier supplier for preferable instances of {@link Random} * @deprecated replaced with * {@link #ContractionHierarchyPrecomputation(Graph, Supplier, ThreadPoolExecutor)} */ @Deprecated public ContractionHierarchyPrecomputation(Graph graph, Supplier randomSupplier) { this(graph, Runtime.getRuntime().availableProcessors(), randomSupplier); } /** * Constructs a new instance of the algorithm for a given {@code graph}, {@code randomSupplier} * and {@code executor}. Provided {@code randomSupplier} should return different random * generators instances, because they are used by different threads. It is up to a user of this * algorithm to handle the creation and termination of the provided {@code executor}. Utility * methods to manage a {@code ThreadPoolExecutor} see {@link ConcurrencyUtil}. * * @param graph graph * @param randomSupplier supplier for preferable instances of {@link Random} * @param executor executor which will be used for parallelization */ public ContractionHierarchyPrecomputation( Graph graph, Supplier randomSupplier, ThreadPoolExecutor executor) { this(graph, randomSupplier, PairingHeap::new, executor); } /** * Constructs a new instance of the algorithm for a given {@code graph}, {@code parallelism} and * {@code randomSupplier}. * * @param graph graph * @param parallelism maximum number of threads used in the computations * @param randomSupplier supplier for preferable instances of {@link Random} * @deprecated replaced with * {@link #ContractionHierarchyPrecomputation(Graph, Supplier, ThreadPoolExecutor)} */ @Deprecated public ContractionHierarchyPrecomputation( Graph graph, int parallelism, Supplier randomSupplier) { this(graph, parallelism, randomSupplier, PairingHeap::new); } /** * Constructs a new instance of the algorithm for a given {@code graph}, {@code parallelism}, * {@code randomSupplier} and {@code shortcutsSearchHeapSupplier}. Provided * {@code randomSupplier} should return different random generators instances, because they are * used by different threads. * * @param graph graph * @param parallelism maximum number of threads used in the computations * @param randomSupplier supplier for preferable instances of {@link Random} * @param shortcutsSearchHeapSupplier supplier for the preferable heap implementation. * @deprecated replaced with * {@link #ContractionHierarchyPrecomputation(Graph, Supplier, Supplier, ThreadPoolExecutor)} */ @Deprecated public ContractionHierarchyPrecomputation( Graph graph, int parallelism, Supplier randomSupplier, Supplier>> shortcutsSearchHeapSupplier) { init( graph, randomSupplier, shortcutsSearchHeapSupplier, ConcurrencyUtil.createThreadPoolExecutor(parallelism)); } /** * Constructs a new instance of the algorithm for a given {@code graph}, {@code parallelism}, * {@code randomSupplier}, {@code shortcutsSearchHeapSupplier} and {@code executor}. Provided * {@code randomSupplier} should return different random generators instances, because they are * used by different threads. It is up to a user of this algorithm to handle the creation and * termination of the provided {@code executor}. For utility methods to manage a * {@code ThreadPoolExecutor} see {@link ConcurrencyUtil}. * * @param graph graph * @param randomSupplier supplier for preferable instances of {@link Random} * @param shortcutsSearchHeapSupplier supplier for the preferable heap implementation. * @param executor executor which will be used for parallelization */ public ContractionHierarchyPrecomputation( Graph graph, Supplier randomSupplier, Supplier>> shortcutsSearchHeapSupplier, ThreadPoolExecutor executor) { init(graph, randomSupplier, shortcutsSearchHeapSupplier, executor); } /** * Initialized field of this algorithm. * * @param graph a graph * @param randomSupplier supplier for preferable instances of {@link Random} * @param shortcutsSearchHeapSupplier supplier for the preferable heap implementation. * @param executor executor which will be used for parallelization */ private void init( Graph graph, Supplier randomSupplier, Supplier>> shortcutsSearchHeapSupplier, ThreadPoolExecutor executor) { this.graph = graph; this.contractionGraph = GraphTypeBuilder ., ContractionEdge> directed().weighted(true) .allowingMultipleEdges(false).allowingSelfLoops(false).buildGraph(); this.parallelism = executor.getMaximumPoolSize(); this.shortcutsSearchHeapSupplier = shortcutsSearchHeapSupplier; vertices = new ArrayList<>(graph.vertexSet().size()); shortcutEdges = new ArrayList<>(Collections.nCopies(graph.vertexSet().size(), null)); verticesData = new ArrayList<>(Collections.nCopies(graph.vertexSet().size(), null)); contractionLevelCounter = new AtomicInteger(); maskedContractionGraph = new MaskSubgraph<>( contractionGraph, v -> verticesData.get(v.vertexId) != null && verticesData.get(v.vertexId).isContracted, e -> false); contractionMapping = new HashMap<>(); completionService = new ExecutorCompletionService<>(executor); tasks = new ArrayList<>(parallelism); computeInitialPrioritiesConsumers = new ArrayList<>(parallelism); for (int i = 0; i < parallelism; ++i) { tasks.add(new ContractionTask(i)); computeInitialPrioritiesConsumers.add(new Consumer<>() { Random random = randomSupplier.get(); @Override public void accept(ContractionVertex vertex) { verticesData.set(vertex.vertexId, getVertexData(vertex, random.nextInt())); } }); } computeIndependentSetConsumer = vertex -> verticesData.get(vertex.vertexId).isIndependent = vertexIsIndependent(vertex); computeShortcutsConsumer = vertex -> shortcutEdges.set(vertex.vertexId, getShortcuts(vertex)); updateNeighboursConsumer = vertex -> updateNeighboursData(vertex); markUpwardEdgesConsumer = vertex -> contractionGraph .outgoingEdgesOf(vertex).forEach( e -> e.isUpward = contractionGraph.getEdgeSource(e).contractionLevel < contractionGraph .getEdgeTarget(e).contractionLevel); } /** * Computes contraction hierarchy for {@code graph}. * * @return contraction hierarchy and mapping of original to contracted vertices */ public ContractionHierarchy computeContractionHierarchy() { fillContractionGraphAndVerticesArray(); // compute initial priorities in parallel submitTasks(0, contractionGraph.vertexSet().size(), computeInitialPrioritiesConsumers); contractVertices(); // mark upward edges in parallel submitTasks(0, contractionGraph.vertexSet().size(), markUpwardEdgesConsumer); return new ContractionHierarchy<>(graph, contractionGraph, contractionMapping); } /** * Fills {@code contractionGraph} and {@code vertices}. If there exist multiple edges between * two vertices in the original graph, the shortest is added to the {@code contractionGraph}. * Self loops of the original graph are ignored. If original graph is undirected, each edge is * transformed into two directed edges in the contraction graph. */ private void fillContractionGraphAndVerticesArray() { int vertexId = 0; for (V vertex : graph.vertexSet()) { ContractionVertex contractionVertex = new ContractionVertex<>(vertex, vertexId); vertices.add(contractionVertex); ++vertexId; contractionGraph.addVertex(contractionVertex); contractionMapping.put(vertex, contractionVertex); } for (E e : graph.edgeSet()) { V source = graph.getEdgeSource(e); V target = graph.getEdgeTarget(e); if (!source.equals(target)) { ContractionVertex contractionSource = contractionMapping.get(source); ContractionVertex contractionTarget = contractionMapping.get(target); double eWeight = graph.getEdgeWeight(e); ContractionEdge oldEdge = contractionGraph.getEdge(contractionSource, contractionTarget); if (oldEdge == null) { ContractionEdge forward = new ContractionEdge<>(e); contractionGraph.addEdge(contractionSource, contractionTarget, forward); contractionGraph.setEdgeWeight(forward, eWeight); if (graph.getType().isUndirected()) { ContractionEdge backward = new ContractionEdge<>(e); contractionGraph.addEdge(contractionTarget, contractionSource, backward); contractionGraph.setEdgeWeight(backward, eWeight); } } else { double oldWeight = contractionGraph.getEdgeWeight(oldEdge); if (eWeight < oldWeight) { contractionGraph.setEdgeWeight(oldEdge, eWeight); oldEdge.edge = e; if (graph.getType().isUndirected()) { ContractionEdge oldBackwardEdge = contractionGraph.getEdge(contractionTarget, contractionSource); oldBackwardEdge.edge = e; contractionGraph.setEdgeWeight(oldBackwardEdge, eWeight); } } } } } } /** * Performs contraction of vertices in {@code contractionGraph}. */ private void contractVertices() { int independentSetStart; int independentSetEnd = graph.vertexSet().size(); while (independentSetEnd != 0) { // compute independent set in parallel submitTasks(0, independentSetEnd, computeIndependentSetConsumer); independentSetStart = partitionIndependentSet(independentSetEnd); // compute shortcuts for independent vertices in parallel submitTasks(independentSetStart, independentSetEnd, computeShortcutsConsumer); contractIndependentSet(independentSetStart, independentSetEnd); // update neighbours priorities in parallel submitTasks(independentSetStart, independentSetEnd, updateNeighboursConsumer); markContracted(independentSetStart, independentSetEnd); independentSetEnd = independentSetStart; } } /** * Determines if a {@code vertex} is independent wrt the overlay graph. * * @param vertex vertex * @return true iff vertex is independent */ private boolean vertexIsIndependent(ContractionVertex vertex) { for (ContractionVertex firstLevelNeighbour : Graphs .neighborSetOf(maskedContractionGraph, vertex)) { if (isGreater(vertex, firstLevelNeighbour)) { return false; } for (ContractionVertex secondLevelNeighbour : Graphs .neighborSetOf(maskedContractionGraph, firstLevelNeighbour)) { if (!secondLevelNeighbour.equals(vertex)) { if (isGreater(vertex, secondLevelNeighbour)) { return false; } } } } return true; } /** * Determines if priority of {@code vertex1} is greater than the priority of {@code vertex2}. If * priorities stored in {@code verticesData} are equal, the tie breaking rule is used. First * random values in {@code verticesData} are checked. If they are also equal, ids of vertices * are inspected. Each vertex has a unique id which guaranties that on each iteration there * exists at least one independent vertex. * * @return true iff priority of {@code vertex1} is greater than {@code vertex2} */ private boolean isGreater(ContractionVertex vertex1, ContractionVertex vertex2) { VertexData data1 = verticesData.get(vertex1.vertexId); VertexData data2 = verticesData.get(vertex2.vertexId); if (data1.priority != data2.priority) { return data1.priority > data2.priority; } // tie breaking if (data1.random != data2.random) { return data1.random > data2.random; } return vertex1.vertexId > vertex2.vertexId; } /** * Partitions vertices in {@code vertices} on the segment $[0,notContractedVerticesEnd)$ into * correspondingly not independent and independent. * * @param notContractedVerticesEnd position after the last not yet contracted vertex in * {@code vertices} * @return position of first independent vertex in created partition */ private int partitionIndependentSet(int notContractedVerticesEnd) { int left = 0; int right = notContractedVerticesEnd - 1; while (left <= right) { while (!verticesData.get(left).isIndependent) { ++left; } while (right >= 0 && verticesData.get(right).isIndependent) { --right; } if (left <= right) { ContractionVertex leftVertex = vertices.get(left); ContractionVertex rightVertex = vertices.get(right); swap(verticesData, left, right); swap(vertices, left, right); swap(shortcutEdges, left, right); int tmpId = leftVertex.vertexId; leftVertex.vertexId = rightVertex.vertexId; rightVertex.vertexId = tmpId; } } return left; } /** * Swaps elements in {@code list} on the positions {@code i} and {@code j}. * * @param list list * @param i position of first element * @param j position of second element */ private void swap(List list, int i, int j) { T tmp = list.get(i); list.set(i, list.get(j)); list.set(j, tmp); } /** * Contracts vertices in the current independent set. This step should be performed sequentially * because the {@code contractionGraph} is not thread-safe. * * @param independentSetStart first vertex in the independent set * @param independentSetEnd position after the last vertex in the independent set */ private void contractIndependentSet(int independentSetStart, int independentSetEnd) { vertices .subList(independentSetStart, independentSetEnd) .forEach(v -> contractVertex(v, contractionLevelCounter.getAndIncrement())); } /** * Contracts provided {@code vertex} and assigns the specified {@code contractionLevel} to it. * * @param vertex vertex to contract * @param contractionLevel vertex contraction level */ private void contractVertex(ContractionVertex vertex, int contractionLevel) { List, ContractionEdge>> shortcuts = this.shortcutEdges.get(vertex.vertexId); // add shortcuts for (Pair, ContractionEdge> shortcut : shortcuts) { ContractionVertex shortcutSource = maskedContractionGraph.getEdgeSource(shortcut.getFirst()); ContractionVertex shortcutTarget = maskedContractionGraph.getEdgeTarget(shortcut.getSecond()); ContractionEdge shortcutEdge = new ContractionEdge<>(shortcut); double shortcutWeight = maskedContractionGraph.getEdgeWeight(shortcut.getFirst()) + maskedContractionGraph.getEdgeWeight(shortcut.getSecond()); boolean added = contractionGraph.addEdge(shortcutSource, shortcutTarget, shortcutEdge); if (added) { contractionGraph.setEdgeWeight(shortcutEdge, shortcutWeight); } else { // update weight of already existing edge ContractionEdge originalEdge = contractionGraph.getEdge(shortcutSource, shortcutTarget); originalEdge.edge = null; originalEdge.bypassedEdges = shortcut; originalEdge.originalEdges = shortcut.getFirst().originalEdges + shortcut.getSecond().originalEdges; contractionGraph.setEdgeWeight(originalEdge, shortcutWeight); } } vertex.contractionLevel = contractionLevel; } /** * Updates neighbours priorities and theirs {@code depth} values for a given {@code vertex}. * Method {@link Graphs#neighborSetOf(Graph, Object)} is used to traverse neighbours. * * @param vertex a vertex in the {@code contractionGraph} */ private void updateNeighboursData(ContractionVertex vertex) { VertexData vertexData = verticesData.get(vertex.vertexId); for (ContractionVertex neighbour : Graphs .neighborSetOf(maskedContractionGraph, vertex)) { VertexData neighbourData = verticesData.get(neighbour.vertexId); neighbourData.depth = Math.max(neighbourData.depth, vertexData.depth + 1); updatePriority(neighbour, neighbourData); } } /** * Creates an instance of {@code VertexData} for {@code vertex} using specified random number * and sets its {@code priority} value. * * @param vertex a vertex in {@code contractionGraph} * @param random random number * @return created {@code VertexData} */ private VertexData getVertexData(ContractionVertex vertex, int random) { VertexData result = new VertexData(random); updatePriority(vertex, result); return result; } /** * Updates {@code priority} field value of {@code data}, which corresponds to the * {@code vertex}. * * @param vertex a vertex in the {@code contractionGraph} * @param data data of vertex */ private void updatePriority(ContractionVertex vertex, VertexData data) { VertexStatistics statistics = getStatistics(vertex); if (statistics.removedContractionEdges * statistics.removedOriginalEdges == 0) { data.priority = data.depth; } else { data.priority = 4.0 * statistics.addedContractionEdges / statistics.removedContractionEdges + 2.0 * statistics.addedOriginalEdges / statistics.removedOriginalEdges + 1.0 * data.depth; } } /** * Computes statistics for specified {@code vertex}. * * @param vertex a vertex in the {@code contractionGraph} * @return statistics of {@code vertex} */ private VertexStatistics getStatistics(ContractionVertex vertex) { ToStatisticsConsumer consumer = new ToStatisticsConsumer(); iterateShortcutEdges(vertex, consumer); maskedContractionGraph.edgesOf(vertex).forEach(edge -> { ++consumer.statistics.removedContractionEdges; consumer.statistics.removedOriginalEdges += edge.originalEdges; }); return consumer.statistics; } /** * Computes shortcuts for vertex {@code vertex} wrt the overlay graph. * * @param vertex a vertex in {@code contractionGraph} * @return list of shortcuts */ private List, ContractionEdge>> getShortcuts( ContractionVertex vertex) { ToListConsumer consumer = new ToListConsumer(); iterateShortcutEdges(vertex, consumer); return consumer.shortcuts; } /** * Runs forward shortest-path searches in current overlay graph to find shortcuts of * {@code vertex}. The {@code vertex} itself is ignored. Applies {@code shortcutConsumer} * whenever a new shortcut is found. To prune the search, keeps track of the value $d(u, v) + * max {c(v, w) : (v, w) \in E^{\prime}}$, where $d(u,v)$ denotes distance between vertex $u$ * and $v$, $c(v,w)$ is weight of the edge $(v,w)$, $E^{\prime}$ is the set of edges of the * overlay graph. If the original graph is undirected each predecessor of {@code vertex} is * considered only once and for each found shortcut in the forward direction another one in the * backward direction is generated. * * @param vertex a vertex in {@code contractionGraph} * @param shortcutConsumer consumer to supply shortcuts to */ private void iterateShortcutEdges( ContractionVertex vertex, BiConsumer, ContractionEdge> shortcutConsumer) { Set> successors = new HashSet<>(); double maxOutgoingEdgeWeight = Double.MIN_VALUE; for (ContractionEdge outEdge : maskedContractionGraph.outgoingEdgesOf(vertex)) { ContractionVertex successor = maskedContractionGraph.getEdgeTarget(outEdge); if (verticesData.get(successor.vertexId) != null && verticesData.get(successor.vertexId).isIndependent) { // does not belong to overlay graph continue; } successors.add(successor); maxOutgoingEdgeWeight = Math.max(maxOutgoingEdgeWeight, contractionGraph.getEdgeWeight(outEdge)); } for (ContractionEdge inEdge : maskedContractionGraph.incomingEdgesOf(vertex)) { ContractionVertex predecessor = contractionGraph.getEdgeSource(inEdge); if (verticesData.get(predecessor.vertexId) != null && verticesData.get(predecessor.vertexId).isIndependent) { // does not belong to overlay graph continue; } boolean containedPredecessor = successors.remove(predecessor); // might contain the // predecessor vertex Map, AddressableHeap.Handle>> distances = iterateToSuccessors( maskedContractionGraph, predecessor, successors, vertex, contractionGraph.getEdgeWeight(inEdge) + maxOutgoingEdgeWeight); for (ContractionVertex successor : successors) { ContractionEdge outEdge = contractionGraph.getEdge(vertex, successor); double pathWeight = contractionGraph.getEdgeWeight(inEdge) + contractionGraph.getEdgeWeight(outEdge); if (!distances.containsKey(successor) || distances.get(successor).getKey() > pathWeight) { shortcutConsumer.accept(inEdge, outEdge); if (graph.getType().isUndirected()) { shortcutConsumer .accept( contractionGraph.getEdge(successor, vertex), contractionGraph.getEdge(vertex, predecessor)); } } } if (containedPredecessor && graph.getType().isDirected()) { // restore predecessor if // needed successors.add(predecessor); } } } /** * Performs Dijkstra search in the {@code graph} starting at vertex {@code source} ignoring * vertex {@code vertexToIgnore}. The search is limited by {@code radius}. The search is * proceeded until all vertices in {@code successors} are reached or there is no vertex left to * traverse. * * @param graph graph to traverse * @param source search start vertex * @param successors vertices to reach * @param vertexToIgnore vertex to ignore * @param radius search distance limit * @return computed distances for reached vertices */ private Map, AddressableHeap.Handle>> iterateToSuccessors( Graph, ContractionEdge> graph, ContractionVertex source, Set> successors, ContractionVertex vertexToIgnore, double radius) { AddressableHeap> heap = shortcutsSearchHeapSupplier.get(); Map, AddressableHeap.Handle>> distanceMap = new HashMap<>(); updateDistance(source, 0.0, heap, distanceMap); int numOfSuccessors = successors.size(); int passedSuccessors = 0; while (!heap.isEmpty()) { AddressableHeap.Handle> min = heap.deleteMin(); ContractionVertex vertex = min.getValue(); double distance = min.getKey(); if (distance > radius) { break; } if (successors.contains(vertex)) { ++passedSuccessors; if (passedSuccessors == numOfSuccessors) { break; } } relaxNode(graph, heap, distanceMap, vertex, distance, vertexToIgnore); } return distanceMap; } /** * Relaxes outgoing edges of {@code vertex} in {@code graph} ignoring successors marked as * independent and {@code vertexToIgnore}. * * @param graph graph * @param heap search priority queue * @param distanceMap vertex distances * @param vertex vertex to relax * @param vertexDistance update distance for {@code vertex} * @param vertexToIgnore vertex to ignore */ private void relaxNode( Graph, ContractionEdge> graph, AddressableHeap> heap, Map, AddressableHeap.Handle>> distanceMap, ContractionVertex vertex, double vertexDistance, ContractionVertex vertexToIgnore) { for (ContractionEdge edge : graph.outgoingEdgesOf(vertex)) { ContractionVertex successor = graph.getEdgeTarget(edge); double edgeWeight = graph.getEdgeWeight(edge); if (edgeWeight < 0) { throw new IllegalArgumentException("Negative edge weight not allowed"); } if (successor.equals(vertexToIgnore) || (verticesData.get(successor.vertexId) != null && verticesData.get(successor.vertexId).isIndependent)) { // skip independent vertices because they do not belong to overlay graph continue; } double updatedDistance = vertexDistance + edgeWeight; updateDistance(successor, updatedDistance, heap, distanceMap); } } /** * Updates distance for {@code vertex} in the {@code heap} if needed. * * @param vertex vertex * @param distance updated distance * @param heap search priority queue * @param distanceMap vertex distances */ private void updateDistance( ContractionVertex vertex, double distance, AddressableHeap> heap, Map, AddressableHeap.Handle>> distanceMap) { AddressableHeap.Handle> node = distanceMap.get(vertex); if (node == null) { node = heap.insert(distance, vertex); distanceMap.put(vertex, node); } else if (distance < node.getKey()) { node.decreaseKey(distance); } } /** * Sets value of {@code isContracted} field of {@code VertexData} for each vertex in the segment * $[independentSetStart,independentSetEnd)$ to $true$. This step should not interfere with * other steps during the contraction because it alters the {@code maskedContractionGraph}. * * @param independentSetStart start of independent set * @param independentSetEnd end of independent set */ private void markContracted(int independentSetStart, int independentSetEnd) { for (int i = independentSetStart; i < independentSetEnd; ++i) { verticesData.get(vertices.get(i).vertexId).isContracted = true; } } /** * Submits {@code tasks} to the {@code completionService} setting start and end of the working * segment and consumer for them * * @param segmentStart start of working segment inclusively * @param segmentEnd start of working segment exclusively * @param consumer consumer */ private void submitTasks( int segmentStart, int segmentEnd, Consumer> consumer) { for (ContractionTask task : tasks) { task.consumer = consumer; task.segmentStart = segmentStart; task.segmentsEnd = segmentEnd; completionService.submit(task, null); } waitForTasksCompletion(tasks.size()); } /** * Submits {@code tasks} to the {@code completionService} setting start and end of the working * segment and an individual instance of consumer provided in {@code consumers}. * * @param segmentStart start of working segment inclusively * @param segmentEnd start of working segment exclusively * @param consumers consumers */ private void submitTasks( int segmentStart, int segmentEnd, List>> consumers) { for (int i = 0; i < tasks.size(); ++i) { ContractionTask task = tasks.get(i); task.consumer = consumers.get(i); task.segmentStart = segmentStart; task.segmentsEnd = segmentEnd; completionService.submit(task, null); } waitForTasksCompletion(tasks.size()); } /** * Takes {@code numOfTasks} tasks from the {@link #completionService}. * * @param numOfTasks number of tasks */ private void waitForTasksCompletion(int numOfTasks) { for (int i = 0; i < numOfTasks; ++i) { try { completionService.take().get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } } /** * Return type of this algorithm. Contains {@code contractionGraph} and * {@code contractionMapping}. * * @param the graph vertex type * @param the graph edge type */ public static class ContractionHierarchy { /** * The underlying graph. */ private Graph graph; /** * Graph that stores the computed contraction hierarchy. */ private Graph, ContractionEdge> contractionGraph; /** * Mapping of the vertices in the original graph to the vertices in the contraction * hierarchy graph. */ private Map> contractionMapping; /** * Returns the underlying graph of this contraction hierarchy. * * @return underlying graph of this contraction hierarchy */ public Graph getGraph() { return graph; } /** * Returns contracted graph. * * @return contracted graph */ public Graph, ContractionEdge> getContractionGraph() { return contractionGraph; } /** * Returns mapping of the vertices in the original graph to the vertices in the contracted * graph. * * @return vertices mapping */ public Map> getContractionMapping() { return contractionMapping; } /** * Constructs a new instance for the given {@code graph}, {@code contractionGraph} and * {@code contractionMapping}. * * @param graph graph * @param contractionGraph contracted graph * @param contractionMapping vertices mapping */ ContractionHierarchy( Graph graph, Graph, ContractionEdge> contractionGraph, Map> contractionMapping) { this.graph = graph; this.contractionGraph = contractionGraph; this.contractionMapping = contractionMapping; } /** * Unpacks {@code edge} by recursively going from target to source. * * @param edge edge to unpack * @param vertexList vertex list of the path * @param edgeList edge list of the path */ public void unpackBackward( ContractionEdge edge, LinkedList vertexList, LinkedList edgeList) { if (edge.bypassedEdges == null) { vertexList.addFirst(contractionGraph.getEdgeSource(edge).vertex); edgeList.addFirst(edge.edge); } else { unpackBackward(edge.bypassedEdges.getSecond(), vertexList, edgeList); unpackBackward(edge.bypassedEdges.getFirst(), vertexList, edgeList); } } /** * Unpacks {@code edge} by recursively going from source to target. * * @param edge edge to unpack * @param vertexList vertex list of the path * @param edgeList edge list of the path */ public void unpackForward( ContractionEdge edge, LinkedList vertexList, LinkedList edgeList) { if (edge.bypassedEdges == null) { vertexList.addLast(contractionGraph.getEdgeTarget(edge).vertex); edgeList.addLast(edge.edge); } else { unpackForward(edge.bypassedEdges.getFirst(), vertexList, edgeList); unpackForward(edge.bypassedEdges.getSecond(), vertexList, edgeList); } } } /** * Vertex for building the contraction hierarchy, which contains an original vertex from * {@code graph}. * * @param type of the original vertex. */ public static class ContractionVertex { /** * Identifies the position in {@code verticesData} and {@code shortcutEdges} lists, that * corresponds to this vertex. */ int vertexId; /** * Original vertex from {@code graph} this instance represents. */ V1 vertex; /** * Level that is assigned to this vertex during contraction which is used to determine * upward edges in the hierarchy. */ int contractionLevel; /** * Constructs a new vertex for given original vertex {@code vertex} and {@code vertexId}. * * @param vertex vertex in {@code graph} * @param vertexId id */ ContractionVertex(V1 vertex, int vertexId) { this.vertexId = vertexId; this.vertex = vertex; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ContractionVertex that = (ContractionVertex) o; return Objects.equals(vertex, that.vertex); } @Override public int hashCode() { return Objects.hash(vertex); } } /** * Edge for building the contraction hierarchy. Each instance of this class contains either an * original edge from {@code graph} or a pair of bypassed edges in case it represents a * shortcut. * * @param type of the original vertex. */ public static class ContractionEdge { /** * Original edge in {@code graph}. */ E1 edge; /** * Pair of edges this edge bypasses in case it is a shortcut. */ Pair, ContractionEdge> bypassedEdges; /** * Determines if this edge source has lower contraction level than its target. */ boolean isUpward; /** * Number of original edges in {@code graph} this edge represents in the contraction * hierarchy. */ int originalEdges; /** * Constructs a contraction edge for the given original {@code edge}. * * @param edge an edge in {@code graph} */ ContractionEdge(E1 edge) { this.edge = edge; this.originalEdges = 1; } /** * Constrcuts a contraction edge for the given pair of bypassed edges. * * @param bypassedEdges skipped edge */ ContractionEdge(Pair, ContractionEdge> bypassedEdges) { this.bypassedEdges = bypassedEdges; this.originalEdges = bypassedEdges.getFirst().originalEdges + bypassedEdges.getSecond().originalEdges; } } /** * Caches passed shortcuts into a list. */ private class ToListConsumer implements BiConsumer, ContractionEdge> { /** * Resulting list of shortcuts. */ List, ContractionEdge>> shortcuts; /** * Constructs an instance of the consumer. */ ToListConsumer() { shortcuts = new ArrayList<>(); } @Override public void accept(ContractionEdge e1, ContractionEdge e2) { shortcuts.add(Pair.of(e1, e2)); } } /** * Uses passed shortcuts to compute {@code addedContractionEdges} and {@code addedOriginalEdges} * statistics. This consumer is used to run $\textit{simulative}$ contraction - a type of * contraction used to compute only the vertex priority. */ private class ToStatisticsConsumer implements BiConsumer, ContractionEdge> { /** * Resulting statistics instance. */ VertexStatistics statistics; /** * Constructs an instance of the consumer. */ ToStatisticsConsumer() { this.statistics = new VertexStatistics(); } @Override public void accept(ContractionEdge e1, ContractionEdge e2) { ++statistics.addedContractionEdges; statistics.addedOriginalEdges += e1.originalEdges + e2.originalEdges; } } /** * Task that is used to perform computing of initial priorities, independent set and shortcuts, * updating neighbours priorities and marking upward edges. To achieve good load balancing * segment of vertices in {@code vertices} is divided into chunks using {@code taskId}. */ private class ContractionTask implements Runnable { /** * Id of this task. */ int taskId; /** * Start if the working segment in {@code vertices} inclusively. */ int segmentStart; /** * End if the working segment in {@code vertices} exclusively. */ int segmentsEnd; /** * Performs needed action with vertices. */ Consumer> consumer; /** * Constructs an instance of the task for the given {@code taskId}. * * @param taskId id of this task */ public ContractionTask(int taskId) { this.taskId = taskId; } @Override public void run() { int start = workerSegmentStart(segmentStart, segmentsEnd); int end = workerSegmentEnd(segmentStart, segmentsEnd); for (int i = start; i < end; ++i) { consumer.accept(vertices.get(i)); } } /** * Computes start of the working chunk for this task. * * @param segmentStart working segment start * @param segmentEnd working segment end * @return working chunk start */ private int workerSegmentStart(int segmentStart, int segmentEnd) { return segmentStart + ((segmentEnd - segmentStart) * taskId) / parallelism; } /** * Computes end of the working chunk for this task. * * @param segmentStart working segment start * @param segmentEnd working segment end * @return working chunk end */ private int workerSegmentEnd(int segmentStart, int segmentEnd) { return segmentStart + ((segmentEnd - segmentStart) * (taskId + 1)) / parallelism; } } /** * Contains information of a vertex needed during the contraction. */ private static class VertexData { /** * Hierarchical depth of a vertex measured in the number of hops that can be performed while * descending into the lower levels of the hierarchy. */ int depth; /** * Random number used for tie breaking during computing independent set. */ int random; /** * Priority of a vertex. */ double priority; /** * Determines if a vertex is already contracted or not. */ boolean isContracted; /** * Determines if a vertex is independent or not. */ boolean isIndependent; /** * Constructs an instance of data for given random value. * * @param random random number */ VertexData(int random) { this.random = random; } } /** * Contains statistics corresponding to a vertex in {@code contractionGraph} needed to compute * its priority. */ private static class VertexStatistics { /** * Number of edges added to the {@code contractionGraph} in case this vertex is contracted. */ int addedContractionEdges; /** * Number of edges removed to the {@code contractionGraph} in case this vertex is * contracted. */ int removedContractionEdges; /** * Sum of the complexities of edges added to the {@code contractionGraph} in case this * vertex is contracted. The complexity of an edge as the number of edges it represents in * the original {@code graph}. */ int addedOriginalEdges; /** * Sum of the complexities of edges removed from the {@code contractionGraph} in case this * vertex is contracted. The complexity of an edge as the number of edges it represents in * the original {@code graph}. */ int removedOriginalEdges; } } DefaultManyToManyShortestPaths.java000066400000000000000000000133221402514743400362420ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; import java.util.function.*; /** * Naive algorithm for many-to-many shortest paths problem using. * *

    * Time complexity of the algorithm is $O(|S||T|C)$, where $S$ is the set of source vertices, $T$ is * the set of target vertices and $C$ is the complexity of the * {@link ShortestPathAlgorithm#getPath(Object, Object)} method of the provided implementation. * *

    * For every pair of {@code source} and {@code target} vertices computes a shortest path between * them using provided implementation of {@link ShortestPathAlgorithm}. By default this * implementation uses {@link BidirectionalDijkstraShortestPath}. If desired, a different * implementation can be provided via the {@code function} constructor parameter. * *

    * The computation complexity of the algorithm consists of two main components - the $|S||T|$ * multiplier and the $C$ multiplier. This yields two bottlenecks for the algorithm. First of them * is the situation when the total number calls to * {@link ShortestPathAlgorithm#getPath(Object, Object)} is large. The second situation is when the * complexity of the individual call to {@link ShortestPathAlgorithm#getPath(Object, Object)} takes * a lot of time. Therefore the ideal use cases for this algorithm are small graphs or large graphs * with low total number of source and target vertices. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @see DijkstraManyToManyShortestPaths * @see CHManyToManyShortestPaths */ public class DefaultManyToManyShortestPaths extends BaseManyToManyShortestPaths { /** * Provides implementation of {@link ShortestPathAlgorithm} for a given graph. */ private final Function, ShortestPathAlgorithm> function; /** * Constructs a new instance of the algorithm for a given {@code graph}. The {@code function} is * defaulted to returning {@link BidirectionalDijkstraShortestPath}. * * @param graph a graph */ public DefaultManyToManyShortestPaths(Graph graph) { this(graph, g -> new BidirectionalDijkstraShortestPath<>(g)); } /** * Constructs a new instance of the algorithm for a given {@code graph} and {@code function}. * * @param graph a graph * @param function provides implementation of {@link ShortestPathAlgorithm} */ public DefaultManyToManyShortestPaths( Graph graph, Function, ShortestPathAlgorithm> function) { super(graph); this.function = function; } @Override public ManyToManyShortestPaths getManyToManyPaths(Set sources, Set targets) { Objects.requireNonNull(sources, "sources cannot be null!"); Objects.requireNonNull(targets, "targets cannot be null!"); ShortestPathAlgorithm algorithm = function.apply(graph); Map>> pathMap = new HashMap<>(); for (V source : sources) { pathMap.put(source, new HashMap<>()); } for (V source : sources) { for (V target : targets) { pathMap.get(source).put(target, algorithm.getPath(source, target)); } } return new DefaultManyToManyShortestPathsImpl<>(sources, targets, pathMap); } /** * Implementation of the * {@link org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths}. * For each pair of source and target vertices stores a corresponding path between them. */ static class DefaultManyToManyShortestPathsImpl extends BaseManyToManyShortestPathsImpl { /** * Map with paths between sources and targets. */ private final Map>> pathsMap; /** * Constructs an instance of the algorithm for the given {@code sources}, {@code targets} * and {@code pathsMap}. * * @param sources source vertices * @param targets target vertices * @param pathsMap map with paths between sources and targets */ DefaultManyToManyShortestPathsImpl( Set sources, Set targets, Map>> pathsMap) { super(sources, targets); this.pathsMap = pathsMap; } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V target) { assertCorrectSourceAndTarget(source, target); return pathsMap.get(source).get(target); } /** * {@inheritDoc} */ @Override public double getWeight(V source, V target) { assertCorrectSourceAndTarget(source, target); GraphPath path = pathsMap.get(source).get(target); if (path == null) { return Double.POSITIVE_INFINITY; } return path.getWeight(); } } } DeltaSteppingShortestPath.java000066400000000000000000000643431402514743400352720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2018-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.util.*; import org.jgrapht.alg.util.*; import java.util.*; import java.util.concurrent.*; /** * Parallel implementation of a single-source shortest path algorithm: the delta-stepping algorithm. * The algorithm computes single source shortest paths in a graphs with non-negative edge weights. * When using multiple threads, this implementation typically outperforms * {@link DijkstraShortestPath} and {@link BellmanFordShortestPath}. *

    * The delta-stepping algorithm is described in the paper: U. Meyer, P. Sanders, $\Delta$-stepping: * a parallelizable shortest path algorithm, Journal of Algorithms, Volume 49, Issue 1, 2003, Pages * 114-152, ISSN 0196-6774. *

    * The $\Delta$-stepping algorithm takes as input a weighted graph $G(V,E)$, a source node $s$ and a * parameter $\Delta > 0$. Let $tent[v]$ be the best known shortest distance from $s$ to vertex * $v\in V$. At the start of the algorithm, $tent[s]=0$, $tent[v]=\infty$ for $v\in V\setminus * \{s\}$. The algorithm partitions vertices in a series of buckets $B=(B_0, B_1, B_2, \dots)$, * where a vertex $v\in V$ is placed in bucket $B_{\lfloor\frac{tent[v]}{\Delta}\rfloor}$. During * the execution of the algorithm, vertices in bucket $B_i$, for $i=0,1,2,\dots$, are removed * one-by-one. For each removed vertex $v$, and for all its outgoing edges $(v,w)$, the algorithm * checks whether $tent[v]+c(v,w) < tent[w]$. If so, $w$ is removed from its current bucket, * $tent[w]$ is updated ($tent[w]=tent[v]+c(v,w)$), and $w$ is placed into bucket * $B_{\lfloor\frac{tent[w]}{\Delta}\rfloor}$. Parallelism is achieved by processing all vertices * belonging to the same bucket concurrently. The algorithm terminates when all buckets are empty. * At this stage the array $tent$ contains the minimal cost from $s$ to every vertex $v \in V$. For * a more detailed description of the algorithm, refer to the aforementioned paper. * *

    * For a given graph $G(V,E)$ and parameter $\Delta$, let a $\Delta$-path be a path of total weight * at most $\Delta$ with no repeated edges. The time complexity of the algorithm is $O(\frac{(|V| + * |E| + n_{\Delta} + m_{\Delta})}{p} + \frac{L}{\Delta}\cdot d\cdot l_{\Delta}\cdot \log n)$, where *

      *
    • $n_{\Delta}$ - number of vertex pairs $(u,v)$, where $u$ and $v$ are connected by some * $\Delta$-path.
    • *
    • $m_{\Delta}$ - number of vertex triples $(u,v^{\prime},v)$, where $u$ and $v^{\prime}$ are * connected by some $\Delta$-path and edge $(v^{\prime},v)$ has weight at most $\Delta$.
    • *
    • $L$ - maximum weight of a shortest path from selected source to any sink.
    • *
    • $d$ - maximum vertex degree.
    • *
    • $l_{\Delta}$ - maximum number of edges in a $\Delta$-path $+1$.
    • *
    * *

    * For parallelization, this implementation relies on the {@link ThreadPoolExecutor} which is * supplied to this algorithm from outside. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @since January 2018 */ public class DeltaSteppingShortestPath extends BaseShortestPathAlgorithm { /** * Error message for reporting the existence of an edge with negative weight. */ private static final String NEGATIVE_EDGE_WEIGHT_NOT_ALLOWED = "Negative edge weight not allowed"; /** * Error message for reporting that delta must be positive. */ private static final String DELTA_MUST_BE_NON_NEGATIVE = "Delta must be non-negative"; /** * Default value for {@link #parallelism}. */ private static final int DEFAULT_PARALLELISM = Runtime.getRuntime().availableProcessors(); /** * Empirically computed amount of tasks per worker thread in the {@link ForkJoinPool} that * yields good performance. */ private static final int TASKS_TO_THREADS_RATIO = 20; /** * The bucket width. A bucket with index $i$ therefore stores a vertex v if and only if v is * queued and tentative distance to v $\in[i\cdot\Delta,(i+1)\cdot\Delta]$ */ private double delta; /** * Maximum number of threads used in the computations. */ private int parallelism; /** * Number of buckets in the bucket structure. */ private int numOfBuckets; /** * Maximum edge weight in the {@link #graph}. */ private double maxEdgeWeight; /** * Map to store predecessor for each vertex in the shortest path tree. */ private Map> distanceAndPredecessorMap; /** * Buckets structure. */ private List> bucketStructure; /** * Decorator for {@link ThreadPoolExecutor} supplied to this algorithm that enables to keep * track of when all submitted tasks are finished. */ private ExecutorCompletionService completionService; /** * Queue of vertices which edges should be relaxed on current iteration. */ private Queue verticesQueue; /** * Task for light edges relaxation. */ private Runnable lightRelaxTask; /** * Task for light edges relaxation. */ private Runnable heavyRelaxTask; /** * Indicates when all the vertices are been added to the {@link #verticesQueue} on each * iteration. */ private volatile boolean allVerticesAdded; /** * Constructs a new instance of the algorithm for a given graph. * * @param graph graph * @deprecated replaced with {@link #DeltaSteppingShortestPath(Graph, ThreadPoolExecutor)} */ @Deprecated public DeltaSteppingShortestPath(Graph graph) { this(graph, DEFAULT_PARALLELISM); } /** * Constructs a new instance of the algorithm for a given graph and {@code executor}. It is up * to a user of this algorithm to handle the creation and termination of the provided * {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see * {@link ConcurrencyUtil}. * * @param graph graph * @param executor executor which will be used for parallelization */ public DeltaSteppingShortestPath(Graph graph, ThreadPoolExecutor executor) { this(graph, 0.0, executor); } /** * Constructs a new instance of the algorithm for a given graph, delta. * * @param graph the graph * @param delta bucket width * @deprecated replaced with * {@link #DeltaSteppingShortestPath(Graph, double, ThreadPoolExecutor)} */ @Deprecated public DeltaSteppingShortestPath(Graph graph, double delta) { this(graph, delta, DEFAULT_PARALLELISM); } /** * Constructs a new instance of the algorithm for a given graph, delta and {@code executor}. It * is up to a user of this algorithm to handle the creation and termination of the provided * {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see * {@link ConcurrencyUtil}. * * @param graph the graph * @param delta bucket width * @param executor executor which will be used for parallelization */ public DeltaSteppingShortestPath(Graph graph, double delta, ThreadPoolExecutor executor) { super(graph); init(graph, delta, executor); } /** * Constructs a new instance of the algorithm for a given graph, parallelism. * * @param graph the graph * @param parallelism maximum number of threads used in the computations * @deprecated replaced with {@link #DeltaSteppingShortestPath(Graph, ThreadPoolExecutor)} */ @Deprecated public DeltaSteppingShortestPath(Graph graph, int parallelism) { this(graph, 0.0, parallelism); } /** * Constructs a new instance of the algorithm for a given graph, delta, parallelism. If delta is * $0.0$ it will be computed during the algorithm execution. In general if the value of * $\frac{maximum edge weight}{maximum outdegree}$ is known beforehand, it is preferable to * specify it via this constructor, because processing the whole graph to compute this value may * significantly slow down the algorithm. * * @param graph the graph * @param delta bucket width * @param parallelism maximum number of threads used in the computations * @deprecated replaced with * {@link #DeltaSteppingShortestPath(Graph, double, ThreadPoolExecutor)} */ @Deprecated public DeltaSteppingShortestPath(Graph graph, double delta, int parallelism) { super(graph); init(graph, delta, ConcurrencyUtil.createThreadPoolExecutor(parallelism)); } /** * Initializes {@code delta}, {@code parallelism}, {@code distanceAndPredecessorMap}, * {@code completionService}, {@code verticesQueue}, {@code lightRelaxTask} and * {@code heavyRelaxTask} fields. * * @param graph a graph * @param delta bucket width * @param executor executor which will be used for parallelization */ private void init(Graph graph, double delta, ThreadPoolExecutor executor) { if (delta < 0) { throw new IllegalArgumentException(DELTA_MUST_BE_NON_NEGATIVE); } this.delta = delta; this.parallelism = executor.getMaximumPoolSize(); distanceAndPredecessorMap = new ConcurrentHashMap<>(graph.vertexSet().size()); completionService = new ExecutorCompletionService<>(executor); verticesQueue = new ConcurrentLinkedQueue<>(); lightRelaxTask = new LightRelaxTask(verticesQueue); heavyRelaxTask = new HeavyRelaxTask(verticesQueue); } /** * Calculates max edge weight in the {@link #graph}. * * @return max edge weight */ private double getMaxEdgeWeight() { ForkJoinTask task = ForkJoinPool .commonPool().submit( new MaxEdgeWeightTask( graph.edgeSet().spliterator(), graph.edgeSet().size() / (TASKS_TO_THREADS_RATIO * parallelism) + 1)); return task.join(); } /** * Is used during the algorithm to compute maximum edge weight of the {@link #graph}. Apart from * computing the maximal edge weight in the graph the task also checks if there exist edges with * negative weights. */ class MaxEdgeWeightTask extends RecursiveTask { /** * Is used to split a collection and create new recursive tasks during the computation. */ Spliterator spliterator; /** * Amount of edges which are processed in parallel. */ long loadBalancing; /** * Constructs a new instance for the given spliterator and loadBalancing * * @param spliterator spliterator * @param loadBalancing loadBalancing */ MaxEdgeWeightTask(Spliterator spliterator, long loadBalancing) { this.spliterator = spliterator; this.loadBalancing = loadBalancing; } /** * Computes maximum edge weight. If amount of edges in {@link #spliterator} is less than * {@link #loadBalancing}, then computation is performed sequentially. If not, the * {@link #spliterator} is used to split the collection and then two new child tasks are * created. * * @return max edge weight */ @Override protected Double compute() { if (spliterator.estimateSize() <= loadBalancing) { double[] max = { 0 }; spliterator.forEachRemaining(e -> { double weight = graph.getEdgeWeight(e); if (weight < 0) { throw new IllegalArgumentException(NEGATIVE_EDGE_WEIGHT_NOT_ALLOWED); } max[0] = Math.max(weight, max[0]); }); return max[0]; } else { MaxEdgeWeightTask t1 = new MaxEdgeWeightTask(spliterator.trySplit(), loadBalancing); t1.fork(); MaxEdgeWeightTask t2 = new MaxEdgeWeightTask(spliterator, loadBalancing); return Math.max(t2.compute(), t1.join()); } } } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V sink) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } return getPaths(source).getPath(sink); } /** * {@inheritDoc} */ @Override public SingleSourcePaths getPaths(V source) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } maxEdgeWeight = getMaxEdgeWeight(); if (delta == 0.0) { // the value should be computed delta = findDelta(); } numOfBuckets = (int) (Math.ceil(maxEdgeWeight / delta) + 1); bucketStructure = new ArrayList<>(numOfBuckets); for (int i = 0; i < numOfBuckets; i++) { bucketStructure.add(new ConcurrentSkipListSet<>()); } fillDistanceAndPredecessorMap(); computeShortestPaths(source); return new TreeSingleSourcePathsImpl<>(graph, source, distanceAndPredecessorMap); } /** * Calculates value of {@link #delta}. The value is calculated as the maximal edge weight * divided by maximal out-degree in the {@link #graph} or $1.0$ if edge set of the * {@link #graph} is empty. * * @return bucket width */ private double findDelta() { if (maxEdgeWeight == 0) { return 1.0; } else { int maxOutDegree = graph.vertexSet().parallelStream().mapToInt(graph::outDegreeOf).max().orElse(0); return maxEdgeWeight / maxOutDegree; } } /** * Fills {@link #distanceAndPredecessorMap} concurrently. */ private void fillDistanceAndPredecessorMap() { graph .vertexSet().parallelStream().forEach( v -> distanceAndPredecessorMap.put(v, Pair.of(Double.POSITIVE_INFINITY, null))); } /** * Performs shortest path computations. * * @param source the source vertex */ private void computeShortestPaths(V source) { relax(source, null, 0.0); List> removed = new ArrayList<>(); while (true) { int firstNonEmptyBucket = 0; while (firstNonEmptyBucket < numOfBuckets && bucketStructure.get(firstNonEmptyBucket).isEmpty()) { // skip empty buckets ++firstNonEmptyBucket; } if (firstNonEmptyBucket == numOfBuckets) { // terminate if all buckets are empty break; } // the content of a bucket is replaced // in order not to handle the same vertices // multiple times Set bucketElements = getContentAndReplace(firstNonEmptyBucket); while (!bucketElements.isEmpty()) { // reinsertions may occur removed.add(bucketElements); findAndRelaxLightRequests(bucketElements); bucketElements = getContentAndReplace(firstNonEmptyBucket); } findAndRelaxHeavyRequests(removed); removed.clear(); } } /** * Manages edge relaxations. Adds all elements from {@code vertices} to the * {@link #verticesQueue} and submits as many {@link #lightRelaxTask} to the * {@link #completionService} as needed. * * @param vertices vertices */ private void findAndRelaxLightRequests(Set vertices) { allVerticesAdded = false; int numOfVertices = vertices.size(); int numOfTasks; if (numOfVertices >= parallelism) { // use as available tasks numOfTasks = parallelism; Iterator iterator = vertices.iterator(); // provide some work to the workers addSetVertices(iterator, parallelism); submitTasks(lightRelaxTask, parallelism - 1); // one thread should // submit rest of vertices addSetRemaining(iterator); submitTasks(lightRelaxTask, 1); // use remaining thread for relaxation } else { // only several relaxation tasks are needed numOfTasks = numOfVertices; addSetRemaining(vertices.iterator()); submitTasks(lightRelaxTask, numOfVertices); } allVerticesAdded = true; waitForTasksCompletion(numOfTasks); } /** * Manages execution of edges relaxation. Adds all elements from {@code vertices} to the * {@link #verticesQueue} and submits as many {@link #heavyRelaxTask} to the * {@link #completionService} as needed. * * @param verticesSets set of sets of vertices */ private void findAndRelaxHeavyRequests(List> verticesSets) { allVerticesAdded = false; int numOfVertices = verticesSets.stream().mapToInt(Set::size).sum(); int numOfTasks; if (numOfVertices >= parallelism) { // use as available tasks numOfTasks = parallelism; Iterator> setIterator = verticesSets.iterator(); // provide some work to the workers Iterator iterator = addSetsVertices(setIterator, parallelism); submitTasks(heavyRelaxTask, parallelism - 1);// one thread should // submit rest of vertices addSetRemaining(iterator); addSetsRemaining(setIterator); submitTasks(heavyRelaxTask, 1); // use remaining thread for relaxation } else { // only several relaxation tasks are needed numOfTasks = numOfVertices; addSetsRemaining(verticesSets.iterator()); submitTasks(heavyRelaxTask, numOfVertices); } allVerticesAdded = true; waitForTasksCompletion(numOfTasks); } /** * Adds {@code numOfVertices} vertices to the {@link #verticesQueue} provided by the * {@code iterator}. * * @param iterator vertices iterator * @param numOfVertices vertices amount */ private void addSetVertices(Iterator iterator, int numOfVertices) { for (int i = 0; i < numOfVertices && iterator.hasNext(); i++) { verticesQueue.add(iterator.next()); } } /** * Adds all remaining vertices to the {@link #verticesQueue} provided by the {@code iterator}. * * @param iterator vertices iterator */ private void addSetRemaining(Iterator iterator) { while (iterator.hasNext()) { verticesQueue.add(iterator.next()); } } /** * Adds {@code numOfVertices} vertices to the {@link #verticesQueue} that are contained in the * sets provided by the {@code setIterator}. Returns iterator of the set which vertex was added * last. * * @param setIterator sets of vertices iterator * @param numOfVertices vertices amount * @return iterator of the last set */ private Iterator addSetsVertices(Iterator> setIterator, int numOfVertices) { int i = 0; Iterator iterator = null; while (setIterator.hasNext() && i < numOfVertices) { iterator = setIterator.next().iterator(); while (iterator.hasNext() && i < numOfVertices) { verticesQueue.add(iterator.next()); i++; } } return iterator; } /** * Adds all remaining vertices to the {@link #verticesQueue} that are contained in the sets * provided by the {@code setIterator}. * * @param setIterator sets of vertices iterator */ private void addSetsRemaining(Iterator> setIterator) { while (setIterator.hasNext()) { verticesQueue.addAll(setIterator.next()); } } /** * Submits the {@code task} {@code numOfTasks} times to the {@link #completionService}. * * @param task task to be submitted * @param numOfTasks amount of times task should be submitted */ private void submitTasks(Runnable task, int numOfTasks) { for (int i = 0; i < numOfTasks; i++) { completionService.submit(task, null); } } /** * Takes {@code numOfTasks} tasks from the {@link #completionService}. * * @param numOfTasks amount of tasks */ private void waitForTasksCompletion(int numOfTasks) { for (int i = 0; i < numOfTasks; i++) { try { completionService.take(); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * Performs relaxation in parallel-safe fashion. Synchronises by {@code vertex}, then if new * tentative distance is less then removes {@code v} from the old bucket, adds is to the new * bucket and updates {@link #distanceAndPredecessorMap} value for {@code v}. * * @param v vertex * @param e edge to predecessor * @param distance distance */ private void relax(V v, E e, double distance) { int updatedBucket = bucketIndex(distance); synchronized (v) { // to make relaxation updates thread-safe Pair oldData = distanceAndPredecessorMap.get(v); if (distance < oldData.getFirst()) { if (!oldData.getFirst().equals(Double.POSITIVE_INFINITY)) { bucketStructure.get(bucketIndex(oldData.getFirst())).remove(v); } bucketStructure.get(updatedBucket).add(v); distanceAndPredecessorMap.put(v, Pair.of(distance, e)); } } } /** * Calculates bucket index for a given {@code distance}. * * @param distance distance * @return bucket index */ private int bucketIndex(double distance) { return (int) Math.round(distance / delta) % numOfBuckets; } /** * Replaces the bucket at the {@code bucketIndex} index with a new instance of the * {@link ConcurrentSkipListSet}. Return the reference to the set that was previously in the * bucket. * * @param bucketIndex bucket index * @return content of the bucket */ private Set getContentAndReplace(int bucketIndex) { Set result = bucketStructure.get(bucketIndex); bucketStructure.set(bucketIndex, new ConcurrentSkipListSet()); return result; } /** * Task that is submitted to the {@link #completionService} during shortest path computation for * light relax requests relaxation. */ class LightRelaxTask implements Runnable { /** * Vertices which edges will be relaxed. */ private Queue vertices; /** * Constructs instance of a new task. * * @param vertices vertices */ LightRelaxTask(Queue vertices) { this.vertices = vertices; } /** * Performs relaxation of edges emanating from {@link #vertices}. */ @Override public void run() { while (true) { V v = vertices.poll(); if (v == null) { // we might have a termination situation if (allVerticesAdded && vertices.isEmpty()) { // need to check // is the queue is empty, because some vertices might have been added // while passing from first if condition to the second break; } } else { for (E e : graph.outgoingEdgesOf(v)) { if (graph.getEdgeWeight(e) <= delta) { relax( Graphs.getOppositeVertex(graph, e, v), e, distanceAndPredecessorMap.get(v).getFirst() + graph.getEdgeWeight(e)); } } } } } } /** * Task that is submitted to the {@link #completionService} during shortest path computation for * heavy relax requests relaxation. */ class HeavyRelaxTask implements Runnable { /** * Vertices which edges will be relaxed. */ private Queue vertices; /** * Constructs instance of a new task. * * @param vertices vertices */ HeavyRelaxTask(Queue vertices) { this.vertices = vertices; } /** * Performs relaxation of edges emanating from {@link #vertices}. */ @Override public void run() { while (true) { V v = vertices.poll(); if (v == null) { if (allVerticesAdded && vertices.isEmpty()) { break; } } else { for (E e : graph.outgoingEdgesOf(v)) { if (graph.getEdgeWeight(e) > delta) { relax( Graphs.getOppositeVertex(graph, e, v), e, distanceAndPredecessorMap.get(v).getFirst() + graph.getEdgeWeight(e)); } } } } } } } DijkstraClosestFirstIterator.java000066400000000000000000000216031402514743400360000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2003-2021, by John V Sichi, Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.alg.util.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; import java.util.function.*; /** * A light-weight version of the closest-first iterator for a directed or undirected graphs. For * this iterator to work correctly the graph must not be modified during iteration. Currently there * are no means to ensure that, nor to fail-fast. The results of such modifications are undefined. * *

    * The metric for closest here is the weighted path length from a start vertex, i.e. * Graph.getEdgeWeight(Edge) is summed to calculate path length. Negative edge weights will result * in an IllegalArgumentException. Optionally, path length may be bounded by a finite radius. This * iterator can use a custom heap implementation. * *

    * NOTE: This is an internal iterator for use in shortest paths algorithms. For an iterator that is * suitable to return to the users see {@link org.jgrapht.traverse.ClosestFirstIterator}. This * implementation is faster since it does not support graph traversal listeners nor disconnected * components. * * @param the graph vertex type * @param the graph edge type * @author John V. Sichi * @author Dimitrios Michail */ class DijkstraClosestFirstIterator implements Iterator { private final Graph graph; private final V source; private final double radius; private final Map>> seen; private AddressableHeap> heap; /** * Creates a new iterator for the specified graph. Iteration will start at the specified start * vertex and will be limited to the connected component that includes that vertex. This * iterator will use pairing heap as a default heap implementation. * * @param graph the graph to be iterated. * @param source the source vertex */ public DijkstraClosestFirstIterator(Graph graph, V source) { this(graph, source, Double.POSITIVE_INFINITY, PairingHeap::new); } /** * Creates a new radius-bounded iterator for the specified graph. Iteration will start at the * specified start vertex and will be limited to the subset of the connected component which * includes that vertex and is reachable via paths of weighted length less than or equal to the * specified radius. This iterator will use pairing heap as a default heap implementation. * * @param graph the graph * @param source the source vertex * @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded search */ public DijkstraClosestFirstIterator(Graph graph, V source, double radius) { this(graph, source, radius, PairingHeap::new); } /** * Creates a new iterator for the specified graph. Iteration will start at the specified start * vertex and will be limited to the connected component that includes that vertex. This * iterator will use heap supplied by the {@code heapSupplier} * * @param graph the graph to be iterated. * @param source the source vertex * @param heapSupplier supplier of the preferable heap implementation */ public DijkstraClosestFirstIterator( Graph graph, V source, Supplier>> heapSupplier) { this(graph, source, Double.POSITIVE_INFINITY, heapSupplier); } /** * Creates a new radius-bounded iterator for the specified graph. Iteration will start at the * specified start vertex and will be limited to the subset of the connected component which * includes that vertex and is reachable via paths of weighted length less than or equal to the * specified radius. This iterator will use the heap supplied by {@code heapSupplier} * * @param graph the graph * @param source the source vertex * @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded search * @param heapSupplier supplier of the preferable heap implementation */ public DijkstraClosestFirstIterator( Graph graph, V source, double radius, Supplier>> heapSupplier) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); this.source = Objects.requireNonNull(source, "Source vertex cannot be null"); Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null"); if (radius < 0.0) { throw new IllegalArgumentException("Radius must be non-negative"); } this.radius = radius; this.seen = new HashMap<>(); this.heap = heapSupplier.get(); // initialize with source vertex updateDistance(source, null, 0d); } /** * {@inheritDoc} */ @Override public boolean hasNext() { if (heap.isEmpty()) { return false; } AddressableHeap.Handle> vNode = heap.findMin(); double vDistance = vNode.getKey(); if (radius < vDistance) { heap.clear(); return false; } return true; } /** * {@inheritDoc} */ @Override public V next() { if (!hasNext()) { throw new NoSuchElementException(); } // settle next node AddressableHeap.Handle> vNode = heap.deleteMin(); V v = vNode.getValue().getFirst(); double vDistance = vNode.getKey(); // relax edges for (E e : graph.outgoingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); double eWeight = graph.getEdgeWeight(e); if (eWeight < 0.0) { throw new IllegalArgumentException("Negative edge weight not allowed"); } updateDistance(u, e, vDistance + eWeight); } return v; } /** * Return the paths computed by this iterator. Only the paths to vertices which are already * returned by the iterator will be shortest paths. Additional paths to vertices which are not * yet returned (settled) by the iterator might be included with the following properties: the * distance will be an upper bound on the actual shortest path and the distance will be inside * the radius of the search. * * @return the single source paths */ public SingleSourcePaths getPaths() { return new TreeSingleSourcePathsImpl<>(graph, source, getDistanceAndPredecessorMap()); } /** * Return all paths using the traditional representation of the shortest path tree, which stores * for each vertex (a) the distance of the path from the source vertex and (b) the last edge * used to reach the vertex from the source vertex. *

    * Only the paths to vertices which are already returned by the iterator will be shortest paths. * Additional paths to vertices which are not yet returned (settled) by the iterator might be * included with the following properties: the distance will be an upper bound on the actual * shortest path and the distance will be inside the radius of the search. * * @return a distance and predecessor map */ public Map> getDistanceAndPredecessorMap() { Map> distanceAndPredecessorMap = new HashMap<>(); for (AddressableHeap.Handle> vNode : seen.values()) { double vDistance = vNode.getKey(); if (radius < vDistance) { continue; } V v = vNode.getValue().getFirst(); distanceAndPredecessorMap.put(v, Pair.of(vDistance, vNode.getValue().getSecond())); } return distanceAndPredecessorMap; } private void updateDistance(V v, E e, double distance) { AddressableHeap.Handle> node = seen.get(v); if (node == null) { node = heap.insert(distance, Pair.of(v, e)); seen.put(v, node); } else if (distance < node.getKey()) { node.decreaseKey(distance); node.setValue(Pair.of(node.getValue().getFirst(), e)); } } } DijkstraManyToManyShortestPaths.java000066400000000000000000000137061402514743400364370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * Naive algorithm for many-to-many shortest paths problem using * {@link DijkstraClosestFirstIterator}. * *

    * Complexity of the algorithm is $O(min(|S|,|T|)*(V\log V + E))$, where $S$ is the set of source * vertices, $T$ is the set of target vertices, $V$ is the set of graph vertices and $E$ is the set * of graph edges of the graph. * *

    * For each source vertex a single source shortest paths search is performed, which is stopped as * soon as all target vertices are reached. Shortest paths trees are constructed using * {@link DijkstraClosestFirstIterator}. In case $|T| > |S|$ the searches are performed on the * reversed graph using $|T|$ as source vertices and $|S|$ as target vertices. This allows to reduce * the total number of searches from $|S|$ to $min(|S|,|T|)$. * *

    * The main bottleneck of this algorithm is the memory usage to store individual shortest paths * trees for every source vertex, as they may take a lot of space. Considering this, the typical use * case of this algorithm are small graphs or large graphs with small total number of source and * target vertices. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @see DefaultManyToManyShortestPaths * @see CHManyToManyShortestPaths */ public class DijkstraManyToManyShortestPaths extends BaseManyToManyShortestPaths { /** * Constructs an instance of the algorithm for a given {@code graph}. * * @param graph underlying graph */ public DijkstraManyToManyShortestPaths(Graph graph) { super(graph); } /** * {@inheritDoc} */ @Override public ManyToManyShortestPaths getManyToManyPaths(Set sources, Set targets) { Objects.requireNonNull(sources, "sources cannot be null!"); Objects.requireNonNull(targets, "targets cannot be null!"); Map> searchSpaces = new HashMap<>(); if (sources.size() >= targets.size()) { for (V source : sources) { searchSpaces.put(source, getShortestPathsTree(graph, source, targets)); } return new DijkstraManyToManyShortestPathsImpl(sources, targets, false, searchSpaces); } else { Graph edgeReversedGraph = new EdgeReversedGraph<>(graph); for (V target : targets) { searchSpaces.put(target, getShortestPathsTree(edgeReversedGraph, target, sources)); } return new DijkstraManyToManyShortestPathsImpl(sources, targets, true, searchSpaces); } } /** * Implementation of the * {@link org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths}. * For each source vertex a single source shortest paths tree is stored. It is used to retrieve * both actual paths and theirs weights. */ private class DijkstraManyToManyShortestPathsImpl extends BaseManyToManyShortestPathsImpl { /** * Indicates is the search spaces were computed on the edge reversed graph. */ private boolean reversed; /** * Map from source vertices to corresponding single source shortest path trees. */ private final Map> searchSpaces; /** * Constructs an instance of the algorithm for the given {@code sources}, {@code targets}, * {@code reversed} and {@code searchSpaces}. * * @param sources source vertices * @param targets target vertices * @param reversed if search spaces are reversed * @param searchSpaces single source shortest paths trees map */ DijkstraManyToManyShortestPathsImpl( Set sources, Set targets, boolean reversed, Map> searchSpaces) { super(sources, targets); this.reversed = reversed; this.searchSpaces = searchSpaces; } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V target) { assertCorrectSourceAndTarget(source, target); if (reversed) { GraphPath reversedPath = searchSpaces.get(target).getPath(source); List vertices = reversedPath.getVertexList(); List edges = reversedPath.getEdgeList(); Collections.reverse(vertices); Collections.reverse(edges); return new GraphWalk<>( graph, source, target, vertices, edges, reversedPath.getWeight()); } else { return searchSpaces.get(source).getPath(target); } } /** * {@inheritDoc} */ @Override public double getWeight(V source, V target) { assertCorrectSourceAndTarget(source, target); if (reversed) { return searchSpaces.get(target).getWeight(source); } return searchSpaces.get(source).getWeight(target); } } } DijkstraShortestPath.java000066400000000000000000000125751402514743400343020ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.function.*; /** * An implementation of Dijkstra's * shortest path algorithm using a pairing heap by default. A custom heap implementation can by * specified during the construction time. * * @param the graph vertex type * @param the graph edge type * @author John V. Sichi */ public final class DijkstraShortestPath extends BaseShortestPathAlgorithm { private final double radius; private final Supplier>> heapSupplier; /** * Constructs a new instance of the algorithm for a given graph. The constructed algorithm will * use pairing heap as a default heap implementation. * * @param graph the graph */ public DijkstraShortestPath(Graph graph) { this(graph, Double.POSITIVE_INFINITY, PairingHeap::new); } /** * Constructs a new instance of the algorithm for a given graph. The constructed algorithm will * use pairing heap as a default heap implementation. * * @param graph the graph * @param radius limit on path length, or Double.POSITIVE_INFINITY for unbounded search */ public DijkstraShortestPath(Graph graph, double radius) { this(graph, radius, PairingHeap::new); } /** * Constructs a new instance of the algorithm for a given graph. The constructed algorithm will * use the heap supplied by the {@code heapSupplier} * * @param graph the graph * @param heapSupplier supplier of the preferable heap implementation */ public DijkstraShortestPath( Graph graph, Supplier>> heapSupplier) { this(graph, Double.POSITIVE_INFINITY, heapSupplier); } /** * Constructs a new instance of the algorithm for a given graph. * * @param graph the graph * @param radius limit on path length, or Double.POSITIVE_INFINITY for unbounded search * @param heapSupplier supplier of the preferable heap implementation */ public DijkstraShortestPath( Graph graph, double radius, Supplier>> heapSupplier) { super(graph); if (radius < 0.0) { throw new IllegalArgumentException("Radius must be non-negative"); } this.heapSupplier = heapSupplier; this.radius = radius; } /** * Find a path between two vertices. For a more advanced search (e.g. limited by radius or using * another heap), use the constructor instead. * * @param graph the graph to be searched * @param source the vertex at which the path should start * @param sink the vertex at which the path should end * @param the graph vertex type * @param the graph edge type * @return a shortest path, or null if no path exists */ public static GraphPath findPathBetween(Graph graph, V source, V sink) { return new DijkstraShortestPath<>(graph).getPath(source, sink); } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V sink) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } if (source.equals(sink)) { return createEmptyPath(source, sink); } DijkstraClosestFirstIterator it = new DijkstraClosestFirstIterator<>(graph, source, radius, heapSupplier); while (it.hasNext()) { V vertex = it.next(); if (vertex.equals(sink)) { break; } } return it.getPaths().getPath(sink); } /** * {@inheritDoc} *

    * Note that in the case of Dijkstra's algorithm it is more efficient to compute all * single-source shortest paths using this method than repeatedly invoking * {@link #getPath(Object, Object)} for the same source but different sink vertex. */ @Override public SingleSourcePaths getPaths(V source) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } DijkstraClosestFirstIterator it = new DijkstraClosestFirstIterator<>(graph, source, radius, heapSupplier); while (it.hasNext()) { it.next(); } return it.getPaths(); } } EppsteinKShortestPath.java000066400000000000000000000057011402514743400344220ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Implementation of the Eppstein`s algorithm for finding $k$ shortest path between two vertices in * a graph. * *

    * The algorithm is originally described in: David Eppstein. 1999. Finding the k Shortest Paths. * SIAM J. Comput. 28, 2 (February 1999), 652-673. DOI=http://dx.doi.org/10.1137/S0097539795290477. * *

    * The main advantage ot this algorithm is that it achieves the complexity of $O(m + n\log n + k\log * k)$ while guaranteeing that the paths are produced in sorted order by weight, where $m$ is the * number of edges in the graph, $n$ is the number of vertices in the graph and $k$ is the number of * paths needed. * *

    * This implementation can only be used for directed simple graphs. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @see EppsteinShortestPathIterator */ public class EppsteinKShortestPath implements KShortestPathAlgorithm { /** * Underlying graph. */ private final Graph graph; /** * Constructs the algorithm instance for the given {@code graph}. * * @param graph graph */ public EppsteinKShortestPath(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null!"); } /** * Computes {@code k} shortest paths between {@code source} and {@code sink}. If the number of * paths is denoted by $n$, the method returns $m = min\{k, n\}$ such paths. The paths are * produced in sorted order by weights. * * @param source the source vertex * @param sink the target vertex * @param k the number of shortest paths to return * @return a list of k shortest paths */ @Override public List> getPaths(V source, V sink, int k) { if (k < 0) { throw new IllegalArgumentException("k must be non-negative"); } List> result = new ArrayList<>(); EppsteinShortestPathIterator iterator = new EppsteinShortestPathIterator<>(graph, source, sink); for (int i = 0; i < k && iterator.hasNext(); i++) { result.add(iterator.next()); } return result; } } EppsteinShortestPathIterator.java000066400000000000000000000620161402514743400360230ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import java.util.*; /** * Iterator over the shortest paths (not required to be simple) between two vertices in a graph * sorted by weight. * *

    * This implementation can only be used for directed simple graphs. Also for this iterator to work * correctly the graph must not be modified during iteration. Currently there are no means to ensure * that, nor to fail-fast. The results of such modifications are undefined. * *

    * First the shortest paths tree in the edge reversed graph starting at {@code sink} is built. Thus * we get distances $d(v)$ from every vertex $v$ to {@code sink}. We then define a sidetrack edge to * be an edge, which is not in the shortest paths tree. The key observation is that every path * between the {@code source} and the {@code sink} can be solely determined by a sub-sequence of its * edges which are sidetracks. * *

    * Let $d(v)$ be the distance from $v$ to {@code sink} and $w()$ be the weight function for edges in * {@code graph}. If $e$ connects a pair of vertices $(u, w)$, the $\delta(e)$ is defined as * $w(e)+d(w)-d(u)$. Intuitively, $\delta(e)$ measures how much distance is lost by being * “sidetracked” along $e$ instead of taking a shortest path to {@code sink}. * *

    * The idea of the algorithm is to build a heap of sidetracks. This heap can be then traversed with * breadth-first search in order to retrieve the implicit representations of the paths between * {@code source} and {@code sink}. * *

    * This implementation has several improvements in comparison to the original description in the * article: * *

      *
    1. An outgoing edge of vertex $v$ is inserted in the paths graph iff it is reachable from the * {@code source}.
    2. *
    3. The cross edges in the paths graph are added only for those vertices which are reachable from * the root vertex.
    4. *
    5. Weights of the edges in the paths graph are mot maintained explicitly, because they are * computed during its traversal.
    6. *
    * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov */ public class EppsteinShortestPathIterator implements Iterator> { /** * Underlying graph. */ private final Graph graph; /** * Source vertex. */ private final V source; /** * Sink vertex. */ private final V sink; /** * Vertex of the paths graph from which the BFS traversal is started. */ private PathsGraphVertex pathsGraphRoot; /** * Shortest paths tree in the edge reversed graph {@code graph} rooted at {@code sink}. */ private Map> distanceAndPredecessorMap; /** * Priority queue of the paths generated during the computation. */ private Queue pathsQueue; /** * For each vertex $v$ in {@code graph} maintains the root of the balanced heap, which * corresponds to it. */ private Map hMapping; /** * Constructs an instance of the algorithm for the given {@code graph}, {@code source} and * {@code sink}. * * @param graph graph * @param source source vertex * @param sink sink vertex */ public EppsteinShortestPathIterator(Graph graph, V source, V sink) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null!"); GraphType type = graph.getType(); if (!(type.isDirected() && type.isSimple())) { throw new IllegalArgumentException("graph must be simple and directed"); } if (!graph.containsVertex(source)) { throw new IllegalArgumentException("Graph does not contain source vertex"); } this.source = source; if (!graph.containsVertex(sink)) { throw new IllegalArgumentException("Graph does not contain sink vertex"); } this.sink = sink; pathsQueue = new PriorityQueue<>(); TreeSingleSourcePathsImpl shortestPaths = (TreeSingleSourcePathsImpl) new DijkstraShortestPath<>(new EdgeReversedGraph<>(graph)).getPaths(sink); GraphPath shortestPath = shortestPaths.getPath(source); if (shortestPath != null) { distanceAndPredecessorMap = shortestPaths.getDistanceAndPredecessorMap(); pathsQueue .add( new EppsteinGraphPath( graph, new ArrayList<>(0), distanceAndPredecessorMap, shortestPath.getWeight())); hMapping = new HashMap<>(); buildPathsGraph(); } } /** * {@inheritDoc} */ @Override public boolean hasNext() { return !pathsQueue.isEmpty(); } /** * {@inheritDoc} */ @Override public GraphPath next() { if (pathsQueue.isEmpty()) { throw new NoSuchElementException(); } EppsteinGraphPath result = pathsQueue.remove(); addOneEdgeExtension(result); return result; } /** * Adds all one-edge extension of the {@code path} wrt the paths graph. * * @param path path to put extensions of */ private void addOneEdgeExtension(EppsteinGraphPath path) { PathsGraphVertex lastPathsGraphVertex; if (path.pathsGraphVertices.isEmpty()) { // if this is shortest path between the source and // sink lastPathsGraphVertex = pathsGraphRoot; } else { lastPathsGraphVertex = path.pathsGraphVertices.get(path.pathsGraphVertices.size() - 1); } if (lastPathsGraphVertex.left != null) { addExtension( path, lastPathsGraphVertex.left, lastPathsGraphVertex.left.delta - lastPathsGraphVertex.delta); } if (lastPathsGraphVertex.right != null) { addExtension( path, lastPathsGraphVertex.right, lastPathsGraphVertex.right.delta - lastPathsGraphVertex.delta); } if (lastPathsGraphVertex.rest != null) { addExtension( path, lastPathsGraphVertex.rest, lastPathsGraphVertex.rest.delta - lastPathsGraphVertex.delta); } if (lastPathsGraphVertex.cross != null) { addExtension(path, lastPathsGraphVertex.cross, lastPathsGraphVertex.cross.delta); } } /** * Adds an extension of {@code paths} with {@code extendingVertex} being its last element. * * @param path path to put extension of * @param extendingVertex vertex to extend path with * @param weight weight of the resulting path */ private void addExtension( EppsteinGraphPath path, PathsGraphVertex extendingVertex, double weight) { List sidetracks = new ArrayList<>(path.pathsGraphVertices); sidetracks.add(extendingVertex); pathsQueue .add( new EppsteinGraphPath( graph, sidetracks, distanceAndPredecessorMap, path.weight + weight)); } /** * Guides the building process of the paths graph. The process is divided into three stages. * First the D(g) is constructed, then cross edges are added and finally the root vertex is * created. */ private void buildPathsGraph() { buildDGraph(); addCrossEdges(); addPathGraphRoot(); } /** * If the {@code graph} is denoted by $G$, then for every vertex $v$ reachable from * {@code source} in $G$ $D(G)$ contains balanced heaps of all outroots, which corresponds to * vertices on the path from $v$ to {@code sink}. If there are no sidetracks on the path from * $v$ to {@code sink}, the value $null$ is stored. An outroot is connected to its rest heap if * the corresponding vertex has more than one sidetrack. */ private void buildDGraph() { DepthFirstIterator it = new DepthFirstIterator<>(graph, source); Deque stack = new ArrayDeque<>(); while (it.hasNext()) { V vertex = it.next(); if (!distanceAndPredecessorMap.containsKey(vertex)) { // sink is unreachable from vertex continue; } if (!hMapping.containsKey(vertex)) { // heap has not been built yet stack.addLast(vertex); while (!stack.isEmpty()) { V v = stack.peekLast(); if (v.equals(sink)) { stack.removeLast(); insertVertex(v, null); } else { V predecessor = Graphs .getOppositeVertex( graph, distanceAndPredecessorMap.get(v).getSecond(), v); if (hMapping.containsKey(predecessor)) { stack.removeLast(); PathsGraphVertex predecessorH = hMapping.get(predecessor); insertVertex(v, predecessorH); } else { stack.addLast(predecessor); } } } } } } /** * Adds cross edges for every vertex $v$ reachable from the root of balanced heap of * {@code source} in the paths graph. If a sidetrack, which corresponds to $v$ connects some * pair of vertices $(u,w)$, a cross edge from $v$ to the root of the balanced heap of $w$ is * added. */ private void addCrossEdges() { Queue queue = new ArrayDeque<>(); PathsGraphVertex sourceMapping = hMapping.get(source); Set seen = new HashSet<>(); if (sourceMapping != null) { // no sidetracks on the paths from source to sink queue.add(sourceMapping); while (!queue.isEmpty()) { PathsGraphVertex v = queue.remove(); seen.add(v); V target = graph.getEdgeTarget(v.edge); v.cross = hMapping.get(target); if (v.left != null && !seen.contains(v.left)) { queue.add(v.left); } if (v.right != null && !seen.contains(v.right)) { queue.add(v.right); } if (v.rest != null && !seen.contains(v.rest)) { queue.add(v.rest); } if (v.cross != null && !seen.contains(v.cross)) { queue.add(v.cross); } } } } /** * Creates the root vertex $r$ of the paths graph and connects it to the root of the balanced * heap of {@code source}. */ private void addPathGraphRoot() { PathsGraphVertex root = new PathsGraphVertex(null, 0); root.cross = hMapping.get(source); pathsGraphRoot = root; } /** * Guides the process of adding the sidetracks of {@code v} to the paths graph. First receives * the outroot and root of the rest heap of {@code v} by calling * {@code getOutrootAndRestHeapRoot(Object)}. If the outroot if $null$ maps $v$ to * {@code predecessorHeap} in {@code hMapping}. Otherwise inserts outroot of $v$ in the balanced * heap rooted at {@code predecessorHeap} and links it to the received rest heap root. * * @param v vertex * @param predecessorHeap balanced heap root */ private void insertVertex(V v, PathsGraphVertex predecessorHeap) { Pair p = getOutrootAndRestHeapRoot(v); PathsGraphVertex outroot = p.getFirst(); PathsGraphVertex restHeapRoot = p.getSecond(); if (outroot == null) { hMapping.put(v, predecessorHeap); } else { PathsGraphVertex mappingVertex = insertPersistently(predecessorHeap, outroot); hMapping.put(v, mappingVertex); mappingVertex.rest = restHeapRoot; } } /** * Inserts {@code vertex} into the balanced heap rooted at {@code root} in a persistent * (non-destructive) way. Return root of the modified heap. * * @param root root of a balanced heap * @param vertex vertex to be inserted * @return root of the modified heap */ private PathsGraphVertex insertPersistently(PathsGraphVertex root, PathsGraphVertex vertex) { if (root == null) { vertex.left = null; vertex.right = null; vertex.size = 1; return vertex; } else { PathsGraphVertex rootCopy = new PathsGraphVertex(root); boolean leftDirection = root.left == null || (root.right != null && root.left.size <= root.right.size); PathsGraphVertex min; PathsGraphVertex max; if (vertex.delta >= rootCopy.delta) { min = rootCopy; max = vertex; } else { vertex.left = rootCopy.left; vertex.right = rootCopy.right; vertex.size = rootCopy.size; rootCopy.left = null; rootCopy.right = null; min = vertex; max = rootCopy; } if (leftDirection) { min.left = insertPersistently(min.left, max); } else { min.right = insertPersistently(min.right, max); } min.size++; return min; } } /** * Builds outroot and heapification of other sidetracks of {@code v}. * * @param v vertex * @return outroot and rest heap root */ private Pair getOutrootAndRestHeapRoot(V v) { List restHeapElements = new ArrayList<>(); PathsGraphVertex outroot = new PathsGraphVertex(null, Double.POSITIVE_INFINITY); // dummy // vertex E predecessor = distanceAndPredecessorMap.get(v).getSecond(); for (E e : graph.outgoingEdgesOf(v)) { if (distanceAndPredecessorMap.containsKey(graph.getEdgeTarget(e))) { if (!e.equals(predecessor)) { double delta = delta(e); if (delta < outroot.delta) { if (outroot.edge != null) { restHeapElements.add(outroot); } outroot = new PathsGraphVertex(e, delta); } else { restHeapElements.add(new PathsGraphVertex(e, delta)); } } } } PathsGraphVertex restHeapRoot = null; int size = restHeapElements.size(); if (size > 0) { heapify(restHeapElements, size); restHeapRoot = getRestHeap(restHeapElements, 0, size); } if (outroot.edge == null) { // it is still dummy vertex return new Pair<>(null, restHeapRoot); } else { return new Pair<>(outroot, restHeapRoot); } } /** * Builds a min-heap out of the {@code vertices} list * * @param vertices vertices * @param size size of vertices */ private void heapify(List vertices, int size) { for (int i = size / 2 - 1; i >= 0; i--) { siftDown(vertices, i, size); } } private void siftDown(List vertices, int i, int size) { int left; int right; int smaller; int current = i; while (true) { left = 2 * current + 1; right = 2 * current + 2; smaller = current; if (left < size && vertices.get(left).compareTo(vertices.get(smaller)) < 0) { smaller = left; } if (right < size && vertices.get(right).compareTo(vertices.get(smaller)) < 0) { smaller = right; } if (smaller == current) { break; } swap(vertices, current, smaller); current = smaller; } } /** * Constructs an explicit tree-like representation of the binary heap contained in * {@code vertices} starting at position {@code i}. * * @param vertices heapified vertices * @param i heap start position * @param size size of vertices * @return root of the built heap */ private PathsGraphVertex getRestHeap(List vertices, int i, int size) { int l = 2 * i + 1; int r = 2 * i + 2; if (l < size) { vertices.get(i).left = getRestHeap(vertices, l, size); } if (r < size) { vertices.get(i).right = getRestHeap(vertices, r, size); } return vertices.get(i); } private void swap(List vertices, int i, int j) { if (i != j) { PathsGraphVertex tmp = vertices.get(i); vertices.set(i, vertices.get(j)); vertices.set(j, tmp); } } /** * Calculates the $\delta(e)$ value for a given edge {@code e}. * * @param e edge * @return value of $\delta(e)$ */ private double delta(E e) { return graph.getEdgeWeight(e) + distanceAndPredecessorMap.get(graph.getEdgeTarget(e)).getFirst() - distanceAndPredecessorMap.get(graph.getEdgeSource(e)).getFirst(); } /** * Represents a path that is generated during the computations. */ private class EppsteinGraphPath implements GraphPath, Comparable { /** * The graph. */ private Graph graph; /** * Vertices of the paths graph this path corresponds to. */ private List pathsGraphVertices; /** * Shortest paths tree in the edge reversed graph {@code graph} rooted at {@code sink}. */ private Map> distanceAndPredecessorMap; /** * Weight of tha path. */ private double weight; EppsteinGraphPath( Graph graph, List pathsGraphVertices, Map> distanceAndPredecessorMap, double weight) { this.graph = graph; this.pathsGraphVertices = pathsGraphVertices; this.distanceAndPredecessorMap = distanceAndPredecessorMap; this.weight = weight; } @Override public Graph getGraph() { return graph; } @Override public V getStartVertex() { return source; } @Override public V getEndVertex() { return sink; } @Override public double getWeight() { return weight; } /** * Given the implicit representation of the path between {@code source} and {@code sink} * constructs the edge list of the path. * * @return edge list of the path */ @Override public List getEdgeList() { List sidetracks = getSidetracks(pathsGraphVertices); List result = new ArrayList<>(); Iterator it = sidetracks.iterator(); V shortestPathSource = source; PathsGraphVertex sidetrack = null; if (it.hasNext()) { sidetrack = it.next(); } while (sidetrack != null) { V sidetrackSource = graph.getEdgeSource(sidetrack.edge); while (!shortestPathSource.equals(sidetrackSource)) { E shortestPathEdge = distanceAndPredecessorMap.get(shortestPathSource).getSecond(); result.add(shortestPathEdge); shortestPathSource = Graphs.getOppositeVertex(graph, shortestPathEdge, shortestPathSource); } PathsGraphVertex curr = sidetrack; PathsGraphVertex next = null; while (it.hasNext()) { next = it.next(); if (graph.getEdgeTarget(curr.edge).equals(graph.getEdgeSource(next.edge))) { result.add(curr.edge); curr = next; next = null; } else { break; } } result.add(curr.edge); sidetrack = next; shortestPathSource = graph.getEdgeTarget(curr.edge); } // only shortest path edges are left while (!shortestPathSource.equals(sink)) { E edge = distanceAndPredecessorMap.get(shortestPathSource).getSecond(); result.add(edge); shortestPathSource = graph.getEdgeTarget(edge); } return result; } /** * Builds sequence of sidetracks in the {@code graph} this path corresponds to. * * @param vertices vertices of the paths graph * @return list of sidetracks */ private List getSidetracks(List vertices) { if (vertices.size() > 1) { List toBeRemoved = new ArrayList<>(); Iterator it = vertices.iterator(); PathsGraphVertex curr = it.next(); PathsGraphVertex next; int currPosition = 0; while (it.hasNext()) { next = it.next(); if (curr.left == next || curr.right == next || curr.rest == next) { toBeRemoved.add(currPosition); } curr = next; currPosition++; } List result = new ArrayList<>(vertices.size() - toBeRemoved.size()); int size = toBeRemoved.size(); for (int i = 0, j = 0; i < vertices.size(); i++) { if (j < size && toBeRemoved.get(j).equals(i)) { j++; } else { result.add(vertices.get(i)); } } return result; } return vertices; } @Override public int compareTo(EppsteinGraphPath o) { return Double.compare(weight, o.weight); } } /** * Vertex of the paths graph. Does not maintain the weights of the edges to {@code left}, * {@code right}, {@code rest} and {@code cross} vertices, because they are computed during the * paths graph traversal. */ private class PathsGraphVertex implements Comparable { /** * Edge this vertex corresponds to. */ E edge; /** * $Delta(edge)$ value. */ double delta; /** * If this vertex is part of a balanced heap of outroots in the path graph, this value is * used to determine where a new vertex should be inserted in order for the heap to remain * balanced. */ int size; // Connections to other vertices in the paths graph. PathsGraphVertex left; PathsGraphVertex right; PathsGraphVertex rest; PathsGraphVertex cross; PathsGraphVertex(E edge, double delta) { this.edge = edge; this.delta = delta; this.size = 1; } /** * Copy constructor. * * @param other other vertex */ PathsGraphVertex(PathsGraphVertex other) { this.edge = other.edge; this.size = other.size; this.delta = other.delta; this.left = other.left; this.right = other.right; this.cross = other.cross; this.rest = other.rest; } @Override public int compareTo(PathsGraphVertex o) { return Double.compare(delta, o.delta); } } } FloydWarshallShortestPaths.java000066400000000000000000000306261402514743400354620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2009-2021, by Tom Larkworthy and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; /** * The Floyd-Warshall algorithm. * *

    * The Floyd-Warshall algorithm * finds all shortest paths (all $n^2$ of them) in $O(n^3)$ time. Note that during construction * time, no computations are performed! All computations are performed the first time one of the * member methods of this class is invoked. The results are stored, so all subsequent calls to the * same method are computationally efficient. * * @param the graph vertex type * @param the graph edge type * * @author Tom Larkworthy * @author Soren Davidsen (soren@tanesha.net) * @author Joris Kinable * @author Dimitrios Michail */ public class FloydWarshallShortestPaths extends BaseShortestPathAlgorithm { private final List vertices; private final List degrees; private final Map vertexIndices; // minimum vertex with degree at least 1 private final int minDegreeOne; // minimum vertex with degree at least 2 private final int minDegreeTwo; private double[][] d = null; private Object[][] backtrace = null; private Object[][] lastHopMatrix = null; /** * Create a new instance of the Floyd-Warshall all-pairs shortest path algorithm. * * @param graph the input graph */ public FloydWarshallShortestPaths(Graph graph) { super(graph); /* * Sort vertices by degree in ascending order and index them. Also compute the minimum * vertex which has degree at least one and at least two. */ this.vertices = new ArrayList<>(graph.vertexSet()); Collections.sort(vertices, VertexDegreeComparator.of(graph)); this.degrees = new ArrayList<>(); this.vertexIndices = CollectionUtil.newHashMapWithExpectedSize(this.vertices.size()); int i = 0; int minDegreeOne = vertices.size(); int minDegreeTwo = vertices.size(); for (V vertex : vertices) { vertexIndices.put(vertex, i); int degree = graph.degreeOf(vertex); degrees.add(degree); if (degree > 1) { if (i < minDegreeOne) { minDegreeOne = i; } if (i < minDegreeTwo) { minDegreeTwo = i; } } else if (i < minDegreeOne && degree == 1) { minDegreeOne = i; } ++i; } this.minDegreeOne = minDegreeOne; this.minDegreeTwo = minDegreeTwo; } /** * Get the total number of shortest paths. Does not count the paths from a vertex to itself. * * @return total number of shortest paths */ public int getShortestPathsCount() { lazyCalculateMatrix(); // count shortest paths int n = vertices.size(); int nShortestPaths = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (i != j && Double.isFinite(d[i][j])) { nShortestPaths++; } } } return nShortestPaths; } /** * {@inheritDoc} */ @Override public GraphPath getPath(V a, V b) { if (!graph.containsVertex(a)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(b)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } lazyCalculateMatrix(); int v_a = vertexIndices.get(a); int v_b = vertexIndices.get(b); if (backtrace[v_a][v_b] == null) { // No path exists return createEmptyPath(a, b); } // Reconstruct the path List edges = new ArrayList<>(); V u = a; while (!u.equals(b)) { int v_u = vertexIndices.get(u); E e = TypeUtil.uncheckedCast(backtrace[v_u][v_b]); edges.add(e); u = Graphs.getOppositeVertex(graph, e, u); } return new GraphWalk<>(graph, a, b, null, edges, d[v_a][v_b]); } /** * {@inheritDoc} */ @Override public double getPathWeight(V source, V sink) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } lazyCalculateMatrix(); return d[vertexIndices.get(source)][vertexIndices.get(sink)]; } /** * {@inheritDoc} */ @Override public SingleSourcePaths getPaths(V source) { return new FloydWarshallSingleSourcePaths(source); } /** * Returns the first hop, i.e., the second node on the shortest path from $a$ to $b$. Lookup * time is $O(1)$. If the shortest path from $a$ to $b$ is $a,c,d,e,b$, this method returns $c$. * If the next invocation would query the first hop on the shortest path from $c$ to $b$, vertex * $d$ would be returned, etc. This method is computationally cheaper than calling * {@link #getPath(Object, Object)} and then reading the first vertex. * * @param a source vertex * @param b target vertex * @return next hop on the shortest path from a to b, or null when there exists no path from $a$ * to $b$. */ public V getFirstHop(V a, V b) { lazyCalculateMatrix(); int v_a = vertexIndices.get(a); int v_b = vertexIndices.get(b); if (backtrace[v_a][v_b] == null) { // No path exists return null; } else { E e = TypeUtil.uncheckedCast(backtrace[v_a][v_b]); return Graphs.getOppositeVertex(graph, e, a); } } /** * Returns the last hop, i.e., the second to last node on the shortest path from $a$ to $b$. * Lookup time is $O(1)$. If the shortest path from $a$ to $b$ is $a,c,d,e,b$, this method * returns $e$. If the next invocation would query the next hop on the shortest path from $c$ to * $e$, vertex $d$ would be returned, etc. This method is computationally cheaper than calling * {@link #getPath(Object, Object)} and then reading the vertex. The first invocation of this * method populates a last hop matrix. * * @param a source vertex * @param b target vertex * @return last hop on the shortest path from $a$ to $b$, or null when there exists no path from * $a$ to $b$. */ public V getLastHop(V a, V b) { lazyCalculateMatrix(); int v_a = vertexIndices.get(a); int v_b = vertexIndices.get(b); if (backtrace[v_a][v_b] == null) { // No path exists return null; } else { populateLastHopMatrix(); E e = TypeUtil.uncheckedCast(lastHopMatrix[v_a][v_b]); return Graphs.getOppositeVertex(graph, e, b); } } /** * Calculates the matrix of all shortest paths, but does not populate the last hops matrix. */ private void lazyCalculateMatrix() { if (d != null) { // already done return; } int n = vertices.size(); // init the backtrace matrix backtrace = new Object[n][n]; // initialize matrix, 0 d = new double[n][n]; for (int i = 0; i < n; i++) { Arrays.fill(d[i], Double.POSITIVE_INFINITY); } // initialize matrix, 1 for (int i = 0; i < n; i++) { d[i][i] = 0.0; } // initialize matrix, 2 if (graph.getType().isUndirected()) { for (E edge : graph.edgeSet()) { V source = graph.getEdgeSource(edge); V target = graph.getEdgeTarget(edge); if (!source.equals(target)) { int v_1 = vertexIndices.get(source); int v_2 = vertexIndices.get(target); double edgeWeight = graph.getEdgeWeight(edge); if (Double.compare(edgeWeight, d[v_1][v_2]) < 0) { d[v_1][v_2] = d[v_2][v_1] = edgeWeight; backtrace[v_1][v_2] = edge; backtrace[v_2][v_1] = edge; } } } } else { // This works for both Directed and Mixed graphs! Iterating over // the arcs and querying source/sink does not suffice for graphs // which contain both edges and arcs for (V v1 : graph.vertexSet()) { int v_1 = vertexIndices.get(v1); for (E e : graph.outgoingEdgesOf(v1)) { V v2 = Graphs.getOppositeVertex(graph, e, v1); if (!v1.equals(v2)) { int v_2 = vertexIndices.get(v2); double edgeWeight = graph.getEdgeWeight(e); if (Double.compare(edgeWeight, d[v_1][v_2]) < 0) { d[v_1][v_2] = edgeWeight; backtrace[v_1][v_2] = e; } } } } } // run fw alg for (int k = minDegreeTwo; k < n; k++) { for (int i = minDegreeOne; i < n; i++) { if (i == k) { continue; } for (int j = minDegreeOne; j < n; j++) { if (i == j || j == k) { continue; } double ik_kj = d[i][k] + d[k][j]; if (Double.compare(ik_kj, d[i][j]) < 0) { d[i][j] = ik_kj; backtrace[i][j] = backtrace[i][k]; } } } } } /** * Populate the last hop matrix, using the earlier computed backtrace matrix. */ private void populateLastHopMatrix() { lazyCalculateMatrix(); if (lastHopMatrix != null) { return; } // Initialize matrix int n = vertices.size(); lastHopMatrix = new Object[n][n]; // Populate matrix for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (i == j || lastHopMatrix[i][j] != null || backtrace[i][j] == null) { continue; } // Reconstruct the path from i to j V u = vertices.get(i); V b = vertices.get(j); while (!u.equals(b)) { int v_u = vertexIndices.get(u); E e = TypeUtil.uncheckedCast(backtrace[v_u][j]); V other = Graphs.getOppositeVertex(graph, e, u); lastHopMatrix[i][vertexIndices.get(other)] = e; u = other; } } } } class FloydWarshallSingleSourcePaths implements SingleSourcePaths { private final V source; public FloydWarshallSingleSourcePaths(V source) { this.source = source; } @Override public Graph getGraph() { return graph; } @Override public V getSourceVertex() { return source; } @Override public double getWeight(V sink) { return FloydWarshallShortestPaths.this.getPathWeight(source, sink); } @Override public GraphPath getPath(V sink) { return FloydWarshallShortestPaths.this.getPath(source, sink); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/GraphMeasurer.java000066400000000000000000000214731402514743400327770ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; /** * Algorithm class which computes a number of distance related metrics. A summary of various * distance metrics can be found * here. * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable, Alexandru Valeanu */ public class GraphMeasurer { /* Input graph */ private final Graph graph; /* All-pairs shortest path algorithm */ private final ShortestPathAlgorithm shortestPathAlgorithm; /* Vertex eccentricity map */ private Map eccentricityMap = null; /* Diameter of the graph */ private double diameter = 0; /* Radius of the graph */ private double radius = Double.POSITIVE_INFINITY; /** * Constructs a new instance of GraphMeasurer. {@link FloydWarshallShortestPaths} is used as the * default shortest path algorithm. * * @param graph input graph */ public GraphMeasurer(Graph graph) { this(graph, new FloydWarshallShortestPaths(graph)); } /** * Constructs a new instance of GraphMeasurer. * * @param graph input graph * @param shortestPathAlgorithm shortest path algorithm used to compute shortest paths between * all pairs of vertices. Recommended algorithms are: * {@link org.jgrapht.alg.shortestpath.JohnsonShortestPaths} (Runtime complexity: * $O(|V||E| + |V|^2 log|V|)$) or * {@link org.jgrapht.alg.shortestpath.FloydWarshallShortestPaths} (Runtime complexity: * $O(|V|^3)$. */ public GraphMeasurer(Graph graph, ShortestPathAlgorithm shortestPathAlgorithm) { this.graph = graph; this.shortestPathAlgorithm = shortestPathAlgorithm; } /** * Compute the diameter of the * graph. The diameter of a graph is defined as $\max_{v\in V}\epsilon(v)$, where $\epsilon(v)$ * is the eccentricity of vertex $v$. In other words, this method computes the 'longest shortest * path'. Two special cases exist. If the graph has no vertices, the diameter is 0. If the graph * is disconnected, the diameter is {@link Double#POSITIVE_INFINITY}. * * @return the diameter of the graph. */ public double getDiameter() { computeEccentricityMap(); return diameter; } /** * Compute the radius of the graph. * The radius of a graph is defined as $\min_{v\in V}\epsilon(v)$, where $\epsilon(v)$ is the * eccentricity of vertex $v$. Two special cases exist. If the graph has no vertices, the radius * is 0. If the graph is disconnected, the diameter is {@link Double#POSITIVE_INFINITY}. * * @return the diameter of the graph. */ public double getRadius() { computeEccentricityMap(); return radius; } /** * Compute the eccentricity of * each vertex in the graph. The eccentricity of a vertex $u$ is defined as $\max_{v}d(u,v)$, * where $d(u,v)$ is the shortest path between vertices $u$ and $v$. If the graph is * disconnected, the eccentricity of each vertex is {@link Double#POSITIVE_INFINITY}. The * runtime complexity of this method is $O(n^2+L)$, where $L$ is the runtime complexity of the * shortest path algorithm provided during construction of this class. * * @return a map containing the eccentricity of each vertex. */ public Map getVertexEccentricityMap() { computeEccentricityMap(); return Collections.unmodifiableMap(this.eccentricityMap); } /** * Compute the graph center. The * center of a graph is the set of vertices of graph eccentricity equal to the graph radius. * * @return the graph center */ public Set getGraphCenter() { computeEccentricityMap(); Set graphCenter = new LinkedHashSet<>(); ToleranceDoubleComparator comp = new ToleranceDoubleComparator(); for (Map.Entry entry : eccentricityMap.entrySet()) { if (comp.compare(entry.getValue(), radius) == 0) graphCenter.add(entry.getKey()); } return graphCenter; } /** * Compute the graph periphery. * The periphery of a graph is the set of vertices of graph eccentricity equal to the graph * diameter. * * @return the graph periphery */ public Set getGraphPeriphery() { computeEccentricityMap(); Set graphPeriphery = new LinkedHashSet<>(); ToleranceDoubleComparator comp = new ToleranceDoubleComparator(); for (Map.Entry entry : eccentricityMap.entrySet()) { if (comp.compare(entry.getValue(), diameter) == 0) graphPeriphery.add(entry.getKey()); } return graphPeriphery; } /** * Compute the graph pseudo-periphery. The pseudo-periphery of a graph is the set of all * pseudo-peripheral vertices. A pseudo-peripheral vertex $v$ has the property that for any * vertex $u$, if $v$ is as far away from $u$ as possible, then $u$ is as far away from $v$ as * possible. Formally, a vertex $u$ is pseudo-peripheral, if for each vertex $v$ with * $d(u,v)=\epsilon(u)$ holds $\epsilon(u)=\epsilon(v)$, where $\epsilon(u)$ is the eccentricity * of vertex $u$. * * @return the graph pseudo-periphery */ public Set getGraphPseudoPeriphery() { computeEccentricityMap(); Set graphPseudoPeriphery = new LinkedHashSet<>(); ToleranceDoubleComparator comp = new ToleranceDoubleComparator(); for (Map.Entry entry : eccentricityMap.entrySet()) { V u = entry.getKey(); for (V v : graph.vertexSet()) if (comp.compare(shortestPathAlgorithm.getPathWeight(u, v), entry.getValue()) == 0 && comp.compare(entry.getValue(), eccentricityMap.get(v)) == 0) graphPseudoPeriphery.add(entry.getKey()); } return graphPseudoPeriphery; } /** * Lazy method which computes the eccentricity of each vertex */ private void computeEccentricityMap() { if (eccentricityMap != null) return; // Compute the eccentricity map eccentricityMap = new LinkedHashMap<>(); if (graph.getType().isUndirected()) { List vertices = new ArrayList<>(graph.vertexSet()); double[] eccentricityVector = new double[vertices.size()]; for (int i = 0; i < vertices.size() - 1; i++) { for (int j = i + 1; j < vertices.size(); j++) { double dist = shortestPathAlgorithm.getPathWeight(vertices.get(i), vertices.get(j)); eccentricityVector[i] = Math.max(eccentricityVector[i], dist); eccentricityVector[j] = Math.max(eccentricityVector[j], dist); } } for (int i = 0; i < vertices.size(); i++) eccentricityMap.put(vertices.get(i), eccentricityVector[i]); } else { for (V u : graph.vertexSet()) { double eccentricity = 0; for (V v : graph.vertexSet()) eccentricity = Double.max(eccentricity, shortestPathAlgorithm.getPathWeight(u, v)); eccentricityMap.put(u, eccentricity); } } // Compute the graph diameter and radius if (eccentricityMap.isEmpty()) { diameter = 0; radius = 0; } else { for (V v : graph.vertexSet()) { diameter = Math.max(diameter, eccentricityMap.get(v)); radius = Math.min(radius, eccentricityMap.get(v)); } } } } IntVertexDijkstraShortestPath.java000066400000000000000000000325201402514743400361430ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jheaps.*; import org.jheaps.AddressableHeap.*; import org.jheaps.array.*; import java.io.*; import java.lang.reflect.*; import java.util.*; import java.util.function.*; /** * Dijkstra Shortest Path implementation specialized for graphs with integer vertices. * *

    * This class avoids using hash tables when the vertices are numbered from $0$ to $n-1$ where $n$ is * the number of vertices of the graph. If vertices are not in this range, then they are mapped in * this range using an open addressing hash table (linear probing). * *

    * This implementation should be faster than our more generic one which is * {@link DijkstraShortestPath} since it avoids the extensive use of hash tables. * *

    * By default a 4-ary heap is used. The user is free to use a custom heap implementation during * construction time. Regarding the choice of heap, it is generally known that it depends on the * particular workload. For more details and experiments which can help someone make the choice, * read the following paper: Larkin, Daniel H., Siddhartha Sen, and Robert E. Tarjan. "A * back-to-basics empirical study of priority queues." 2014 Proceedings of the Sixteenth Workshop on * Algorithm Engineering and Experiments (ALENEX). Society for Industrial and Applied Mathematics, * 2014. * * @param the graph edge type * * @author Dimitrios Michail */ public final class IntVertexDijkstraShortestPath extends BaseShortestPathAlgorithm { private final Supplier> heapSupplier; /** * Constructs a new instance of the algorithm for a given graph. * * @param graph the graph */ public IntVertexDijkstraShortestPath(Graph graph) { this(graph, () -> new DaryArrayAddressableHeap<>(4)); } /** * Constructs a new instance of the algorithm for a given graph. * * @param graph the graph * @param heapSupplier supplier of the preferable heap implementation */ public IntVertexDijkstraShortestPath( Graph graph, Supplier> heapSupplier) { super(graph); this.heapSupplier = heapSupplier; } /** * Find a path between two vertices. For a more advanced search (e.g. using another heap), use * the constructor instead. * * @param graph the graph to be searched * @param source the vertex at which the path should start * @param sink the vertex at which the path should end * @param the graph edge type * @return a shortest path, or null if no path exists */ public static GraphPath findPathBetween( Graph graph, Integer source, Integer sink) { return new IntVertexDijkstraShortestPath<>(graph).getPath(source, sink); } @Override public GraphPath getPath(Integer source, Integer sink) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } return new Algorithm().getPath(source, sink); } /** * {@inheritDoc} * *

    * Note that in the case of Dijkstra's algorithm it is more efficient to compute all * single-source shortest paths using this method than repeatedly invoking * {@link #getPath(Integer, Integer)} for the same source but different sink vertex. */ @Override public SingleSourcePaths getPaths(Integer source) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } return new Algorithm().getPaths(source); } /** * The actual implementation class. We use this inner class pattern in order to allow the user * to keep a reference to the implementation class, but allow the garbage collector to collect * the auxiliary memory used during the algorithm's execution. */ private class Algorithm { private int totalVertices; private AddressableHeap heap; private AddressableHeap.Handle[] nodes; private double[] dist; private E[] pred; private IdentifierMap idMap; @SuppressWarnings("unchecked") public Algorithm() { this.totalVertices = graph.vertexSet().size(); this.nodes = (Handle[]) Array .newInstance(AddressableHeap.Handle.class, totalVertices); this.heap = heapSupplier.get(); this.dist = new double[totalVertices]; this.pred = (E[]) new Object[totalVertices]; boolean remapVertices = false; int i = 0; for (Integer v : graph.vertexSet()) { if (v < 0 || v >= totalVertices) { remapVertices = true; } dist[i] = Double.POSITIVE_INFINITY; pred[i] = null; i++; } /* * In case vertices are not in $[0 \ldots n)$ where $n$ is the number of vertices, we * map them. */ if (remapVertices) { idMap = new IdentifierMap(totalVertices); i = 0; for (Integer v : graph.vertexSet()) { idMap.put(v, i++); } } } public SingleSourcePaths getPaths(Integer source) { if (idMap == null) { return getPathsWithoutIdMap(source, null); } else { return getPathsWithIdMap(source, null); } } public SingleSourcePaths getPathsWithoutIdMap(Integer source, Integer target) { dist[source] = 0d; pred[source] = null; nodes[source] = heap.insert(0d, source); while (!heap.isEmpty()) { AddressableHeap.Handle vNode = heap.deleteMin(); Integer v = vNode.getValue(); double vDistance = vNode.getKey(); dist[v] = vDistance; if (target != null && v.intValue() == target.intValue()) { break; } for (E e : graph.outgoingEdgesOf(v)) { Integer u = Graphs.getOppositeVertex(graph, e, v); double eWeight = graph.getEdgeWeight(e); if (eWeight < 0.0) { throw new IllegalArgumentException("Negative edge weight not allowed"); } AddressableHeap.Handle uNode = nodes[u]; double uDist = vDistance + eWeight; if (uNode == null) { nodes[u] = heap.insert(uDist, u); pred[u] = e; } else if (uDist < uNode.getKey()) { uNode.decreaseKey(uDist); pred[u] = e; } } } return new ArrayBasedSingleSourcePathsImpl(source, dist, pred, idMap); } public SingleSourcePaths getPathsWithIdMap(Integer source, Integer target) { dist[idMap.get(source)] = 0d; pred[idMap.get(source)] = null; nodes[idMap.get(source)] = heap.insert(0d, source); while (!heap.isEmpty()) { AddressableHeap.Handle vNode = heap.deleteMin(); Integer v = vNode.getValue(); double vDistance = vNode.getKey(); dist[idMap.get(v)] = vDistance; if (target != null && v.intValue() == target.intValue()) { break; } for (E e : graph.outgoingEdgesOf(v)) { Integer u = Graphs.getOppositeVertex(graph, e, v); double eWeight = graph.getEdgeWeight(e); if (eWeight < 0.0) { throw new IllegalArgumentException("Negative edge weight not allowed"); } AddressableHeap.Handle uNode = nodes[idMap.get(u)]; double uDist = vDistance + eWeight; if (uNode == null) { nodes[idMap.get(u)] = heap.insert(uDist, u); pred[idMap.get(u)] = e; } else if (uDist < uNode.getKey()) { uNode.decreaseKey(uDist); pred[idMap.get(u)] = e; } } } return new ArrayBasedSingleSourcePathsImpl(source, dist, pred, idMap); } public GraphPath getPath(Integer source, Integer target) { if (idMap == null) { return getPathsWithoutIdMap(source, target).getPath(target); } else { return getPathsWithIdMap(source, target).getPath(target); } } } private class ArrayBasedSingleSourcePathsImpl implements SingleSourcePaths, Serializable { private static final long serialVersionUID = 2912496450441089175L; private Integer source; private double[] dist; private E[] pred; private IdentifierMap idMap; public ArrayBasedSingleSourcePathsImpl( Integer source, double[] dist, E[] pred, IdentifierMap idMap) { this.source = source; this.dist = dist; this.pred = pred; this.idMap = idMap; } @Override public Graph getGraph() { return graph; } @Override public Integer getSourceVertex() { return source; } @Override public double getWeight(Integer targetVertex) { if (idMap == null) { return dist[targetVertex]; } else { return dist[idMap.get(targetVertex)]; } } @Override public GraphPath getPath(Integer targetVertex) { if (source.equals(targetVertex)) { return GraphWalk.singletonWalk(graph, source, 0d); } Deque edgeList = new ArrayDeque<>(); Integer cur = targetVertex; double distance; E e; if (idMap != null) { if (pred[idMap.get(cur)] == null) { return null; } while ((e = pred[idMap.get(cur)]) != null) { edgeList.addFirst(e); cur = Graphs.getOppositeVertex(graph, e, cur); } distance = dist[idMap.get(targetVertex)]; } else { if (pred[cur] == null) { return null; } while ((e = pred[cur]) != null) { edgeList.addFirst(e); cur = Graphs.getOppositeVertex(graph, e, cur); } distance = dist[targetVertex]; } return new GraphWalk<>( graph, source, targetVertex, null, new ArrayList<>(edgeList), distance); } } /** * A very special case linear probing hash table, fit for this particular use case. The code * assumes several invariants such as that the user will never add more elements than its * capacity, etc. */ private class IdentifierMap { private int[] keys; private int[] values; private int m; public IdentifierMap(int m) { this.m = m; this.keys = new int[m]; Arrays.fill(keys, -1); this.values = new int[m]; } public void put(int key, int value) { int i; for (i = hash(key); keys[i] != -1; i = (i + 1) % m) { if (keys[i] == key) { values[i] = value; return; } } keys[i] = key; values[i] = value; } public int get(int key) { for (int i = hash(key); keys[i] != -1; i = (i + 1) % m) { if (keys[i] == key) { return values[i]; } } return -1; } private int hash(int key) { return (key & 0x7fffffff) % m; } } } JohnsonShortestPaths.java000066400000000000000000000307641402514743400343300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.*; /** * Johnson's all pairs shortest paths algorithm. * *

    * Finds the shortest paths between all pairs of vertices in a sparse graph. Edge weights can be * negative, but no negative-weight cycles may exist. It first executes the Bellman-Ford algorithm * to compute a transformation of the input graph that removes all negative weights, allowing * Dijkstra's algorithm to be used on the transformed graph. * *

    * Running time is $O(n m + n^2 \log n)$. * *

    * Since Johnson's algorithm creates additional vertices, this implementation requires the user to * provide a graph which is initialized with a vertex supplier. * *

    * In case the algorithm detects a negative weight cycle it will throw an exception of type * {@link NegativeCycleDetectedException} which will contain the detected negative weight cycle. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class JohnsonShortestPaths extends BaseShortestPathAlgorithm { private double[][] distance; private E[][] pred; private Map vertexIndices; private final Comparator comparator; /** * Construct a new instance. * * @param graph the input graph */ public JohnsonShortestPaths(Graph graph) { this(graph, ToleranceDoubleComparator.DEFAULT_EPSILON); } /** * Construct a new instance. * * @param graph the input graph * @param epsilon tolerance when comparing floating point values */ public JohnsonShortestPaths(Graph graph, double epsilon) { super(graph); this.comparator = new ToleranceDoubleComparator(epsilon); } /** * {@inheritDoc} * * @throws IllegalArgumentException in case the provided vertex factory creates vertices which * are already in the original graph * @throws NegativeCycleDetectedException in case a negative weight cycle is detected */ @Override public GraphPath getPath(V source, V sink) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } run(); if (source.equals(sink)) { return GraphWalk.singletonWalk(graph, source, 0d); } int vSource = vertexIndices.get(source); int vSink = vertexIndices.get(sink); V cur = sink; E e = pred[vSource][vSink]; if (e == null) { return null; } LinkedList edgeList = new LinkedList<>(); while (e != null) { edgeList.addFirst(e); cur = Graphs.getOppositeVertex(graph, e, cur); e = pred[vSource][vertexIndices.get(cur)]; } return new GraphWalk<>(graph, source, sink, null, edgeList, distance[vSource][vSink]); } /** * {@inheritDoc} * * @throws IllegalArgumentException in case the provided vertex factory creates vertices which * are already in the original graph */ @Override public double getPathWeight(V source, V sink) { if (!graph.containsVertex(source)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX); } if (!graph.containsVertex(sink)) { throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX); } run(); return distance[vertexIndices.get(source)][vertexIndices.get(sink)]; } /** * {@inheritDoc} * * @throws IllegalArgumentException in case the provided vertex factory creates vertices which * are already in the original graph * @throws NegativeCycleDetectedException in case a negative weight cycle is detected */ @Override public SingleSourcePaths getPaths(V source) { run(); return new JohnsonSingleSourcePaths(source); } /** * Executes the actual algorithm. */ private void run() { if (pred != null) { return; } GraphTests.requireDirectedOrUndirected(graph); E detectedNegativeEdge = null; for (E e : graph.edgeSet()) { if (comparator.compare(graph.getEdgeWeight(e), 0.0) < 0) { detectedNegativeEdge = e; break; } } if (detectedNegativeEdge != null) { if (graph.getType().isUndirected()) { V source = graph.getEdgeSource(detectedNegativeEdge); double weight = graph.getEdgeWeight(detectedNegativeEdge); GraphWalk cycle = new GraphWalk<>( graph, source, source, Arrays.asList(detectedNegativeEdge, detectedNegativeEdge), 2d * weight); throw new NegativeCycleDetectedException( GRAPH_CONTAINS_A_NEGATIVE_WEIGHT_CYCLE, cycle); } runWithNegativeEdgeWeights(graph); } else { runWithPositiveEdgeWeights(graph); } } /** * Graph has no edges with negative weights. Only perform the last step of Johnson's algorithm: * run Dijkstra's algorithm from every vertex. * * @param g the input graph */ private void runWithPositiveEdgeWeights(Graph g) { /* * Create vertex numbering for array representation of results. */ vertexIndices = computeVertexIndices(g); final int n = g.vertexSet().size(); distance = new double[n][n]; pred = TypeUtil.uncheckedCast(new Object[n][n]); /* * Execute Dijkstra multiple times */ for (V v : g.vertexSet()) { DijkstraClosestFirstIterator it = new DijkstraClosestFirstIterator<>(g, v, Double.POSITIVE_INFINITY); while (it.hasNext()) { it.next(); } Map> distanceAndPredecessorMap = it.getDistanceAndPredecessorMap(); // transform result for (V u : g.vertexSet()) { Pair pair = distanceAndPredecessorMap .getOrDefault(u, Pair.of(Double.POSITIVE_INFINITY, null)); distance[vertexIndices.get(v)][vertexIndices.get(u)] = pair.getFirst(); pred[vertexIndices.get(v)][vertexIndices.get(u)] = pair.getSecond(); } } } /** * Graph contains edges with negative weights. Transform the input graph, thereby ensuring that * there are no edges with negative weights. Then run Dijkstra's algorithm for all vertices. * * @param g the input graph */ private void runWithNegativeEdgeWeights(Graph g) { /* * Compute vertex weights using Bellman-Ford */ Map vertexWeights = computeVertexWeights(g); /* * Compute new non-negative edge weights */ Map newEdgeWeights = new HashMap<>(); for (E e : g.edgeSet()) { V u = g.getEdgeSource(e); V v = g.getEdgeTarget(e); double weight = g.getEdgeWeight(e); newEdgeWeights.put(e, weight + vertexWeights.get(u) - vertexWeights.get(v)); } /* * Create graph with new edge weights */ Graph newEdgeWeightsGraph = new AsWeightedGraph<>(g, newEdgeWeights); /* * Create vertex numbering, for array representation of results */ vertexIndices = computeVertexIndices(g); final int n = g.vertexSet().size(); distance = new double[n][n]; pred = TypeUtil.uncheckedCast(new Object[n][n]); /* * Run Dijkstra using new weights for all vertices */ for (V v : g.vertexSet()) { DijkstraClosestFirstIterator it = new DijkstraClosestFirstIterator<>( newEdgeWeightsGraph, v, Double.POSITIVE_INFINITY); while (it.hasNext()) { it.next(); } Map> distanceAndPredecessorMap = it.getDistanceAndPredecessorMap(); // transform distances to original weights for (V u : g.vertexSet()) { Pair oldPair = distanceAndPredecessorMap.get(u); Pair newPair; if (oldPair != null) { newPair = Pair .of( oldPair.getFirst() - vertexWeights.get(v) + vertexWeights.get(u), oldPair.getSecond()); } else { newPair = Pair.of(Double.POSITIVE_INFINITY, null); } distance[vertexIndices.get(v)][vertexIndices.get(u)] = newPair.getFirst(); pred[vertexIndices.get(v)][vertexIndices.get(u)] = newPair.getSecond(); } } } /** * Compute vertex weights for edge re-weighting using Bellman-Ford. * * @param g the input graph * @return the vertex weights */ private Map computeVertexWeights(Graph g) { assert g.getType().isDirected(); // create extra graph Graph extraGraph = GraphTypeBuilder . directed().allowingMultipleEdges(true).allowingSelfLoops(true) .edgeSupplier(graph.getEdgeSupplier()).vertexSupplier(graph.getVertexSupplier()) .buildGraph(); // add new vertex V s = extraGraph.addVertex(); if (s == null) { throw new IllegalArgumentException( "Invalid vertex supplier (does not return unique vertices on each call)."); } // add new edges with zero weight Map zeroWeightFunction = new HashMap<>(); for (V v : g.vertexSet()) { extraGraph.addVertex(v); zeroWeightFunction.put(extraGraph.addEdge(s, v), 0d); } /* * Union extra and input graph */ Graph unionGraph = new AsGraphUnion<>(new AsWeightedGraph<>(extraGraph, zeroWeightFunction), g); /* * Run Bellman-Ford from new vertex */ SingleSourcePaths paths = new BellmanFordShortestPath<>(unionGraph).getPaths(s); Map weights = new HashMap<>(); for (V v : g.vertexSet()) { weights.put(v, paths.getWeight(v)); } return weights; } /** * Compute a unique integer for each vertex of the graph * * @param g the graph * @return a map with the result */ private Map computeVertexIndices(Graph g) { Map numbering = new HashMap<>(); int num = 0; for (V v : g.vertexSet()) { numbering.put(v, num++); } return numbering; } class JohnsonSingleSourcePaths implements SingleSourcePaths { private V source; public JohnsonSingleSourcePaths(V source) { this.source = source; } @Override public Graph getGraph() { return graph; } @Override public V getSourceVertex() { return source; } @Override public double getWeight(V sink) { return JohnsonShortestPaths.this.getPathWeight(source, sink); } @Override public GraphPath getPath(V sink) { return JohnsonShortestPaths.this.getPath(source, sink); } } } ListMultiObjectiveSingleSourcePathsImpl.java000066400000000000000000000051511402514743400400740ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.MultiObjectiveShortestPathAlgorithm.*; import org.jgrapht.graph.*; import java.io.*; import java.util.*; /** * An implementation of {@link MultiObjectiveSingleSourcePaths} which stores one list of paths per * vertex. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class ListMultiObjectiveSingleSourcePathsImpl implements MultiObjectiveSingleSourcePaths, Serializable { private static final long serialVersionUID = -6213225353391554721L; /** * The graph */ protected Graph graph; /** * The source vertex of all paths */ protected V source; /** * One path per vertex */ protected Map>> paths; /** * Construct a new instance. * * @param graph the graph * @param source the source vertex * @param paths a list of paths per target vertex */ public ListMultiObjectiveSingleSourcePathsImpl( Graph graph, V source, Map>> paths) { this.graph = Objects.requireNonNull(graph, "Graph is null"); this.source = Objects.requireNonNull(source, "Source vertex is null"); this.paths = Objects.requireNonNull(paths, "Paths are null"); } @Override public Graph getGraph() { return graph; } @Override public V getSourceVertex() { return source; } @Override public List> getPaths(V targetVertex) { List> p = paths.get(targetVertex); if (p == null) { if (source.equals(targetVertex)) { return Collections.singletonList(GraphWalk.singletonWalk(graph, source, 0d)); } else { return Collections.emptyList(); } } else { return p; } } } ListSingleSourcePathsImpl.java000066400000000000000000000061271402514743400352320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.graph.*; import java.io.*; import java.util.*; /** * An implementation of {@link SingleSourcePaths} which stores one path per vertex. * *

    * This is an explicit representation which stores all paths. For a more compact representation see * {@link TreeSingleSourcePathsImpl}. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class ListSingleSourcePathsImpl implements SingleSourcePaths, Serializable { private static final long serialVersionUID = -60070018446561686L; /** * The graph */ protected Graph graph; /** * The source vertex of all paths */ protected V source; /** * One path per vertex */ protected Map> paths; /** * Construct a new instance. * * @param graph the graph * @param source the source vertex * @param paths one path per target vertex */ public ListSingleSourcePathsImpl(Graph graph, V source, Map> paths) { this.graph = Objects.requireNonNull(graph, "Graph is null"); this.source = Objects.requireNonNull(source, "Source vertex is null"); this.paths = Objects.requireNonNull(paths, "Paths are null"); } /** * {@inheritDoc} */ @Override public Graph getGraph() { return graph; } /** * {@inheritDoc} */ @Override public V getSourceVertex() { return source; } /** * {@inheritDoc} */ @Override public double getWeight(V targetVertex) { GraphPath p = paths.get(targetVertex); if (p == null) { if (source.equals(targetVertex)) { return 0d; } else { return Double.POSITIVE_INFINITY; } } else { return p.getWeight(); } } /** * {@inheritDoc} */ @Override public GraphPath getPath(V targetVertex) { GraphPath p = paths.get(targetVertex); if (p == null) { if (source.equals(targetVertex)) { return GraphWalk.singletonWalk(graph, source, 0d); } else { return null; } } else { return p; } } } MartinShortestPath.java000066400000000000000000000214531402514743400337540ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jheaps.*; import org.jheaps.array.*; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * Martin's algorithm for the multi-objective shortest paths problem. * *

    * Martin's label setting algorithm is a multiple objective extension of Dijkstra's algorithm, where * the minimum operator is replaced by a dominance test. It computes a maximal complete set of * efficient paths when all the cost values are non-negative. * *

    * Note that the multi-objective shortest path problem is a well-known NP-hard problem. * * @author Dimitrios Michail * * @param the vertex type * @param the edge type */ public class MartinShortestPath extends BaseMultiObjectiveShortestPathAlgorithm { // the edge weight function private final Function edgeWeightFunction; // the number of objectives private final int objectives; // final labels for each node private final Map> nodeLabels; // temporary labels ordered lexicographically private final Heap

    * The algorithm is running k sequential Dijkstra iterations to find the shortest path at each step. * Hence, yielding a complexity of k*O(Dijkstra). * *

    * For further reference see * Wikipedia page *

      *
    • Suurballe, J. W.; Tarjan, R. E. (1984), A quick method for finding shortest pairs of disjoint * paths. *
    * * @param the graph vertex type * @param the graph edge type * * @author Assaf Mizrachi */ public class SuurballeKDisjointShortestPaths extends BaseKDisjointShortestPathsAlgorithm { private ShortestPathAlgorithm.SingleSourcePaths singleSourcePaths; /** * Creates a new instance of the algorithm. * * @param graph graph on which shortest paths are searched. * * @throws IllegalArgumentException if the graph is null. * @throws IllegalArgumentException if the graph is undirected. * @throws IllegalArgumentException if the graph is not simple. */ public SuurballeKDisjointShortestPaths(Graph graph) { super(graph); } @Override protected void transformGraph(List previousPath) { for (E edge : this.workingGraph.edgeSet()) { V source = workingGraph.getEdgeSource(edge); V target = workingGraph.getEdgeTarget(edge); double modifiedWeight = this.workingGraph.getEdgeWeight(edge) - singleSourcePaths.getWeight(target) + singleSourcePaths.getWeight(source); this.workingGraph.setEdgeWeight(edge, modifiedWeight); } E reversedEdge; for (E originalEdge : previousPath) { double zeroWeight = workingGraph.getEdgeWeight(originalEdge); if (zeroWeight != 0) { throw new IllegalStateException("Expected zero weight edge along the path"); } V source = workingGraph.getEdgeSource(originalEdge); V target = workingGraph.getEdgeTarget(originalEdge); workingGraph.removeEdge(originalEdge); workingGraph.addEdge(target, source); reversedEdge = workingGraph.getEdge(target, source); workingGraph.setEdgeWeight(reversedEdge, zeroWeight); } } @Override protected GraphPath calculateShortestPath(V startVertex, V endVertex) { this.singleSourcePaths = new DijkstraShortestPath<>(this.workingGraph).getPaths(startVertex); return singleSourcePaths.getPath(endVertex); } } TransitNodeRoutingPrecomputation.java000066400000000000000000001444521402514743400367120ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.Graph; import org.jgrapht.GraphPath; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm; import org.jgrapht.alg.util.Pair; import org.jgrapht.graph.EdgeReversedGraph; import org.jgrapht.graph.MaskSubgraph; import org.jgrapht.util.CollectionUtil; import org.jgrapht.util.ConcurrencyUtil; import org.jheaps.AddressableHeap; import org.jheaps.tree.PairingHeap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.function.Supplier; import java.util.stream.Collectors; import static org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionEdge; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionHierarchy; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionVertex; import static org.jgrapht.alg.shortestpath.DefaultManyToManyShortestPaths.DefaultManyToManyShortestPathsImpl; /** * Parallel implementation of the * transit node routing route planning precomputation technique. * *

    * This implementation is based on the {@link ContractionHierarchyPrecomputation}. * *

    * The precomputation algorithm is described in the article: Arz, Julian & Luxen, Dennis & * Sanders, Peter. (2013). Transit Node Routing Reconsidered. 7933. 10.1007/978-3-642-38527-8_7. * *

    * As mentioned is the original paper, TNR in itself is not a complete algorithm, but a framework * which is used to speed up shortest paths computations. Formally the framework consists of the * following parts: *

    *

      *
    • set $T ⊆ V$ of transit vertices;
    • *
    • distance table $D_{T} : T × T → {\rm I\!R}^{+}_{0}$ of shortest path distances between the * transit vertices;
    • *
    • forward (backward) access vertex mapping $A^{↑} : V → 2^{T}$ ($A^{↓} : V → 2^{T}$). For any * shortest $s$–$t$-path $P$ containing transit vertices, $A^{↑}(s)$ ($A^{↓}(t)$) must contain the * first (last) transit vertex on $P$;
    • *
    • locality filter $L : V × V → \{true, false\}$. $L(s, t)$ must be $true$ when no shortest path * between $s$ and $t$ contains a transit vertex. False positives are allowed, i.e., $L(s, t)$ may * sometimes be $true$ even when a shortest path contains a transit vertex.
    • *
    * *

    * To implement the TNR framework means to define how to select transit vertices and how to compute * distance table $D_{T}$, access vertices and locality filter. This implementation selects transit * vertices to be to $k$ vertices form the contraction hierarchy. For the details of how other parts * of this TNR work please refer to the original paper. * *

    * For parallelization, this implementation relies on the {@link ThreadPoolExecutor} which is * supplied to this algorithm from outside. * * @param graph vertex type * @param graph edge type * @author Semen Chudakov * @see ContractionHierarchyPrecomputation */ class TransitNodeRoutingPrecomputation { /** * Special Voronoi diagram cell id to indicate, that a vertex does not belong to any cells. For * usual Voronoi cell the ids of contracted vertices are used. Once those ids are non-negative, * this value is guaranteed to be unique. */ private static final int NO_VORONOI_CELL = -1; /** * Contraction hierarchy which is used to compute transit node routing. */ private ContractionHierarchy contractionHierarchy; /** * Contracted graph. */ private Graph, ContractionEdge> contractionGraph; /** * Mapping of vertices in the initial graph to contracted vertices. */ private Map> contractionMapping; /** * Number of transit vertices in the graph. */ private int numberOfTransitVertices; /** * Maximum number of threads used in the computations. */ private int parallelism; /** * Supplier for the preferable heap implementation. Provided heap is used to build Voronoi * diagram. */ private Supplier>> heapSupplier; /** * List of contracted vertices. It is used to evenly distribute work between threads in the * parallel computations. */ private List> contractionVertices; /** * Algorithm which is used to compute many-to-many shortest paths between transit vertices. */ private ManyToManyShortestPathsAlgorithm manyToManyShortestPathsAlgorithm; /** * Set of contracted transit vertices. */ private Set> contractedTransitVerticesSet; /** * Set of transit vertices. */ private Set transitVerticesSet; /** * List of transit vertices. */ private List transitVerticesList; /** * Voronoi diagram for the contraction graph. Here the transit vertices are used as cells * centers. */ private VoronoiDiagram voronoiDiagram; /** * Many-to-many shortest paths between transit vertices. */ private ManyToManyShortestPaths transitVerticesPaths; /** * Executor to which parallel computation tasks are submitted. */ private ExecutorService executor; /** * Decorator for {@code executor} that allows to keep track of when all submitted tasks are * finished. */ private ExecutorCompletionService completionService; /** * Constructs an instance of the algorithm for a given {@code graph} and {@code executor}. It is * up to a user of this algorithm to handle the creation and termination of the provided * {@code executor}. Utility methods to manage a {@code ThreadPoolExecutor} see * {@link org.jgrapht.util.ConcurrencyUtil}. * * @param graph graph * @param executor executor which will be used for parallelization */ public TransitNodeRoutingPrecomputation(Graph graph, ThreadPoolExecutor executor) { this( new ContractionHierarchyPrecomputation<>(graph, executor).computeContractionHierarchy(), executor); } /** * Constructs an instance of the algorithm for the given {@code contractionHierarchy} and * {@code executor}. It is up to a user of this algorithm to handle the creation and termination * of the provided {@code executor}. Utility methods to manage a {@code ThreadPoolExecutor} see * {@link org.jgrapht.util.ConcurrencyUtil}. * * @param hierarchy contraction hierarchy * @param executor executor which will be used for parallelization */ public TransitNodeRoutingPrecomputation( ContractionHierarchy hierarchy, ThreadPoolExecutor executor) { this(hierarchy, (int) Math.sqrt(hierarchy.getGraph().vertexSet().size()), executor); } /** * Constructs an instance of the algorithm for a given {@code contractionHierarchy}, * {@code numberOfTransitVertices} and {@code executor}. It is up to a user of this algorithm to * handle the creation and termination of the provided {@code executor}. Utility methods to * manage a {@code ThreadPoolExecutor} see {@link org.jgrapht.util.ConcurrencyUtil}. * * @param hierarchy contraction hierarchy * @param numberOfTransitVertices number of transit vertices * @param executor executor which will be used for parallelization */ public TransitNodeRoutingPrecomputation( ContractionHierarchy hierarchy, int numberOfTransitVertices, ThreadPoolExecutor executor) { this(hierarchy, numberOfTransitVertices, PairingHeap::new, executor); } /** * Constructs an instance of the algorithm for a given {@code contractionHierarchy}, * {@code parallelism}, {@code numberOfTransitVertices}, {@code heapSupplier} and * {@code executor}. Heap provided by the {@code heapSupplier} is used which computing the * Voronoi diagram. It is up to a user of this algorithm to handle the creation and termination * of the provided {@code executor}. Utility methods to manage a {@code ThreadPoolExecutor} see * {@link org.jgrapht.util.ConcurrencyUtil}. * * @param hierarchy contraction hierarchy * @param numberOfTransitVertices number of transit vertices * @param heapSupplier supplier for preferable heap implementation * @param executor executor which will be used for parallelization */ public TransitNodeRoutingPrecomputation( ContractionHierarchy hierarchy, int numberOfTransitVertices, Supplier>> heapSupplier, ThreadPoolExecutor executor) { if (numberOfTransitVertices > hierarchy.getGraph().vertexSet().size()) { throw new IllegalArgumentException( "number of transit vertices is larger than the number of vertices in the graph"); } this.contractionHierarchy = hierarchy; this.contractionGraph = hierarchy.getContractionGraph(); this.contractionMapping = hierarchy.getContractionMapping(); this.numberOfTransitVertices = numberOfTransitVertices; this.parallelism = executor.getMaximumPoolSize(); this.heapSupplier = heapSupplier; this.contractionVertices = new ArrayList<>(Collections.nCopies(contractionGraph.vertexSet().size(), null)); this.manyToManyShortestPathsAlgorithm = new CHManyToManyShortestPaths<>(hierarchy); this.executor = executor; this.completionService = new ExecutorCompletionService<>(this.executor); } /** * Computes transit node routing based on {@code contractionHierarchy}. * * @return transit node routing */ public TransitNodeRouting computeTransitNodeRouting() { fillContractionVerticesList(); contractedTransitVerticesSet = selectTopKTransitVertices(numberOfTransitVertices); transitVerticesSet = contractedTransitVerticesSet .stream().map(v -> v.vertex).collect(Collectors.toCollection(HashSet::new)); transitVerticesList = new ArrayList<>(transitVerticesSet); VoronoiDiagramComputation voronoiDiagramComputation = new VoronoiDiagramComputation(); voronoiDiagram = voronoiDiagramComputation.computeVoronoiDiagram(); ManyToManyShortestPaths contractedPaths = manyToManyShortestPathsAlgorithm .getManyToManyPaths(transitVerticesSet, transitVerticesSet); transitVerticesPaths = unpackPaths(contractedPaths); Pair, LocalityFilter> avAndLf = computeAVAndLF(); return new TransitNodeRouting<>( contractionHierarchy, contractedTransitVerticesSet, transitVerticesPaths, voronoiDiagram, avAndLf.getFirst(), avAndLf.getSecond()); } /** * Fills {@code contractionVertices} with vertices from {@code contractionGraph}. For each * vertex its position in the list is equal to its {@code id}. */ private void fillContractionVerticesList() { for (ContractionVertex v : contractionGraph.vertexSet()) { contractionVertices.set(v.vertexId, v); } } /** * Unpacks in parallel contracted paths stored in {@code shortestPaths}. Unpacked path are * returned as {@link DefaultManyToManyShortestPathsImpl}. * * @param shortestPaths contracted many-to-many shortest paths * @return unpacked paths */ private ManyToManyShortestPaths unpackPaths(ManyToManyShortestPaths shortestPaths) { Map>> pathsMap = CollectionUtil.newHashMapWithExpectedSize(numberOfTransitVertices); for (V v : transitVerticesList) { pathsMap.put(v, CollectionUtil.newHashMapWithExpectedSize(numberOfTransitVertices)); } for (int taskId = 0; taskId < parallelism; ++taskId) { PathsUnpackingTask task = new PathsUnpackingTask(taskId, transitVerticesList, pathsMap, shortestPaths); completionService.submit(task, null); } waitForTasksCompletion(parallelism); return new DefaultManyToManyShortestPathsImpl<>( transitVerticesSet, transitVerticesSet, pathsMap); } /** * Selects top {@code numberOfTransitVertices} vertices in the contraction hierarchy as transit * vertices. * * @param numberOfTransitVertices number of transit vertices to select * @return set of transit vertices */ private Set> selectTopKTransitVertices(int numberOfTransitVertices) { int numberOfVertices = contractionGraph.vertexSet().size(); Set> result = CollectionUtil.newHashSetWithExpectedSize(numberOfTransitVertices); for (ContractionVertex vertex : contractionGraph.vertexSet()) { if (vertex.contractionLevel >= numberOfVertices - numberOfTransitVertices) { result.add(vertex); } } return result; } /** * Computes in parallel access vertices and locality filter for the transit node routing. * * @return pair of access vertices and locality filter. */ private Pair, LocalityFilter> computeAVAndLF() { LocalityFilterBuilder localityFilterBuilder = new LocalityFilterBuilder(contractionGraph.vertexSet().size()); AccessVerticesBuilder accessVerticesBuilder = new AccessVerticesBuilder(contractionGraph.vertexSet().size()); ContractionHierarchyBFS forwardBFS = new ContractionHierarchyBFS( new MaskSubgraph<>(contractionGraph, v -> false, e -> !e.isUpward)); ContractionHierarchyBFS backwardBFS = new ContractionHierarchyBFS( new MaskSubgraph<>( new EdgeReversedGraph<>(contractionGraph), v -> false, e -> e.isUpward)); for (int taskId = 0; taskId < parallelism; ++taskId) { AVAndLFConstructionTask task = new AVAndLFConstructionTask( taskId, localityFilterBuilder, accessVerticesBuilder, forwardBFS, backwardBFS); completionService.submit(task, null); } waitForTasksCompletion(parallelism); return Pair .of(accessVerticesBuilder.buildVertices(), localityFilterBuilder.buildLocalityFilter()); } /** * Takes {@code numberOfTasks} tasks from the {@code completionService}. * * @param numberOfTasks number of tasks */ private void waitForTasksCompletion(int numberOfTasks) { for (int i = 0; i < numberOfTasks; ++i) { try { completionService.take().get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } } /** * Algorithm which computes Voronoi diagram for the {@code contractionGraph}. It uses * {@code transitVertices} as Voronoi cells centers. To build the diagram runs a Dijkstra`s * algorithm with multiple sources on a reversed {@code graph}. Uses Voronoi cells centers as * initial sources. During the computations for each vertex maintains distance to the closest * cell center as well as the id if this cell center. */ private class VoronoiDiagramComputation { /** * Priority queue which stores vertices ordered by theirs distances to the corresponding * Voronoi cell center. */ private AddressableHeap> heap; /** * For every vertex added to the {@code heap} stores a corresponding handle. */ private Map, AddressableHeap.Handle>> seen; /** * For every vertex stores an id of a corresponding Voronoi cell center. */ private int[] voronoiCells; /** * For every vertex stores distance to the closest Voronoi cell center. */ private double[] distanceToCenter; /** * Constructs a new instance of the algorithm. */ VoronoiDiagramComputation() { this.heap = heapSupplier.get(); this.seen = new HashMap<>(); } /** * Computes Voronoi diagram for {@code graph}. * * @return Voronoi diagram */ VoronoiDiagram computeVoronoiDiagram() { int numberOfVertices = contractionGraph.vertexSet().size(); voronoiCells = new int[numberOfVertices]; distanceToCenter = new double[numberOfVertices]; Arrays.fill(voronoiCells, NO_VORONOI_CELL); Arrays.fill(distanceToCenter, Double.POSITIVE_INFINITY); // mask all shortcuts in the contraction graph Graph, ContractionEdge> searchGraph = new EdgeReversedGraph<>( new MaskSubgraph<>(contractionGraph, v -> false, e -> e.edge == null)); for (ContractionVertex transitVertex : contractedTransitVerticesSet) { updateDistance(transitVertex, transitVertex, 0.0); } while (!heap.isEmpty()) { AddressableHeap.Handle> entry = heap.deleteMin(); double distance = entry.getKey(); ContractionVertex v = entry.getValue(); for (ContractionEdge edge : searchGraph.outgoingEdgesOf(v)) { ContractionVertex successor = Graphs.getOppositeVertex(searchGraph, edge, v); double updatedDistance = distance + searchGraph.getEdgeWeight(edge); if (updatedDistance < distanceToCenter[successor.vertexId]) { updateDistance(successor, v, updatedDistance); } } } return new VoronoiDiagram<>(voronoiCells); } /** * If necessary updates distance of the {@code vertex} in the {@code heap}. * * @param vertex vertex * @param predecessor predecessor of {@code vertex} in the shortest paths tree * @param distance distance to vertex */ private void updateDistance( ContractionVertex vertex, ContractionVertex predecessor, double distance) { AddressableHeap.Handle> handle = seen.get(vertex); if (handle == null) { handle = heap.insert(distance, vertex); seen.put(vertex, handle); visitVertex(vertex, predecessor, distance); } else if (distance < handle.getKey()) { handle.decreaseKey(distance); handle.setValue(handle.getValue()); visitVertex(vertex, predecessor, distance); } } /** * If necessary updates Voronoi cell id and distance in {@code voronoiCells} and * {@code distanceToCenter} for vertex. * * @param vertex vertex * @param predecessor predecessor of {@code vertex} in the shortest paths tree * @param distance distance to vertex */ private void visitVertex( ContractionVertex vertex, ContractionVertex predecessor, double distance) { int updatedVoronoiCell; if (vertex.vertexId == predecessor.vertexId) { updatedVoronoiCell = vertex.vertexId; } else { updatedVoronoiCell = this.voronoiCells[predecessor.vertexId]; } this.voronoiCells[vertex.vertexId] = updatedVoronoiCell; this.distanceToCenter[vertex.vertexId] = distance; } } /** * BFS algorithm which is used to compute access vertices and locality filter. Runs a CH BFS * query over contractionGraph. Does not traverse edges leaving transit vertices. Reports all * traversed transit vertices as access vertices. For every traversed non-transit vertex reports * a corresponding Voronoi cell id. Those ids are then used to construct locality filter. */ private class ContractionHierarchyBFS { /** * Search graph. */ private Graph, ContractionEdge> contractionGraph; /** * Constructs a new instance of the algorithm for the given {@code graph}. * * @param contractionGraph contraction graph */ public ContractionHierarchyBFS( Graph, ContractionEdge> contractionGraph) { this.contractionGraph = contractionGraph; } /** * Runs a forward CH BFS query to calculate access vertices and ids of visited Voronoi * cells. * * @param vertex search starting vertex * @return access vertices and visited Voronoi cells ids */ public Pair, Set> runSearch(ContractionVertex vertex) { Set accessVertices = new HashSet<>(); Set visitedVoronoiCells = new HashSet<>(); Set visitedVerticesIds = new HashSet<>(); Queue> queue = new LinkedList<>(); queue.add(vertex); while (!queue.isEmpty()) { ContractionVertex v = queue.remove(); visitedVerticesIds.add(v.vertexId); if (contractedTransitVerticesSet.contains(v)) { accessVertices.add(v.vertex); } else { visitedVoronoiCells.add(voronoiDiagram.getVoronoiCellId(v)); for (ContractionEdge e : contractionGraph.outgoingEdgesOf(v)) { ContractionVertex successor = Graphs.getOppositeVertex(contractionGraph, e, v); if (!visitedVerticesIds.contains(successor.vertexId)) { queue.add(successor); } } } } return Pair.of(accessVertices, visitedVoronoiCells); } } /** * Provides API to build an {@code AccessVertices} object. */ private class AccessVerticesBuilder { /** * For every vertex in {@code contractionGraph} stores a list of forward access vertices. Id * of a contracted vertex is equal to the index in this list, at which corresponding access * vertices are stored. */ private List>> forwardAccessVertices; /** * For every vertex in {@code contractionGraph} stores a list of backward access vertices. * Id of a contracted vertex is equal to the index in this list, at which corresponding * access vertices are stored. */ private List>> backwardAccessVertices; /** * Constructs an instance for the given {@code numberOfVertices}. * * @param numberOfVertices number of vertices in a m graph */ public AccessVerticesBuilder(int numberOfVertices) { this.forwardAccessVertices = new ArrayList<>(numberOfVertices); this.backwardAccessVertices = new ArrayList<>(numberOfVertices); for (int i = 0; i < numberOfVertices; ++i) { forwardAccessVertices.add(new ArrayList<>()); backwardAccessVertices.add(new ArrayList<>()); } } /** * Builds a new instance of {@code AccessVertices} using {@code forwardAccessVertices} and * {@code backwardAccessVertices}. * * @return access vertices */ public AccessVertices buildVertices() { return new AccessVertices<>(forwardAccessVertices, backwardAccessVertices); } /** * Computes a list of forward access vertices for {@code v} using {@code vertices} and adds * them to the {@code forwardAccessVertices}. * * @param v vertex * @param vertices transit vertices */ public void addForwardAccessVertices(ContractionVertex v, Set vertices) { ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths manyToManyShortestPaths = manyToManyShortestPathsAlgorithm .getManyToManyPaths(Collections.singleton(v.vertex), vertices); Set prunedVertices = getPrunedAccessVertices(v.vertex, vertices, manyToManyShortestPaths, true); List> accessVerticesList = forwardAccessVertices.get(v.vertexId); for (V unpackedVertex : vertices) { if (!prunedVertices.contains(unpackedVertex)) { accessVerticesList .add( new AccessVertex<>( unpackedVertex, manyToManyShortestPaths.getPath(v.vertex, unpackedVertex))); } } } /** * Computes a list of backward access vertices for {@code v} using {@code vertices} and adds * them to the {@code backwardAccessVertices}. * * @param v vertex * @param vertices transit vertices */ public void addBackwardAccessVertices(ContractionVertex v, Set vertices) { ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths manyToManyShortestPaths = manyToManyShortestPathsAlgorithm .getManyToManyPaths(vertices, Collections.singleton(v.vertex)); Set prunedVertices = getPrunedAccessVertices(v.vertex, vertices, manyToManyShortestPaths, false); List> accessVerticesList = backwardAccessVertices.get(v.vertexId); for (V unpackedVertex : vertices) { if (!prunedVertices.contains(unpackedVertex)) { accessVerticesList .add( new AccessVertex<>( unpackedVertex, manyToManyShortestPaths.getPath(unpackedVertex, v.vertex))); } } } /** * Selects redundant access vertices from {@code vertices}. * * @param v vertex * @param vertices transit vertices * @param manyToManyShortestPaths transit vertices paths * @param forwardAccessVertices if {@code vertices} are forward access vertices for not wrt * {@code v} * @return redundant access vertices */ private Set getPrunedAccessVertices( V v, Set vertices, ManyToManyShortestPaths manyToManyShortestPaths, boolean forwardAccessVertices) { Set result = new HashSet<>(); for (V v1 : vertices) { if (!result.contains(v1)) { for (V v2 : vertices) { if (!v1.equals(v2) && !result.contains(v2)) { if (forwardAccessVertices) { if (manyToManyShortestPaths.getWeight(v, v1) + transitVerticesPaths .getWeight(v1, v2) <= manyToManyShortestPaths.getWeight(v, v2)) { result.add(v2); } } else { if (transitVerticesPaths.getWeight(v2, v1) + manyToManyShortestPaths .getWeight(v1, v) <= manyToManyShortestPaths.getWeight(v2, v)) { result.add(v2); } } } } } } return result; } } /** * Provides API to build a {@code LocalityFilter} object. */ private class LocalityFilterBuilder { /** * Visited Voronoi cells by a forward {@code ContractionHierarchyBFS} search. */ private List> visitedForwardVoronoiCells; /** * Visited Voronoi cells by a backward {@code ContractionHierarchyBFS} search. */ private List> visitedBackwardVoronoiCells; /** * Constructs an instance for the given {@code numberOfVertices}. * * @param numberOfVertices number of vertices in graph */ public LocalityFilterBuilder(int numberOfVertices) { this.visitedForwardVoronoiCells = new ArrayList<>(numberOfVertices); this.visitedBackwardVoronoiCells = new ArrayList<>(numberOfVertices); for (int i = 0; i < numberOfVertices; ++i) { visitedForwardVoronoiCells.add(null); visitedBackwardVoronoiCells.add(null); } } /** * Adds {@code visitedVoronoiCells} to this builder in the forward direction for * {@code vertex}. * * @param vertex vertex * @param visitedVoronoiCells visited Voronoi cells */ public void addForwardVisitedVoronoiCells( ContractionVertex vertex, Set visitedVoronoiCells) { this.visitedForwardVoronoiCells.set(vertex.vertexId, visitedVoronoiCells); } /** * Adds {@code visitedVoronoiCells} to this builder in the backward direction for * {@code vertex}. * * @param vertex vertex * @param visitedVoronoiCells visited Voronoi cells */ public void addBackwardVisitedVoronoiCells( ContractionVertex vertex, Set visitedVoronoiCells) { this.visitedBackwardVoronoiCells.set(vertex.vertexId, visitedVoronoiCells); } /** * Builds an instance of {@code LocalityFilter} using {@code visitedForwardVoronoiCells} and * {@code visitedBackwardVoronoiCells}. * * @return locality filter */ public LocalityFilter buildLocalityFilter() { return new LocalityFilter<>( contractionMapping, visitedForwardVoronoiCells, visitedBackwardVoronoiCells); } } /** * This class represents return type of this algorithm and contains all data computed during the * execution of the algorithm. Formally it consists of: * *

      *
    • {@link ContractionHierarchy} which was used to compute this transit node routing;
    • *
    • set of selected transit vertices;
    • *
    • {@link ManyToManyShortestPaths} between transit vertices;
    • *
    • {@link VoronoiDiagram} computed using transit vertices a cell centers;
    • *
    • {@link AccessVertices};
    • *
    • {@link LocalityFilter}.
    • *
    * * @param graph vertex type * @param graph edge type */ static class TransitNodeRouting { /** * Contraction hierarchy based on which this transit node routing was computed. */ private ContractionHierarchy contractionHierarchy; /** * Selected transit vertices. */ private Set> transitVertices; /** * Paths between every pair of transit vertices. */ private ManyToManyShortestPaths transitVerticesPaths; /** * Voronoi diagram of the graph using {@code transitVertices} as cells centers. */ private VoronoiDiagram voronoiDiagram; /** * Forward and backward access vertices for every vertex in the contraction graph. */ private AccessVertices accessVertices; /** * Locality filter of this transit node routing. */ private LocalityFilter localityFilter; /** * Returns contraction hierarchy of this transit node routing. * * @return contraction hierarchy of this transit node routing */ public ContractionHierarchy getContractionHierarchy() { return contractionHierarchy; } /** * Returns transit vertices of this transit node routing. * * @return transit vertices of this transit node routing */ public Set> getTransitVertices() { return transitVertices; } /** * Returns paths between every pair of {@code transitVertices}. * * @return paths between every pair of {@code transitVertices} */ public ManyToManyShortestPaths getTransitVerticesPaths() { return transitVerticesPaths; } /** * Returns Voronoi diagram of this transit node routing. * * @return Voronoi diagram of this transit node routing */ public VoronoiDiagram getVoronoiDiagram() { return voronoiDiagram; } /** * Returns access vertices of this transit node routing. * * @return access vertices of this transit node routing */ public AccessVertices getAccessVertices() { return accessVertices; } /** * Returns locality filter of this transit node routing. * * @return locality filter of this transit node routing */ public LocalityFilter getLocalityFilter() { return localityFilter; } /** * Constructs a new instance for the given {@code contractionHierarchy}, * {@code transitVertices}, {@code transitVerticesPaths}, {@code voronoiDiagram}, * {@code accessVertices} and {@code localityFilter}. * * @param contractionHierarchy contraction hierarchy * @param transitVertices transit vertices * @param transitVerticesPaths paths between transit vertices * @param voronoiDiagram Voronoi diagram * @param accessVertices access vertices * @param localityFilter locality filter */ public TransitNodeRouting( ContractionHierarchy contractionHierarchy, Set> transitVertices, ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths transitVerticesPaths, VoronoiDiagram voronoiDiagram, AccessVertices accessVertices, LocalityFilter localityFilter) { this.contractionHierarchy = contractionHierarchy; this.transitVertices = transitVertices; this.transitVerticesPaths = transitVerticesPaths; this.voronoiDiagram = voronoiDiagram; this.localityFilter = localityFilter; this.accessVertices = accessVertices; } } /** * Voronoi diagram for a graph. Formally each cell in the diagram is defined as $Vor(v) = \{u ∈ * V : ∀w ∈ T$ \ $ \{v\} : \mu(u, v) ≤ \mu(u, w)\}$, where $V$ is the vertex set, $T$ is a set * of vertaccess verticesices representing Voronoi cells centers and $\mu(u,v)$ denotes distance * between vertices $u$ and $v$. * * @param graph vertex type */ public static class VoronoiDiagram { /** * For each vertex in {@code contractionGraph} contains id of its Voronoi cell, or * {@code NO_VORONOI_CELL} if it does not belong to any cell. */ private int[] voronoiCells; /** * Constructs a new instance for the given {@code voronoiCells}. * * @param voronoiCells Voronoi cells ids */ public VoronoiDiagram(int[] voronoiCells) { this.voronoiCells = voronoiCells; } /** * Returns Voronoi cell id which corresponds to {@code vertex}. * * @param vertex vertex * @return Voronoi cell id */ public int getVoronoiCellId(ContractionVertex vertex) { return voronoiCells[vertex.vertexId]; } } /** * Search space based locality filter. * *

    * Formally a locality filter is defined as $L : V × V → \{true, false\}$. $L(s, t)$ must be * $true$ when no shortest path between $s$ and $t$ contains a transit vertex. * *

    * For every vertex in the {@code contractionGraph} stores two sets of visited Voronoi cells by * forward and backward {@code ContractionHierarchyBFS}. * * @param graph vertex type */ public static class LocalityFilter { /** * Mapping of vertices in the initial graph to the vertices in the contraction graph. */ private Map> contractionMapping; /** * For every vertex in the contraction graph stores visited Voronoi cells ids by a forward * {@code ContractionHierarchyBFS}. */ private List> visitedForwardVoronoiCells; /** * For every vertex in the contraction graph stores visited Voronoi cells ids by a backward * {@code ContractionHierarchyBFS}. */ private List> visitedBackwardVoronoiCells; /** * Constructs a new instance for the given {@code contractionMapping}, * {@code visitedForwardVoronoiCells} and {@code visitedBackwardVoronoiCells}. * * @param contractionMapping contraction mapping * @param visitedForwardVoronoiCells visited Voronoi cells ids by a forward search * @param visitedBackwardVoronoiCells visited Voronoi cells ids by a backward search */ public LocalityFilter( Map> contractionMapping, List> visitedForwardVoronoiCells, List> visitedBackwardVoronoiCells) { this.contractionMapping = contractionMapping; this.visitedForwardVoronoiCells = visitedForwardVoronoiCells; this.visitedBackwardVoronoiCells = visitedBackwardVoronoiCells; } /** * Returns $true$ when no shortest paths between {@code source} and {@code sink} contains a * transit vertex. * * @param source source vertex * @param sink sink vertex * @return $true$ iff no shortest paths between {@code source} and {@code sink} contains a * transit vertex */ public boolean isLocal(V source, V sink) { ContractionVertex contractedSource = contractionMapping.get(source); ContractionVertex contractedSink = contractionMapping.get(sink); Set sourceVisitedVoronoiCells = visitedForwardVoronoiCells.get(contractedSource.vertexId); Set sinkVisitedVoronoiCells = visitedBackwardVoronoiCells.get(contractedSink.vertexId); if (sourceVisitedVoronoiCells.contains(NO_VORONOI_CELL) || sinkVisitedVoronoiCells.contains(NO_VORONOI_CELL)) { return true; } Set smallerSet; Set largerSet; if (sourceVisitedVoronoiCells.size() <= sinkVisitedVoronoiCells.size()) { smallerSet = sourceVisitedVoronoiCells; largerSet = sinkVisitedVoronoiCells; } else { smallerSet = sinkVisitedVoronoiCells; largerSet = sourceVisitedVoronoiCells; } for (Integer visitedVoronoiCell : smallerSet) { if (largerSet.contains(visitedVoronoiCell)) { return true; } } return false; } } /** * Stores forward and backward access vertices computed for the transit node routing. * * @param graph vertex type * @param graph edge type */ public static class AccessVertices { /** * For each vertex in {@code contractionGraph} stores corresponding forward access vertices. */ private List>> forwardAccessVertices; /** * For each vertex in {@code contractionGraph} stores corresponding backward access * vertices. */ private List>> backwardAccessVertices; /** * Constructs a new instance for the given {@code forwardAccessVertices} and * {@code backwardAccessVertices}. * * @param forwardAccessVertices forward access vertices * @param backwardAccessVertices backward access vertices */ public AccessVertices( List>> forwardAccessVertices, List>> backwardAccessVertices) { this.forwardAccessVertices = forwardAccessVertices; this.backwardAccessVertices = backwardAccessVertices; } /** * Given a contraction vertex {@code vertex} returns its forward access vertices * * @param vertex vertex * @return list of forward access vertices */ public List> getForwardAccessVertices(ContractionVertex vertex) { return forwardAccessVertices.get(vertex.vertexId); } /** * Given a contraction vertex {@code vertex} returns its backward access vertices * * @param vertex vertex * @return list of backward access vertices */ public List> getBackwardAccessVertices(ContractionVertex vertex) { return backwardAccessVertices.get(vertex.vertexId); } } /** * Forward or backward access vertex computed for a certain vertex $v$ in the graph. *

    * In the transit node routing if $u$ is a forward access vertex for $v$, it means that if you * want go far away from $v$, it is highly likely that you would need to pass through $u$. * Correspondingly, if $u$ is a backward access vertex for $v$, it means that if you want to go * to $v$ from far away, you would highly likely go through $u$. * *

    * Stores transit vertex and the shortest path between $v$ and {@code vertex}. If this is a * forward access vertex, then {@code vertex} is the ending vertex in the {@code path}, * Otherwise it is a starting vertex of the {@code path}. * * @param graph vertex type * @param graph edge type */ public static class AccessVertex { /** * Transit vertex. */ private V vertex; /** * Path between a vertex $v$ this access vertex is computed for and {@code vertex}. */ private GraphPath path; /** * Returns a transit vertex of this access vertex. * * @return transit vertex of this access vertex */ public V getVertex() { return vertex; } /** * Returns path between a vertex in the graph and {@code vertex}. * * @return path between a vertex in the graph and {@code vertex}. */ public GraphPath getPath() { return path; } /** * Constructs a new instance for the given {@code vertex} and {@code path}. * * @param vertex a transit vertex * @param path path between a vertex in the graph and {@code vertex} */ public AccessVertex(V vertex, GraphPath path) { this.vertex = vertex; this.path = path; } } /** * Task which is used to perform {@code ContractionHierarchyBFS} in parallel. */ private class AVAndLFConstructionTask implements Runnable { /** * Id of this task. */ private int taskId; /** * Builder object for a {@code LocalityFilter} instance. */ private LocalityFilterBuilder localityFilterBuilder; /** * Builder object for a {@code AccessVertices} instance. */ private AccessVerticesBuilder accessVerticesBuilder; /** * Is used to run forward CH BFS query over the {@code contractionGraph}. */ private ContractionHierarchyBFS forwardBFS; /** * Is used to run backward CH BFS query over the {@code contractionGraph}. */ private ContractionHierarchyBFS backwardBFS; /** * Constructs a new instance for the give {@code taskId}, {@code localityFilterBuilder}, * {@code accessVerticesBuilder}, {@code forwardBFS} and {@code backwardBFS}. * * @param taskId id of this task * @param localityFilterBuilder builder object for {@code LocalityFilter} * @param accessVerticesBuilder builder object for {@code AccessVertices} * @param forwardBFS forward {@code ContractionHierarchyBFS} * @param backwardBFS backward {@code ContractionHierarchyBFS} */ public AVAndLFConstructionTask( int taskId, LocalityFilterBuilder localityFilterBuilder, AccessVerticesBuilder accessVerticesBuilder, ContractionHierarchyBFS forwardBFS, ContractionHierarchyBFS backwardBFS) { this.taskId = taskId; this.localityFilterBuilder = localityFilterBuilder; this.accessVerticesBuilder = accessVerticesBuilder; this.forwardBFS = forwardBFS; this.backwardBFS = backwardBFS; } @Override public void run() { int start = workerSegmentStart(0, contractionVertices.size(), taskId); int end = workerSegmentEnd(0, contractionVertices.size(), taskId); for (int i = start; i < end; ++i) { ContractionVertex v = contractionVertices.get(i); Pair, Set> forwardData = forwardBFS.runSearch(v); Pair, Set> backwardData = backwardBFS.runSearch(v); accessVerticesBuilder.addForwardAccessVertices(v, forwardData.getFirst()); accessVerticesBuilder.addBackwardAccessVertices(v, backwardData.getFirst()); localityFilterBuilder.addForwardVisitedVoronoiCells(v, forwardData.getSecond()); localityFilterBuilder.addBackwardVisitedVoronoiCells(v, backwardData.getSecond()); } } } /** * Task which is used to unpack contracted many-to-many shortest paths between transit vertices. */ private class PathsUnpackingTask implements Runnable { /** * Id of this task. */ private int taskId; /** * Selected transit vertices. */ private List transitVertices; /** * Map where the unpacked paths will be stored. */ private Map>> pathsMap; /** * Many-to-many shortest paths to be unpacked. */ private ManyToManyShortestPaths shortestPaths; /** * Constructs a new instance for the given {@code taskId}, {@code transitVertices}, * {@code pathsMap} and {@code shortestPaths}. * * @param taskId id of this task * @param transitVertices transit vertices * @param pathsMap map for unpacked paths * @param shortestPaths paths to be unpacked */ public PathsUnpackingTask( int taskId, List transitVertices, Map>> pathsMap, ManyToManyShortestPaths shortestPaths) { this.taskId = taskId; this.transitVertices = transitVertices; this.pathsMap = pathsMap; this.shortestPaths = shortestPaths; } @Override public void run() { int start = workerSegmentStart(0, transitVertices.size(), taskId); int end = workerSegmentEnd(0, transitVertices.size(), taskId); for (int i = start; i < end; ++i) { V v1 = transitVertices.get(i); Map> targetToPathsMap = pathsMap.get(v1); for (V v2 : transitVertices) { targetToPathsMap.put(v2, shortestPaths.getPath(v1, v2)); } } } } /** * Computes start of the working chunk for this task. * * @param segmentStart working segment start * @param segmentEnd working segment end * @return working chunk start */ private int workerSegmentStart(int segmentStart, int segmentEnd, int taskId) { return segmentStart + ((segmentEnd - segmentStart) * taskId) / parallelism; } /** * Computes end of the working chunk for this task. * * @param segmentStart working segment start * @param segmentEnd working segment end * @return working chunk end */ private int workerSegmentEnd(int segmentStart, int segmentEnd, int taskId) { return segmentStart + ((segmentEnd - segmentStart) * (taskId + 1)) / parallelism; } } TransitNodeRoutingShortestPath.java000066400000000000000000000306721402514743400363270ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.Graph; import org.jgrapht.GraphPath; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm; import org.jgrapht.alg.util.Pair; import org.jgrapht.graph.GraphWalk; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.concurrent.ThreadPoolExecutor; import static org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionHierarchy; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionVertex; import static org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.AccessVertex; import static org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.AccessVertices; import static org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.LocalityFilter; import static org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.TransitNodeRouting; /** * Implementation of the shortest paths algorithm based on {@link TransitNodeRoutingPrecomputation}. * *

    * The algorithm is originally described the article: Arz, Julian & Luxen, Dennis & Sanders, * Peter. (2013). Transit Node Routing Reconsidered. 7933. 10.1007/978-3-642-38527-8_7. * *

    * The shortest paths between vertices $u$ and $v$ is computed in the following way. First, a * locality filter is used to determine if the vertices are local to each other. If so, a fallback * shortest path algorithm is used to compute the path. Otherwise, there is a shortest path between * the vertices which contains a transit vertex. Therefore the forward access vertices of $u$ and * backward access vertices of $v$ are inspected to find a pair of such access vertices $(a_u, a_v)$ * so that the value of $d(u,a_u) + d(a_u, a_v) + d(a_u, v)$ is minimum over all such pairs. Here * $d(s,t)$ is the distance from vertex $s$ to vertex $t$. * *

    * The algorithm is designed to operate on sparse graphs with low average outdegree. Comparing to * {@link ContractionHierarchyBidirectionalDijkstra} it uses significantly more time on the * precomputation stage. Because of that it makes sense to use this algorithm on large instances * (i.e. with more than 10.000 vertices), where it shows substantially better performance results * than {@link ContractionHierarchyBidirectionalDijkstra}. Typically this algorithm is used as the * backend for large scale shortest path search engines, e.g. * OpenStreetMap. * *

    * The precomputation in this algorithm is performed in a lazy fashion. It can be performed by * directly calling the {@code #performPrecomputation()} method. Otherwise, this method is called * during the first call to either the {@code #getPath()} or {@code #getPathWeight()} methods. * * @param graph vertex type * @param graph edge type * @author Semen Chudakov * @see TransitNodeRoutingPrecomputation * @see BidirectionalDijkstraShortestPath */ public class TransitNodeRoutingShortestPath extends BaseShortestPathAlgorithm { /** * Executor which is used for parallelization in this algorithm. */ private ThreadPoolExecutor executor; /** * Contraction hierarchy which is used to compute shortest paths. */ private ContractionHierarchy contractionHierarchy; /** * Fallback shortest path algorithm for local queries. */ private ShortestPathAlgorithm localQueriesAlgorithm; /** * Many-to-many shortest paths between transit vertices. */ private ManyToManyShortestPaths manyToManyShortestPaths; /** * Stores access vertices for each vertex in the {@code graph}. */ private AccessVertices accessVertices; /** * Locality filter which is used to determine if two vertices in the graph are local to each * other or not. */ private LocalityFilter localityFilter; /** * Constructs a new instance for the given {@code graph} and {@code executor}. It is up to a * user of this algorithm to handle the creation and termination of the provided * {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see * {@link org.jgrapht.util.ConcurrencyUtil}. * * @param graph graph * @param executor executor which will be used for computing {@code TransitNodeRouting} */ public TransitNodeRoutingShortestPath(Graph graph, ThreadPoolExecutor executor) { super(graph); this.executor = Objects.requireNonNull(executor, "executor cannot be null!"); } /** * Constructs a new instance of the algorithm for a given {@code transitNodeRouting}. * * @param transitNodeRouting transit node routing for {@code graph} */ TransitNodeRoutingShortestPath(TransitNodeRouting transitNodeRouting) { super(transitNodeRouting.getContractionHierarchy().getGraph()); initialize(transitNodeRouting); } /** * This method performs precomputation for this algorithm in the lazy fashion. The result of the * precomputation stage is the {@code TransitNodeRouting} object which contains * {@code #contractionHierarchy}, {@code #localityFilter}, {@code #accessVertices} and * {@code #manyToManyShortestPaths} objects for this algorithm. If not called directly this * method will be invoked in either of {@code getPath()} or {@code getPathWeight()} methods. */ public void performPrecomputation() { if (contractionHierarchy != null) { return; } TransitNodeRouting routing = new TransitNodeRoutingPrecomputation<>(graph, executor).computeTransitNodeRouting(); initialize(routing); } /** * Initializes fields {@code contractionHierarchy}, {@code localityFilter}, * {@code accessVertices}, {@code manyToManyShortestPaths} and {@code localQueriesAlgorithm}. * * @param transitNodeRouting transit node routing. */ private void initialize(TransitNodeRouting transitNodeRouting) { this.contractionHierarchy = transitNodeRouting.getContractionHierarchy(); this.localityFilter = transitNodeRouting.getLocalityFilter(); this.accessVertices = transitNodeRouting.getAccessVertices(); this.manyToManyShortestPaths = transitNodeRouting.getTransitVerticesPaths(); this.localQueriesAlgorithm = new ContractionHierarchyBidirectionalDijkstra<>( transitNodeRouting.getContractionHierarchy()); } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V sink) { performPrecomputation(); if (localityFilter.isLocal(source, sink)) { return localQueriesAlgorithm.getPath(source, sink); } else { Pair, AccessVertex> p = getMinWeightAccessVertices(source, sink); AccessVertex forwardAccessVertex = p.getFirst(); AccessVertex backwardAccessVertex = p.getSecond(); if (forwardAccessVertex == null) { return createEmptyPath(source, sink); } return mergePaths( forwardAccessVertex.getPath(), manyToManyShortestPaths .getPath(forwardAccessVertex.getVertex(), backwardAccessVertex.getVertex()), backwardAccessVertex.getPath()); } } /** * {@inheritDoc} */ @Override public double getPathWeight(V source, V sink) { performPrecomputation(); if (localityFilter.isLocal(source, sink)) { return localQueriesAlgorithm.getPathWeight(source, sink); } else { Pair, AccessVertex> p = getMinWeightAccessVertices(source, sink); AccessVertex forwardAccessVertex = p.getFirst(); AccessVertex backwardAccessVertex = p.getSecond(); if (forwardAccessVertex == null) { return Double.POSITIVE_INFINITY; } return forwardAccessVertex.getPath().getWeight() + manyToManyShortestPaths .getWeight(forwardAccessVertex.getVertex(), backwardAccessVertex.getVertex()) + backwardAccessVertex.getPath().getWeight(); } } /** * For vertices {@code source} and {@code sink} finds pair of access vertices with smallest * weight over all pairs. * * @param source source vertex * @param sink sink vertex * @return pair of access vertices with shortest path between them */ private Pair, AccessVertex> getMinWeightAccessVertices( V source, V sink) { ContractionVertex contractedSource = contractionHierarchy.getContractionMapping().get(source); ContractionVertex contractedSink = contractionHierarchy.getContractionMapping().get(sink); AccessVertex forwardAccessVertex = null; AccessVertex backwardAccessVertex = null; double minimumWeight = Double.POSITIVE_INFINITY; for (AccessVertex sourceAccessVertex : accessVertices .getForwardAccessVertices(contractedSource)) { for (AccessVertex sinkAccessVertex : accessVertices .getBackwardAccessVertices(contractedSink)) { double currentWeight = sourceAccessVertex.getPath().getWeight() + manyToManyShortestPaths .getWeight(sourceAccessVertex.getVertex(), sinkAccessVertex.getVertex()) + sinkAccessVertex.getPath().getWeight(); if (currentWeight < minimumWeight) { minimumWeight = currentWeight; forwardAccessVertex = sourceAccessVertex; backwardAccessVertex = sinkAccessVertex; } } } if (minimumWeight == Double.POSITIVE_INFINITY) { return new Pair<>(null, null); } return Pair.of(forwardAccessVertex, backwardAccessVertex); } /** * Computes a path which consists of {@code first}, {@code second} and {@code third} paths. * * @param first first part of the path * @param second second part of the path * @param third third part of the path * @return merged path */ private GraphPath mergePaths( GraphPath first, GraphPath second, GraphPath third) { V startVertex = first.getStartVertex(); V endVertex = third.getEndVertex(); double totalWeight = first.getWeight() + second.getWeight() + third.getWeight(); int vertexListSize = first.getVertexList().size() + second.getVertexList().size() + third.getVertexList().size() - 2; List vertexList = new ArrayList<>(vertexListSize); int edgeListSize = first.getLength() + second.getLength() + third.getLength(); List edgeList = new ArrayList<>(edgeListSize); // form vertex list Iterator firstIt = first.getVertexList().iterator(); while (firstIt.hasNext()) { V element = firstIt.next(); if (firstIt.hasNext()) { vertexList.add(element); } } vertexList.addAll(second.getVertexList()); Iterator thirdIt = third.getVertexList().iterator(); thirdIt.next(); while (thirdIt.hasNext()) { vertexList.add(thirdIt.next()); } // form edge list edgeList.addAll(first.getEdgeList()); edgeList.addAll(second.getEdgeList()); edgeList.addAll(third.getEdgeList()); return new GraphWalk<>(graph, startVertex, endVertex, vertexList, edgeList, totalWeight); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/TreeMeasurer.java000066400000000000000000000061721402514743400326340ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.util.*; /** * Algorithm class which computes a number of distance related metrics for trees. * * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public class TreeMeasurer { /* Input graph */ private final Graph graph; /** * Constructs a new instance of TreeMeasurer. * * @param graph input graph * @throws NullPointerException if {@code graph} is {@code null} */ public TreeMeasurer(Graph graph) { this.graph = Objects.requireNonNull(graph); } private V computeFarthestVertex(BreadthFirstIterator bfs) { V farthest = null; int dist = Integer.MIN_VALUE; while (bfs.hasNext()) { V v = bfs.next(); int depth = bfs.getDepth(v); if (dist < depth) { farthest = v; dist = depth; } } return farthest; } /** * Compute the graph center. The * center of a graph is the set of vertices of graph eccentricity equal to the graph radius. * *

    * Note: The input graph must be undirected. *

    * * @return the graph center * @throws IllegalArgumentException if {@code graph} is not undirected */ public Set getGraphCenter() { GraphTests.requireUndirected(graph); if (graph.vertexSet().isEmpty()) return new LinkedHashSet<>(); V r = graph.vertexSet().iterator().next(); V v1 = computeFarthestVertex(new BreadthFirstIterator<>(graph, r)); BreadthFirstIterator bfs = new BreadthFirstIterator<>(graph, v1); V v2 = computeFarthestVertex(bfs); List diameterPath = new ArrayList<>(); do { diameterPath.add(v2); v2 = bfs.getParent(v2); } while (v2 != null); Set graphCenter; if (diameterPath.size() % 2 == 1) graphCenter = Collections.singleton(diameterPath.get(diameterPath.size() / 2)); else { graphCenter = CollectionUtil.newLinkedHashSetWithExpectedSize(2); graphCenter.add(diameterPath.get(diameterPath.size() / 2)); graphCenter.add(diameterPath.get(diameterPath.size() / 2 - 1)); } return graphCenter; } } TreeSingleSourcePathsImpl.java000066400000000000000000000110521402514743400352070ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.io.*; import java.util.*; /** * An implementation of {@link SingleSourcePaths} which uses linear space. * *

    * This implementation uses the traditional representation of maintaining for each vertex the * predecessor in the shortest path tree. In order to keep space to linear, the paths are recomputed * in each invocation of the {@link #getPath(Object)} method. The complexity of * {@link #getPath(Object)} is linear to the number of edges of the path while the complexity of * {@link #getWeight(Object)} is $O(1)$. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class TreeSingleSourcePathsImpl implements SingleSourcePaths, Serializable { private static final long serialVersionUID = -5914007312734512847L; /** * The graph */ protected Graph g; /** * The source vertex */ protected V source; /** * A map which keeps for each target vertex the predecessor edge and the total length of the * shortest path. */ protected Map> map; /** * Construct a new instance. * * @param g the graph * @param source the source vertex * @param distanceAndPredecessorMap a map which contains for each vertex the distance and the * last edge that was used to discover the vertex. The map does not need to contain any * entry for the source vertex. In case it does contain the predecessor at the source * vertex must be null. */ public TreeSingleSourcePathsImpl( Graph g, V source, Map> distanceAndPredecessorMap) { this.g = Objects.requireNonNull(g, "Graph is null"); this.source = Objects.requireNonNull(source, "Source vertex is null"); this.map = Objects .requireNonNull(distanceAndPredecessorMap, "Distance and predecessor map is null"); } /** * {@inheritDoc} */ @Override public Graph getGraph() { return g; } /** * {@inheritDoc} */ @Override public V getSourceVertex() { return source; } /** * Get the internal map used for representing the paths. * * @return the internal distance and predecessor map used for representing the paths. */ public Map> getDistanceAndPredecessorMap() { return Collections.unmodifiableMap(map); } /** * {@inheritDoc} */ @Override public double getWeight(V targetVertex) { Pair p = map.get(targetVertex); if (p == null) { if (source.equals(targetVertex)) { return 0d; } else { return Double.POSITIVE_INFINITY; } } else { return p.getFirst(); } } /** * {@inheritDoc} */ @Override public GraphPath getPath(V targetVertex) { if (source.equals(targetVertex)) { return GraphWalk.singletonWalk(g, source, 0d); } LinkedList edgeList = new LinkedList<>(); V cur = targetVertex; Pair p = map.get(cur); if (p == null || p.getFirst().equals(Double.POSITIVE_INFINITY)) { return null; } double weight = 0d; while (p != null && !cur.equals(source)) { E e = p.getSecond(); if (e == null) { break; } edgeList.addFirst(e); weight += g.getEdgeWeight(e); cur = Graphs.getOppositeVertex(g, e, cur); p = map.get(cur); } return new GraphWalk<>(g, source, targetVertex, null, edgeList, weight); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/YenKShortestPath.java000066400000000000000000000072401402514743400334450ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Implementation of Yen`s algorithm for finding $k$ shortest loopless paths. * *

    * The time complexity of the algorithm is $O(kn(m + n log n))$, where $n$ is the number of vertices * in the graph, $m$ is the number of edges in the graph and $k$ is the number of paths needed. * *

    * The algorithm is originally described in: Q. V. Martins, Ernesto and M. B. Pascoal, Marta. * (2003). A new implementation of Yen’s ranking loopless paths algorithm. Quarterly Journal of the * Belgian, French and Italian Operations Research Societies. 1. 121-133. 10.1007/s10288-002-0010-2. * *

    * The implementation iterates over the existing loopless path between the {@code source} and the * {@code sink} and forms the resulting list. It is possible to provide a {@link PathValidator} to * filter the resulting path list * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @see YenShortestPathIterator * @see PathValidator */ public class YenKShortestPath implements KShortestPathAlgorithm { /** * Underlying graph. */ private final Graph graph; /** * Provides validation for the paths which will be computed. If the validator is {@code null}, * this means that all paths are valid. */ private PathValidator pathValidator; /** * Constructs an instance of the algorithm for the given {@code graph}. * * @param graph graph */ public YenKShortestPath(Graph graph) { this(graph, null); } /** * Constructs an instance of the algorithm for the given {@code graph} and * {@code pathValidator}. * * @param graph graph * @param pathValidator validator for computed paths */ public YenKShortestPath(Graph graph, PathValidator pathValidator) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null!"); this.pathValidator = pathValidator; } /** * Computes {@code k} shortest loopless paths between {@code source} and {@code sink}. If the * overall number of such paths is denoted by $n$, the method returns $m = min\{k, n\}$ such * paths. The paths are produced in sorted order by weights. * * @param source the source vertex * @param sink the target vertex * @param k the number of shortest paths to return * @return a list of k shortest paths */ @Override public List> getPaths(V source, V sink, int k) { if (k < 0) { throw new IllegalArgumentException("k should be positive"); } List> result = new ArrayList<>(); YenShortestPathIterator iterator = new YenShortestPathIterator<>(graph, source, sink, pathValidator); for (int i = 0; i < k && iterator.hasNext(); i++) { result.add(iterator.next()); } return result; } } YenShortestPathIterator.java000066400000000000000000000633361402514743400347750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; import java.util.function.*; /** * Iterator over the shortest loopless paths between two vertices in a graph sorted by weight. * *

    * For this iterator to work correctly the graph must not be modified during iteration. Currently * there are no means to ensure that, nor to fail-fast. The results of such modifications are * undefined. * *

    * The main idea of this algorithm is to divide each path between the {@code source} and the * {@code sink} into the root part - the part that coincides within some of the paths computed so * far, and the spur part, the part that deviates from all other paths computed so far. Therefore, * for each path the algorithm maintains a vertex, at which the path deviates from its "parent" path * (the candidate path using which it was computed). * *

    * First the algorithm finds the shortest path between the {@code source} and the {@code sink}, * which is put into the candidates heap. The {@code source} is assigned to be its deviation vertex. * Then on each iteration the algorithm takes a candidate from the heap with minimum weight, puts it * into the result list and builds all possible deviations from it wrt. other paths, that are in the * result list. By generating spur paths starting only from the vertices that are after the * deviation vertex of current path (including the deviation vertex) it is possible to avoid * building duplicated candidates. * *

    * Additionally, the algorithm supports path validation by means of {@link PathValidator}. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @see PathValidator */ public class YenShortestPathIterator implements Iterator> { /** * Underlying graph. */ private final Graph graph; /** * Source vertex. */ private final V source; /** * Sink vertex. */ private final V sink; /** * Provides possibility to validate computed paths and exclude invalid ones. Whenever a * candidate path $P$ first deviation vertex $u$ is produces by this algorithm, it is passed to * {@code getLastValidDeviation()} to find the last valid deviation vertex $v$ for it. The * algorithm puts obtained vertex in {@code lastDeviations} map. If vertex $v$ is {@code null}, * the candidate is considered correct. Otherwise for the path $P$ deviation are built only from * vertices between $u$ and $v$ inclusive. */ private PathValidator pathValidator; /** * List of the paths returned so far via the {@link #next()} method. */ private List> resultList; /** * Heap of the candidate path generated so far and sorted my their weights. There is a boolean * flag for every candidate in the queue, which indicates, if the path is valid ot not. An * invalid path is a path which contains an edge which fails the {@code pathValidator} check. * Invalid paths are kept in the queue, because it is possible to build a valid path by * deviating from an invalid one. */ private AddressableHeap, Boolean>> candidatePaths; /** * For each path $P$, stores its deviation point. *

    * A path deviation point is a first node in the path that doesn't belong to the parent path. If * the path doesn't have a parent (which is only possible for one shortest path between the * {@code source} and the {@code sink}), this map stores its first node. */ private Map, V> firstDeviations; /** * For each path $P$ stores the vertex $u$ such that $pathValidator#isValidPath([start_vertex, * u], (u,v)) = false$, where $[start_vertex, u]$ denotes the subpath of $P$ from its start to * vertex $u$ and $v$ is the next vertex in $P$ after $u$. Stores {@code null}, if there is no * such vertex. */ private Map, V> lastDeviations; /** * Stores number of valid candidates in {@code candidatePaths}. */ private int numberOfValidPathInQueue; /** * Indicates if the {@code lazyInitializePathHeap} procedure has already been executed. */ private boolean shortestPathComputed; /** * Constructs an instance of the algorithm for given {@code graph}, {@code source} and * {@code sink}. * * @param graph graph * @param source source vertex * @param sink sink vertex */ public YenShortestPathIterator(Graph graph, V source, V sink) { this(graph, source, sink, PairingHeap::new); } /** * Constructs an instance of the algorithm for given {@code graph}, {@code source}, {@code sink} * and {@code pathValidator}. The {@code pathValidator} can be {@code null}, which will indicate * that all paths are valid. * * @param graph graph * @param source source vertex * @param sink sink vertex * @param pathValidator validator to computed paths */ public YenShortestPathIterator( Graph graph, V source, V sink, PathValidator pathValidator) { this(graph, source, sink, PairingHeap::new, pathValidator); } /** * Constructs an instance of the algorithm for given {@code graph}, {@code source}, {@code sink} * and {@code heapSupplier}. * * @param graph graph * @param source source vertex * @param sink sink vertex * @param heapSupplier supplier of the preferable heap implementation */ public YenShortestPathIterator( Graph graph, V source, V sink, Supplier, Boolean>>> heapSupplier) { this(graph, source, sink, heapSupplier, null); } /** * Constructs an instance of the algorithm for given {@code graph}, {@code source}, * {@code sink}, {@code heapSupplier} and {@code pathValidator}. The {@code pathValidator} can * be {@code null}, which will indicate that all paths are valid. * * @param graph graph * @param source source vertex * @param sink sink vertex * @param heapSupplier supplier of the preferable heap implementation * @param pathValidator validator for computed paths */ public YenShortestPathIterator( Graph graph, V source, V sink, Supplier, Boolean>>> heapSupplier, PathValidator pathValidator) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null!"); if (!graph.containsVertex(source)) { throw new IllegalArgumentException("Graph should contain source vertex!"); } this.source = source; if (!graph.containsVertex(sink)) { throw new IllegalArgumentException("Graph should contain sink vertex!"); } this.sink = sink; this.pathValidator = pathValidator; Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null"); this.resultList = new ArrayList<>(); this.candidatePaths = heapSupplier.get(); this.firstDeviations = new HashMap<>(); this.lastDeviations = new HashMap<>(); } /** * Lazily initializes the path heap by computing the shortest path between the {@code source} * and the {@code sink} and building a necessary amount of paths until at least one valid path * is found. * * Note: this computation is done only once during the first call to either {@code hasNext()} or * {@code next()}. */ private void lazyInitializePathHeap() { if (!shortestPathComputed) { GraphPath shortestPath = DijkstraShortestPath.findPathBetween(graph, source, sink); if (shortestPath != null) { V lastValidDeviation = getLastValidDeviation(shortestPath, source); boolean shortestPathIsValid = lastValidDeviation == null; candidatePaths .insert(shortestPath.getWeight(), Pair.of(shortestPath, shortestPathIsValid)); firstDeviations.put(shortestPath, source); lastDeviations.put(shortestPath, lastValidDeviation); if (shortestPathIsValid) { ++numberOfValidPathInQueue; } ensureAtLeastOneValidPathInQueue(); } } shortestPathComputed = true; } /** * This method is used to make sure that there exist at least one valid path on the queue. * During the iteration if the candidates queue is not empty then the iterator has next value. * Otherwise is does not. */ private void ensureAtLeastOneValidPathInQueue() { while (numberOfValidPathInQueue == 0 && !candidatePaths.isEmpty()) { Pair, Boolean> p = candidatePaths.deleteMin().getValue(); GraphPath currentPath = p.getFirst(); resultList.add(currentPath); int numberOfValidDeviations = addDeviations(currentPath); numberOfValidPathInQueue += numberOfValidDeviations; } } /** * Computes vertex $u$ such that $pathValidator#isValidPath([start_vertex, u], (u,v)) = false$, * where $[start_vertex, u]$ denotes the subpath of $P$ from its start to vertex $u$ and $v$ is * the next vertex in $P$ after $u$. Returns null if there is no such vertex. * * @param path graph path * @param firstDeviation vertex at which {@code path} deviates from its parent path * @return vertex which is last valid deviation for {@code path} */ private V getLastValidDeviation(GraphPath path, V firstDeviation) { if (pathValidator == null) { return null; } List vertices = path.getVertexList(); List edges = path.getEdgeList(); V result = null; double partialPathWeight = 0.0; int firstDeviationIndex = vertices.indexOf(firstDeviation); for (int i = firstDeviationIndex; i < edges.size(); ++i) { GraphPath partialPath = new GraphWalk<>( path.getGraph(), path.getStartVertex(), vertices.get(i), vertices.subList(0, i + 1), edges.subList(0, i), partialPathWeight); E edge = edges.get(i); boolean isValid = pathValidator.isValidPath(partialPath, edge); if (!isValid) { result = vertices.get(i); break; } partialPathWeight += graph.getEdgeWeight(edge); } return result; } /** * {@inheritDoc} */ @Override public boolean hasNext() { lazyInitializePathHeap(); return !candidatePaths.isEmpty(); } /** * {@inheritDoc} */ @Override public GraphPath next() { if (!hasNext()) { throw new NoSuchElementException(); } GraphPath result = null; while (result == null) { Pair, Boolean> p = candidatePaths.deleteMin().getValue(); GraphPath path = p.getFirst(); boolean isValid = p.getSecond(); if (isValid) { result = path; --numberOfValidPathInQueue; } resultList.add(path); int numberOfValidDeviations = addDeviations(path); numberOfValidPathInQueue += numberOfValidDeviations; } ensureAtLeastOneValidPathInQueue(); return result; } /** * Builds unique loopless deviations from the given path in the {@code graph}. First receives * the deviation vertex of the current path as well as sets of vertices and edges to be masked * during the computations. Then creates an instance of the {@link MaskSubgraph} and builds a * reversed shortest paths tree starting at {@code sink} in it. Finally builds new candidate * paths by deviating from the vertices of the provided {@code path}. Puts only those candidates * in the {@code candidatesList}, which deviate from {@code path} between $firstDeviation$ and * $lastDeviation$. $firstDeviation$ and $lastDeviation$ are obtainer from * {@code firstDeviations} and {@code lastDeviations} correspondingly. * *

    * For more information on this step refer to the article with the original description of the * algorithm. * * @param path path to build deviations of * * @return number of computed valid deviations */ private int addDeviations(GraphPath path) { int result = 0; // initializations V pathDeviation = firstDeviations.get(path); List pathVertices = path.getVertexList(); List pathEdges = path.getEdgeList(); int pathVerticesSize = pathVertices.size(); int pathDeviationIndex = pathVertices.indexOf(pathDeviation); // receive masked vertices and edges Pair, Set> p = getMaskedVerticesAndEdges(path, pathDeviation, pathDeviationIndex); Set maskedVertices = p.getFirst(); Set maskedEdges = p.getSecond(); // build reversed shortest paths tree Graph maskSubgraph = new MaskSubgraph<>(graph, maskedVertices::contains, maskedEdges::contains); Graph reversedMaskedGraph = new EdgeReversedGraph<>(maskSubgraph); DijkstraShortestPath shortestPath = new DijkstraShortestPath<>(reversedMaskedGraph); TreeSingleSourcePathsImpl singleSourcePaths = (TreeSingleSourcePathsImpl) shortestPath.getPaths(sink); Map> distanceAndPredecessorMap = new HashMap<>(singleSourcePaths.getDistanceAndPredecessorMap()); YenShortestPathsTree customTree = new YenShortestPathsTree( maskSubgraph, maskedVertices, maskedEdges, distanceAndPredecessorMap, sink); // get index of last deviation V lastDeviation = lastDeviations.get(path); int lastDeviationIndex; if (lastDeviation == null) { // path is valid lastDeviationIndex = pathVerticesSize - 2; } else { lastDeviationIndex = pathVertices.indexOf(lastDeviation); } // build spur paths by iteratively recovering vertices of the current path boolean proceed = true; for (int i = pathVerticesSize - 2; i >= 0 && proceed; i--) { V recoverVertex = pathVertices.get(i); if (recoverVertex.equals(pathDeviation)) { proceed = false; } // recover vertex customTree.recoverVertex(recoverVertex); customTree.correctDistanceForward(recoverVertex); GraphPath spurPath = customTree.getPath(recoverVertex); // construct a new path if possible if (spurPath != null) { customTree.correctDistanceBackward(recoverVertex); if (i <= lastDeviationIndex) { // candidate path can be valid GraphPath candidate = getCandidatePath(path, i, spurPath); double candidateWeight = candidate.getWeight(); V candidateLastDeviation = getLastValidDeviation(candidate, recoverVertex); boolean candidateIsValid = candidateLastDeviation == null; candidatePaths.insert(candidateWeight, Pair.of(candidate, candidateIsValid)); firstDeviations.put(candidate, recoverVertex); lastDeviations.put(candidate, candidateLastDeviation); if (candidateIsValid) { ++result; } } } // recover edge V recoverVertexSuccessor = pathVertices.get(i + 1); E edge = pathEdges.get(i); customTree.recoverEdge(edge); double recoverVertexUpdatedDistance = maskSubgraph.getEdgeWeight(edge) + customTree.map.get(recoverVertexSuccessor).getFirst(); if (customTree.map.get(recoverVertex).getFirst() > recoverVertexUpdatedDistance) { customTree.map.put(recoverVertex, Pair.of(recoverVertexUpdatedDistance, edge)); customTree.correctDistanceBackward(recoverVertex); } } return result; } /** * For the given {@code path} builds sets of vertices and edges to be masked. First masks all * edges and vertices of the provided {@code path} except for the {@code sink}. Then for each * path in the {@code resultList} that coincides in the {@code path} until the * {@code pathDeviation} masks the edge between the {@code pathDeviation} and its successor in * this path. * * @param path path to mask vertices and edges of * @param pathDeviation deviation vertex of the path * @param pathDeviationIndex index of the deviation vertex in the vertices list of the path * @return pair of sets of masked vertices and edges */ private Pair, Set> getMaskedVerticesAndEdges( GraphPath path, V pathDeviation, int pathDeviationIndex) { List pathVertices = path.getVertexList(); List pathEdges = path.getEdgeList(); Set maskedVertices = new HashSet<>(); Set maskedEdges = new HashSet<>(); int pathVerticesSize = pathVertices.size(); // mask vertices and edges of the current path for (int i = 0; i < pathVerticesSize - 1; i++) { maskedVertices.add(pathVertices.get(i)); maskedEdges.add(pathEdges.get(i)); } // mask corresponding edges of coinciding paths int resultListSize = resultList.size(); for (int i = 0; i < resultListSize - 1; i++) { // the vertex of the current paths has been // masked already GraphPath resultPath = resultList.get(i); List resultPathVertices = resultPath.getVertexList(); int deviationIndex = resultPathVertices.indexOf(pathDeviation); if (deviationIndex < 0 || deviationIndex != pathDeviationIndex || !equalLists(pathVertices, resultPathVertices, deviationIndex)) { continue; } maskedEdges.add(resultPath.getEdgeList().get(deviationIndex)); } return Pair.of(maskedVertices, maskedEdges); } /** * Builds a candidate path based on the information provided in the methods parameters. First * adds the root part of the candidate by traversing the vertices and edges of the {@code path} * until the {@code recoverVertexIndex}. Then adds vertices and edges of the {@code spurPath}. * * @param path path the candidate path deviates from * @param recoverVertexIndex vertex that is being recovered * @param spurPath spur path of the candidate * @return candidate path */ private GraphPath getCandidatePath( GraphPath path, int recoverVertexIndex, GraphPath spurPath) { List pathVertices = path.getVertexList(); List pathEdges = path.getEdgeList(); List candidatePathVertices = new LinkedList<>(); List candidatePathEdges = new LinkedList<>(); double rootPathWeight = 0.0; for (int i = 0; i < recoverVertexIndex; i++) { E edge = pathEdges.get(i); rootPathWeight += graph.getEdgeWeight(edge); candidatePathEdges.add(edge); candidatePathVertices.add(pathVertices.get(i)); } ListIterator spurPathVerticesIterator = spurPath.getVertexList().listIterator(spurPath.getVertexList().size()); while (spurPathVerticesIterator.hasPrevious()) { candidatePathVertices.add(spurPathVerticesIterator.previous()); } ListIterator spurPathEdgesIterator = spurPath.getEdgeList().listIterator(spurPath.getEdgeList().size()); while (spurPathEdgesIterator.hasPrevious()) { candidatePathEdges.add(spurPathEdgesIterator.previous()); } double candidateWeight = rootPathWeight + spurPath.getWeight(); return new GraphWalk<>( graph, source, sink, candidatePathVertices, candidatePathEdges, candidateWeight); } /** * Checks if the lists have the same content until the {@code index} (inclusive). * * @param first first list * @param second second list * @param index position in the lists * @return true iff the contents of the list are equal until the index */ private boolean equalLists(List first, List second, int index) { for (int i = 0; i <= index; i++) { if (!first.get(i).equals(second.get(i))) { return false; } } return true; } /** * Helper class which represents the shortest paths tree using which the spur parts are computed * and appended to the candidate paths */ class YenShortestPathsTree extends TreeSingleSourcePathsImpl { /** * Vertices which are masked in the {@code g}. */ Set maskedVertices; /** * Edges which are masked in the {@code g}. */ Set maskedEdges; /** * Constructs an instance of the shortest paths tree for the given {@code maskSubgraph}, * {@code maskedVertices}, {@code maskedEdges}, {@code reversedTree}, {@code treeSource}. * * @param maskSubgraph graph which has removed vertices and edges * @param maskedVertices vertices removed form the graph * @param maskedEdges edges removed from the graph * @param reversedTree shortest path tree in the edge reversed {@code maskSubgraph} starting * at {@code treeSource}. * @param treeSource source vertex of the {@code reversedTree} */ YenShortestPathsTree( Graph maskSubgraph, Set maskedVertices, Set maskedEdges, Map> reversedTree, V treeSource) { super(maskSubgraph, treeSource, reversedTree); this.maskedVertices = maskedVertices; this.maskedEdges = maskedEdges; } /** * Restores vertex {@code v} in the {@code g}. * * @param v vertex to be recovered */ void recoverVertex(V v) { maskedVertices.remove(v); } /** * Restores edge {@code e} in the {@code g}. * * @param e edge to be recovered */ void recoverEdge(E e) { maskedEdges.remove(e); } /** * Updates the distance of provided vertex {@code v} in the shortest paths tree based on the * current distances of its successors in the {@code g}. * * @param v vertex which should be updated */ void correctDistanceForward(V v) { super.map.putIfAbsent(v, new Pair<>(Double.POSITIVE_INFINITY, null)); for (E e : super.g.outgoingEdgesOf(v)) { V successor = Graphs.getOppositeVertex(super.g, e, v); if (successor.equals(v)) { continue; } double updatedDistance = Double.POSITIVE_INFINITY; if (super.map.containsKey(successor)) { updatedDistance = super.map.get(successor).getFirst(); } updatedDistance += super.g.getEdgeWeight(e); double currentDistance = super.map.get(v).getFirst(); if (currentDistance > updatedDistance) { super.map.put(v, Pair.of(updatedDistance, e)); } } } /** * Updates the distance of relevant predecessors of the input vertex. * * @param v vertex which distance should be updated */ void correctDistanceBackward(V v) { List vertices = new LinkedList<>(); vertices.add(v); while (!vertices.isEmpty()) { V vertex = vertices.remove(0); double vertexDistance = super.map.get(vertex).getFirst(); for (E e : super.g.incomingEdgesOf(vertex)) { V predecessor = Graphs.getOppositeVertex(super.g, e, vertex); if (predecessor.equals(vertex)) { continue; } double predecessorDistance = Double.POSITIVE_INFINITY; if (super.map.containsKey(predecessor)) { predecessorDistance = super.map.get(predecessor).getFirst(); } double updatedDistance = vertexDistance + super.g.getEdgeWeight(e); if (predecessorDistance > updatedDistance) { super.map.put(predecessor, Pair.of(updatedDistance, e)); vertices.add(predecessor); } } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/shortestpath/package-info.java000066400000000000000000000001231402514743400325430ustar00rootroot00000000000000/** * Shortest-path related algorithms. */ package org.jgrapht.alg.shortestpath; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/similarity/000077500000000000000000000000001402514743400270165ustar00rootroot00000000000000ZhangShashaTreeEditDistance.java000066400000000000000000000662471402514743400351210ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/similarity/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.similarity; import org.jgrapht.Graph; import org.jgrapht.GraphTests; import org.jgrapht.Graphs; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; import java.util.function.ToDoubleBiFunction; import java.util.function.ToDoubleFunction; /** * Dynamic programming algorithm for computing edit distance between trees. * *

    * The algorithm is originally described in Zhang, Kaizhong & Shasha, Dennis. (1989). Simple Fast * Algorithms for the Editing Distance Between Trees and Related Problems. SIAM J. Comput.. 18. * 1245-1262. 10.1137/0218082. * *

    * The time complexity of the algorithm if $O(|T_1|\cdot|T_2|\cdot min(depth(T_1),leaves(T_1)) \cdot * min(depth(T_2),leaves(T_2)))$. Space complexity is $O(|T_1|\cdot |T_2|)$, where $|T_1|$ and * $|T_2|$ denote number of vertices in trees $T_1$ and $T_2$ correspondingly, $leaves()$ function * returns number of leaf vertices in a tree. * * *

    * The tree edit distance problem is defined in a following way. Consider $2$ trees $T_1$ and $T_2$ * with root vertices $r_1$ and $r_2$ correspondingly. For those trees there are 3 elementary * modification actions: * *

      *
    • Remove a vertex $v$ from $T_1$.
    • *
    • Insert a vertex $v$ into $T_2$.
    • *
    • Change vertex $v_1$ in $T_1$ to vertex $v_2$ in $T_2$.
    • *
    * * The algorithm assigns a cost to each of those operations which also depends on the vertices. The * problem is then to compute a sequence of edit operations which transforms $T_1$ into $T_2$ and * has a minimum cost over all such sequences. Here the cost of a sequence of edit operations is * defined as sum of costs of individual operations. * *

    * The algorithm is based on a dynamic programming principle and assigns a label to each vertex in * the trees which is equal to its index in post-oder traversal. It also uses a notion of a keyroot * which is defined as a vertex in a tree which has a left sibling. Additionally a special $l()$ * function is introduced with returns for every vertex the index of its leftmost child wrt the * post-order traversal in the tree. * *

    * Solving the tree edit problem distance is divided into computing edit distance for every pair of * subtrees rooted at vertices $v_1$ and $v_2$ where $v_1$ is a keyroot in the first tree and $v_2$ * is a keyroot in the second tree. * * @param graph vertex type * @param graph edge type * @author Semen Chudakov */ public class ZhangShashaTreeEditDistance { /** * First tree for which the distance is computed by this algorithm. */ private Graph tree1; /** * Root vertex of the {@code tree1}. */ private V root1; /** * Second tree for which the distance is computed by this algorithm. */ private Graph tree2; /** * Root vertex of the {@code tree2}. */ private V root2; /** * Function which computes cost of inserting a particular vertex into {@code tree2}. */ private ToDoubleFunction insertCost; /** * Function which computes cost of removing a particular vertex from {2code tree1}. */ private ToDoubleFunction removeCost; /** * Function which computes cost of changing a vertex $v1$ in {@code tree1} to vertex $v2$ in * {@code tree2}. */ private ToDoubleBiFunction changeCost; /** * Array with edit distances between subtrees of {@code tree1} and {@code tree2}. Formally, * $treeDistances[i][j]$ stores edit distance between subtree of {@code tree1} rooted at vertex * $i+1$ and subtree of {@code tree2} rooted at vertex $j+1$, where $i$ and $j$ are vertex * indices from the corresponding tree orderings. */ private double[][] treeDistances; /** * Array with lists of edit operations which transform subtrees of {@code tree1} into subtrees * {@code tree2}. Formally, editOperationLists[i][j]$ stores a list of edit operations which * transform subtree {@code tree1} rooted at vertex $i$ into subtree of {@code tree2} rooted at * vertex $j$, where $i$ and $j$ are vertex indices from the corresponding tree orderings. */ private List>>> editOperationLists; /** * Helper field which indicates whether the algorithm has already been executed for * {@code tree1} and {@code tree2}. */ private boolean algorithmExecuted; /** * Constructs an instance of the algorithm for the given {@code tree1}, {@code root1}, * {@code tree2} and {@code root2}. This constructor sets following default values for the * distance functions. The {@code insertCost} and {@code removeCost} always return $1.0$, the * {@code changeCost} return $0.0$ if vertices are equal and {@code 1.0} otherwise. * * @param tree1 a tree * @param root1 root vertex of {@code tree1} * @param tree2 a tree * @param root2 root vertex of {@code tree2} */ public ZhangShashaTreeEditDistance(Graph tree1, V root1, Graph tree2, V root2) { this(tree1, root1, tree2, root2, v -> 1.0, v -> 1.0, (v1, v2) -> { if (v1.equals(v2)) { return 0.0; } return 1.0; }); } /** * Constructs an instance of the algorithm for the given {@code tree1}, {@code root1}, * {@code tree2}, {@code root2}, {@code insertCost}, {@code removeCost} and {@code changeCost}. * * @param tree1 a tree * @param root1 root vertex of {@code tree1} * @param tree2 a tree * @param root2 root vertex of {@code tree2} * @param insertCost cost function for inserting a node into {@code tree1} * @param removeCost cost function for removing a node from {@code tree2} * @param changeCost cost function of changing a node in {@code tree1} to a node in * {@code tree2} */ public ZhangShashaTreeEditDistance( Graph tree1, V root1, Graph tree2, V root2, ToDoubleFunction insertCost, ToDoubleFunction removeCost, ToDoubleBiFunction changeCost) { this.tree1 = Objects.requireNonNull(tree1, "graph1 cannot be null!"); this.root1 = Objects.requireNonNull(root1, "root1 cannot be null!"); this.tree2 = Objects.requireNonNull(tree2, "graph2 cannot be null!"); this.root2 = Objects.requireNonNull(root2, "root2 cannot be null!"); this.insertCost = Objects.requireNonNull(insertCost, "insertCost cannot be null!"); this.removeCost = Objects.requireNonNull(removeCost, "removeCost cannot be null!"); this.changeCost = Objects.requireNonNull(changeCost, "changeCost cannot be null!"); if (!GraphTests.isTree(tree1)) { throw new IllegalArgumentException("graph1 must be a tree!"); } if (!GraphTests.isTree(tree2)) { throw new IllegalArgumentException("graph2 must be a tree!"); } int m = tree1.vertexSet().size(); int n = tree2.vertexSet().size(); treeDistances = new double[m][n]; editOperationLists = new ArrayList<>(m); for (int i = 0; i < m; ++i) { editOperationLists.add(new ArrayList<>(Collections.nCopies(n, null))); } } /** * Computes edit distance for {@code tree1} and {@code tree2}. * * @return edit distance between {@code tree1} and {@code tree2} */ public double getDistance() { lazyRunAlgorithm(); int m = tree1.vertexSet().size(); int n = tree2.vertexSet().size(); return treeDistances[m - 1][n - 1]; } /** * Computes a list of edit operations which transform {@code tree1} into {@code tree2}. * * @return list of edit operations */ public List> getEditOperationLists() { lazyRunAlgorithm(); int m = tree1.vertexSet().size(); int n = tree2.vertexSet().size(); return Collections.unmodifiableList(editOperationLists.get(m - 1).get(n - 1)); } /** * Performs lazy computations of this algorithm and stores cached data in {@code treeDistances} * and {@code editOperationList}. */ private void lazyRunAlgorithm() { if (!algorithmExecuted) { TreeOrdering ordering1 = new TreeOrdering(tree1, root1); TreeOrdering ordering2 = new TreeOrdering(tree2, root2); for (int keyroot1 : ordering1.keyroots) { for (Integer keyroot2 : ordering2.keyroots) { treeDistance(keyroot1, keyroot2, ordering1, ordering2); } } algorithmExecuted = true; } } /** * Computes edit distance and list of edit operations for vertex $v1$ from {@code tree1} which * has tree ordering index equal to $i$ and vertex $v2$ from {@code tree2} which has tree * ordering index equal to $j$. Both $v1$ and $v2$ must be keyroots in the corresponding trees. * * @param i ordering index of a keyroot in {@code tree1} * @param j ordering index of a keywoot in {@code tree2} * @param ordering1 ordering of {@code tree1} * @param ordering2 ordering of {@code tree2} */ private void treeDistance(int i, int j, TreeOrdering ordering1, TreeOrdering ordering2) { int li = ordering1.indexToLValueList.get(i); int lj = ordering2.indexToLValueList.get(j); int m = i - li + 2; int n = j - lj + 2; double[][] forestdist = new double[m][n]; List> cachedOperations = new ArrayList<>(m); for (int k = 0; k < m; ++k) { cachedOperations.add(new ArrayList<>(Collections.nCopies(n, null))); } int iOffset = li - 1; int jOffset = lj - 1; for (int i1 = li; i1 <= i; ++i1) { V i1Vertex = ordering1.indexToVertexList.get(i1); int iIndex = i1 - iOffset; forestdist[iIndex][0] = forestdist[iIndex - 1][0] + removeCost.applyAsDouble(i1Vertex); CacheEntry entry = new CacheEntry( iIndex - 1, 0, new EditOperation<>(OperationType.REMOVE, i1Vertex, null)); cachedOperations.get(iIndex).set(0, entry); } for (int j1 = lj; j1 <= j; ++j1) { V j1Vertex = ordering2.indexToVertexList.get(j1); int jIndex = j1 - jOffset; forestdist[0][jIndex] = forestdist[0][jIndex - 1] + removeCost.applyAsDouble(j1Vertex); CacheEntry entry = new CacheEntry( 0, jIndex - 1, new EditOperation<>(OperationType.INSERT, j1Vertex, null)); cachedOperations.get(0).set(jIndex, entry); } for (int i1 = li; i1 <= i; ++i1) { V i1Vertex = ordering1.indexToVertexList.get(i1); int li1 = ordering1.indexToLValueList.get(i1); for (int j1 = lj; j1 <= j; ++j1) { V j1Vertex = ordering2.indexToVertexList.get(j1); int lj1 = ordering2.indexToLValueList.get(j1); int iIndex = i1 - iOffset; int jIndex = j1 - jOffset; if (li1 == li && lj1 == lj) { double dist1 = forestdist[iIndex - 1][jIndex] + removeCost.applyAsDouble(i1Vertex); double dist2 = forestdist[iIndex][jIndex - 1] + insertCost.applyAsDouble(j1Vertex); double dist3 = forestdist[iIndex - 1][jIndex - 1] + changeCost.applyAsDouble(i1Vertex, j1Vertex); double result = Math.min(dist1, Math.min(dist2, dist3)); CacheEntry entry; if (result == dist1) { // remove operation entry = new CacheEntry( iIndex - 1, jIndex, new EditOperation<>(OperationType.REMOVE, i1Vertex, null)); } else if (result == dist2) { // insert operation entry = new CacheEntry( iIndex, jIndex - 1, new EditOperation<>(OperationType.INSERT, j1Vertex, null)); } else { // result == dist3 => change operation entry = new CacheEntry( iIndex - 1, jIndex - 1, new EditOperation<>(OperationType.CHANGE, i1Vertex, j1Vertex)); } cachedOperations.get(iIndex).set(jIndex, entry); forestdist[iIndex][jIndex] = result; treeDistances[i1 - 1][j1 - 1] = result; editOperationLists .get(i1 - 1) .set(j1 - 1, restoreOperationsList(cachedOperations, iIndex, jIndex)); } else { int i2 = li1 - 1 - iOffset; int j2 = lj1 - 1 - jOffset; double dist1 = forestdist[iIndex - 1][jIndex] + removeCost.applyAsDouble(i1Vertex); double dist2 = forestdist[iIndex][jIndex - 1] + insertCost.applyAsDouble(j1Vertex); double dist3 = forestdist[i2][j2] + treeDistances[i1 - 1][j1 - 1]; double result = Math.min(dist1, Math.min(dist2, dist3)); forestdist[iIndex][jIndex] = result; CacheEntry entry; if (result == dist1) { entry = new CacheEntry( iIndex - 1, jIndex, new EditOperation<>(OperationType.REMOVE, i1Vertex, null)); } else if (result == dist2) { entry = new CacheEntry( iIndex, jIndex - 1, new EditOperation<>(OperationType.INSERT, j1Vertex, null)); } else { entry = new CacheEntry(i2, j2, null); entry.treeDistanceI = i1 - 1; entry.treeDistanceJ = j1 - 1; } cachedOperations.get(iIndex).set(jIndex, entry); } } } } /** * Restores list of edit operations which have been cached in {@code cachedOperations} during * the edit distance computation. Starting from a cache entry at index $(i,j)$. * * @param cachedOperations 2-dimensional list with cached operations * @param i starting operation index * @param j starting operation index * @return list of edit operations */ private List> restoreOperationsList( List> cachedOperations, int i, int j) { List> result = new ArrayList<>(); CacheEntry it = cachedOperations.get(i).get(j); while (it != null) { if (it.editOperation == null) { result.addAll(editOperationLists.get(it.treeDistanceI).get(it.treeDistanceJ)); } else { result.add(it.editOperation); } it = cachedOperations.get(it.cachePreviousPosI).get(it.cachePreviousPosJ); } return result; } /** * Auxiliary class which for computes keyroot vertices, tree ordering and $l()$ function for a * particular tree. * *

    * A keyroot of a tree is a vertex which has a left sibling. Ordering of a tree assings an * integer index to every its vertex. Indices are assigned using post-order traversal. $l()$ * function for every vertex in a tree returns ordering index of its leftmost child. For leaf * vertex the function returns its own ordering index. */ private class TreeOrdering { /** * Underlying tree of this ordering. */ final Graph tree; /** * Root vertex of {@code tree}. */ final V treeRoot; /** * List of keyroots of {@code tree}. */ List keyroots; /** * List which at very position $i$ stores a vertex from {@code tree} which has ordering * index equal to $i$. */ List indexToVertexList; /** * List which at every position $i$ stores value of $l()$ function for a vertex from * {@code tree} whihc has ordering index equal to $i$. */ List indexToLValueList; /** * Ordering index to be assigned to the next traversed vertex. */ int currentIndex; /** * Constructs an instance of the tree ordering for the given {@code graph} and * {@code treeRoot}. * * @param tree a tree * @param treeRoot root vertex of {@code tree} */ public TreeOrdering(Graph tree, V treeRoot) { this.tree = tree; this.treeRoot = treeRoot; int numberOfVertices = tree.vertexSet().size(); keyroots = new ArrayList<>(); indexToVertexList = new ArrayList<>(Collections.nCopies(numberOfVertices + 1, null)); indexToLValueList = new ArrayList<>(Collections.nCopies(numberOfVertices + 1, null)); currentIndex = 1; computeKeyrootsAndMapping(treeRoot); } /** * Runs post-order DFS on {@code tree} starting at {@code treeRoot}. Assigns consecutive * integer index to every traversed vertex and computes keyroots for {@code tree}. * * @param treeRoot root vertex of {@code tree} */ private void computeKeyrootsAndMapping(V treeRoot) { List stack = new ArrayList<>(); stack.add(new StackEntry(treeRoot, true)); while (!stack.isEmpty()) { StackEntry entry = stack.get(stack.size() - 1); if (entry.state == 0) { if (stack.size() > 1) { entry.vParent = stack.get(stack.size() - 2).v; } entry.vChildIterator = Graphs.successorListOf(tree, entry.v).iterator(); entry.state = 1; } else if (entry.state == 1) { if (entry.vChildIterator.hasNext()) { entry.vChild = entry.vChildIterator.next(); if (entry.vParent == null || !entry.vChild.equals(entry.vParent)) { stack.add(new StackEntry(entry.vChild, entry.isKeyrootArg)); entry.state = 2; } } else { entry.state = 3; } } else if (entry.state == 2) { entry.isKeyrootArg = true; if (entry.lValue == -1) { entry.lValue = entry.lVChild; } entry.state = 1; } else if (entry.state == 3) { if (entry.lValue == -1) { entry.lValue = currentIndex; } if (entry.isKeyroot) { keyroots.add(currentIndex); } indexToVertexList.set(currentIndex, entry.v); indexToLValueList.set(currentIndex, entry.lValue); ++currentIndex; if (stack.size() > 1) { stack.get(stack.size() - 2).lVChild = entry.lValue; } stack.remove(stack.size() - 1); } } } /** * Auxiliary class which stores all needed variables to emulate recursive execution of DFS * algorithm in {@code computeKeyrootsAndMapping()} method. */ private class StackEntry { /** * A vertex from {@code tree}. */ V v; /** * Indites if {@code v} is a keyroot wrt {@code tree}. */ boolean isKeyroot; /** * Parent vertex of {@code v} in {@code tree} or $null$ if {@code v} is root of * {@code tree}. */ V vParent; /** * Indicates if the next vertex returned by {@code vChildIterator} will be a keyroot. */ boolean isKeyrootArg; /** * Value of the $l()$ function for {@code v}; */ int lValue; /** * Iterates over children of $v$ in {@code tree}. */ Iterator vChildIterator; /** * Current child vertex of {@code v}. */ V vChild; /** * Value of $l()$ function for {@code vChild}. */ int lVChild; /** * Auxiliary field which helps to identify which part of the recursive procedure should * be executed next for this stack entry. */ int state; /** * Constructs an instance of the stack entry for the given {@code v} and * {@code isKeyroot} * * @param v a vertex from {@code tree} * @param isKeyroot true iff {@code v} is a keyroot */ public StackEntry(V v, boolean isKeyroot) { this.v = v; this.isKeyroot = isKeyroot; this.lValue = -1; } } } /** * Represents elementary action which changes the structure of a tree. * * @param tree vertex type */ public static class EditOperation { /** * Type of this operation. */ private final OperationType type; /** * Vertex of a tree which is the first operand of this operations. */ private final V firstOperand; /** * Vertex of a tree which is a second operand of this operation. For * {@code OperationsType.INSERT} and {@code OperationsType.REMOVE} this field is null. */ private final V secondOperand; /** * Returns type of this operation. * * @return oeration type */ public OperationType getType() { return type; } /** * Returns first operand of this operation * * @return first operand */ public V getFirstOperand() { return firstOperand; } /** * Returns second operand of this operation. * * @return second operand */ public V getSecondOperand() { return secondOperand; } /** * Constructs an instance of edit operation for the given {@code type}, {@code firstOperand} * and {@code secondOperand}. * * @param type type of the operation * @param firstOperand first operand of the operation * @param secondOperand second operand of the operation */ public EditOperation(OperationType type, V firstOperand, V secondOperand) { this.type = type; this.firstOperand = firstOperand; this.secondOperand = secondOperand; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; EditOperation editOperation = (EditOperation) o; if (type != editOperation.type) return false; if (!firstOperand.equals(editOperation.firstOperand)) return false; return secondOperand != null ? secondOperand.equals(editOperation.secondOperand) : editOperation.secondOperand == null; } @Override public int hashCode() { int result = type.hashCode(); result = 31 * result + firstOperand.hashCode(); result = 31 * result + (secondOperand != null ? secondOperand.hashCode() : 0); return result; } @Override public String toString() { if (type.equals(OperationType.INSERT) || type.equals(OperationType.REMOVE)) { return type + " " + firstOperand; } return type + " " + firstOperand + " -> " + secondOperand; } } /** * Type of an edit operation. */ public enum OperationType { /** * Indicates that an edit operation is inserting a vertex into a tree. */ INSERT, /** * Indicates that an edit operation is removing a vertex into a tree. */ REMOVE, /** * Indicates that an edit operation is changing a vertex in one tree to a vertex in another * three. */ CHANGE } /** * Auxiliary class which is used in {@code treeDistance()} function to store intermediate edit * operations during dynamic programming computation. */ private class CacheEntry { /** * Outer index of the previous entry which is part of the computed optimal solution. */ int cachePreviousPosI; /** * Inner index of the previous entry which is part of the computed optimal solution. */ int cachePreviousPosJ; /** * Edit operation stored in this entry. Is this field is $null$ this indicates that * operations from $editOperationLists[treeDistanceI][treeDistanceJ]$. */ EditOperation editOperation; /** * Outer index of an entry in $editOperationLists$ which should be taken in case * {@code editOperation} is $null$. */ int treeDistanceI; /** * Inner index of an entry in $editOperationLists$ which should be taken in case * {@code editOperation} is $null$. */ int treeDistanceJ; /** * Constructs an instance of entry for the given {@code cachePreviousPosI} * {@code cachePreviousPosJ} and {@code editOperation}. * * @param cachePreviousPosI outer index of the previous cache entry * @param cachePreviousPosJ inner index of the previous cache entry * @param editOperation edit operation of this entry */ public CacheEntry( int cachePreviousPosI, int cachePreviousPosJ, EditOperation editOperation) { this.cachePreviousPosI = cachePreviousPosI; this.cachePreviousPosJ = cachePreviousPosJ; this.editOperation = editOperation; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/similarity/package-info.java000066400000000000000000000001421402514743400322020ustar00rootroot00000000000000/** * Algorithms for computing graph similarity metrics. */ package org.jgrapht.alg.similarity; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/000077500000000000000000000000001402514743400264455ustar00rootroot00000000000000AbstractCapacitatedMinimumSpanningTree.java000066400000000000000000000402621402514743400367750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/* * (C) Copyright 2018-2021, by Christoph Grüne and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.util.*; /** * This is an abstract class for capacitated minimum spanning tree algorithms. This class manages * the basic instance information and a solution representation {see * CapacitatedSpanningTreeSolutionRepresentation} for a capacitated spanning tree. * * @param the vertex type * @param the edge type * * @author Christoph Grüne * @since July 18, 2018 */ public abstract class AbstractCapacitatedMinimumSpanningTree implements CapacitatedSpanningTreeAlgorithm { /** * the input graph. */ protected final Graph graph; /** * the designated root of the CMST. */ protected final V root; /** * the maximal capacity for each subtree. */ protected final double capacity; /** * the demand function over all vertices. */ protected final Map demands; /** * representation of the solution */ protected CapacitatedSpanningTreeSolutionRepresentation bestSolution; /** * Construct a new abstract capacitated minimum spanning tree algorithm. * * @param graph the base graph to calculate the capacitated spanning tree for * @param root the root of the capacitated spanning tree * @param capacity the edge capacity constraint * @param demands the demands of the vertices */ protected AbstractCapacitatedMinimumSpanningTree( Graph graph, V root, double capacity, Map demands) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); if (!graph.getType().isUndirected()) { throw new IllegalArgumentException("Graph must be undirected"); } if (!new ConnectivityInspector<>(graph).isConnected()) { throw new IllegalArgumentException( "Graph must be connected. Otherwise, there is no capacitated minimum spanning tree."); } this.root = Objects.requireNonNull(root, "Root cannot be null"); this.capacity = capacity; this.demands = Objects.requireNonNull(demands, "Demands cannot be null"); for (V vertex : graph.vertexSet()) { if (vertex != root) { Double demand = demands.get(vertex); if (demand == null) { throw new IllegalArgumentException( "Demands does not provide a demand for every vertex."); } if (demand > capacity) { throw new IllegalArgumentException( "Demands must not be greater than the capacity. Otherwise, there is no capacitated minimum spanning tree."); } } } this.bestSolution = new CapacitatedSpanningTreeSolutionRepresentation(); } @Override public abstract CapacitatedSpanningTree getCapacitatedSpanningTree(); /** * This class represents a solution instance by managing the labels and the partition mapping. * With the help of this class, a capacitated spanning tree based on the label and partition * mapping can be calculated. */ protected class CapacitatedSpanningTreeSolutionRepresentation implements Cloneable { /** * labeling of the improvement graph vertices. There are two vertices in the improvement * graph for every vertex in the input graph: the vertex indicating the vertex itself and * the vertex indicating the subtree. */ private Map labels; /** * the implicit partition defined by the subtrees */ private Map, Double>> partition; /** * the next free label */ private int nextFreeLabel; /** * Constructs a new solution representation for the CMST problem. */ public CapacitatedSpanningTreeSolutionRepresentation() { this(new HashMap<>(), new HashMap<>()); } /** * Constructs a new solution representation for the CMST problem based on * labels and partition. All labels have to be positive. * * @param labels the labels of the subsets in the partition * @param partition the partition map */ public CapacitatedSpanningTreeSolutionRepresentation( Map labels, Map, Double>> partition) { for (Integer i : labels.values()) { if (i < 0) { throw new IllegalArgumentException("Labels are not non-negative"); } } for (Integer i : partition.keySet()) { if (i < 0) { throw new IllegalArgumentException("Labels are not non-negative"); } } this.labels = labels; this.partition = partition; getNextFreeLabel(); } /** * Calculates the resulting spanning tree based on this solution representation. * * @return the resulting spanning tree based on this solution representation */ public CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree calculateResultingSpanningTree() { Set spanningTreeEdges = new HashSet<>(); double weight = 0; for (Pair, Double> part : partition.values()) { // get spanning tree on the part inclusive the root vertex Set set = part.getFirst(); set.add(root); SpanningTreeAlgorithm.SpanningTree subtree = new PrimMinimumSpanningTree<>(new AsSubgraph<>(graph, set, graph.edgeSet())) .getSpanningTree(); set.remove(root); // add the partial solution to the overall solution spanningTreeEdges.addAll(subtree.getEdges()); weight += subtree.getWeight(); } return new CapacitatedSpanningTreeImpl<>(labels, partition, spanningTreeEdges, weight); } /** * Moves vertex from the subset represented by fromLabel to the * subset represented by toLabel. * * @param vertex the vertex to move * @param fromLabel the subset to move the vertex from * @param toLabel the subset to move the vertex to */ public void moveVertex(V vertex, Integer fromLabel, Integer toLabel) { labels.put(vertex, toLabel); Set oldPart = partition.get(fromLabel).getFirst(); oldPart.remove(vertex); partition .put( fromLabel, Pair.of(oldPart, partition.get(fromLabel).getSecond() - demands.get(vertex))); if (!partition.keySet().contains(toLabel)) { partition.put(toLabel, Pair.of(new HashSet<>(), 0.0)); } Set newPart = partition.get(toLabel).getFirst(); newPart.add(vertex); partition .put( toLabel, Pair.of(newPart, partition.get(toLabel).getSecond() + demands.get(vertex))); } /** * Moves all vertices in vertices from the subset represented by * fromLabel to the subset represented by toLabel. * * @param vertices the vertices to move * @param fromLabel the subset to move the vertices from * @param toLabel the subset to move the vertices to */ public void moveVertices(Set vertices, Integer fromLabel, Integer toLabel) { // update labels and calculate weight change double weightOfVertices = 0; for (V v : vertices) { weightOfVertices += demands.get(v); labels.put(v, toLabel); } // update partition if (!partition.keySet().contains(toLabel)) { partition.put(toLabel, Pair.of(new HashSet<>(), 0.0)); } Set newPart = partition.get(toLabel).getFirst(); newPart.addAll(vertices); partition .put( toLabel, Pair.of(newPart, partition.get(toLabel).getSecond() + weightOfVertices)); Set oldPart = partition.get(fromLabel).getFirst(); oldPart.removeAll(vertices); partition .put( fromLabel, Pair.of(oldPart, partition.get(fromLabel).getSecond() - weightOfVertices)); } /** * Refines the partition by adding new subsets if the designated root has more than one * subtree in the subset label of the partition. * * @param vertexSubset the subset represented by label, that is the subset that * has to be refined * @param label the label of the subset of the partition that were refined * * @return the set of all labels of subsets that were changed during the refinement */ public Set partitionSubtreesOfSubset(Set vertexSubset, int label) { List> subtreesOfSubset = new LinkedList<>(); if (vertexSubset.isEmpty()) { return new HashSet<>(); } // initialize a subgraph containing the MST of the subset vertexSubset.add(root); SpanningTreeAlgorithm.SpanningTree spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, vertexSubset, graph.edgeSet())).getSpanningTree(); Graph spanningTreeGraph = new AsSubgraph<>(graph, vertexSubset, spanningTree.getEdges()); int degreeOfRoot = spanningTreeGraph.degreeOf(root); if (degreeOfRoot == 1) { vertexSubset.remove(root); return new HashSet<>(); } // store the affected labels Set affectedLabels = new HashSet<>(); // search for subtrees rooted at root DepthFirstIterator depthFirstIterator = new DepthFirstIterator<>(spanningTreeGraph, root); if (depthFirstIterator.hasNext()) { depthFirstIterator.next(); } int numberOfRootEdgesExplored = 0; Set currentSubtree = new HashSet<>(); while (depthFirstIterator.hasNext()) { V next = depthFirstIterator.next(); // exploring new subtree if (spanningTreeGraph.containsEdge(root, next)) { if (!currentSubtree.isEmpty()) { subtreesOfSubset.add(currentSubtree); currentSubtree = new HashSet<>(); } numberOfRootEdgesExplored++; // we do not have to move more vertices if (numberOfRootEdgesExplored == degreeOfRoot) { break; } } currentSubtree.add(next); } // move the subtrees to new subsets in the partition for (Set subtree : subtreesOfSubset) { int nextLabel = this.getNextFreeLabel(); this.moveVertices(subtree, label, nextLabel); affectedLabels.add(nextLabel); } vertexSubset.remove(root); return affectedLabels; } /** * Cleans up the solution representation by removing all empty sets from the partition. */ public void cleanUp() { partition.entrySet().removeIf(entry -> entry.getValue().getFirst().isEmpty()); } /** * Returns the next free label in the label map respectively partition * * @return the next free label in the label map respectively partition */ public int getNextFreeLabel() { int freeLabel = nextFreeLabel; nextFreeLabel++; while (partition.keySet().contains(nextFreeLabel)) { nextFreeLabel++; } return freeLabel; } /** * Returns the label of the subset that contains vertex. * * @param vertex the vertex to return the label from * * @return the label of vertex */ public int getLabel(V vertex) { return labels.get(vertex); } /** * Returns all labels of all subsets. * * @return the labels of all subsets */ public Set getLabels() { return partition.keySet(); } /** * Returns the set of vertices that are in the subset with label label. * * @param label the label of the subset to return the vertices from * * @return the set of vertices that are in the subset with label label */ public Set getPartitionSet(Integer label) { return partition.get(label).getFirst(); } /** * Returns the sum of the weights of all vertices that are in the subset with label * label. * * @param label the label of the subset to return the weight from * * @return the sum of the weights of all vertices that are in the subset with label * label */ public double getPartitionWeight(Integer label) { return partition.get(label).getSecond(); } /** * Returns a shallow copy of this solution representation instance. Vertices are not cloned. * * @return a shallow copy of this solution representation. * * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ public CapacitatedSpanningTreeSolutionRepresentation clone() { try { CapacitatedSpanningTreeSolutionRepresentation capacitatedSpanningTreeSolutionRepresentation = TypeUtil.uncheckedCast(super.clone()); capacitatedSpanningTreeSolutionRepresentation.labels = new HashMap<>(labels); capacitatedSpanningTreeSolutionRepresentation.partition = new HashMap<>(); for (Map.Entry, Double>> entry : this.partition.entrySet()) { capacitatedSpanningTreeSolutionRepresentation.partition .put( entry.getKey(), Pair .of( new HashSet<>(entry.getValue().getFirst()), entry.getValue().getSecond())); } capacitatedSpanningTreeSolutionRepresentation.nextFreeLabel = this.nextFreeLabel; return capacitatedSpanningTreeSolutionRepresentation; } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new RuntimeException(); } } } } AhujaOrlinSharmaCapacitatedMinimumSpanningTree.java000066400000000000000000002164651402514743400404340ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/* * (C) Copyright 2018-2021, by Christoph Grüne and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import java.util.*; /** * Implementation of an algorithm for the capacitated minimum spanning tree problem using a cyclic * exchange neighborhood, based on Ravindra K. Ahuja, James B. Orlin, Dushyant Sharma, A composite * very large-scale neighborhood structure for the capacitated minimum spanning tree problem, * Operations Research Letters, Volume 31, Issue 3, 2003, Pages 185-194, ISSN 0167-6377, * https://doi.org/10.1016/S0167-6377(02)00236-5. * (http://www.sciencedirect.com/science/article/pii/S0167637702002365) *

    * A Capacitated Minimum * Spanning Tree (CMST) is a rooted minimal cost spanning tree that satisfies the capacity * constrained on all trees that are connected to the designated root. The problem is NP-hard. The * hard part of the problem is the implicit partition defined by the subtrees. If one can find the * correct partition, the MSTs can be calculated in polynomial time. *

    * This algorithm is a very large scale neighborhood search algorithm using a cyclic exchange * neighborhood until a local minimum is found. It makes frequently use of a MST algorithm and the * algorithm for subset disjoint cycles by Ahuja et al. That is, the algorithm may run in * exponential time. This algorithm is implemented in two different version: a local search and a * tabu search. In both cases we have to find the best neighbor of the current capacitated spanning * tree. * * @param the vertex type * @param the edge type * @author Christoph Grüne * @since July 11, 2018 */ public class AhujaOrlinSharmaCapacitatedMinimumSpanningTree extends AbstractCapacitatedMinimumSpanningTree { /** * the maximal length of the cycle in the neighborhood */ private final int lengthBound; /** * contains whether the best (if true) or the first improvement (if false) is returned in the * neighborhood search */ private final boolean bestImprovement; /** * the number of the most profitable operations considered in the GRASP procedure for the * initial solution. */ private final int numberOfOperationsParameter; /** * the initial solution */ private CapacitatedSpanningTree initialSolution; /** * contains whether the local search uses the vertex operation */ private final boolean useVertexOperation; /** * contains whether the local search uses the subtree operation */ private final boolean useSubtreeOperation; /** * contains whether a tabu search is used */ private final boolean useTabuSearch; /** * the tabu time that is the number of iterations an element is in the tabu list */ private final int tabuTime; /** * the upper limit of non-improving exchanges, this is the stopping criterion in the tabu search */ private final int upperLimitTabuExchanges; /** * contains whether the algorithm was executed */ private boolean isAlgorithmExecuted; /** * Constructs a new instance of this algorithm. * * @param graph the base graph * @param root the designated root of the CMST * @param capacity the edge capacity constraint * @param demands the demands of the vertices * @param lengthBound the length bound of the cycle detection algorithm * @param numberOfOperationsParameter the number of operations that are considered in the * randomized Esau-Williams algorithm * {@link EsauWilliamsCapacitatedMinimumSpanningTree} @see * EsauWilliamsCapacitatedMinimumSpanningTree */ public AhujaOrlinSharmaCapacitatedMinimumSpanningTree( Graph graph, V root, double capacity, Map demands, int lengthBound, int numberOfOperationsParameter) { this( graph, root, capacity, demands, lengthBound, false, numberOfOperationsParameter, true, true, true, 10, 50); } /** * Constructs a new instance of this algorithm with the proposed initial solution. * * @param initialSolution the initial solution * @param graph the base graph * @param root the designated root of the CMST * @param capacity the edge capacity constraint * @param demands the demands of the vertices * @param lengthBound the length bound of the cycle detection algorithm */ public AhujaOrlinSharmaCapacitatedMinimumSpanningTree( CapacitatedSpanningTree initialSolution, Graph graph, V root, double capacity, Map demands, int lengthBound) { this( initialSolution, graph, root, capacity, demands, lengthBound, false, true, true, true, 10, 50); } /** * Constructs a new instance of this algorithm. * * @param graph the base graph * @param root the designated root of the CMST * @param capacity the edge capacity constraint * @param demands the demands of the vertices * @param lengthBound the length bound of the cycle detection algorithm * @param bestImprovement contains whether the best (if true) or the first improvement (if * false) is returned in the neighborhood search * @param numberOfOperationsParameter the number of operations that are considered in the * randomized Esau-Williams algorithm * {@link EsauWilliamsCapacitatedMinimumSpanningTree} @see * EsauWilliamsCapacitatedMinimumSpanningTree * @param useVertexOperation contains whether the local search uses the vertex operation * @param useSubtreeOperation contains whether the local search uses the subtree operation * @param useTabuSearch contains whether a tabu search is used * @param tabuTime the tabu time that is the number of iterations an element is in the tabu list * @param upperLimitTabuExchanges the upper limit of non-improving exchanges, this is the * stopping criterion in the tabu search */ public AhujaOrlinSharmaCapacitatedMinimumSpanningTree( Graph graph, V root, double capacity, Map demands, int lengthBound, boolean bestImprovement, int numberOfOperationsParameter, boolean useVertexOperation, boolean useSubtreeOperation, boolean useTabuSearch, int tabuTime, int upperLimitTabuExchanges) { super(graph, root, capacity, demands); this.lengthBound = lengthBound; this.bestImprovement = bestImprovement; this.numberOfOperationsParameter = numberOfOperationsParameter; if (!useSubtreeOperation && !useVertexOperation) { throw new IllegalArgumentException( "At least one of the options has to be enabled, otherwise it is not possible to excute the local search: useVertexOperation and useSubtreeOperation."); } this.useVertexOperation = useVertexOperation; this.useSubtreeOperation = useSubtreeOperation; this.useTabuSearch = useTabuSearch; this.tabuTime = tabuTime; this.upperLimitTabuExchanges = upperLimitTabuExchanges; this.isAlgorithmExecuted = false; } /** * Constructs a new instance of this algorithm with the proposed initial solution. * * @param initialSolution the initial solution * @param graph the base graph * @param root the designated root of the CMST * @param capacity the edge capacity constraint * @param demands the demands of the vertices * @param lengthBound the length bound of the cycle detection algorithm * @param bestImprovement contains whether the best (if true) or the first improvement (if * false) is returned in the neighborhood search * @param useVertexOperation contains whether the local search uses the vertex operation * @param useSubtreeOperation contains whether the local search uses the subtree operation * @param useTabuSearch contains whether a tabu search is used * @param tabuTime the tabu time that is the number of iterations an element is in the tabu list * @param upperLimitTabuExchanges the upper limit of non-improving exchanges, this is the * stopping criterion in the tabu search */ public AhujaOrlinSharmaCapacitatedMinimumSpanningTree( CapacitatedSpanningTree initialSolution, Graph graph, V root, double capacity, Map demands, int lengthBound, boolean bestImprovement, boolean useVertexOperation, boolean useSubtreeOperation, boolean useTabuSearch, int tabuTime, int upperLimitTabuExchanges) { this( graph, root, capacity, demands, lengthBound, bestImprovement, 0, useVertexOperation, useSubtreeOperation, useTabuSearch, tabuTime, upperLimitTabuExchanges); if (!initialSolution.isCapacitatedSpanningTree(graph, root, capacity, demands)) { throw new IllegalArgumentException( "The initial solution is not a valid capacitated spanning tree."); } this.initialSolution = initialSolution; } @Override public CapacitatedSpanningTree getCapacitatedSpanningTree() { if (isAlgorithmExecuted) { return bestSolution.calculateResultingSpanningTree(); } // calculates initial solution on which we base the local search bestSolution = getInitialSolution(); // map that contains all spanning trees of the current partition Map> partitionSpanningTrees = new HashMap<>(); // map that contains the subtrees of all vertices Map, Double>> subtrees = new HashMap<>(); // set that contains all part of the partition that were affected by an exchange operation Pair, Set> affected = Pair.of(bestSolution.getLabels(), new HashSet<>()); // the improvement graph ImprovementGraph improvementGraph = new ImprovementGraph(bestSolution); // tabu list Set tabuList = new HashSet<>(); // tabu time list Map> tabuTimeList = new HashMap<>(); // tabu timer int tabuTimer = 0; // number of tabu echanges int numberOfTabuExchanges = 0; // the solution int he current iteration CapacitatedSpanningTreeSolutionRepresentation currentSolution = bestSolution; // the difference from the current solution and the best solution double costDifference = 0; double currentCost; // do local improvement steps while (true) { partitionSpanningTrees = calculateSpanningTrees( currentSolution, partitionSpanningTrees, affected.getFirst()); if (useSubtreeOperation) { subtrees = calculateSubtreesOfVertices( currentSolution, subtrees, partitionSpanningTrees, affected.getFirst()); } improvementGraph .updateImprovementGraph( currentSolution, subtrees, partitionSpanningTrees, affected.getFirst(), tabuList); AhujaOrlinSharmaCyclicExchangeLocalAugmentation< Pair, DefaultWeightedEdge> ahujaOrlinSharmaCyclicExchangeLocalAugmentation = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>( improvementGraph.improvementGraph, lengthBound, improvementGraph.cycleAugmentationLabels, bestImprovement); GraphWalk, DefaultWeightedEdge> cycle = ahujaOrlinSharmaCyclicExchangeLocalAugmentation.getLocalAugmentationCycle(); currentCost = cycle.getWeight(); costDifference += currentCost; if (useTabuSearch) { // do tabu search step if (currentCost < 0) { affected = executeNeighborhoodOperation( currentSolution, improvementGraph.improvementGraphVertexMapping, improvementGraph.pathExchangeVertexMapping, subtrees, cycle); if (costDifference < 0) { bestSolution = currentSolution; costDifference = 0; } } else { if (upperLimitTabuExchanges <= numberOfTabuExchanges) { break; } // clone solution such that a non-improving exchange does not override a good // solution if (currentSolution == bestSolution) { currentSolution = currentSolution.clone(); } affected = executeNeighborhoodOperation( currentSolution, improvementGraph.improvementGraphVertexMapping, improvementGraph.pathExchangeVertexMapping, subtrees, cycle); // update tabu list tabuList.addAll(affected.getSecond()); tabuTimeList.put(tabuTimer, affected.getSecond()); numberOfTabuExchanges++; } // update tabu list Set set = tabuTimeList.remove(tabuTimer - tabuTime - 1); if (set != null) { tabuList.removeAll(set); } tabuTimer++; } else { // do normal local search step if (currentCost < 0) { affected = executeNeighborhoodOperation( currentSolution, improvementGraph.improvementGraphVertexMapping, improvementGraph.pathExchangeVertexMapping, subtrees, cycle); } else { break; } } } this.isAlgorithmExecuted = true; return bestSolution.calculateResultingSpanningTree(); } /** * Calculates an initial solution depending on whether an initial solution was transferred while * construction of the algorithm. If no initial solution was proposed, the algorithm of * Esau-Williams is used. * * @return an initial solution */ private CapacitatedSpanningTreeSolutionRepresentation getInitialSolution() { if (initialSolution != null) { return new CapacitatedSpanningTreeSolutionRepresentation( initialSolution.getLabels(), initialSolution.getPartition()); } return new EsauWilliamsCapacitatedMinimumSpanningTree<>( graph, root, capacity, demands, numberOfOperationsParameter).getSolution(); } /** * Executes the move operations induced by the calculated cycle in the improvement graph. It * returns the set of labels of the subsets that were affected by the move operations. * * @param improvementGraphVertexMapping the mapping from the index of the improvement graph * vertex to the correspondent vertex in the base graph * @param pathExchangeVertexMapping the mapping from the improvement graph pseudo vertices to * their subset that they represent * @param subtrees the map containing the subtree for every vertex * @param cycle the calculated cycle in the improvement graph * @return the set of affected labels of subsets that were affected by the move operations */ private Pair, Set> executeNeighborhoodOperation( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map improvementGraphVertexMapping, Map, Integer> pathExchangeVertexMapping, Map, Double>> subtrees, GraphWalk, DefaultWeightedEdge> cycle) { Set affectedVertices = new HashSet<>(); Set affectedLabels = new HashSet<>(); Iterator> it = cycle.getVertexList().iterator(); if (it.hasNext()) { Pair cur = it.next(); Integer firstLabel; switch (cur.getSecond()) { case SINGLE: firstLabel = currentSolution.getLabel(improvementGraphVertexMapping.get(cur.getFirst())); break; case SUBTREE: firstLabel = currentSolution.getLabel(improvementGraphVertexMapping.get(cur.getFirst())); break; default: firstLabel = -1; } while (it.hasNext()) { Pair next = it.next(); switch (cur.getSecond()) { /* * A vertex is moved form the part of cur to the part of next. Therefore, both parts * are affected. We only consider the label of cur to be affected for now, the label * of next will be add to the affected set in the next iteration. */ case SINGLE: { V curVertex = improvementGraphVertexMapping.get(cur.getFirst()); Integer curLabel = currentSolution.getLabel(curVertex); Integer nextLabel; if (it.hasNext()) { switch (next.getSecond()) { case SINGLE: nextLabel = currentSolution .getLabel(improvementGraphVertexMapping.get(next.getFirst())); break; case SUBTREE: nextLabel = currentSolution .getLabel(improvementGraphVertexMapping.get(next.getFirst())); break; case PSEUDO: nextLabel = pathExchangeVertexMapping.get(next); break; default: throw new IllegalStateException( "This is a bug. There are invalid types of vertices in the cycle."); } } else { nextLabel = firstLabel; } affectedVertices.add(curVertex); affectedLabels.add(curLabel); currentSolution.moveVertex(curVertex, curLabel, nextLabel); break; } /* * A subtree is moved from the part of cur to the part of next. Therefore, the part * of cur is affected. */ case SUBTREE: { V curVertex = improvementGraphVertexMapping.get(cur.getFirst()); Integer curLabel = currentSolution.getLabel(curVertex); Integer nextLabel; if (it.hasNext()) { switch (next.getSecond()) { case SINGLE: nextLabel = currentSolution .getLabel(improvementGraphVertexMapping.get(next.getFirst())); break; case SUBTREE: nextLabel = currentSolution .getLabel(improvementGraphVertexMapping.get(next.getFirst())); break; case PSEUDO: nextLabel = pathExchangeVertexMapping.get(next); break; default: throw new IllegalStateException( "This is a bug. There are invalid types of vertices in the cycle."); } } else { nextLabel = firstLabel; } affectedVertices.add(curVertex); affectedLabels.add(curLabel); // get the whole subtree that has to be moved Set subtreeToMove = subtrees.get(curVertex).getFirst(); currentSolution.moveVertices(subtreeToMove, curLabel, nextLabel); break; } /* * cur is the end of a path exchange. Thus, the part of cur is affected because * vertices were inserted. */ case PSEUDO: { Integer curLabel = pathExchangeVertexMapping.get(cur); affectedLabels.add(curLabel); break; } /* * This is the beginning of a path exchange. We have nothing to do. */ case ORIGIN: { break; } default: throw new IllegalStateException( "This is a bug. There are invalid types of vertices in the cycle."); } cur = next; } } /* * The subsets in the partition may include more than one subtree rooted at root. We create * a subset for all subtrees rooted at root. */ Set moreAffectedLabels = new HashSet<>(); Iterator affectedLabelIterator = affectedLabels.iterator(); while (affectedLabelIterator.hasNext()) { int label = affectedLabelIterator.next(); Set vertexSubset = currentSolution.getPartitionSet(label); if (vertexSubset.isEmpty()) { affectedLabelIterator.remove(); } else { moreAffectedLabels .addAll(currentSolution.partitionSubtreesOfSubset(vertexSubset, label)); } } affectedLabels.addAll(moreAffectedLabels); // clean up the partition such that only current subsets are represented currentSolution.cleanUp(); return Pair.of(affectedLabels, affectedVertices); } /** * Updates the map containing the MSTs for every subset of the partition. * * @param partitionSpanningTrees the map containing the MST for every subset of the partition * @param affectedLabels the labels of the subsets of the partition that were changed due to the * multi-exchange * @return the updated map containing the MST for every subset of the partition */ private Map> calculateSpanningTrees( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map> partitionSpanningTrees, Set affectedLabels) { for (Integer label : affectedLabels) { Set set = currentSolution.getPartitionSet(label); currentSolution.getPartitionSet(label).add(root); partitionSpanningTrees .put( label, new PrimMinimumSpanningTree<>(new AsSubgraph<>(graph, set)).getSpanningTree()); currentSolution.getPartitionSet(label).remove(root); } return partitionSpanningTrees; } /** * Updates the map containing the subtrees of all vertices in the graph with respect to the MST * in the partition and returns them in map. * * @param subtrees the subtree map to update * @param partitionSpanningTree the map containing the MST for every subset of the partition * @param affectedLabels the labels of the subsets of the partition that were changed due to the * multi-exchange * @return the updated map of vertices to their subtrees */ private Map, Double>> calculateSubtreesOfVertices( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Map> partitionSpanningTree, Set affectedLabels) { for (Integer label : affectedLabels) { Set modifiableSet = new HashSet<>(currentSolution.getPartitionSet(label)); modifiableSet.add(root); for (V v : currentSolution.getPartitionSet(label)) { Pair, Double> currentSubtree = subtree(currentSolution, modifiableSet, v, partitionSpanningTree); subtrees.put(v, currentSubtree); } } return subtrees; } /** * Calculates the subtree of v with respect to the MST given in * partitionSpanningTree. * * @param v the vertex to calculate the subtree for * @param partitionSpanningTree the map from labels to spanning trees of the partition. * @return the subtree of v with respect to the MST given in * partitionSpanningTree. */ private Pair, Double> subtree( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Set modifiableSet, V v, Map> partitionSpanningTree) { /* * initializes graph that is the MST of the current subset rooted */ SpanningTreeAlgorithm.SpanningTree partSpanningTree = partitionSpanningTree.get(currentSolution.getLabel(v)); Graph spanningTree = new AsSubgraph<>(graph, modifiableSet, partSpanningTree.getEdges()); /* * calculate subtree rooted at v */ Set subtree = new HashSet<>(); double subtreeWeight = 0; Iterator depthFirstIterator = new DepthFirstIterator<>(spanningTree, v); Set currentPath = new HashSet<>(); double currentWeight = 0; boolean storeCurrentPath = true; while (depthFirstIterator.hasNext()) { V next = depthFirstIterator.next(); if (spanningTree.containsEdge(next, v)) { storeCurrentPath = true; subtree.addAll(currentPath); subtreeWeight += currentWeight; currentPath = new HashSet<>(); currentWeight = 0; } /* * This part of the subtree is connected to the root, thus, this particular tree is not * part of the subtree of the current vertex v. */ if (next.equals(root)) { storeCurrentPath = false; currentPath = new HashSet<>(); currentWeight = 0; } if (storeCurrentPath) { currentPath.add(next); currentWeight += demands.get(next); } } return Pair.of(subtree, subtreeWeight); } /** * This enums contains the vertex types of the improvement graph. */ private enum ImprovementGraphVertexType { SINGLE, SUBTREE, PSEUDO, ORIGIN } /** * This class realises the improvement graph for the composite multi-exchange large neighborhood * search. The improvement graph encodes two exchange classes: - cyclic exchange (on vertices * and subtrees) - path exchange (on vertices and subtrees) *

    * DEFINITION EXCHANGES Let T[i] be the subtree rooted at i of the MST implicitly defined by the * vertex partition. Cyclic Exchange: A cyclic exchange is defined on vertices i_1, ..., i_r, * i_1, where the vertices represent either itself in the base graph or the subtrees rooted at * i_k for k = 1, ..., r, where T[i_a] != T[i_b] for a != b. The cyclic exchange on i_1, ..., * i_r, i_1 moves the i_a (or T[i_a]) to the subset of i_b, where b = a+1 mod r+1. Such a cyclic * exchange is feasible if the capacity constraint is not violated. We can represent the cost of * the cyclic exchange by the following formulas: Let S[i_k] be the subset of i_k in the * implicitly defined partition. - exchange of vertices: $$ c(T_new) - c(T) = \sum_{a = 1}^{r} * c(\{i_{a - 1}\} \cup S[i_{i_a}] \setminus \{i_a\}] $$ - exchange of rooted subtrees: $$ * c(T_new) - c(T) = \sum_{a = 1}^{r} c(T[i_{a - 1}] \cup S[i_{i_a}] \setminus T[i_a]] $$ where * c is the given edge cost function and T_new is the CMST resulting by executing the cyclic * exchange. Thus, an exchange is profitable if c(T_new) - c(T) < 0. *

    * Path Exchange: A path exchange follows the same idea as the cyclic exchange but it does not * end at the same vertex. That is, the path exchange is defined on i_1, ..., i_r. The cost * function has to be adapted at the start and end point of the path. *

    * DEFINITION NEIGHBORHOOD Furthermore, we have to define the neighborhood. These are all * capacitated spanning trees that are reachable by using such an exchange as given above. *

    * DEFINITION IMPROVEMENT GRAPH The improvement graph is based on a feasible capacitated * spanning tree and uses a one-to-one correspondence between the vertices in the base graph and * the vertices in the improvement graph. We want to define the arc set of the improvement graph * such that each subset disjoint directed cycle (see construction) correspond to a cyclic * exchange (or a path exchange, we come to that later). Furthermore, the cost of the cycle in * the improvement graph and the cost of the corresponding cyclic exchange has to be equal. *

    * CONSTRUCTION OF THE IMPROVEMENT GRAPH The improvement graph IG = (V, A) has the vertex set V, * which is equal to the vertex set of the base graph. The arc set A is defined in the * following: A directed arc (i, j) in IG represents that we move the node i (or the subtree * T[i]) to the subset in which vertex j is. That is, vertex i and j are removed from their * subset and i (or the subtree T[i]) is moved to the subset of j. This arc only exists if the * exchange is feasible. Then, the cost can be defined as $$ c(\{T[i]\} \cup S[j] \setminus * \{T[j]\}) - c(S[j]). $$ A directed cycle i_1, ..., i_r, i_1 in this graph subset disjoint if * the subsets of the nodes are pairwise disjoint. By this definition, there is a one-to-one * cost-preserving correspondence between the cyclic exchanges and the subset disjoint directed * cycles in the improvement graph IG. *

    * Identifying path exchanges: For the conversion of path exchanges into subset disjoint cycles, * we have to introduce two more node types in the improvement graph: pseudo nodes and a origin * node. On the one hand, pseudo nodes represent a subset of the implicitly defined partition * and mark the end of the end of a path exchange. On the other hand, the origin node marks a * beginning of a path exchange. Therefore, the pseudo node are connected to the origin node to * induce subset disjoint cycles. The costs of the arcs from and to the pseudo nodes and the * origin nodes are defined as follows: We denoted the original nodes in the improvement graph * as regular nodes - c(p, o) = 0 for all pseudo nodes p and origin node o - c(o, r) = c(S[j] * \setminus \{T[j]\}) - c(S[j]) for origin node o and for all regular nodes r - c(r, p) = * c(\{T[i]\} \cup S[j]) - c(S[j]) for all regular nodes r and for all pseudo nodes p Again, * those arc exists only if the exchange is feasible. *

    * IDENTIFYING SUBSET DISJOINT CYCLES This is done via a heuristic which can be found here * {@link AhujaOrlinSharmaCyclicExchangeLocalAugmentation} @see * AhujaOrlinSharmaCyclicExchangeLocalAugmentation. */ private class ImprovementGraph { /** * the improvement graph itself */ Graph, DefaultWeightedEdge> improvementGraph; /** * the current solution corresponding to the improvement graph */ CapacitatedSpanningTreeSolutionRepresentation capacitatedSpanningTreeSolutionRepresentation; /** * mapping form all improvement graph vertices to their labels corresponding to the base * graph for the CMST problem */ Map, Integer> cycleAugmentationLabels; /** * mapping from the vertex index in the improvement graph to the vertex in the base graph */ Map improvementGraphVertexMapping; /** * mapping from the base graph vertex to the vertex index in the improvement graph */ Map initialVertexMapping; /** * mapping from the label of the subsets to the corresponding vertex mapping */ Map> pseudoVertexMapping; /** * mapping from the pseudo vertices to the label of the subset they are representing */ Map, Integer> pathExchangeVertexMapping; /** * the origin vertex */ Pair origin; /** * dummy label of the origin vertex */ final Integer originVertexLabel = -1; /** * Constructs an new improvement graph object for this CMST algorithm instance. */ public ImprovementGraph( CapacitatedSpanningTreeSolutionRepresentation capacitatedSpanningTreeSolutionRepresentation) { this.capacitatedSpanningTreeSolutionRepresentation = capacitatedSpanningTreeSolutionRepresentation; this.improvementGraphVertexMapping = new HashMap<>(); this.initialVertexMapping = new HashMap<>(); this.pseudoVertexMapping = new HashMap<>(); this.pathExchangeVertexMapping = new HashMap<>(); /* * We initialize this map such that it can be used in the subset-disjoint cycle * detection algorithm. This map redirects the getters to the corresponding maps in this * improvement graph such that it realises the correct functionality. */ this.cycleAugmentationLabels = getImprovementGraphLabelMap(); this.improvementGraph = createImprovementGraph(); } /** * Initializes the improvement graph, i.e. adds single, subtree and pseudo vertices as well * as the origin vertex. Furthermore, it initializes all mappings. * * @return the improvement graph itself. */ public Graph, DefaultWeightedEdge> createImprovementGraph() { Graph, DefaultWeightedEdge> improvementGraph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); int counter = 0; for (V v : graph.vertexSet()) { if (v.equals(root)) { continue; } if (useVertexOperation) { Pair singleVertex = new Pair<>(counter, ImprovementGraphVertexType.SINGLE); improvementGraph.addVertex(singleVertex); } if (useSubtreeOperation) { Pair subtreeVertex = new Pair<>(counter, ImprovementGraphVertexType.SUBTREE); improvementGraph.addVertex(subtreeVertex); } // we have to add these only once improvementGraphVertexMapping.put(counter, v); initialVertexMapping.put(v, counter); counter++; } Pair origin = new Pair<>(counter, ImprovementGraphVertexType.ORIGIN); improvementGraph.addVertex(origin); this.origin = origin; pathExchangeVertexMapping.put(origin, originVertexLabel); for (Integer label : capacitatedSpanningTreeSolutionRepresentation.getLabels()) { Pair pseudoVertex = new Pair<>(origin.getFirst() + label + 1, ImprovementGraphVertexType.PSEUDO); pseudoVertexMapping.put(label, pseudoVertex); pathExchangeVertexMapping.put(pseudoVertex, label); improvementGraph.addVertex(pseudoVertex); } /* * connection of pseudo nodes and origin node */ for (Pair v : pseudoVertexMapping.values()) { improvementGraph.setEdgeWeight(improvementGraph.addEdge(v, origin), 0); } return improvementGraph; } /** * Updates the improvement graph. It updates the vertices and edges in the parts specified * in labelsToUpdate. * * @param currentSolution the current solution * @param subtrees the mapping from vertices to their subtree * @param partitionSpanningTrees the mapping from labels of subsets to their spanning tree * @param labelsToUpdate the labels of all subsets that has to be updated (because of the * multi-exchange operation) */ public void updateImprovementGraph( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Map> partitionSpanningTrees, Set labelsToUpdate, Set tabuList) { this.capacitatedSpanningTreeSolutionRepresentation = currentSolution; this.cycleAugmentationLabels = getImprovementGraphLabelMap(); updatePseudoNodesOfNewLabels(currentSolution); for (V v1 : graph.vertexSet()) { if (v1.equals(root)) { continue; } Pair vertexOfV1Single = Pair.of(initialVertexMapping.get(v1), ImprovementGraphVertexType.SINGLE); Pair vertexOfV1Subtree = Pair.of(initialVertexMapping.get(v1), ImprovementGraphVertexType.SUBTREE); if (updateTabuVertices(tabuList, v1, vertexOfV1Single, vertexOfV1Subtree)) { continue; } updateOriginNodeConnections( currentSolution, subtrees, partitionSpanningTrees, labelsToUpdate, v1, vertexOfV1Single, vertexOfV1Subtree); /* * update the connections to regular nodes and pseudo nodes */ for (Integer label : currentSolution.getLabels()) { /* * only update if there is a change induced by a changed part. This potentially * saves a lot of time. */ if (label.equals(currentSolution.getLabel(v1)) || (!labelsToUpdate.contains(currentSolution.getLabel(v1)) && !labelsToUpdate.contains(label))) { continue; } Pair pseudoVertex = pseudoVertexMapping.get(label); Set modifiableSet = new HashSet<>(currentSolution.getPartitionSet(label)); // add root to the set for MST calculations modifiableSet.add(root); double oldWeight = partitionSpanningTrees.get(label).getWeight(); updateSingleNode( currentSolution, subtrees, tabuList, label, oldWeight, modifiableSet, pseudoVertex, v1, vertexOfV1Single); updateSubtreeNode( currentSolution, subtrees, tabuList, label, oldWeight, modifiableSet, pseudoVertex, v1, vertexOfV1Subtree); } } } /** * Updates the pseudo nodes corresponding to new subsets in the partition. That is, new * pseudo nodes for new labels in the label set are added and pseudo nodes of labels that * are no more in the label set are removed. * * @param currentSolution the current solution in the iteration */ private void updatePseudoNodesOfNewLabels( CapacitatedSpanningTreeSolutionRepresentation currentSolution) { if (!currentSolution.getLabels().equals(pseudoVertexMapping.keySet())) { for (Integer label : currentSolution.getLabels()) { if (!pseudoVertexMapping.keySet().contains(label)) { Pair pseudoVertex = new Pair<>( origin.getFirst() + label + 1, ImprovementGraphVertexType.PSEUDO); pseudoVertexMapping.put(label, pseudoVertex); pathExchangeVertexMapping.put(pseudoVertex, label); improvementGraph.addVertex(pseudoVertex); DefaultWeightedEdge newEdge = improvementGraph.addEdge(pseudoVertex, origin); improvementGraph.setEdgeWeight(newEdge, 0); } } if (currentSolution.getLabels().size() != pseudoVertexMapping.keySet().size()) { Iterator labelIterator = pseudoVertexMapping.keySet().iterator(); while (labelIterator.hasNext()) { int label = labelIterator.next(); if (!currentSolution.getLabels().contains(label)) { Pair pseudoVertex = new Pair<>( origin.getFirst() + label + 1, ImprovementGraphVertexType.PSEUDO); labelIterator.remove(); pathExchangeVertexMapping.remove(pseudoVertex); improvementGraph.removeVertex(pseudoVertex); } } } } } /** * Updates all nodes that correspond to v1 and returns if the vertex * v1. That is, all incident edges of v1 are removed if * v1 is in the tabu list. * * @param tabuList the tabu list of the current iteration * @param v1 the vertex to update the nodes in the improvement graph for * @param vertexOfV1Single the node in the improvement graph representing the exchange of * the vertex v1 * @param vertexOfV1Subtree the node in the improvement graph representing the exchange of * the subtree rooted at v1 * @return true iff v1 is in the tabu list */ private boolean updateTabuVertices( Set tabuList, V v1, Pair vertexOfV1Single, Pair vertexOfV1Subtree) { if (tabuList.contains(v1)) { // remove all edges from the vertex if (useVertexOperation) { improvementGraph.removeVertex(vertexOfV1Single); improvementGraph.addVertex(vertexOfV1Single); } if (useSubtreeOperation) { improvementGraph.removeVertex(vertexOfV1Subtree); improvementGraph.addVertex(vertexOfV1Subtree); } return true; } return false; } /** * Updates the edges to the origin vertex. * * @param currentSolution the current solution in the iteration * @param subtrees the mapping from vertices to their subtree * @param partitionSpanningTrees the mapping from labels of subsets to their spanning tree * @param labelsToUpdate the labels of all subsets that has to be updated (because of the * multi-exchange operation) * @param v1 the vertex to update the nodes in the improvement graph for * @param vertexOfV1Single the node in the improvement graph representing the exchange of * the vertex v1 * @param vertexOfV1Subtree the node in the improvement graph representing the exchange of * the subtree rooted at v1 */ private void updateOriginNodeConnections( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Map> partitionSpanningTrees, Set labelsToUpdate, V v1, Pair vertexOfV1Single, Pair vertexOfV1Subtree) { double newWeight, oldWeight; SpanningTreeAlgorithm.SpanningTree spanningTree; /* * update connections to origin node */ if (labelsToUpdate.contains(currentSolution.getLabel(v1))) { oldWeight = partitionSpanningTrees.get(currentSolution.getLabel(v1)).getWeight(); /* * edge for v1 vertex remove operation */ Set partitionSetOfV1 = currentSolution.getPartitionSet(currentSolution.getLabel(v1)); partitionSetOfV1.add(root); if (useVertexOperation) { partitionSetOfV1.remove(v1); spanningTree = new PrimMinimumSpanningTree<>(new AsSubgraph<>(graph, partitionSetOfV1)) .getSpanningTree(); if (spanningTree.getEdges().size() == partitionSetOfV1.size() - 1) { newWeight = spanningTree.getWeight(); } else { newWeight = Double.NaN; } updateImprovementGraphEdge(origin, vertexOfV1Single, 0, newWeight - oldWeight); partitionSetOfV1.add(v1); } /* * edge for v1 subtree remove operation If the subtree of v1 contains only the * vertex itself, it is the same operation as removing v1 as vertex. Thus, do not * add edges. */ if (useSubtreeOperation) { if (subtrees.get(v1).getFirst().size() > 1 || !useVertexOperation) { partitionSetOfV1.removeAll(subtrees.get(v1).getFirst()); spanningTree = new PrimMinimumSpanningTree<>(new AsSubgraph<>(graph, partitionSetOfV1)) .getSpanningTree(); if (spanningTree.getEdges().size() == partitionSetOfV1.size() - 1) { newWeight = spanningTree.getWeight(); } else { newWeight = Double.NaN; } updateImprovementGraphEdge( origin, vertexOfV1Subtree, 0, newWeight - oldWeight); partitionSetOfV1.addAll(subtrees.get(v1).getFirst()); } else { improvementGraph.removeVertex(vertexOfV1Subtree); improvementGraph.addVertex(vertexOfV1Subtree); } } partitionSetOfV1.remove(root); } } /** * Updates all edges from vertexOfV1Single to nodes in the subset represented * by label. * * @param currentSolution the current solution in the iteration * @param subtrees the mapping from vertices to their subtree * @param tabuList the tabu list of the current iteration * @param label the current label to update the edges for * @param oldWeight the old weight of the subset * @param modifiableSet a modifiable version of the subset of nodes represented by label * inclusive the root node * @param pseudoVertex the pseudo vertex representing the subset represented by label * @param v1 the vertex to update the nodes in the improvement graph for * @param vertexOfV1Single the node in the improvement graph representing the exchange of * the vertex v1 */ private void updateSingleNode( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Set tabuList, int label, double oldWeight, Set modifiableSet, Pair pseudoVertex, V v1, Pair vertexOfV1Single) { double newCapacity, newWeight; SpanningTreeAlgorithm.SpanningTree spanningTree; // add v1 to the set for MST calculations modifiableSet.add(v1); /* * Adding of edges for v1 vertex replacing an object in v2. We need to considers this * only if vertex operations should be used. */ if (useVertexOperation) { for (V v2 : currentSolution.getPartitionSet(label)) { if (v2.equals(root)) { throw new IllegalStateException( "The root is in the partition. This is a bug."); } if (tabuList.contains(v2)) { continue; } /* * edge for v1 vertex replacing v2 vertex */ modifiableSet.remove(v2); spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())).getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + demands.get(v1) - demands.get(v2)); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Single, Pair.of(initialVertexMapping.get(v2), ImprovementGraphVertexType.SINGLE), newCapacity, newWeight - oldWeight); modifiableSet.add(v2); // end edge for v1 vertex replacing v2 vertex /* * edge for v1 vertex replacing v2 subtree If the subtree of v2 contains only * the vertex itself and both operations are used, it is the same operation as * moving v2 as vertex. Thus, do not add edges. */ if (useSubtreeOperation) { if (subtrees.get(v2).getFirst().size() > 1) { modifiableSet.removeAll(subtrees.get(v2).getFirst()); spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())) .getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + demands.get(v1) - subtrees.get(v2).getSecond()); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Single, Pair .of( initialVertexMapping.get(v2), ImprovementGraphVertexType.SUBTREE), newCapacity, newWeight - oldWeight); modifiableSet.addAll(subtrees.get(v2).getFirst()); } } // end edge for v1 vertex replacing v2 subtree } /* * edge for v1 vertex replacing no object */ spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())).getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + demands.get(v1)); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Single, pseudoVertex, newCapacity, newWeight - oldWeight); // end edge for v1 vertex replacing no object // remove v1 from the set modifiableSet.remove(v1); } } /** * Updates all edges from vertexOfV1Single to nodes in the subset represented * by label. This method does adds the subtree of v1 to * modifiableSet. * * @param currentSolution the current solution in the iteration * @param subtrees the mapping from vertices to their subtree * @param tabuList the tabu list of the current iteration * @param label the current label to update the edges for * @param oldWeight the old weight of the subset * @param modifiableSet a modifiable version of the subset of nodes represented by label * @param pseudoVertex the pseudo vertex representing the subset represented by label * @param v1 the vertex to update the nodes in the improvement graph for * @param vertexOfV1Subtree the node in the improvement graph representing the exchange of * the subtree rooted at v1 */ private void updateSubtreeNode( CapacitatedSpanningTreeSolutionRepresentation currentSolution, Map, Double>> subtrees, Set tabuList, int label, double oldWeight, Set modifiableSet, Pair pseudoVertex, V v1, Pair vertexOfV1Subtree) { double newCapacity, newWeight; SpanningTreeAlgorithm.SpanningTree spanningTree; /* * Adding of edges for v1 subtree replacing an object in v2. We need to considers this * only if subtree operations should be used. * * If the subtree of v1 contains only the vertex itself and both operations are used, it * is the same operation as moving v1 as vertex. Thus, do not add edges. */ if (useSubtreeOperation && (subtrees.get(v1).getFirst().size() > 1 || !useVertexOperation)) { // add the subtree of v1 to the set for MST calculations modifiableSet.addAll(subtrees.get(v1).getFirst()); for (V v2 : currentSolution.getPartitionSet(label)) { if (v2.equals(root)) { throw new IllegalStateException( "The root is in the partition. This is a bug."); } if (tabuList.contains(v2)) { continue; } /* * edge for v1 subtree replacing v2 vertex */ if (useVertexOperation) { modifiableSet.remove(v2); spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())) .getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + subtrees.get(v1).getSecond() - demands.get(v2)); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Subtree, Pair .of( initialVertexMapping.get(v2), ImprovementGraphVertexType.SINGLE), newCapacity, newWeight - oldWeight); modifiableSet.add(v2); } // end edge for v1 subtree replacing v2 vertex /* * edge for v1 subtree replacing v2 subtree */ modifiableSet.removeAll(subtrees.get(v2).getFirst()); spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())).getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(currentSolution.getLabel(v2)) + subtrees.get(v1).getSecond() - subtrees.get(v2).getSecond()); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Subtree, Pair.of(initialVertexMapping.get(v2), ImprovementGraphVertexType.SUBTREE), newCapacity, newWeight - oldWeight); modifiableSet.addAll(subtrees.get(v2).getFirst()); // end edge for v1 subtree replacing v2 subtree } /* * edge for v1 subtree replacing no object */ spanningTree = new PrimMinimumSpanningTree<>( new AsSubgraph<>(graph, modifiableSet, graph.edgeSet())).getSpanningTree(); if (spanningTree.getEdges().size() == modifiableSet.size() - 1) { newCapacity = calculateMaximumDemandOfSubtrees( modifiableSet, spanningTree, currentSolution.getPartitionWeight(label) + subtrees.get(v1).getSecond()); newWeight = spanningTree.getWeight(); } else { newCapacity = Double.NaN; newWeight = Double.NaN; } updateImprovementGraphEdge( vertexOfV1Subtree, pseudoVertex, newCapacity, newWeight - oldWeight); // end edge for v1 subtree replacing no object } } /** * Adds an edge between v1 and v2 to the improvement graph if * newCapacity does not exceed the capacity constraint. The weight of the edge * is newCost. * * @param v1 start vertex (the vertex or subtree induced by v1 that will be * moved to the subset of v2) * @param v2 end vertex (the vertex or subtree induced by v2 that will be * removed from the subset of v2) * @param newCapacity the used capacity by adding the vertex or subtree induced by * v1 to the subset of v2 and deleting the vertex or * subtree induced by v2 * @param newCost the cost of the edge (the cost induced by the operation induced by * v1 and v2) */ public void updateImprovementGraphEdge( Pair v1, Pair v2, double newCapacity, double newCost) { if (!Double.isNaN(newCapacity) && newCapacity <= capacity && !Double.isNaN(newCost)) { DefaultWeightedEdge edge; edge = improvementGraph.getEdge(v1, v2); if (edge == null) { edge = improvementGraph.addEdge(v1, v2); } improvementGraph.setEdgeWeight(edge, newCost); } else { improvementGraph.removeEdge(v1, v2); } } /** * Calculates the maximum demand over all new subtrees induced by the minimum spanning tree * spanningTree. A spanning tree induces more than one subset in the partition * if the root vertex of the base graph connects more than one subtree of the spanning tree. * * @param vertexSubset the vertex subset spanning Tree is defined on * @param spanningTree the spanning tree * @param totalDemand the total demand of the whole spanning tree * @return the maximum demand over all new subtrees induced by the minimum spanning tree * spanningTree */ public double calculateMaximumDemandOfSubtrees( Set vertexSubset, SpanningTreeAlgorithm.SpanningTree spanningTree, double totalDemand) { Graph spanningTreeGraph = new AsSubgraph<>(graph, vertexSubset, spanningTree.getEdges()); /* * The subtree does not evolve to more than 1 partition subsets, thus, we can return the * total demand. */ int degreeOfRoot = spanningTreeGraph.degreeOf(root); if (degreeOfRoot == 1) { return totalDemand; } double maximumDemand = 0; DepthFirstIterator depthFirstIterator = new DepthFirstIterator<>(spanningTreeGraph, root); if (depthFirstIterator.hasNext()) { depthFirstIterator.next(); } int numberOfRootEdgesExplored = 0; double exploredVerticesDemand = 0; double currentDemand = 0; while (depthFirstIterator.hasNext()) { V next = depthFirstIterator.next(); // exploring new subtree if (spanningTreeGraph.containsEdge(root, next)) { exploredVerticesDemand += currentDemand; if (maximumDemand < currentDemand) { maximumDemand = currentDemand; } // we can stop the exploration if (maximumDemand >= 0.5 * totalDemand || exploredVerticesDemand + maximumDemand >= totalDemand) { return maximumDemand; } // we can stop the exploration, all subtrees but one are explored if (numberOfRootEdgesExplored + 1 == degreeOfRoot) { return Math.max(maximumDemand, totalDemand - exploredVerticesDemand); } numberOfRootEdgesExplored++; currentDemand = 0; } currentDemand += demands.get(next); } return maximumDemand; } /** * Returns the mapping that is used in the valid cycle detection algorithm, i.e. the vertex * label map. * * @return the vertex label map used in the valid cycle detection algorithm */ private Map, Integer> getImprovementGraphLabelMap() { return new AbstractMap, Integer>() { @Override public int size() { return improvementGraphVertexMapping.size() + pathExchangeVertexMapping.size() + (origin == null ? 0 : 1); } @Override public boolean isEmpty() { return improvementGraphVertexMapping.isEmpty() && pathExchangeVertexMapping.isEmpty() && origin == null; } @Override public boolean containsKey(Object key) { if (key instanceof Pair) { return improvementGraphVertexMapping.containsKey(((Pair) key).getFirst()) || pathExchangeVertexMapping.containsKey(key) || key.equals(origin); } return false; } @Override public boolean containsValue(Object value) { return improvementGraphVertexMapping.containsValue(value) || pathExchangeVertexMapping.containsValue(value) || value.equals(originVertexLabel); } @Override public Integer get(Object key) { if (key instanceof Pair) { if (improvementGraphVertexMapping.containsKey(((Pair) key).getFirst())) { return capacitatedSpanningTreeSolutionRepresentation .getLabel( improvementGraphVertexMapping.get(((Pair) key).getFirst())); } if (key.equals(origin)) { return originVertexLabel; } } return pathExchangeVertexMapping.get(key); } @Override public Integer put(Pair key, Integer value) { throw new IllegalStateException(); } @Override public Integer remove(Object key) { throw new IllegalStateException(); } @Override public void putAll( Map, ? extends Integer> m) { throw new IllegalStateException(); } @Override public void clear() { throw new IllegalStateException(); } @Override public Set> keySet() { Set> keySet = new HashSet<>(); for (Integer i : improvementGraphVertexMapping.keySet()) { if (useVertexOperation) { keySet.add(Pair.of(i, ImprovementGraphVertexType.SINGLE)); } if (useSubtreeOperation) { keySet.add(Pair.of(i, ImprovementGraphVertexType.SUBTREE)); } } keySet.addAll(pathExchangeVertexMapping.keySet()); keySet.add(origin); return keySet; } @Override public Collection values() { return capacitatedSpanningTreeSolutionRepresentation.getLabels(); } @Override public Set, Integer>> entrySet() { Set, Integer>> entrySet = new HashSet<>(); for (Integer i : improvementGraphVertexMapping.keySet()) { Integer label = capacitatedSpanningTreeSolutionRepresentation .getLabel(improvementGraphVertexMapping.get(i)); if (useVertexOperation) { entrySet .add( new AbstractMap.SimpleEntry<>( Pair.of(i, ImprovementGraphVertexType.SINGLE), label)); } if (useSubtreeOperation) { entrySet .add( new AbstractMap.SimpleEntry<>( Pair.of(i, ImprovementGraphVertexType.SUBTREE), label)); } } for (Pair pseudoVertex : pathExchangeVertexMapping .keySet()) { entrySet .add( new AbstractMap.SimpleEntry<>( pseudoVertex, pathExchangeVertexMapping.get(pseudoVertex))); } entrySet.add(new AbstractMap.SimpleEntry<>(origin, originVertexLabel)); return entrySet; } }; } } } BoruvkaMinimumSpanningTree.java000066400000000000000000000112341402514743400345150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; /** * Borůvka's algorithm for the computation of a minimum spanning tree. * *

    * See the article on * wikipedia for more * information on the history of the algorithm. * *

    * This implementation uses a union-find data structure (with union by rank and path compression * heuristic) in order to track components. In graphs where edges have identical weights, edges with * equal weights are ordered lexicographically. The running time is $O((E+V) \log V)$ under the * assumption that the union-find uses path-compression. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class BoruvkaMinimumSpanningTree implements SpanningTreeAlgorithm { private final Graph graph; private final Comparator comparator; /** * Construct a new instance of the algorithm. * * @param graph the input graph */ public BoruvkaMinimumSpanningTree(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); this.comparator = new ToleranceDoubleComparator(); } /** * {@inheritDoc} */ @Override public SpanningTree getSpanningTree() { // create result placeholder Set mstEdges = new LinkedHashSet<>(); double mstWeight = 0d; // fix edge order for unique comparison of edge weights Map edgeOrder = new HashMap<>(); int i = 0; for (E e : graph.edgeSet()) { edgeOrder.put(e, i++); } // initialize forest UnionFind forest = new UnionFind<>(graph.vertexSet()); Map bestEdge = new LinkedHashMap<>(); do { // find safe edges bestEdge.clear(); for (E e : graph.edgeSet()) { V sTree = forest.find(graph.getEdgeSource(e)); V tTree = forest.find(graph.getEdgeTarget(e)); if (sTree.equals(tTree)) { // same tree, skip continue; } double eWeight = graph.getEdgeWeight(e); // check if better edge E sTreeEdge = bestEdge.get(sTree); if (sTreeEdge == null) { bestEdge.put(sTree, e); } else { double sTreeEdgeWeight = graph.getEdgeWeight(sTreeEdge); int c = comparator.compare(eWeight, sTreeEdgeWeight); if (c < 0 || (c == 0 && edgeOrder.get(e) < edgeOrder.get(sTreeEdge))) { bestEdge.put(sTree, e); } } // check if better edge E tTreeEdge = bestEdge.get(tTree); if (tTreeEdge == null) { bestEdge.put(tTree, e); } else { double tTreeEdgeWeight = graph.getEdgeWeight(tTreeEdge); int c = comparator.compare(eWeight, tTreeEdgeWeight); if (c < 0 || (c == 0 && edgeOrder.get(e) < edgeOrder.get(tTreeEdge))) { bestEdge.put(tTree, e); } } } // add safe edges to forest for (V v : bestEdge.keySet()) { E e = bestEdge.get(v); V sTree = forest.find(graph.getEdgeSource(e)); V tTree = forest.find(graph.getEdgeTarget(e)); if (sTree.equals(tTree)) { // same tree, skip continue; } mstEdges.add(e); mstWeight += graph.getEdgeWeight(e); forest.union(sTree, tTree); } } while (!bestEdge.isEmpty()); // return mst return new SpanningTreeImpl<>(mstEdges, mstWeight); } } EsauWilliamsCapacitatedMinimumSpanningTree.java000066400000000000000000000353761402514743400376430ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/* * (C) Copyright 2018-2021, by Christoph Grüne and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.util.*; import java.util.*; /** * Implementation of a randomized version of the Esau-Williams heuristic, a greedy randomized * adaptive search heuristic (GRASP) for the capacitated minimum spanning tree (CMST) problem. It * calculates a suboptimal CMST. The original version can be found in L. R. Esau and K. C. Williams. * 1966. On teleprocessing system design: part II a method for approximating the optimal network. * IBM Syst. J. 5, 3 (September 1966), 142-147. DOI=http://dx.doi.org/10.1147/sj.53.0142 This * implementation runs in polynomial time O(|V|^3). *

    * This implementation is a randomized version described in Ahuja, Ravindra K., Orlin, James B., and * Sharma, Dushyant, (1998). New neighborhood search structures for the capacitated minimum spanning * tree problem, No WP 4040-98. Working papers, Massachusetts Institute of Technology (MIT), Sloan * School of Management. *

    * This version runs in polynomial time dependent on the number of considered operations per * iteration numberOfOperationsParameter (denoted by p), such that runs is in $O(|V|^3 * + p|V|) = O(|V|^3)$ since $p \leq |V|$. *

    * A Capacitated Minimum * Spanning Tree (CMST) is a rooted minimal cost spanning tree that satisfies the capacity * constrained on all trees that are connected to the designated root. The problem is NP-hard. * * @param the vertex type * @param the edge type * * @author Christoph Grüne * @since July 12, 2018 */ public class EsauWilliamsCapacitatedMinimumSpanningTree extends AbstractCapacitatedMinimumSpanningTree { /** * the number of the most profitable operations for every iteration considered in the procedure. */ private final int numberOfOperationsParameter; /** * contains whether the algorithm was executed */ private boolean isAlgorithmExecuted; /** * Constructs an Esau-Williams GRASP algorithm instance. * * @param graph the graph * @param root the root of the CMST * @param capacity the capacity constraint of the CMST * @param weights the weights of the vertices * @param numberOfOperationsParameter the parameter how many best vertices are considered in the * procedure */ public EsauWilliamsCapacitatedMinimumSpanningTree( Graph graph, V root, double capacity, Map weights, int numberOfOperationsParameter) { super(graph, root, capacity, weights); this.numberOfOperationsParameter = numberOfOperationsParameter; this.isAlgorithmExecuted = false; } /** * {@inheritDoc} *

    * Returns a capacitated spanning tree computed by the Esau-Williams algorithm. */ @Override public CapacitatedSpanningTree getCapacitatedSpanningTree() { if (isAlgorithmExecuted) { return bestSolution.calculateResultingSpanningTree(); } bestSolution = getSolution(); CapacitatedSpanningTree cmst = bestSolution.calculateResultingSpanningTree(); isAlgorithmExecuted = true; if (!cmst.isCapacitatedSpanningTree(graph, root, capacity, demands)) { throw new IllegalArgumentException( "This graph does not have a capacitated minimum spanning tree with the given capacity and demands."); } return cmst; } /** * Calculates a partition representation of the capacitated spanning tree. With that, it is * possible to calculate the to the partition corresponding capacitated spanning tree in * polynomial time by calculating the MSTs. The labels of the partition that are returned are * non-negative. * * @return a representation of the partition of the capacitated spanning tree that has * non-negative labels. */ protected CapacitatedSpanningTreeSolutionRepresentation getSolution() { /* * labeling of the improvement graph vertices. There are two vertices in the improvement * graph for every vertex in the input graph: the vertex indicating the vertex itself and * the vertex indicating the subtree. */ Map labels = new HashMap<>(); /* * the implicit partition defined by the subtrees */ Map, Double>> partition = new HashMap<>(); /* * initialize labels and partitions by assigning every vertex to a new part and create * solution representation */ int counter = 0; for (V v : graph.vertexSet()) { if (v != root) { labels.put(v, counter); Set currentPart = new HashSet<>(); currentPart.add(v); partition.put(counter, Pair.of(currentPart, demands.get(v))); counter++; } } /* * construct a new solution representation with the initialized labels and partition */ bestSolution = new CapacitatedSpanningTreeSolutionRepresentation(labels, partition); /* * map that contains the current savings for all vertices */ Map savings = new HashMap<>(); /* * map that contains the current closest vertex for all vertices */ Map closestVertex = new HashMap<>(); /* * map that contains all labels of partition the vertex cannot be assigned because the * capacity would be exceeded */ Map> restrictionMap = new HashMap<>(); /* * map that contains the vertex that is nearest to the root vertex for all labels of the * partition */ Map shortestGate = new HashMap<>(); /* * set of vertices that have to be considered in the current iteration */ Set vertices = new HashSet<>(graph.vertexSet()); vertices.remove(root); while (true) { for (Iterator it = vertices.iterator(); it.hasNext();) { V v = it.next(); V closestVertexToV = calculateClosestVertex(v, restrictionMap, shortestGate); if (closestVertexToV == null) { // there is not valid closest vertex to connect with, i.e. v will not be // connected to any vertex it.remove(); savings.remove(v); continue; } // store closest vertex to v1 closestVertex.put(v, closestVertexToV); // store the maximum saving and the corresponding vertex savings .put( v, getDistance(shortestGate.getOrDefault(bestSolution.getLabel(v), v), root) - getDistance(v, closestVertexToV)); } // calculate list of best operations LinkedList bestVertices = getListOfBestOptions(savings); if (!bestVertices.isEmpty()) { V vertexToMove = bestVertices.get((int) (Math.random() * bestVertices.size())); // update shortestGate Integer labelOfVertexToMove = bestSolution.getLabel(vertexToMove); V closestMoveVertex = closestVertex.get(vertexToMove); Integer labelOfClosestMoveVertex = bestSolution.getLabel(closestMoveVertex); V shortestGate1 = shortestGate.getOrDefault(labelOfVertexToMove, vertexToMove); V shortestGate2 = shortestGate.getOrDefault(labelOfClosestMoveVertex, closestMoveVertex); /* * Do improving move. The case distinction is important such that the the * restriction map uses minimal space. If the restriction map contains the label of * the part with bigger weight, i.e. the vertex cannot be connected to this part, * the label is still correct and is not the label of the old part, which will be * deleted by the move operation. */ if (bestSolution.getPartitionWeight(labelOfVertexToMove) < bestSolution .getPartitionWeight(labelOfClosestMoveVertex)) { bestSolution .moveVertices( bestSolution.getPartitionSet(labelOfVertexToMove), labelOfVertexToMove, labelOfClosestMoveVertex); if (getDistance(shortestGate1, root) < getDistance(shortestGate2, root)) { shortestGate.put(labelOfClosestMoveVertex, shortestGate1); } else { shortestGate.put(labelOfClosestMoveVertex, shortestGate2); } } else { bestSolution .moveVertices( bestSolution.getPartitionSet(labelOfClosestMoveVertex), labelOfClosestMoveVertex, labelOfVertexToMove); if (getDistance(shortestGate1, root) < getDistance(shortestGate2, root)) { shortestGate.put(labelOfVertexToMove, shortestGate1); } else { shortestGate.put(labelOfVertexToMove, shortestGate2); } } } else { break; } } CapacitatedSpanningTreeSolutionRepresentation result = new CapacitatedSpanningTreeSolutionRepresentation(labels, partition); result.cleanUp(); Set labelSet = new HashSet<>(result.getLabels()); for (Integer label : labelSet) { result.partitionSubtreesOfSubset(result.getPartitionSet(label), label); } return result; } /** * Returns the list of the best options as stored in savings. * * @param savings the savings calculated in the algorithm (see getSolution()) * @return the list of the numberOfOperationsParameter best options */ private LinkedList getListOfBestOptions(Map savings) { LinkedList bestVertices = new LinkedList<>(); for (Map.Entry entry : savings.entrySet()) { /* * insert current tradeOffFunction entry at the position such that the list is order by * the tradeOff and the size of the list is at most numberOfOperationsParameter */ int position = 0; for (V v : bestVertices) { if (savings.get(v) < entry.getValue()) { break; } position++; } if (bestVertices.size() == numberOfOperationsParameter) { if (position < bestVertices.size()) { bestVertices.removeLast(); bestVertices.add(position, entry.getKey()); } } else { bestVertices.addLast(entry.getKey()); } } return bestVertices; } /** * Calculates the closest vertex to vertex such that the connection of * vertex to the subtree of the closest vertex does not violate the capacity * constraint and the savings are positive. Otherwise null is returned. * * @param vertex the vertex to find a valid closest vertex for * @param restrictionMap the set of labels of sets of the partition, in which the capacity * constraint is violated. * @return the closest valid vertex and null, if no valid vertex exists */ private V calculateClosestVertex( V vertex, Map> restrictionMap, Map shortestGate) { V closestVertexToV1 = null; double distanceToRoot; V shortestGateOfV = shortestGate.get(bestSolution.getLabel(vertex)); if (shortestGateOfV != null) { distanceToRoot = getDistance(shortestGateOfV, root); } else { distanceToRoot = getDistance(vertex, root); } // calculate closest vertex to v1 for (Integer label : bestSolution.getLabels()) { Set restrictionSet = restrictionMap.get(vertex); if (restrictionSet == null || !restrictionSet.contains(label)) { Set part = bestSolution.getPartitionSet(label); if (!part.contains(vertex)) { for (V v2 : part) { if (graph.containsEdge(vertex, v2)) { double newWeight = bestSolution .getPartitionWeight(bestSolution.getLabel(v2)) + bestSolution.getPartitionWeight(bestSolution.getLabel(vertex)); if (newWeight <= capacity) { double currentEdgeWeight = getDistance(vertex, v2); if (currentEdgeWeight < distanceToRoot) { closestVertexToV1 = v2; distanceToRoot = currentEdgeWeight; } } else { /* * the capacity would be exceeded if the vertex would be assigned to * this part, so add the part to the restricted parts */ Set restriction = restrictionMap.computeIfAbsent(vertex, k -> new HashSet<>()); restriction.add(bestSolution.getLabel(v2)); break; } } } } } } return closestVertexToV1; } private double getDistance(V v1, V v2) { E e = graph.getEdge(v1, v2); if (e == null) { return Double.MAX_VALUE; } return graph.getEdgeWeight(e); } } GreedyMultiplicativeSpanner.java000066400000000000000000000224311402514743400347150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; /** * Greedy algorithm for $(2k-1)$-multiplicative spanner construction (for any integer * {@literal k >= 1}). * *

    * The spanner is guaranteed to contain $O(n^{1+1/k})$ edges and the shortest path distance between * any two vertices in the spanner is at most $2k-1$ times the corresponding shortest path distance * in the original graph. Here n denotes the number of vertices of the graph. * *

    * The algorithm is described in: Althoefer, Das, Dobkin, Joseph, Soares. * On Sparse Spanners of Weighted Graphs. Discrete * Computational Geometry 9(1):81-100, 1993. * *

    * If the graph is unweighted the algorithm runs in $O(m n^{1+1/k})$ time. Setting $k$ to infinity * will result in a slow version of Kruskal's algorithm where cycle detection is performed by a BFS * computation. In such a case use the implementation of Kruskal with union-find. Here n and m are * the number of vertices and edges of the graph respectively. * *

    * If the graph is weighted the algorithm runs in $O(m (n^{1+1/k} + n \log n))$ time by using * Dijkstra's algorithm. Edge weights must be non-negative. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class GreedyMultiplicativeSpanner implements SpannerAlgorithm { private final Graph graph; private final int k; private static final int MAX_K = 1 << 29; /** * Constructs instance to compute a $(2k-1)$-spanner of an undirected graph. * * @param graph an undirected graph * @param k positive integer. * * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if k is not positive */ public GreedyMultiplicativeSpanner(Graph graph, int k) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); if (!graph.getType().isUndirected()) { throw new IllegalArgumentException("graph is not undirected"); } if (k <= 0) { throw new IllegalArgumentException( "k should be positive in (2k-1)-spanner construction"); } this.k = Math.min(k, MAX_K); } @Override public Spanner getSpanner() { if (graph.getType().isWeighted()) { return new WeightedSpannerAlgorithm().run(); } else { return new UnweightedSpannerAlgorithm().run(); } } // base algorithm implementation private abstract class SpannerAlgorithmBase { public abstract boolean isSpannerReachable(V s, V t, double distance); public abstract void addSpannerEdge(V s, V t, double weight); public Spanner run() { // sort edges ArrayList allEdges = new ArrayList<>(graph.edgeSet()); allEdges.sort(Comparator.comparingDouble(graph::getEdgeWeight)); // check precondition double minWeight = graph.getEdgeWeight(allEdges.get(0)); if (minWeight < 0.0) { throw new IllegalArgumentException("Illegal edge weight: negative"); } // run main loop Set edgeList = new LinkedHashSet<>(); double edgeListWeight = 0d; for (E e : allEdges) { V s = graph.getEdgeSource(e); V t = graph.getEdgeTarget(e); if (!s.equals(t)) { // self-loop? double eWeight = graph.getEdgeWeight(e); if (!isSpannerReachable(s, t, (2 * k - 1) * eWeight)) { edgeList.add(e); edgeListWeight += eWeight; addSpannerEdge(s, t, eWeight); } } } return new SpannerImpl<>(edgeList, edgeListWeight); } } private class UnweightedSpannerAlgorithm extends SpannerAlgorithmBase { protected Graph spanner; protected Map vertexDistance; protected Deque queue; protected Deque touchedVertices; public UnweightedSpannerAlgorithm() { spanner = GraphTypeBuilder . undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .edgeSupplier(graph.getEdgeSupplier()).buildGraph(); touchedVertices = new ArrayDeque<>(graph.vertexSet().size()); for (V v : graph.vertexSet()) { spanner.addVertex(v); touchedVertices.push(v); } vertexDistance = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); queue = new ArrayDeque<>(); } /** * Check if two vertices are reachable by a BFS in the spanner graph using only a certain * number of hops. * * We execute this procedure repeatedly, therefore we need to keep track of what it touches * and only clean those before the next execution. */ @Override public boolean isSpannerReachable(V s, V t, double hops) { // initialize distances and queue while (!touchedVertices.isEmpty()) { V u = touchedVertices.pop(); vertexDistance.put(u, Integer.MAX_VALUE); } while (!queue.isEmpty()) { queue.pop(); } // do BFS touchedVertices.push(s); queue.push(s); vertexDistance.put(s, 0); while (!queue.isEmpty()) { V u = queue.pop(); Integer uDistance = vertexDistance.get(u); if (u.equals(t)) { // found return uDistance <= hops; } for (E e : spanner.edgesOf(u)) { V v = Graphs.getOppositeVertex(spanner, e, u); Integer vDistance = vertexDistance.get(v); if (vDistance == Integer.MAX_VALUE) { touchedVertices.push(v); vertexDistance.put(v, uDistance + 1); queue.push(v); } } } return false; } @Override public void addSpannerEdge(V s, V t, double weight) { spanner.addEdge(s, t); } } private class WeightedSpannerAlgorithm extends SpannerAlgorithmBase { protected Graph spanner; protected AddressableHeap heap; protected Map> nodes; public WeightedSpannerAlgorithm() { this.spanner = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); for (V v : graph.vertexSet()) { spanner.addVertex(v); } this.heap = new PairingHeap<>(); this.nodes = new LinkedHashMap<>(); } @Override public boolean isSpannerReachable(V s, V t, double distance) { // init heap.clear(); nodes.clear(); AddressableHeap.Handle sNode = heap.insert(0d, s); nodes.put(s, sNode); while (!heap.isEmpty()) { AddressableHeap.Handle uNode = heap.deleteMin(); double uDistance = uNode.getKey(); V u = uNode.getValue(); if (uDistance > distance) { return false; } if (u.equals(t)) { // found return true; } for (DefaultWeightedEdge e : spanner.edgesOf(u)) { V v = Graphs.getOppositeVertex(spanner, e, u); AddressableHeap.Handle vNode = nodes.get(v); double vDistance = uDistance + spanner.getEdgeWeight(e); if (vNode == null) { // no distance vNode = heap.insert(vDistance, v); nodes.put(v, vNode); } else if (vDistance < vNode.getKey()) { vNode.decreaseKey(vDistance); } } } return false; } @Override public void addSpannerEdge(V s, V t, double weight) { Graphs.addEdge(spanner, s, t, weight); } } } KruskalMinimumSpanningTree.java000066400000000000000000000047101402514743400345210ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/* * (C) Copyright 2010-2021, by Tom Conerly and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; /** * An implementation of Kruskal's minimum * spanning tree algorithm. If the given graph is connected it computes the minimum spanning * tree, otherwise it computes the minimum spanning forest. The algorithm runs in time $O(E \log * E)$. This implementation uses the hashCode and equals method of the vertices. * * @param the graph vertex type * @param the graph edge type * * @author Tom Conerly */ public class KruskalMinimumSpanningTree implements SpanningTreeAlgorithm { private final Graph graph; /** * Construct a new instance of the algorithm. * * @param graph the input graph */ public KruskalMinimumSpanningTree(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); } /** * {@inheritDoc} */ @Override public SpanningTree getSpanningTree() { UnionFind forest = new UnionFind<>(graph.vertexSet()); ArrayList allEdges = new ArrayList<>(graph.edgeSet()); allEdges.sort(Comparator.comparingDouble(graph::getEdgeWeight)); double spanningTreeCost = 0; Set edgeList = new HashSet<>(); for (E edge : allEdges) { V source = graph.getEdgeSource(edge); V target = graph.getEdgeTarget(edge); if (forest.find(source).equals(forest.find(target))) { continue; } forest.union(source, target); edgeList.add(edge); spanningTreeCost += graph.getEdgeWeight(edge); } return new SpanningTreeImpl<>(edgeList, spanningTreeCost); } } PrimMinimumSpanningTree.java000066400000000000000000000110671402514743400340170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/* * (C) Copyright 2013-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.util.*; import org.jheaps.*; import org.jheaps.tree.*; import java.lang.reflect.*; import java.util.*; /** * An implementation of Prim's * algorithm that finds a minimum spanning tree/forest subject to connectivity of the supplied * weighted undirected graph. The algorithm was developed by Czech mathematician V. Jarník and later * independently by computer scientist Robert C. Prim and rediscovered by E. Dijkstra. * * This implementation relies on a Fibonacci heap, and runs in $O(|E| + |V|log(|V|))$. * * * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu * @author Alexey Kudinkin */ public class PrimMinimumSpanningTree implements SpanningTreeAlgorithm { private final Graph g; /** * Construct a new instance of the algorithm. * * @param graph the input graph */ public PrimMinimumSpanningTree(Graph graph) { this.g = Objects.requireNonNull(graph, "Graph cannot be null"); } /** * {@inheritDoc} */ @Override @SuppressWarnings("unchecked") public SpanningTree getSpanningTree() { Set minimumSpanningTreeEdgeSet = CollectionUtil.newHashSetWithExpectedSize(g.vertexSet().size()); double spanningTreeWeight = 0d; final int N = g.vertexSet().size(); /* * Normalize the graph by mapping each vertex to an integer. */ VertexToIntegerMapping vertexToIntegerMapping = Graphs.getVertexToIntegerMapping(g); Map vertexMap = vertexToIntegerMapping.getVertexMap(); List indexList = vertexToIntegerMapping.getIndexList(); VertexInfo[] vertices = (VertexInfo[]) Array.newInstance(VertexInfo.class, N); AddressableHeap.Handle[] fibNodes = (AddressableHeap.Handle[]) Array .newInstance(AddressableHeap.Handle.class, N); AddressableHeap fibonacciHeap = new FibonacciHeap<>(); for (int i = 0; i < N; i++) { vertices[i] = new VertexInfo(); vertices[i].id = i; vertices[i].distance = Double.MAX_VALUE; fibNodes[i] = fibonacciHeap.insert(vertices[i].distance, vertices[i]); } while (!fibonacciHeap.isEmpty()) { AddressableHeap.Handle fibNode = fibonacciHeap.deleteMin(); VertexInfo vertexInfo = fibNode.getValue(); V p = indexList.get(vertexInfo.id); vertexInfo.spanned = true; // Add the edge from its parent to the spanning tree (if it exists) if (vertexInfo.edgeFromParent != null) { minimumSpanningTreeEdgeSet.add(vertexInfo.edgeFromParent); spanningTreeWeight += g.getEdgeWeight(vertexInfo.edgeFromParent); } // update all (unspanned) neighbors of p for (E e : g.edgesOf(p)) { V q = Graphs.getOppositeVertex(g, e, p); int id = vertexMap.get(q); // if the vertex is not explored and we found a better edge, then update the info if (!vertices[id].spanned) { double cost = g.getEdgeWeight(e); if (cost < vertices[id].distance) { vertices[id].distance = cost; vertices[id].edgeFromParent = e; fibNodes[id].decreaseKey(cost); } } } } return new SpanningTreeImpl<>(minimumSpanningTreeEdgeSet, spanningTreeWeight); } private class VertexInfo { public int id; public boolean spanned; public double distance; public E edgeFromParent; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/spanning/package-info.java000066400000000000000000000001231402514743400316300ustar00rootroot00000000000000/** * Spanning tree and spanner algorithms. */ package org.jgrapht.alg.spanning; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/000077500000000000000000000000001402514743400256215ustar00rootroot00000000000000ChristofidesThreeHalvesApproxMetricTSP.java000066400000000000000000000135221402514743400361160ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.matching.blossom.v5.*; import org.jgrapht.alg.spanning.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; import java.util.stream.*; /** * A $3/2$-approximation algorithm for the metric TSP problem. *

    * The travelling salesman * problem (TSP) asks the following question: "Given a list of cities and the distances between * each pair of cities, what is the shortest possible route that visits each city exactly once and * returns to the origin city?". In the metric TSP, the intercity distances satisfy the triangle * inequality. *

    * This is an implementation of the * Christofides algorithm. The algorithms is a $3/2$-approximation assuming that the input graph * satisfies triangle inequality and all edge weights are nonnegative. The implementation requires * the input graph to be undirected and complete. The worst case running time * complexity is $\mathcal{O}(V^3E)$. *

    * The algorithm performs following steps to compute the resulting tour: *

      *
    1. Compute a minimum spanning tree in the input graph.
    2. *
    3. Find vertices with odd degree in the MST.
    4. *
    5. Compute minimum weight perfect matching in the induced subgraph on odd degree vertices.
    6. *
    7. Add edges from the minimum weight perfect matching to the MST (forming a pseudograph).
    8. *
    9. Compute an Eulerian cycle in the obtained pseudograph and form a closed tour in this * cycle.
    10. *
    *

    * The following two observations yield the $3/2$ approximation bound: *

      *
    • The cost of every minimum spanning tree is less than or equal to the cost of every * Hamiltonian cycle since after one edge removal every Hamiltonian cycle becomes a spanning * tree
    • *
    • Twice the cost of a perfect matching in a graph is less than or equal to the cost of every * Hamiltonian cycle. This follows from the fact that after forming a closed tour using the edges of * a perfect matching the cost of the edges not from the matching is greater than or equal to the * cost of the matching edges.
    • *
    *

    * For more details, see Christofides, N.: Worst-case analysis of a new heuristic for the * travelling salesman problem. Graduate School of Industrial Administration, Carnegie Mellon * University (1976). * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @author Dimitrios Michail */ public class ChristofidesThreeHalvesApproxMetricTSP extends HamiltonianCycleAlgorithmBase { /** * Computes a $3/2$-approximate tour. * * @param graph the input graph * @return a tour * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if the graph is not complete * @throws IllegalArgumentException if the graph contains no vertices */ @Override public GraphPath getTour(Graph graph) { checkGraph(graph); int n = graph.vertexSet().size(); if (n == 1) { return getSingletonTour(graph); } // add all vertices of the graph to the auxiliary graph Graph mstAndMatching = new Pseudograph<>(null, DefaultEdge::new, false); graph.vertexSet().forEach(mstAndMatching::addVertex); // add all edges of a minimum spanning tree to the auxiliary graph SpanningTreeAlgorithm spanningTreeAlgorithm = new KruskalMinimumSpanningTree<>(graph); spanningTreeAlgorithm .getSpanningTree().getEdges() .forEach(e -> mstAndMatching.addEdge(graph.getEdgeSource(e), graph.getEdgeTarget(e))); // find odd degree vertices Set oddDegreeVertices = mstAndMatching .vertexSet().stream().filter(v -> (mstAndMatching.edgesOf(v).size() & 1) == 1) .collect(Collectors.toSet()); /* * Form an induced subgraph on odd degree vertices, find minimum weight perfect matching and * add its edges to the auxiliary graph */ Graph subgraph = new AsSubgraph<>(graph, oddDegreeVertices); MatchingAlgorithm matchingAlgorithm = new KolmogorovWeightedPerfectMatching<>(subgraph); matchingAlgorithm .getMatching().getEdges() .forEach(e -> mstAndMatching.addEdge(graph.getEdgeSource(e), graph.getEdgeTarget(e))); // find an Eulerian cycle in the auxiliary graph EulerianCycleAlgorithm eulerianCycleAlgorithm = new HierholzerEulerianCycle<>(); GraphPath eulerianCycle = eulerianCycleAlgorithm.getEulerianCycle(mstAndMatching); // form a closed tour from the Hamiltonian cycle Set visited = CollectionUtil.newHashSetWithExpectedSize(n); List tourVertices = eulerianCycle .getVertexList().stream().filter(visited::add).collect(Collectors.toList()); return vertexListToTour(tourVertices, graph); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/GreedyHeuristicTSP.java000066400000000000000000000117071402514743400321600ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Peter Harman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.util.*; import java.util.*; import java.util.stream.*; /** * The greedy heuristic algorithm for the TSP problem. * *

    * The travelling salesman problem (TSP) asks the following question: "Given a list of cities and * the distances between each pair of cities, what is the shortest possible route that visits each * city exactly once and returns to the origin city?". *

    * *

    * The Greedy heuristic gradually constructs a tour by repeatedly selecting the shortest edge and * adding it to the tour as long as it doesn’t create a cycle with less than N edges, or increases * the degree of any node to more than 2. We must not add the same edge twice of course. *

    * *

    * The implementation of this class is based on:
    * Nilsson, Christian. "Heuristics for the traveling salesman problem." Linkoping University 38 * (2003) *

    * *

    * The runtime complexity of this class is $O(V^2 log(V))$. *

    * *

    * This algorithm requires that the graph is complete. *

    * * @param the graph vertex type * @param the graph edge type * * @author Peter Harman */ public class GreedyHeuristicTSP extends HamiltonianCycleAlgorithmBase { /** * Computes a tour using the greedy heuristic. * * @param graph the input graph * @return a tour * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if the graph is not complete * @throws IllegalArgumentException if the graph contains no vertices */ @Override public GraphPath getTour(Graph graph) { checkGraph(graph); int n = graph.vertexSet().size(); if (n == 1) { return getSingletonTour(graph); } // Sort all the edges by weight Deque edges = graph .edgeSet().stream() .sorted((e1, e2) -> Double.compare(graph.getEdgeWeight(e1), graph.getEdgeWeight(e2))) .collect(Collectors.toCollection(ArrayDeque::new)); Set tourEdges = CollectionUtil.newHashSetWithExpectedSize(n); // Create a Map to track the degree of each vertex in tour Map vertexDegree = CollectionUtil.newHashMapWithExpectedSize(n); // Create a UnionFind to track forming of loops UnionFind tourSet = new UnionFind<>(graph.vertexSet()); // Iterate until the tour is complete while (!edges.isEmpty() && tourEdges.size() < n) { // Select the shortest available edge E edge = edges.pollFirst(); V vertex1 = graph.getEdgeSource(edge); V vertex2 = graph.getEdgeTarget(edge); // If it matches constraints, add it to the tour if (canAddEdge(vertexDegree, tourSet, vertex1, vertex2, tourEdges.size() == n - 1)) { tourEdges.add(edge); vertexDegree.merge(vertex1, 1, Integer::sum); vertexDegree.merge(vertex2, 1, Integer::sum); tourSet.union(vertex1, vertex2); } } // Build the tour into a GraphPath return edgeSetToTour(tourEdges, graph); } /** * Tests if an edge can be added. Returns false if it would increase the degree of a vertex to * more than 2. Returns false if a cycle is created and we are not at the last edge, or false if * we do not create a cycle and are at the last edge. * * @param vertexDegree A Map tracking the degree of each vertex in the tour * @param tourSet A UnionFind tracking the connectivity of the tour * @param vertex1 First vertex of proposed edge * @param vertex2 Second vertex of proposed edge * @param lastEdge true if we are looking for the last edge * @return true if this edge can be added */ private boolean canAddEdge( Map vertexDegree, UnionFind tourSet, V vertex1, V vertex2, boolean lastEdge) { // Would form a tree rather than loop if (vertexDegree.getOrDefault(vertex1, 0) > 1 || vertexDegree.getOrDefault(vertex2, 0) > 1) { return false; } // Test if a path already exists between the vertices return tourSet.inSameSet(vertex1, vertex2) ? lastEdge : !lastEdge; } } HamiltonianCycleAlgorithmBase.java000066400000000000000000000104701402514743400342740ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/* * (C) Copyright 2019-2021, by Peter Harman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import java.util.*; /** * Base class for TSP solver algorithms. * *

    * This class provides implementations of utilities for TSP solver classes. *

    * * @param the graph vertex type * @param the graph edge type * * @author Peter Harman * @author Hannes Wellmann */ public abstract class HamiltonianCycleAlgorithmBase implements HamiltonianCycleAlgorithm { /** * Transform from a List representation to a graph path. * * @param tour a list containing the vertices of the tour (is modified) * @param graph the graph * @return a graph path */ protected GraphPath vertexListToTour(List tour, Graph graph) { tour.add(tour.get(0)); return closedVertexListToTour(tour, graph); } /** * Transform from a closed List representation (first and last vertex element are the same) to a * graph path. * * @param tour a closed list containing the vertices of the tour * @param graph the graph * @return a graph path */ protected GraphPath closedVertexListToTour(List tour, Graph graph) { assert tour.get(0) == tour.get(tour.size() - 1); List edges = new ArrayList<>(tour.size() - 1); double tourWeight = 0d; V u = tour.get(0); for (V v : tour.subList(1, tour.size())) { E e = graph.getEdge(u, v); edges.add(e); tourWeight += graph.getEdgeWeight(e); u = v; } return new GraphWalk<>(graph, tour.get(0), tour.get(0), tour, edges, tourWeight); } /** * Transform from a Set representation to a graph path. * * @param tour a set containing the edges of the tour * @param graph the graph * @return a graph path */ protected GraphPath edgeSetToTour(Set tour, Graph graph) { List vertices = new ArrayList<>(tour.size() + 1); MaskSubgraph tourGraph = new MaskSubgraph<>(graph, v -> false, e -> !tour.contains(e)); new DepthFirstIterator<>(tourGraph).forEachRemaining(vertices::add); return vertexListToTour(vertices, graph); } /** * Creates a tour for a graph with 1 vertex * * @param graph The graph * @return A tour with a single vertex */ protected GraphPath getSingletonTour(Graph graph) { assert graph.vertexSet().size() == 1; V start = graph.vertexSet().iterator().next(); return new GraphWalk<>( graph, start, start, Collections.singletonList(start), Collections.emptyList(), 0d); } /** * Checks that graph is undirected, complete, and non-empty * * @param graph the graph * @throws IllegalArgumentException if graph is not undirected * @throws IllegalArgumentException if graph is not complete * @throws IllegalArgumentException if graph contains no vertices */ protected void checkGraph(Graph graph) { GraphTests.requireUndirected(graph); requireNotEmpty(graph); if (!GraphTests.isComplete(graph)) { throw new IllegalArgumentException("Graph is not complete"); } } /** * Checks that graph is not empty * * @param graph the graph * @throws IllegalArgumentException if graph contains no vertices */ protected void requireNotEmpty(Graph graph) { if (graph.vertexSet().isEmpty()) { throw new IllegalArgumentException("Graph contains no vertices"); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/HeldKarpTSP.java000066400000000000000000000156021402514743400305510ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * A dynamic programming algorithm for the TSP problem. * *

    * The travelling salesman problem (TSP) asks the following question: "Given a list of cities and * the distances between each pair of cities, what is the shortest possible route that visits each * city exactly once and returns to the origin city?". * *

    * This is an implementation of the Held-Karp algorithm which returns a optimal, minimum-cost * Hamiltonian tour. The implementation requires the input graph to contain at least one vertex. The * running time is $O(2^{|V|} \times |V|^2)$ and it takes $O(2^{|V|} \times |V|)$ extra memory. * *

    * See wikipedia for more * details about TSP. * *

    * See wikipedia for more * details about the dynamic programming algorithm. * * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public class HeldKarpTSP extends HamiltonianCycleAlgorithmBase { private double memo(int previousNode, int state, double[][] C, double[][] W) { // have we seen this state before? if (C[previousNode][state] != Double.MIN_VALUE) { return C[previousNode][state]; } // no cycle has been found yet double totalCost = Double.MAX_VALUE; // check if all nodes have been visited (i.e. state + 1 == 2^n) if (state == (1 << W.length) - 1) { // check if there is a return edge we can use if (W[previousNode][0] != Double.MAX_VALUE) { totalCost = W[previousNode][0]; } } else { // try to find the 'best' next (i.e. unvisited and adjacent to previousNode) node in the // tour for (int i = 0; i < W.length; i++) { if (((state >> i) & 1) == 0 && W[previousNode][i] != Double.MAX_VALUE) { totalCost = Math.min(totalCost, W[previousNode][i] + memo(i, state ^ (1 << i), C, W)); } } } return C[previousNode][state] = totalCost; } /** * Computes a minimum-cost Hamiltonian tour. * * @param graph the input graph * @return a minimum-cost tour if one exists, null otherwise * @throws IllegalArgumentException if the graph contains no vertices * @throws IllegalArgumentException if the graph contains more than 31 vertices */ @Override public GraphPath getTour(Graph graph) { requireNotEmpty(graph); final int n = graph.vertexSet().size(); // number of nodes if (n == 1) { return getSingletonTour(graph); } if (n > 31) { throw new IllegalArgumentException( "The internal representation of the dynamic programming state " + "space cannot represent graphs containing more than 31 vertices. " + "The runtime complexity of this implementation, O(2^|V| x |V|^2), makes it unsuitable " + "for graphs with more than 31 vertices."); } // Normalize the graph by mapping each vertex to an integer. VertexToIntegerMapping vertexToIntegerMapping = Graphs.getVertexToIntegerMapping(graph); // W[u, v] = the cost of the minimum weight between u and v double[][] W = computeMinimumWeights(vertexToIntegerMapping.getVertexMap(), graph); // C[prevNode, state] = the minimum cost of a tour that ends in prevNode and contains all // nodes in the bitmask state double[][] C = new double[n][1 << n]; fill(C, Double.MIN_VALUE); // start the tour from node 0 (because the tour is a cycle the start vertex does not matter) double tourWeight = memo(0, 1, C, W); // check if there is no tour if (tourWeight == Double.MAX_VALUE) { return null; } List vertexList = reconstructTour(vertexToIntegerMapping.getIndexList(), W, C); return vertexListToTour(vertexList, graph); } private double[][] computeMinimumWeights(Map vertexMap, Graph graph) { final int n = vertexMap.size(); double[][] W = new double[n][n]; fill(W, Double.MAX_VALUE); for (E e : graph.edgeSet()) { V source = graph.getEdgeSource(e); V target = graph.getEdgeTarget(e); int u = vertexMap.get(source); int v = vertexMap.get(target); // use Math.min in case we deal with a multigraph W[u][v] = Math.min(W[u][v], graph.getEdgeWeight(e)); // If the graph is undirected we need to also consider the reverse edge if (graph.getType().isUndirected()) { W[v][u] = Math.min(W[v][u], graph.getEdgeWeight(e)); } } return W; } private static void fill(double[][] array, double value) { for (double[] element : array) { Arrays.fill(element, value); } } private List reconstructTour(List indexList, double[][] W, double[][] C) { final int n = indexList.size(); List vertexList = new ArrayList<>(n); int lastNode = 0; int lastState = 1; vertexList.add(indexList.get(lastNode)); for (int step = 1; step < n; step++) { int nextNode = -1; for (int node = 1; node < n; node++) { if ((lastState & (1 << node)) == 0 && W[lastNode][node] != Double.MAX_VALUE && C[node][lastState ^ (1 << node)] != Double.MIN_VALUE && Double .compare( C[node][lastState ^ (1 << node)] + W[lastNode][node], C[lastNode][lastState]) == 0) { nextNode = node; break; } } assert nextNode != -1; vertexList.add(indexList.get(nextNode)); lastState ^= 1 << nextNode; lastNode = nextNode; } return vertexList; } } NearestInsertionHeuristicTSP.java000066400000000000000000000303441402514743400341540ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/* * (C) Copyright 2019-2021, by Peter Harman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import java.util.*; import java.util.stream.*; /** * The nearest insertion heuristic algorithm for the TSP problem. * *

    * The travelling salesman problem (TSP) asks the following question: "Given a list of cities and * the distances between each pair of cities, what is the shortest possible route that visits each * city exactly once and returns to the origin city?". *

    * *

    * Insertion heuristics are quite straightforward, and there are many variants to choose from. The * basics of insertion heuristics is to start with a tour of a subset of all cities, and then * inserting the rest by some heuristic. The initial sub-tour is often a triangle or the convex * hull. One can also start with a single edge as sub-tour. This implementation uses the shortest * edge by default as the initial sub-tour. *

    * *

    * The implementation of this class is based on:
    * Nilsson, Christian. "Heuristics for the traveling salesman problem." Linkoping University 38 * (2003) *

    * *

    * This implementation can also be used in order to augment an existing partial tour. See * constructor {@link #NearestInsertionHeuristicTSP(GraphPath)}. *

    * *

    * The runtime complexity of this class is $O(V^2)$. *

    * *

    * This algorithm requires that the graph is complete. *

    * * @param the graph vertex type * @param the graph edge type * * @author Peter Harman */ public class NearestInsertionHeuristicTSP extends HamiltonianCycleAlgorithmBase { private GraphPath subtour; /** * Constructor. By default a sub-tour is chosen based on the shortest edge */ public NearestInsertionHeuristicTSP() { this(null); } /** * Constructor * * Specifies an existing sub-tour that will be augmented to form a complete tour when * {@link #getTour(org.jgrapht.Graph) } is called * * @param subtour Initial sub-tour, or null to start with shortest edge */ public NearestInsertionHeuristicTSP(GraphPath subtour) { this.subtour = subtour; } /** * Computes a tour using the nearest insertion heuristic. * * @param graph the input graph * @return a tour * @throws IllegalArgumentException If the graph is not undirected * @throws IllegalArgumentException If the graph is not complete * @throws IllegalArgumentException If the graph contains no vertices * @throws IllegalArgumentException If the specified sub-tour is for a different Graph instance * @throws IllegalArgumentException If the graph does not contain specified sub-tour vertices * @throws IllegalArgumentException If the graph does not contain specified sub-tour edges */ @Override public GraphPath getTour(Graph graph) { checkGraph(graph); if (graph.vertexSet().size() == 1) { return getSingletonTour(graph); } return vertexListToTour(augment(subtour(graph), graph), graph); } /** * Get or create a sub-tour to start augmenting * * @param graph The graph * @return Vertices of an initial sub-tour * @throws IllegalArgumentException If the specified sub-tour is for a different Graph instance * @throws IllegalArgumentException If the graph does not contain specified sub-tour vertices * @throws IllegalArgumentException If the graph does not contain specified sub-tour edges */ private List subtour(Graph graph) { List subtourVertices = new ArrayList<>(); if (subtour != null) { if (subtour.getGraph() != null && !graph.equals(subtour.getGraph())) { throw new IllegalArgumentException( "Specified sub-tour is for a different Graph instance"); } if (!graph.vertexSet().containsAll(subtour.getVertexList())) { throw new IllegalArgumentException( "Graph does not contain specified sub-tour vertices"); } if (!graph.edgeSet().containsAll(subtour.getEdgeList())) { throw new IllegalArgumentException( "Graph does not contain specified sub-tour edges"); } if (subtour.getStartVertex().equals(subtour.getEndVertex())) { subtourVertices .addAll(subtour.getVertexList().subList(1, subtour.getVertexList().size())); } else { subtourVertices.addAll(subtour.getVertexList()); } } if (subtourVertices.isEmpty()) { // If no initial subtour exists, create one based on the shortest edge E shortestEdge = Collections .min( graph.edgeSet(), (e1, e2) -> Double.compare(graph.getEdgeWeight(e1), graph.getEdgeWeight(e2))); subtourVertices.add(graph.getEdgeSource(shortestEdge)); subtourVertices.add(graph.getEdgeTarget(shortestEdge)); } return subtourVertices; } /** * Initialise the Map storing closest unvisited vertex for each tour vertex * * @param tourVertices Current tour vertices * @param unvisited Set of unvisited vertices * @param graph The graph * @return Map storing closest unvisited vertex for each tour vertex */ private Map> getClosest(List tourVertices, Set unvisited, Graph graph) { return tourVertices .stream().collect(Collectors.toMap(v -> v, v -> getClosest(v, unvisited, graph))); } /** * Determines closest unvisited vertex to a vertex in the current tour * * @param tourVertex Vertex in the current tour * @param unvisited Set of vertices not in the current tour * @param graph The graph * @return Closest unvisited vertex */ private Closest getClosest(V tourVertex, Set unvisited, Graph graph) { V closest = null; double minDist = Double.MAX_VALUE; for (V unvisitedVertex : unvisited) { double vDist = graph.getEdgeWeight(graph.getEdge(tourVertex, unvisitedVertex)); if (vDist < minDist) { closest = unvisitedVertex; minDist = vDist; } } return new Closest<>(tourVertex, closest, minDist); } /** * Update the Map storing closest unvisited vertex for each tour vertex * * @param currentClosest Map storing closest unvisited vertex for each tour vertex * @param chosen Latest vertex added to tour * @param unvisited Set of unvisited vertices * @param graph The graph */ private void updateClosest( Map> currentClosest, Closest chosen, Set unvisited, Graph graph) { // Update the set of unvisited vertices, and exit if none remain unvisited.remove(chosen.getUnvisitedVertex()); if (unvisited.isEmpty()) { currentClosest.clear(); return; } // Update any entries impacted by the choice of new vertex currentClosest.replaceAll((v, c) -> { if (chosen.getTourVertex().equals(v) || chosen.getUnvisitedVertex().equals(c.getUnvisitedVertex())) { return getClosest(v, unvisited, graph); } return c; }); currentClosest .put( chosen.getUnvisitedVertex(), getClosest(chosen.getUnvisitedVertex(), unvisited, graph)); } /** * Chooses the closest unvisited vertex to the sub-tour * * @param closestVertices Map storing closest unvisited vertex for each tour vertex * @return First result of sorting values */ private Closest chooseClosest(Map> closestVertices) { return Collections.min(closestVertices.values()); } /** * Augment an existing tour to give a complete tour * * @param subtour The vertices of the existing tour * @param graph The graph * @return List of vertices representing the complete tour */ private List augment(List subtour, Graph graph) { Set unvisited = new HashSet<>(graph.vertexSet()); unvisited.removeAll(subtour); return augment(subtour, getClosest(subtour, unvisited, graph), unvisited, graph); } /** * Augment an existing tour to give a complete tour * * @param subtour The vertices of the existing tour * @param closestVertices Map of data for closest unvisited vertices * @param unvisited Set of unvisited vertices * @param graph The graph * @return List of vertices representing the complete tour */ private List augment( List subtour, Map> closestVertices, Set unvisited, Graph graph) { while (!unvisited.isEmpty()) { // Select a city not in the subtour, having the shortest distance to any one of the // cities in the subtoor. Closest closestVertex = chooseClosest(closestVertices); // Determine the vertices either side of the selected tour vertex int i = subtour.indexOf(closestVertex.getTourVertex()); V vertexBefore = subtour.get(i == 0 ? subtour.size() - 1 : i - 1); V vertexAfter = subtour.get(i == subtour.size() - 1 ? 0 : i + 1); // Find an edge in the subtour such that the cost of inserting the selected city between // the edge’s cities will be minimal. // Making assumption this is a neighbouring edge, test the edges before and after double insertionCostBefore = graph.getEdgeWeight(graph.getEdge(vertexBefore, closestVertex.getUnvisitedVertex())) + closestVertex.getDistance() - graph .getEdgeWeight(graph.getEdge(vertexBefore, closestVertex.getTourVertex())); double insertionCostAfter = graph.getEdgeWeight(graph.getEdge(vertexAfter, closestVertex.getUnvisitedVertex())) + closestVertex.getDistance() - graph .getEdgeWeight(graph.getEdge(vertexAfter, closestVertex.getTourVertex())); // Add the selected vertex to the tour if (insertionCostBefore < insertionCostAfter) { subtour.add(i, closestVertex.getUnvisitedVertex()); } else { subtour.add(i + 1, closestVertex.getUnvisitedVertex()); } // Repeat until no more cities remain updateClosest(closestVertices, closestVertex, unvisited, graph); } return subtour; } /** * Class holding data for the closest unvisited vertex to a particular vertex in the tour. * * @param vertex type */ private static class Closest implements Comparable> { private final V tourVertex; private final V unvisitedVertex; private final double distance; Closest(V tourVertex, V unvisitedVertex, double distance) { this.tourVertex = tourVertex; this.unvisitedVertex = unvisitedVertex; this.distance = distance; } public V getTourVertex() { return tourVertex; } public V getUnvisitedVertex() { return unvisitedVertex; } public double getDistance() { return distance; } @Override public int compareTo(Closest o) { return Double.compare(distance, o.distance); } } } NearestNeighborHeuristicTSP.java000066400000000000000000000206401402514743400337350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/* * (C) Copyright 2019-2021, by Peter Harman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import java.util.*; import static org.jgrapht.util.ArrayUtil.*; /** * The nearest neighbour heuristic algorithm for the TSP problem. * *

    * The travelling salesman problem (TSP) asks the following question: "Given a list of cities and * the distances between each pair of cities, what is the shortest possible route that visits each * city exactly once and returns to the origin city?". *

    * *

    * This is perhaps the simplest and most straightforward TSP heuristic. The key to this algorithm is * to always visit the nearest city. *

    * *

    * The tour computed with a {@code Nearest-Neighbor-Heuristic} can vary depending on the first * vertex visited. The first vertex for the next or for multiple subsequent tour computations (calls * of {@link #getTour(Graph)}) can be specified in the constructors * {@link #NearestNeighborHeuristicTSP(Object)} or {@link #NearestNeighborHeuristicTSP(Iterable)}. * This can be used for example to ensure that the first vertices visited are different for * subsequent calls of {@code getTour(Graph)}. Once each specified first vertex is used, the first * vertex in subsequent tour computations is selected randomly from the graph. Alternatively * {@link #NearestNeighborHeuristicTSP(Random)} or {@link #NearestNeighborHeuristicTSP(long)} can be * used to specify a {@code Random} used to randomly select the vertex visited first. *

    * *

    * The implementation of this class is based on:
    * Nilsson, Christian. "Heuristics for the traveling salesman problem." Linkoping University 38 * (2003) *

    * *

    * The runtime complexity of this class is $O(V^2)$. *

    * *

    * This algorithm requires that the graph is complete. *

    * * @param the graph vertex type * @param the graph edge type * * @author Peter Harman * @author Hannes Wellmann */ public class NearestNeighborHeuristicTSP extends HamiltonianCycleAlgorithmBase { private Random rng; /** Nulled, if it has no next */ private Iterator initiaVertex; /** * Constructor. By default a random vertex is chosen to start. */ public NearestNeighborHeuristicTSP() { this(null, new Random()); } /** * Constructor * * @param first First vertex to visit * @throws NullPointerException if first is null */ public NearestNeighborHeuristicTSP(V first) { this( Collections .singletonList( Objects.requireNonNull(first, "Specified initial vertex cannot be null")), new Random()); } /** * Constructor * * @param initialVertices The Iterable of vertices visited first in subsequent tour computations * (per call of {@link #getTour(Graph)} another vertex of the Iterable is used as first) * @throws NullPointerException if first is null */ public NearestNeighborHeuristicTSP(Iterable initialVertices) { this( Objects.requireNonNull(initialVertices, "Specified initial vertices cannot be null"), new Random()); } /** * Constructor * * @param seed seed for the random number generator */ public NearestNeighborHeuristicTSP(long seed) { this(null, new Random(seed)); } /** * Constructor * * @param rng Random number generator * @throws NullPointerException if rng is null */ public NearestNeighborHeuristicTSP(Random rng) { this(null, Objects.requireNonNull(rng, "Random number generator cannot be null")); } /** * Constructor * * @param initialVertices The Iterable of vertices visited first in subsequent tour * computations, or null to choose at random * @param rng Random number generator */ private NearestNeighborHeuristicTSP(Iterable initialVertices, Random rng) { if (initialVertices != null) { Iterator iterator = initialVertices.iterator(); this.initiaVertex = iterator.hasNext() ? iterator : null; } this.rng = rng; } // algorithm /** * Computes a tour using the nearest neighbour heuristic. * * @param graph the input graph * @return a tour * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if the graph is not complete * @throws IllegalArgumentException if the graph contains no vertices * @throws IllegalArgumentException if the specified initial vertex is not in the graph */ @Override public GraphPath getTour(Graph graph) { checkGraph(graph); if (graph.vertexSet().size() == 1) { return getSingletonTour(graph); } Set vertexSet = graph.vertexSet(); int n = vertexSet.size(); @SuppressWarnings("unchecked") V[] path = (V[]) vertexSet.toArray(new Object[n + 1]); List pathList = Arrays.asList(path); // List backed by path-array // move initial vertex to the beginning int initalIndex = getFirstVertexIndex(pathList); swap(path, 0, initalIndex); // search nearest neighbors int limit = n - 1; // last vertex won't be changed -> no need to check it for (int i = 1; i < limit; i++) { V v = path[i - 1]; // path before i is established. The element at i must be the closest to element at i-1. // -> get nearest of remaining elements (index >= i) and set it as next in path int nearestNeighbor = getNearestNeighbor(v, path, i, graph); swap(path, i, nearestNeighbor); } path[n] = path[0]; // close tour manually. Arrays.asList does not support add return closedVertexListToTour(pathList, graph); } /** * Returns the start vertex of the tour about to compute. * * @param path the initial path, containing all vertices in unspecified order * @return the vertex to start with * @throws IllegalArgumentException if the specified initial vertex is not in the graph */ private int getFirstVertexIndex(List path) { if (initiaVertex != null) { V first = initiaVertex.next(); if (!initiaVertex.hasNext()) { initiaVertex = null; // release the resource backing the iterator immediately } int initialIndex = path.indexOf(first); if (initialIndex < 0) { throw new IllegalArgumentException("Specified initial vertex is not in graph"); } return initialIndex; } else { // first not specified return rng.nextInt(path.size() - 1); // path has size n+1 } } /** * Find the vertex in the range staring at {@code from} that is closest to the element at index * from-1. * * @param current the vertex for which the nearest neighbor is searched * @param vertices the vertices of the graph. The unvisited neighbors start at index * {@code start} * @param start the index of the first vertex to consider * @param g the graph containing the vertices * * @return the index of the unvisited vertex closest to the vertex at firstNeighbor-1. */ private static int getNearestNeighbor(V current, V[] vertices, int start, Graph g) { int closest = -1; double minDist = Double.MAX_VALUE; int n = vertices.length - 1; // last element in vertices is null for (int i = start; i < n; i++) { V v = vertices[i]; double vDist = g.getEdgeWeight(g.getEdge(current, v)); if (vDist < minDist) { closest = i; minDist = vDist; } } return closest; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/PalmerHamiltonianCycle.java000066400000000000000000000136601402514743400330560ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import java.util.*; import static org.jgrapht.util.ArrayUtil.*; /** * Palmer's algorithm for computing Hamiltonian cycles in graphs that meet Ore's condition. Ore gave * a sufficient condition for a graph to be Hamiltonian, essentially stating that a graph with * sufficiently many edges must contain a Hamilton cycle. * * Specifically, Ore's theorem considers the sum of the degrees of pairs of non-adjacent vertices: * if every such pair has a sum that at least equals the total number of vertices in the graph, then * the graph is Hamiltonian. * *

    * A Hamiltonian cycle, also called a Hamiltonian circuit, Hamilton cycle, or Hamilton circuit, is a * graph cycle (i.e., closed loop) through a graph that visits each node exactly once (Skiena 1990, * p. 196). *

    * *

    * This is an implementation of the CRISS-CROSS algorithm described by E. M. Palmer in his paper. * The algorithm takes a simple graph that meets Ore's condition (see * {@link GraphTests#hasOreProperty(Graph)}) and returns a Hamiltonian cycle. The algorithm runs in * $O(|V|^3)$ time and uses $O(|V|)$ space. In contrast to the most other algorithms in this package * this algorithm does only attempt to find any Hamiltonian cycle in the graph and does not attempt * to find the shortest cycle. The advantage of this algorithm is that accepted graphs only need to * meet Ore's condition which is less strict than the completeness requirement of most of the other * algorithms. *

    * *

    * The original algorithm is described in: Palmer, E. M. (1997), "The hidden algorithm of Ore's * theorem on Hamiltonian cycles", Computers & Mathematics with Applications, 34 (11): 113–119, * doi:10.1016/S0898-1221(97)00225-3 * * See wikipedia for a short description * of Ore's theorem and Palmer's algorithm. *

    * * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu * @author Hannes Wellmann */ public class PalmerHamiltonianCycle extends HamiltonianCycleAlgorithmBase { /** * Computes a Hamiltonian tour. * * @param graph the input graph * @return a Hamiltonian tour * * @throws IllegalArgumentException if the graph doesn't meet Ore's condition * @see GraphTests#hasOreProperty(Graph) */ @Override public GraphPath getTour(Graph graph) { if (!GraphTests.hasOreProperty(graph)) { // requires vertexSet().size() >= 3 throw new IllegalArgumentException("Graph doesn't have Ore's property"); } Set vertices = graph.vertexSet(); final int n = vertices.size(); // number of vertices @SuppressWarnings("unchecked") V[] tour = (V[]) vertices.toArray(new Object[n + 1]); while (searchAndCloseGap(tour, n, graph)) { // repeat until not gap exists anymore } tour[n] = tour[0]; // close tour manually. Arrays.asList does not support add return closedVertexListToTour(Arrays.asList(tour), graph); } private static boolean searchAndCloseGap(V[] tour, final int n, Graph graph) { // search for a gap, i.e.: two consecutive vertices v and vN that are not adjacent in the // graph (connected by an edge) V v = tour[n - 1]; // start with last so "last to first"-connection is checked first for (int i = 0; i < n; i++) { V vN = tour[i]; // vN - the successor of v, i - its index // check if we found a gap in our cycle if (!graph.containsEdge(v, vN)) { // Search for a node 'u' such that the four vertices v, vN, u, and uN are all // distinct and that the graph contains edges from v to u and from vN to uN // ("a pair of crossing chords from the vertices of the gap to some other pair of // consecutive vertices that may or may not be adjacent") V u = tour[n - 1]; // again, start with last vertex for (int j = 0; j < n; j++) { V uN = tour[j]; // uN - the successor of u, j - its index boolean distinct = v != u && vN != u && v != uN; // v != u implies vN != uN if (distinct && graph.containsEdge(v, u) && graph.containsEdge(vN, uN)) { // found "a pair of crossing chords" reverseInCircle(tour, i, j - 1); return true; } u = uN; } throw new IllegalStateException("Found a gap but no mean to close it"); } v = vN; } return false; } private static void reverseInCircle(V[] array, int start, int end) { if (start < end) { // interval to reverse is completely contained in the array bounds reverse(array, start, end); } else { // interval to reverse wraps around the array end // Happens if the first gap to swap is closer to the "end" than the second gap. // Since it is all relative, instead of swapping "over the end" the opposite segment // (within the array) is swapped. reverse(array, end + 1, start - 1); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/RandomTourTSP.java000066400000000000000000000045751402514743400311600ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import java.util.*; /** * Generate a random tour. * *

    * This class generates a random Hamiltonian Cycle. This is a simple unoptimised solution to the * Travelling Salesman Problem, or more usefully is a starting point for optimising a tour using * TwoOptHeuristicTSP. *

    * * @param the graph vertex type * @param the graph edge type * * @author Peter Harman * @author Dimitrios Michail */ public class RandomTourTSP extends HamiltonianCycleAlgorithmBase { private final Random rng; /** * Construct with default random number generator */ public RandomTourTSP() { this(new Random()); } /** * Construct with specified random number generator * * @param rng The random number generator */ public RandomTourTSP(Random rng) { this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); } /** * Computes a tour using the greedy heuristic. * * @param graph the input graph * @return a tour * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if the graph is not complete * @throws IllegalArgumentException if the graph contains no vertices */ @Override public GraphPath getTour(Graph graph) { // Check that graph is appropriate checkGraph(graph); List vertices = new ArrayList<>(graph.vertexSet()); if (vertices.size() == 1) { return getSingletonTour(graph); } // Randomly permute the vertex list Collections.shuffle(vertices, rng); return vertexListToTour(vertices, graph); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/TwoApproxMetricTSP.java000066400000000000000000000064511402514743400321700ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.alg.spanning.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.util.*; /** * A 2-approximation algorithm for the metric TSP problem. * *

    * The travelling salesman problem (TSP) asks the following question: "Given a list of cities and * the distances between each pair of cities, what is the shortest possible route that visits each * city exactly once and returns to the origin city?". In the metric TSP, the intercity distances * satisfy the triangle inequality. * *

    * This is an implementation of the folklore algorithm which returns a depth-first ordering of the * minimum spanning tree. The algorithm is a 2-approximation assuming that the instance satisfies * the triangle inequality. The implementation requires the input graph to be undirected and * complete. The running time is $O(|V|^2 \log |V|)$. * *

    * See wikipedia for more * details. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class TwoApproxMetricTSP extends HamiltonianCycleAlgorithmBase { /** * Computes a 2-approximate tour. * * @param graph the input graph * @return a tour * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if the graph is not complete * @throws IllegalArgumentException if the graph contains no vertices */ @Override public GraphPath getTour(Graph graph) { checkGraph(graph); Set vertices = graph.vertexSet(); int n = vertices.size(); if (vertices.size() == 1) { return getSingletonTour(graph); } // Create MST Graph mst = new SimpleGraph<>(null, DefaultEdge::new, false); vertices.forEach(mst::addVertex); for (E e : new KruskalMinimumSpanningTree<>(graph).getSpanningTree().getEdges()) { mst.addEdge(graph.getEdgeSource(e), graph.getEdgeTarget(e)); } // Perform a depth-first-search traversal Set found = CollectionUtil.newHashSetWithExpectedSize(n); List tour = new ArrayList<>(n + 1); V start = vertices.iterator().next(); Iterator dfsIt = new DepthFirstIterator<>(mst, start); while (dfsIt.hasNext()) { V v = dfsIt.next(); if (found.add(v)) { tour.add(v); } } // Explicitly build the path. return vertexListToTour(tour, graph); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/TwoOptHeuristicTSP.java000066400000000000000000000240701402514743400321720ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.util.*; import java.util.*; import static org.jgrapht.util.ArrayUtil.*; /** * The 2-opt heuristic algorithm for the TSP problem. * *

    * The travelling salesman problem (TSP) asks the following question: "Given a list of cities and * the distances between each pair of cities, what is the shortest possible route that visits each * city exactly once and returns to the origin city?". *

    * *

    * This is an implementation of the 2-opt improvement heuristic algorithm. The algorithm generates * passes initial tours and then iteratively improves the tours until a local minimum is * reached. In each iteration it applies the best possible 2-opt move which means to find the best * pair of edges $(i,i+1)$ and $(j,j+1)$ such that replacing them with $(i,j)$ and $(i+1,j+1)$ * minimizes the tour length. The default initial tours use RandomTour, however an alternative * algorithm can be provided to create the initial tour. Initial tours generated using * NearestNeighborHeuristicTSP give good results and performance. *

    * *

    * See wikipedia for more details. * *

    * This implementation can also be used in order to try to improve an existing tour. See method * {@link #improveTour(GraphPath)}. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail * @author Hannes Wellmann */ public class TwoOptHeuristicTSP extends HamiltonianCycleAlgorithmBase implements HamiltonianCycleImprovementAlgorithm { private final int passes; private final HamiltonianCycleAlgorithm initializer; private final double minCostImprovement; private Graph graph; private int n; private double[][] dist; private Map index; private List revIndex; /** * Constructor. By default one initial random tour is used. */ public TwoOptHeuristicTSP() { this(1, new Random()); } /** * Constructor * * @param passes how many initial random tours to check */ public TwoOptHeuristicTSP(int passes) { this(passes, new Random()); } /** * Constructor * * @param passes how many initial random tours to check * @param seed seed for the random number generator */ public TwoOptHeuristicTSP(int passes, long seed) { this(passes, new Random(seed)); } /** * Constructor * * @param passes how many initial random tours to check * @param rng random number generator */ public TwoOptHeuristicTSP(int passes, Random rng) { this(passes, new RandomTourTSP<>(rng)); } /** * Constructor * * @param passes how many initial random tours to check * @param rng random number generator * @param minCostImprovement Minimum cost improvement per iteration */ public TwoOptHeuristicTSP(int passes, Random rng, double minCostImprovement) { this(passes, new RandomTourTSP<>(rng), minCostImprovement); } /** * Constructor * * @param initializer Algorithm to generate initial tour */ public TwoOptHeuristicTSP(HamiltonianCycleAlgorithm initializer) { this(1, initializer); } /** * Constructor * * @param passes how many initial tours to check * @param initializer Algorithm to generate initial tour */ public TwoOptHeuristicTSP(int passes, HamiltonianCycleAlgorithm initializer) { this(passes, initializer, 1e-8); } /** * Constructor * * @param passes how many initial tours to check * @param initializer Algorithm to generate initial tours * @param minCostImprovement Minimum cost improvement per iteration */ public TwoOptHeuristicTSP( int passes, HamiltonianCycleAlgorithm initializer, double minCostImprovement) { if (passes < 1) { throw new IllegalArgumentException("passes must be at least one"); } this.passes = passes; this.initializer = Objects.requireNonNull(initializer, "Initial solver algorithm cannot be null"); this.minCostImprovement = Math.abs(minCostImprovement); } // algorithm /** * Computes a 2-approximate tour. * * @param graph the input graph * @return a tour * @throws IllegalArgumentException if the graph is not undirected * @throws IllegalArgumentException if the graph is not complete * @throws IllegalArgumentException if the graph contains no vertices */ @Override public GraphPath getTour(Graph graph) { checkGraph(graph); if (graph.vertexSet().size() == 1) { return getSingletonTour(graph); } // Initialize vertex index and distances init(graph); // Execute 2-opt for the specified number of passes and a new permutation in each pass GraphPath best = tourToPath(improve(createInitialTour())); for (int i = 1; i < passes; i++) { GraphPath other = tourToPath(improve(createInitialTour())); if (other.getWeight() < best.getWeight()) { best = other; } } return best; } /** * Try to improve a tour by running the 2-opt heuristic. * * @param tour a tour * @return a possibly improved tour */ @Override public GraphPath improveTour(GraphPath tour) { init(tour.getGraph()); return tourToPath(improve(pathToTour(tour))); } /** * Initialize graph and mapping to integer vertices. * * @param graph the input graph */ private void init(Graph graph) { this.graph = graph; this.n = graph.vertexSet().size(); this.dist = new double[n][n]; VertexToIntegerMapping vertex2index = new VertexToIntegerMapping<>(graph.vertexSet()); this.index = vertex2index.getVertexMap(); this.revIndex = vertex2index.getIndexList(); for (E e : graph.edgeSet()) { V s = graph.getEdgeSource(e); int si = index.get(s); V t = graph.getEdgeTarget(e); int ti = index.get(t); double weight = graph.getEdgeWeight(e); dist[si][ti] = weight; dist[ti][si] = weight; } } /** * Create an initial tour * * @return a complete tour */ private int[] createInitialTour() { return pathToTour(initializer.getTour(graph)); } /** * Improve the tour using the 2-opt heuristic. In each iteration it applies the best possible * 2-opt move which means to find the best pair of edges $(i,i+1)$ and $(j,j+1)$ such that * replacing them with $(i,j)$ and $(i+1,j+1)$ minimizes the tour length. * *

    * The returned array instance might or might not be the input array. * * @param tour the input tour * @return a possibly improved tour */ private int[] improve(int[] tour) { double minChange; while (true) { minChange = -minCostImprovement; int mini = -1; int minj = -1; for (int i = 0; i < n - 2; i++) { for (int j = i + 2; j < n; j++) { int ci = tour[i]; int ci1 = tour[i + 1]; int cj = tour[j]; int cj1 = tour[j + 1]; double change = dist[ci][cj] + dist[ci1][cj1] - dist[ci][ci1] - dist[cj][cj1]; if (change < minChange) { minChange = change; mini = i; minj = j; } } } if (mini != -1 && minj != -1) { // apply move: reverse path from mini+1 to minj (both inclusive) reverse(tour, mini + 1, minj); } else { return tour; } } } /** * Transform from an array representation to a graph path. * * @param tour an array containing the index of the vertices of the tour * @return a graph path */ private GraphPath tourToPath(int[] tour) { List tourVertices = new ArrayList<>(n + 1); for (int vi : tour) { V v = revIndex.get(vi); tourVertices.add(v); } return closedVertexListToTour(tourVertices, graph); } /** * Transform from a path representation to an array representation. * * @param path graph path * @return an array containing the index of the vertices of the tour */ private int[] pathToTour(GraphPath path) { boolean[] visited = new boolean[n]; List vertexList = path.getVertexList(); // first and last element are the starting vertex if (vertexList.size() != n + 1) { throw new IllegalArgumentException("Not a valid tour"); } int[] tour = new int[n + 1]; for (int i = 0; i < n; i++) { int vi = index.get(vertexList.get(i)); if (visited[vi]) { throw new IllegalArgumentException("Not a valid tour"); } visited[vi] = true; tour[i] = vi; } tour[n] = tour[0]; return tour; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/tour/package-info.java000066400000000000000000000001111402514743400310010ustar00rootroot00000000000000/** * Graph tours related algorithms. */ package org.jgrapht.alg.tour; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/transform/000077500000000000000000000000001402514743400266435ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/transform/LineGraphConverter.java000066400000000000000000000122011402514743400332430ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.transform; import org.jgrapht.*; import java.util.*; import java.util.function.*; /** * Converter which produces the line graph * of a given input graph. The line graph of an undirected graph $G$ is another graph $L(G)$ that * represents the adjacencies between edges of $G$. The line graph of a directed graph $G$ is the * directed graph $L(G)$ whose vertex set corresponds to the arc set of $G$ and having an arc * directed from an edge $e_1$ to an edge $e_2$ if in $G$, the head of $e_1$ meets the tail of $e_2$ * *

    * More formally, let $G = (V, E)$ be a graph then its line graph $L(G)$ is such that *

      *
    • Each vertex of $L(G)$ represents an edge of $G$
    • *
    • If $G$ is undirected: two vertices of $L(G)$ are adjacent if and only if their corresponding * edges share a common endpoint ("are incident") in $G$
    • *
    • If $G$ is directed: two vertices of $L(G)$ corresponding to respectively arcs $(u,v)$ and * $(r,s)$ in $G$ are adjacent if and only if $v=r$.
    • *
    *

    * * @author Joris Kinable * @author Nikhil Sharma * * * @param vertex type of input graph * @param edge type of input graph * @param edge type of target graph * */ public class LineGraphConverter { private final Graph graph; /** * Line Graph Converter * * @param graph graph to be converted. This implementation supports multigraphs and * pseudographs. */ public LineGraphConverter(Graph graph) { this.graph = Objects.requireNonNull(graph, "Graph cannot be null"); } /** * Constructs a line graph $L(G)$ of the input graph $G(V,E)$. If the input graph is directed, * the result is a line digraph. The result is stored in the target graph. * * @param target target graph */ public void convertToLineGraph(final Graph target) { this.convertToLineGraph(target, null); } /** * Constructs a line graph of the input graph. If the input graph is directed, the result is a * line digraph. The result is stored in the target graph. A weight function is provided to set * edge weights of the line graph edges. Notice that the target graph must be a weighted graph * for this to work. Recall that in a line graph $L(G)$ of a graph $G(V,E)$ there exists an edge * $e$ between $e_1\in E$ and $e_2\in E$ if the head of $e_1$ is incident to the tail of $e_2$. * To determine the weight of $e$ in $L(G)$, the weight function takes as input $e_1$ and $e_2$. * *

    * Note: a special case arises when graph $G$ contains self-loops. Self-loops (as well as * multiple edges) simply add additional nodes to line graph $L(G)$. When $G$ is * directed, a self-loop $e=(v,v)$ in $G$ results in a vertex $e$ in $L(G)$, and in * addition a self-loop $(e,e)$ in $L(G)$, since, by definition, the head of $e$ in $G$ is * incident to its own tail. When $G$ is undirected, a self-loop $e=(v,v)$ in $G$ * results in a vertex $e$ in $L(G)$, but no self-loop $(e,e)$ is added to $L(G)$, * since, by convention, the line graph of an undirected graph is commonly assumed to be a * simple graph. * * @param target target graph * @param weightFunction weight function */ public void convertToLineGraph( final Graph target, final BiFunction weightFunction) { Graphs.addAllVertices(target, graph.edgeSet()); if (graph.getType().isDirected()) { for (V vertex : graph.vertexSet()) { for (E e1 : graph.incomingEdgesOf(vertex)) { for (E e2 : graph.outgoingEdgesOf(vertex)) { EE edge = target.addEdge(e1, e2); if (weightFunction != null) target.setEdgeWeight(edge, weightFunction.apply(e1, e2)); } } } } else { // undirected graph for (V v : graph.vertexSet()) { for (E e1 : graph.edgesOf(v)) { for (E e2 : graph.edgesOf(v)) { if (e1 != e2) { EE edge = target.addEdge(e1, e2); if (weightFunction != null) target.setEdgeWeight(edge, weightFunction.apply(e1, e2)); } } } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/transform/package-info.java000066400000000000000000000001151402514743400320270ustar00rootroot00000000000000/** * Package for graph transformers */ package org.jgrapht.alg.transform; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/000077500000000000000000000000001402514743400256055ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/AliasMethodSampler.java000066400000000000000000000115321402514743400321700ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import java.util.*; /** * The alias method for sampling from a discrete probability distribution. * *

    * The implementation is described in the paper: Michael D. Vose. A Linear Algorithm for Generating * Random Numbers with a Given Distribution. IEEE Transactions on Software Engineering, * 17(9):972--975, 1991. * *

    * Initialization takes $O(n)$ where $n$ is the number of items. Sampling takes $O(1)$. * * @author Dimitrios Michail */ public class AliasMethodSampler { private final Random rng; private Comparator comparator; private final double[] prob; private final int[] alias; /** * Constructor * * @param p the probability distribution where position i of the array is $Prob(X=i)$ * @throws IllegalArgumentException in case of a non-valid probability distribution */ public AliasMethodSampler(double[] p) { this(p, new Random(), ToleranceDoubleComparator.DEFAULT_EPSILON); } /** * Constructor * * @param p the probability distribution where position $i$ of the array is $Prob(X=i)$ * @param seed seed to use for the random number generator */ public AliasMethodSampler(double[] p, long seed) { this(p, new Random(seed), ToleranceDoubleComparator.DEFAULT_EPSILON); } /** * Constructor * * @param p the probability distribution where position $i$ of the array is $Prob(X=i)$ * @param rng the random number generator * @throws IllegalArgumentException in case of a non-valid probability distribution */ public AliasMethodSampler(double[] p, Random rng) { this(p, rng, ToleranceDoubleComparator.DEFAULT_EPSILON); } /** * Constructor * * @param p the probability distribution where position $i$ of the array is $Prob(X=i)$ * @param rng the random number generator * @param epsilon tolerance used when comparing floating-point values * @throws IllegalArgumentException in case of a non-valid probability distribution */ public AliasMethodSampler(double[] p, Random rng, double epsilon) { this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); this.comparator = new ToleranceDoubleComparator(epsilon); if (p == null || p.length < 1) { throw new IllegalArgumentException("Probabilities cannot be empty"); } double sum = 0d; for (int i = 0; i < p.length; i++) { if (comparator.compare(p[i], 0d) < 0) { throw new IllegalArgumentException("Non valid probability distribution"); } sum += p[i]; } if (comparator.compare(sum, 1d) != 0) { throw new IllegalArgumentException("Non valid probability distribution"); } /* * Initialize large and small */ int n = p.length; int[] large = new int[n]; int[] small = new int[n]; double threshold = 1d / n; int l = 0, s = 0; for (int j = 0; j < n; j++) { if (comparator.compare(p[j], threshold) > 0) { large[l++] = j; } else { small[s++] = j; } } /* * Compute probability and alias */ this.prob = new double[n]; this.alias = new int[n]; while (s != 0 && l != 0) { int j = small[--s]; int k = large[--l]; prob[j] = n * p[j]; alias[j] = k; p[k] += p[j] - threshold; if (comparator.compare(p[k], threshold) > 0) { large[l++] = k; } else { small[s++] = k; } } while (s > 0) { prob[small[--s]] = 1d; } while (l > 0) { prob[large[--l]] = 1d; } } /** * Sample a value from the distribution. * * @return a sample from the distribution */ public int next() { double u = rng.nextDouble() * prob.length; int j = (int) Math.floor(u); if (comparator.compare(u - j, prob[j]) <= 0) { return j; } else { return alias[j]; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/AlwaysEqualComparator.java000066400000000000000000000021001402514743400327210ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import java.util.*; /** * A default implementation for a check on equality (that always holds) * * @param type of elements to be compared * @deprecated use a lambda like {@code (t1,t2) -> 0} instead */ @Deprecated(forRemoval = true, since = "1.5.1") public class AlwaysEqualComparator implements Comparator { @Override public int compare(T arg0, T arg1) { return 0; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/FixedSizeIntegerQueue.java000066400000000000000000000051131402514743400326650ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; /** * Primitive but efficient implementation of a fixed size queue for integers. Note: this queue is * not implemented as a ring, so at most $N$ enqueue operations are allowed, where $N$ is the * maximum capacity of the queue! After that, queue.clear() must be invoked. * * @author Joris Kinable */ public final class FixedSizeIntegerQueue { /* Storage array for the elements in the queue */ private final int[] vs; /* Index of left most element in the queue */ private int i = 0; /* Index of right most element in the queue. If i==n, the queue is empty */ private int n = 0; /** * Create a queue of fixed size. * * @param capacity size of the queue */ public FixedSizeIntegerQueue(int capacity) { assert capacity > 0; vs = new int[capacity]; } /** * Add an element to the queue. * * @param e element */ public void enqueue(int e) { assert n < vs.length; vs[n++] = e; } /** * Poll the first element from the queue. * * @return the first element. */ public int poll() { assert !isEmpty(); return vs[i++]; } /** * Check if the queue has any items. * * @return true if the queue is empty */ public boolean isEmpty() { return i == n; } /** * Returns the number of items in the queue. * * @return number of items in the queue */ public int size() { return n - i; } /** Empty the queue. */ public void clear() { i = 0; n = 0; } /** * Returns a textual representation of the queue. * * @return a textual representation of the queue. */ public String toString() { StringBuilder s = new StringBuilder(); for (int j = i; j < n; j++) s.append(vs[j]).append(" "); return s.toString(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/NeighborCache.java000066400000000000000000000202401402514743400311270ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Szabolcs Besenyei and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import org.jgrapht.*; import org.jgrapht.event.*; import org.jgrapht.util.*; import java.util.*; import java.util.function.*; /** * Maintains a cache of each vertex's neighbors. While lists of neighbors can be obtained from * {@link Graphs}, they are re-calculated at each invocation by walking a vertex's incident edges, * which becomes inordinately expensive when performed often. * *

    * The cache also keeps track of successors and predecessors for each vertex. This means that the * result of the union of calling predecessorsOf(v) and successorsOf(v) is equal to the result of * calling neighborsOf(v) for a given vertex v. * * @param the vertex type * @param the edge type * * @author Szabolcs Besenyei */ public class NeighborCache implements GraphListener { private Map> successors = new HashMap<>(); private Map> predecessors = new HashMap<>(); private Map> neighbors = new HashMap<>(); private Graph graph; /** * Constructor * * @param graph the input graph * @throws NullPointerException if the input graph is null */ public NeighborCache(Graph graph) { this.graph = Objects.requireNonNull(graph); } /** * Returns the unique predecessors of the given vertex if it exists in the cache, otherwise it * is initialized. * * @param v the given vertex * @return the unique predecessors of the given vertex */ public Set predecessorsOf(V v) { return fetch(v, predecessors, k -> new Neighbors<>(Graphs.predecessorListOf(graph, v))); } /** * Returns the unique successors of the given vertex if it exists in the cache, otherwise it is * initialized. * * @param v the given vertex * @return the unique successors of the given vertex */ public Set successorsOf(V v) { return fetch(v, successors, k -> new Neighbors<>(Graphs.successorListOf(graph, v))); } /** * Returns the unique neighbors of the given vertex if it exists in the cache, otherwise it is * initialized. * * @param v the given vertex * @return the unique neighbors of the given vertex */ public Set neighborsOf(V v) { return fetch(v, neighbors, k -> new Neighbors<>(Graphs.neighborListOf(graph, v))); } /** * Returns a list of vertices which are adjacent to a specified vertex. If the graph is a * multigraph, vertices may appear more than once in the returned list. Because a list of * neighbors can not be efficiently maintained, it is reconstructed on every invocation, by * duplicating entries in the neighbor set. It is thus more efficient to use * {@link #neighborsOf} unless duplicate neighbors are important. * * @param v the vertex whose neighbors are desired * * @return all neighbors of the specified vertex */ public List neighborListOf(V v) { Neighbors nbrs = neighbors.get(v); if (nbrs == null) { nbrs = new Neighbors<>(Graphs.neighborListOf(graph, v)); neighbors.put(v, nbrs); } return nbrs.getNeighborList(); } private Set fetch(V vertex, Map> map, Function> func) { return map.computeIfAbsent(vertex, func).getNeighbors(); } @Override public void edgeAdded(GraphEdgeChangeEvent e) { assert e .getSource() == this.graph : "This NeighborCache is added as a listener to a graph other than the one specified during the construction of this NeighborCache!"; V source = e.getEdgeSource(); V target = e.getEdgeTarget(); if (successors.containsKey(source)) { successors.get(source).addNeighbor(target); } if (predecessors.containsKey(target)) { predecessors.get(target).addNeighbor(source); } if (neighbors.containsKey(source)) { neighbors.get(source).addNeighbor(target); } if (neighbors.containsKey(target)) { neighbors.get(target).addNeighbor(source); } } @Override public void edgeRemoved(GraphEdgeChangeEvent e) { assert e .getSource() == this.graph : "This NeighborCache is added as a listener to a graph other than the one specified during the construction of this NeighborCache!"; V source = e.getEdgeSource(); V target = e.getEdgeTarget(); if (successors.containsKey(source)) { successors.get(source).removeNeighbor(target); } if (predecessors.containsKey(target)) { predecessors.get(target).removeNeighbor(source); } if (neighbors.containsKey(source)) { neighbors.get(source).removeNeighbor(target); } if (neighbors.containsKey(target)) { neighbors.get(target).removeNeighbor(source); } } @Override public void vertexAdded(GraphVertexChangeEvent e) { // Nothing to cache until there are edges } @Override public void vertexRemoved(GraphVertexChangeEvent e) { assert e .getSource() == this.graph : "This NeighborCache is added as a listener to a graph other than the one specified during the construction of this NeighborCache!"; successors.remove(e.getVertex()); predecessors.remove(e.getVertex()); neighbors.remove(e.getVertex()); } /** * Stores cached neighbors for a single vertex. Includes support for live neighbor sets and * duplicate neighbors. */ static class Neighbors { private Map neighborCounts = new LinkedHashMap<>(); // TODO could eventually make neighborSet modifiable, resulting // in edge removals from the graph private Set neighborSet = Collections.unmodifiableSet(neighborCounts.keySet()); public Neighbors(Collection neighbors) { // add all current neighbors for (V neighbor : neighbors) { addNeighbor(neighbor); } } public void addNeighbor(V v) { ModifiableInteger count = neighborCounts.get(v); if (count == null) { count = new ModifiableInteger(1); neighborCounts.put(v, count); } else { count.increment(); } } public void removeNeighbor(V v) { ModifiableInteger count = neighborCounts.get(v); if (count == null) { throw new IllegalArgumentException( "Attempting to remove a neighbor that wasn't present"); } count.decrement(); if (count.getValue() == 0) { neighborCounts.remove(v); } } public Set getNeighbors() { return neighborSet; } public List getNeighborList() { List neighbors = new ArrayList<>(); for (Map.Entry entry : neighborCounts.entrySet()) { V v = entry.getKey(); int count = entry.getValue().intValue(); for (int i = 0; i < count; i++) { neighbors.add(v); } } return neighbors; } @Override public String toString() { return neighborSet.toString(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/Pair.java000066400000000000000000000063721402514743400273530ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import java.io.*; import java.util.*; /** * Generic pair. * * @param the first element type * @param the second element type * */ public class Pair implements Serializable { private static final long serialVersionUID = 8176288675989092842L; /** * The first pair element */ protected A first; /** * The second pair element */ protected B second; /** * Create a new pair * * @param a the first element * @param b the second element */ public Pair(A a, B b) { this.first = a; this.second = b; } /** * Get the first element of the pair * * @return the first element of the pair */ public A getFirst() { return first; } /** * Get the second element of the pair * * @return the second element of the pair */ public B getSecond() { return second; } /** * Set the first element of the pair. * * @param f the element to be assigned. */ public void setFirst(A f) { first = f; } /** * Set the second element of the pair. * * @param s the element to be assigned. */ public void setSecond(B s) { second = s; } /** * Assess if this pair contains an element. * * @param e The element in question * * @return true if contains the element, false otherwise * * @param the element type */ @SuppressWarnings("unlikely-arg-type") public boolean hasElement(E e) { if (e == null) { return first == null || second == null; } else { return e.equals(first) || e.equals(second); } } @Override public String toString() { return "(" + first + "," + second + ")"; } @Override public boolean equals(Object o) { if (this == o) return true; else if (!(o instanceof Pair)) return false; @SuppressWarnings("unchecked") Pair other = (Pair) o; return Objects.equals(first, other.first) && Objects.equals(second, other.second); } @Override public int hashCode() { return Objects.hash(first, second); } /** * Create a new pair. * * @param a first element * @param b second element * @param the first element type * @param the second element type * @return new pair */ public static Pair of(A a, B b) { return new Pair<>(a, b); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/ToleranceDoubleComparator.java000066400000000000000000000041261402514743400335520ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import java.io.*; import java.util.*; /** * A double comparator with adjustable tolerance. * * @author Dimitrios Michail */ public class ToleranceDoubleComparator implements Comparator, Serializable { private static final long serialVersionUID = -3819451375975842372L; /** * Default tolerance used by the comparator. */ public static final double DEFAULT_EPSILON = 1e-9; private final double epsilon; /** * Construct a new comparator with a {@link #DEFAULT_EPSILON} tolerance. */ public ToleranceDoubleComparator() { this(DEFAULT_EPSILON); } /** * Construct a new comparator with a specified tolerance. * * @param epsilon the tolerance */ public ToleranceDoubleComparator(double epsilon) { if (epsilon <= 0.0) { throw new IllegalArgumentException("Tolerance must be positive"); } this.epsilon = epsilon; } /** * Compares two floating point values. Returns 0 if they are equal, -1 if {@literal o1 < o2}, 1 * otherwise * * @param o1 the first value * @param o2 the second value * @return 0 if they are equal, -1 if {@literal o1 < o2}, 1 otherwise */ @Override public int compare(Double o1, Double o2) { if (Math.abs(o1 - o2) < epsilon) { return 0; } else { return Double.compare(o1, o2); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/Triple.java000066400000000000000000000075601402514743400277170ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import java.io.*; import java.util.*; /** * Generic triple (3-tuple). * * @param the first element type * @param the second element type * @param the third element type * * @author Dimitrios Michail */ public class Triple implements Serializable { private static final long serialVersionUID = -7076291895521537427L; /** * The first element */ protected A first; /** * The second element */ protected B second; /** * The third element */ protected C third; /** * Create a new triple * * @param a the first element * @param b the second element * @param c the third element */ public Triple(A a, B b, C c) { this.first = a; this.second = b; this.third = c; } /** * Get the first element * * @return the first element */ public A getFirst() { return first; } /** * Get the second element * * @return the second element */ public B getSecond() { return second; } /** * Get the third element * * @return the third element */ public C getThird() { return third; } /** * Set the first element * * @param a the element to be assigned */ public void setFirst(A a) { first = a; } /** * Set the second element * * @param b the element to be assigned */ public void setSecond(B b) { second = b; } /** * Set the third element * * @param c the element to be assigned */ public void setThird(C c) { third = c; } /** * Assess if this triple contains an element. * * @param e The element in question * @return true if contains the element, false otherwise * @param the element type */ @SuppressWarnings("unlikely-arg-type") public boolean hasElement(E e) { if (e == null) { return first == null || second == null || third == null; } else { return e.equals(first) || e.equals(second) || e.equals(third); } } @Override public String toString() { return "(" + first + "," + second + "," + third + ")"; } @Override public boolean equals(Object o) { if (this == o) return true; else if (!(o instanceof Triple)) return false; @SuppressWarnings("unchecked") Triple other = (Triple) o; return Objects.equals(first, other.first) && Objects.equals(second, other.second) && Objects.equals(third, other.third); } @Override public int hashCode() { return Objects.hash(first, second, third); } /** * Create a new triple. * * @param a first element * @param b second element * @param c third element * @param the first element type * @param the second element type * @param the third element type * @return new triple */ public static Triple of(A a, B b, C c) { return new Triple<>(a, b, c); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/UnionFind.java000066400000000000000000000154471402514743400303540ustar00rootroot00000000000000/* * (C) Copyright 2010-2021, by Tom Conerly and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import java.util.*; import java.util.stream.*; /** * An implementation of Union * Find data structure. Union Find is a disjoint-set data structure. It supports two operations: * finding the set a specific element is in, and merging two sets. The implementation uses union by * rank and path compression to achieve an amortized cost of $O(\alpha(n))$ per operation where * $\alpha$ is the inverse Ackermann function. UnionFind uses the hashCode and equals method of the * elements it operates on. * * @param element type * * @author Tom Conerly */ public class UnionFind { private final Map parentMap; private final Map rankMap; private int count; // number of components /** * Creates a UnionFind instance with all the elements in separate sets. * * @param elements the initial elements to include (each element in a singleton set). */ public UnionFind(Set elements) { parentMap = new LinkedHashMap<>(); rankMap = new HashMap<>(); for (T element : elements) { parentMap.put(element, element); rankMap.put(element, 0); } count = elements.size(); } /** * Adds a new element to the data structure in its own set. * * @param element The element to add. */ public void addElement(T element) { if (parentMap.containsKey(element)) throw new IllegalArgumentException( "element is already contained in UnionFind: " + element); parentMap.put(element, element); rankMap.put(element, 0); count++; } /** * @return map from element to parent element */ protected Map getParentMap() { return parentMap; } /** * @return map from element to rank */ protected Map getRankMap() { return rankMap; } /** * Returns the representative element of the set that element is in. * * @param element The element to find. * * @return The element representing the set the element is in. */ public T find(final T element) { if (!parentMap.containsKey(element)) { throw new IllegalArgumentException( "element is not contained in this UnionFind data structure: " + element); } T current = element; while (true) { T parent = parentMap.get(current); if (parent.equals(current)) { break; } current = parent; } final T root = current; current = element; while (!current.equals(root)) { T parent = parentMap.get(current); parentMap.put(current, root); current = parent; } return root; } /** * Merges the sets which contain element1 and element2. No guarantees are given as to which * element becomes the representative of the resulting (merged) set: this can be either * find(element1) or find(element2). * * @param element1 The first element to union. * @param element2 The second element to union. */ public void union(T element1, T element2) { if (!parentMap.containsKey(element1) || !parentMap.containsKey(element2)) { throw new IllegalArgumentException("elements must be contained in given set"); } T parent1 = find(element1); T parent2 = find(element2); // check if the elements are already in the same set if (parent1.equals(parent2)) { return; } int rank1 = rankMap.get(parent1); int rank2 = rankMap.get(parent2); if (rank1 > rank2) { parentMap.put(parent2, parent1); } else if (rank1 < rank2) { parentMap.put(parent1, parent2); } else { parentMap.put(parent2, parent1); rankMap.put(parent1, rank1 + 1); } count--; } /** * Tests whether two elements are contained in the same set. * * @param element1 first element * @param element2 second element * @return true if element1 and element2 are contained in the same set, false otherwise. */ public boolean inSameSet(T element1, T element2) { return find(element1).equals(find(element2)); } /** * Returns the number of sets. Initially, all items are in their own set. The smallest number of * sets equals one. * * @return the number of sets */ public int numberOfSets() { assert count >= 1 && count <= parentMap.keySet().size(); return count; } /** * Returns the total number of elements in this data structure. * * @return the total number of elements in this data structure. */ public int size() { return parentMap.size(); } /** * Resets the UnionFind data structure: each element is placed in its own singleton set. */ public void reset() { for (T element : parentMap.keySet()) { parentMap.put(element, element); rankMap.put(element, 0); } count = parentMap.size(); } /** * Returns a string representation of this data structure. Each component is represented as * $\left{v_i:v_1,v_2,v_3,...v_n\right}$, where $v_i$ is the representative of the set. * * @return string representation of this data structure */ public String toString() { Map> setRep = new LinkedHashMap<>(); for (T t : parentMap.keySet()) { T representative = find(t); if (!setRep.containsKey(representative)) setRep.put(representative, new LinkedHashSet<>()); setRep.get(representative).add(t); } return setRep .keySet().stream() .map( key -> "{" + key + ":" + setRep .get(key).stream().map(Objects::toString).collect(Collectors.joining(",")) + "}") .collect(Collectors.joining(", ", "{", "}")); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/UnorderedPair.java000066400000000000000000000051331402514743400312150ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import java.io.*; import java.util.*; /** * Generic unordered pair. * *

    * Although the instances of this class are immutable, it is impossible to ensure that the * references passed to the constructor will not be modified by the caller. * * @param the first element type * @param the second element type * * @author Joris Kinable * */ public class UnorderedPair extends Pair implements Serializable { private static final long serialVersionUID = -3110454174542533876L; /** * Create a new unordered pair * * @param a an element * @param b another element */ public UnorderedPair(A a, B b) { super(a, b); } @Override public String toString() { return "{" + first + "," + second + "}"; } @Override public boolean equals(Object o) { if (this == o) return true; else if (!(o instanceof UnorderedPair)) return false; @SuppressWarnings("unchecked") UnorderedPair other = (UnorderedPair) o; return (Objects.equals(first, other.first) && Objects.equals(second, other.second)) || (Objects.equals(first, other.second) && Objects.equals(second, other.first)); } @Override public int hashCode() { int hash1 = first == null ? 0 : first.hashCode(); int hash2 = second == null ? 0 : second.hashCode(); return hash1 > hash2 ? hash1 * 31 + hash2 : hash2 * 31 + hash1; } /** * Creates new unordered pair of elements pulling of the necessity to provide corresponding * types of the elements supplied. * * @param a first element * @param b second element * @param the first element type * @param the second element type * @return new unordered pair */ public static UnorderedPair of(A a, B b) { return new UnorderedPair<>(a, b); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/VertexDegreeComparator.java000066400000000000000000000104151402514743400330720ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Linda Buisman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import org.jgrapht.*; import java.util.*; /** * Compares two vertices based on their degree. * *

    * Used by greedy algorithms that need to sort vertices by their degree. Two vertices are considered * equal if their degrees are equal. *

    * * @param the graph vertex type * @param the graph edge type * * @author Linda Buisman * */ public class VertexDegreeComparator implements Comparator { /** * Returns a {@link Comparator} that compares vertices by their degrees in the specified graph. *

    * The comparator compares in ascending order of degrees (lower degree first). To obtain a * comparator that compares in descending order call {@link Comparator#reversed()} on the * returned comparator. *

    * * @param the graph vertex type * @param g graph with respect to which the degree is calculated. * @return a {@code Comparator} to compare vertices by their degree in ascending order */ public static Comparator of(Graph g) { return Comparator.comparingInt(g::degreeOf); } // TODO: after next release remove everything below this line and remove implementation of // comparator (and the type parameters) /** * Order in which we sort the vertices: ascending vertex degree or descending vertex degree * * @deprecated use {@link VertexDegreeComparator#of(Graph)} */ @Deprecated(forRemoval = true, since = "1.5.1") public enum Order { ASCENDING, DESCENDING } /** * The graph that contains the vertices to be compared. */ private Graph graph; /** * Order in which the vertices are sorted: ascending or descending */ private Order order; /** * Creates a comparator for comparing the degrees of vertices in the specified graph. The * comparator compares in ascending order of degrees (lowest first). * * @param g graph with respect to which the degree is calculated. * @deprecated use {@link VertexDegreeComparator#of(Graph)} */ @Deprecated(forRemoval = true, since = "1.5.1") public VertexDegreeComparator(Graph g) { this(g, Order.ASCENDING); } /** * Creates a comparator for comparing the degrees of vertices in the specified graph. * * @param g graph with respect to which the degree is calculated. * @param order order in which the vertices are sorted (ascending or descending) * @deprecated use {@link VertexDegreeComparator#of(Graph)} for ascending order or * {@link Comparator#reversed() reverse the comparator } for descending order. */ @Deprecated(forRemoval = true, since = "1.5.1") public VertexDegreeComparator(Graph g, Order order) { graph = g; this.order = order; } /** * Compare the degrees of v1 and v2, taking into account whether * ascending or descending order is used. * * @param v1 the first vertex to be compared. * @param v2 the second vertex to be compared. * * @return -1 if v1 comes before v2, +1 if * v1 comes after v2, 0 if equal. * @deprecated use {@link VertexDegreeComparator#of(Graph)} */ @Override @Deprecated(forRemoval = true, since = "1.5.1") public int compare(V v1, V v2) { int comparison = Integer.compare(graph.degreeOf(v1), graph.degreeOf(v2)); if (order == Order.ASCENDING) { return comparison; } else { return -1 * comparison; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/000077500000000000000000000000001402514743400276215ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/Extension.java000066400000000000000000000023721402514743400324440ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util.extension; /** * Class which represents an abstract extension/encapsulation object. An object, from here on * denoted as original,can be encapsulated in or extended by another object. An example would be the * relation between an edge (original) and an annotated edge. The annotated edge * encapsulates/extends an edge, thereby augmenting it with additional data. In symbolic form, if b * is the original class, then a(b) would be its extension. This concept is similar to java's * extension where one class is derived from (extends) another class (original). */ public interface Extension { } ExtensionFactory.java000066400000000000000000000017721402514743400337200ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/* * (C) Copyright 2015-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util.extension; /** * Factory class which creates extension/encapsulation objects * * @param class-type of extension */ public interface ExtensionFactory { /** * Factory method which creates a new object which extends Extension * * @return new object which extends Extension */ B create(); } ExtensionManager.java000066400000000000000000000050751402514743400336630ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/* * (C) Copyright 2015-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util.extension; import java.util.*; /** * Convenience class to manage extensions/encapsulations. This class creates and manages object * extensions and encapsulations. An object, from here on denoted as 'original', can be encapsulated * in or extended by another object. An example would be the relation between an edge (original) and * an annotated edge. The annotated edge encapsulates/extends an edge, thereby augmenting it with * additional data. In symbolic form, if b is the original class, then a(b) would be its extension. * This concept is similar to java's extension where one class is derived from (extends) another * class (original). * * @param class-type to be extended (class-type of original) * @param class-type of extension * */ public class ExtensionManager { /* Factory class to create new extensions */ private ExtensionFactory extensionFactory; /* Mapping of originals to their extensions */ private Map originalToExtensionMap = new HashMap<>(); /** * Create a new extension manager. * * @param factory the extension factory to use */ public ExtensionManager(ExtensionFactory factory) { this.extensionFactory = factory; } /** * Creates and returns an extension object. * * @return Extension object */ public B createExtension() { return extensionFactory.create(); } /** * Creates a new singleton extension object for original t if no such object exists, returns the * old one otherwise. * * @param t the original object * @return the extension object */ public B getExtension(T t) { if (originalToExtensionMap.containsKey(t)) { return originalToExtensionMap.get(t); } B extension = createExtension(); originalToExtensionMap.put(t, extension); return extension; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/extension/package-info.java000066400000000000000000000001531402514743400330070ustar00rootroot00000000000000/** * Utility classes for managing extensions/encapsulations. */ package org.jgrapht.alg.util.extension; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/util/package-info.java000066400000000000000000000001171402514743400307730ustar00rootroot00000000000000/** * Utilities used by JGraphT algorithms. */ package org.jgrapht.alg.util; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/000077500000000000000000000000001402514743400272045ustar00rootroot00000000000000BarYehudaEvenTwoApproxVCImpl.java000066400000000000000000000077411402514743400354220ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * Implementation of the 2-opt algorithm for a minimum weighted vertex cover by R. Bar-Yehuda and S. * Even. A linear time approximation algorithm for the weighted vertex cover problem. J. of * Algorithms 2:198-203, 1981. The solution is guaranteed to be within $2$ times the optimum * solution. An easier-to-read version of this algorithm can be found here:
    https://www.cs.umd.edu/class/spring2011/cmsc651/vc.pdf * * Note: this class supports pseudo-graphs Runtime: $O(|E|)$ This is a fast algorithm, guaranteed to * give a $2$-approximation. A solution of higher quality (same approximation ratio) at the * expensive of a higher runtime can be obtained using {@link BarYehudaEvenTwoApproxVCImpl}. * * * TODO: Remove the UndirectedSubgraph dependency! Querying vertex degrees on these graphs is * actually slow! This does affect the runtime complexity. Better would be to just work on a clone * of the original graph! * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class BarYehudaEvenTwoApproxVCImpl implements VertexCoverAlgorithm { private final Graph graph; private final Map vertexWeightMap; /** * Constructs a new BarYehudaEvenTwoApproxVCImpl instance where all vertices have uniform * weights. * * @param graph input graph */ public BarYehudaEvenTwoApproxVCImpl(Graph graph) { this.graph = GraphTests.requireUndirected(graph); this.vertexWeightMap = graph .vertexSet().stream().collect(Collectors.toMap(Function.identity(), vertex -> 1.0)); } /** * Constructs a new BarYehudaEvenTwoApproxVCImpl instance * * @param graph input graph * @param vertexWeightMap mapping of vertex weights */ public BarYehudaEvenTwoApproxVCImpl(Graph graph, Map vertexWeightMap) { this.graph = GraphTests.requireUndirected(graph); this.vertexWeightMap = Objects.requireNonNull(vertexWeightMap); } @Override public VertexCover getVertexCover() { Set cover = new LinkedHashSet<>(); double weight = 0; Graph copy = new AsSubgraph<>(graph, null, null); Map W = new HashMap<>(); for (V v : graph.vertexSet()) W.put(v, vertexWeightMap.get(v)); // Main loop Set edgeSet = copy.edgeSet(); while (!edgeSet.isEmpty()) { // Pick arbitrary edge E e = edgeSet.iterator().next(); V p = copy.getEdgeSource(e); V q = copy.getEdgeTarget(e); if (W.get(p) <= W.get(q)) { W.put(q, W.get(q) - W.get(p)); cover.add(p); weight += vertexWeightMap.get(p); copy.removeVertex(p); } else { W.put(p, W.get(p) - W.get(q)); cover.add(q); weight += vertexWeightMap.get(q); copy.removeVertex(q); } } return new VertexCoverAlgorithm.VertexCoverImpl<>(cover, weight); } } ClarksonTwoApproxVCImpl.java000066400000000000000000000124601402514743400345060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.vertexcover.util.*; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * Implementation of the 2-opt algorithm for a minimum weighted vertex cover by Clarkson, Kenneth L. * "A modification of the greedy algorithm for vertex cover." Information Processing Letters 16.1 * (1983): 23-25. The solution is guaranteed to be within $2$ times the optimum solution. Runtime: * $O(|E|\log |V|)$ * * Note: this class supports pseudo-graphs * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class ClarksonTwoApproxVCImpl implements VertexCoverAlgorithm { private static int vertexCounter = 0; private final Graph graph; private final Map vertexWeightMap; /** * Constructs a new ClarksonTwoApproxVCImpl instance where all vertices have uniform weights. * * @param graph input graph */ public ClarksonTwoApproxVCImpl(Graph graph) { this.graph = GraphTests.requireUndirected(graph); this.vertexWeightMap = graph .vertexSet().stream().collect(Collectors.toMap(Function.identity(), vertex -> 1.0)); } /** * Constructs a new ClarksonTwoApproxVCImpl instance * * @param graph input graph * @param vertexWeightMap mapping of vertex weights */ public ClarksonTwoApproxVCImpl(Graph graph, Map vertexWeightMap) { this.graph = GraphTests.requireUndirected(graph); this.vertexWeightMap = Objects.requireNonNull(vertexWeightMap); } @Override public VertexCoverAlgorithm.VertexCover getVertexCover() { // Result Set cover = new LinkedHashSet<>(); double weight = 0; // Create working graph: for every vertex, create a RatioVertex which maintains its own list // of neighbors Map> vertexEncapsulationMap = new HashMap<>(); graph .vertexSet().stream().filter(v -> graph.degreeOf(v) > 0).forEach( v -> vertexEncapsulationMap .put(v, new RatioVertex(vertexCounter++, v, vertexWeightMap.get(v)))); for (E e : graph.edgeSet()) { V u = graph.getEdgeSource(e); RatioVertex ux = vertexEncapsulationMap.get(u); V v = graph.getEdgeTarget(e); RatioVertex vx = vertexEncapsulationMap.get(v); ux.addNeighbor(vx); vx.addNeighbor(ux); assert (ux.neighbors .get(vx).equals( vx.neighbors .get( ux))) : " in an undirected graph, if vx is a neighbor of ux, then ux must be a neighbor of vx"; } TreeSet> workingGraph = new TreeSet<>(); workingGraph.addAll(vertexEncapsulationMap.values()); assert (workingGraph.size() == vertexEncapsulationMap .size()) : "vertices in vertexEncapsulationMap: " + graph.vertexSet().size() + "vertices in working graph: " + workingGraph.size(); while (!workingGraph.isEmpty()) { // Continue until all edges are covered // Find a vertex vx for which W(vx)/degree(vx) is minimal RatioVertex vx = workingGraph.pollFirst(); assert (workingGraph .parallelStream().allMatch( ux -> vx.getRatio() <= ux .getRatio())) : "vx does not have the smallest ratio among all elements. VX: " + vx + " WorkingGraph: " + workingGraph; // Iterate over all the neighbors ux of vx and update ux.W double ratio = vx.getRatio(); for (RatioVertex nx : vx.neighbors.keySet()) { if (nx == vx) // Ignore self loops continue; workingGraph.remove(nx); nx.weight -= ratio * vx.neighbors.get(nx); // Delete vx from nx' neighbor list. Delete nx from the graph and place it back, // thereby updating the ordering of the graph nx.removeNeighbor(vx); if (nx.getDegree() > 0) workingGraph.add(nx); } // Update cover cover.add(vx.v); weight += vertexWeightMap.get(vx.v); assert (!workingGraph .parallelStream() .anyMatch(ux -> ux.ID == vx.ID)) : "vx should no longer exist in the working graph"; } return new VertexCoverAlgorithm.VertexCoverImpl<>(cover, weight); } } EdgeBasedTwoApproxVCImpl.java000066400000000000000000000070031402514743400345320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2003-2021, by Linda Buisman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * Finds a 2-approximation for a minimum vertex cover A vertex cover is a set of vertices that * touches all the edges in the graph. The graph's vertex set is a trivial cover. However, a * minimal vertex set (or at least an approximation for it) is usually desired. Finding a * true minimal vertex cover is an NP-Complete problem. For more on the vertex cover problem, see * * http://mathworld.wolfram.com/VertexCover.html * * Note: this class supports pseudo-graphs * * @param the graph vertex type * @param the graph edge type * * @author Linda Buisman */ public class EdgeBasedTwoApproxVCImpl implements VertexCoverAlgorithm { private final Graph graph; /** * Constructs a new EdgeBasedTwoApproxVCImpl instance * * @param graph input graph */ public EdgeBasedTwoApproxVCImpl(Graph graph) { this.graph = GraphTests.requireUndirected(graph); } /** * Finds a 2-approximation for a minimal vertex cover of the specified graph. The algorithm * promises a cover that is at most double the size of a minimal cover. The algorithm takes * O(|E|) time. * * Note: this class supports pseudo-graphs Runtime: O(|E|) * * Albeit the fact that this is a 2-approximation algorithm for vertex cover, its results are * often of lower quality than the results produced by {@link BarYehudaEvenTwoApproxVCImpl} or * {@link ClarksonTwoApproxVCImpl}. * *

    * For more details see Jenny Walter, CMPU-240: Lecture notes for Language Theory and * Computation, Fall 2002, Vassar College, * * http://www.cs.vassar.edu/~walter/cs241index/lectures/PDF/approx.pdf. *

    * * * @return a set of vertices which is a vertex cover for the specified graph. */ @Override public VertexCoverAlgorithm.VertexCover getVertexCover() { // C <-- {} Set cover = new LinkedHashSet<>(); // G'=(V',E') <-- G(V,E) Graph sg = new AsSubgraph<>(graph, null, null); // while E' is non-empty while (sg.edgeSet().size() != 0) { // let (u,v) be an arbitrary edge of E' E e = sg.edgeSet().iterator().next(); // C <-- C U {u,v} V u = graph.getEdgeSource(e); V v = graph.getEdgeTarget(e); cover.add(u); cover.add(v); // remove from E' every edge incident on either u or v sg.removeVertex(u); sg.removeVertex(v); } return new VertexCoverAlgorithm.VertexCoverImpl<>(cover); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/GreedyVCImpl.java000066400000000000000000000135731402514743400323520ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.vertexcover.util.*; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * Greedy algorithm to find a vertex cover for a graph. A vertex cover is a set of vertices that * touches all the edges in the graph. The graph's vertex set is a trivial cover. However, a * minimal vertex set (or at least an approximation for it) is usually desired. Finding a * true minimal vertex cover is an NP-Complete problem. For more on the vertex cover problem, see * * http://mathworld.wolfram.com/VertexCover.html * * Note: this class supports pseudo-graphs Runtime: $O(|E| \log |V|)$ This class produces often, but * not always, better solutions than the 2-approximation algorithms. Nevertheless, there are * instances where the solution is significantly worse. In those cases, consider using * {@link ClarksonTwoApproxVCImpl}. * * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class GreedyVCImpl implements VertexCoverAlgorithm { private static int vertexCounter = 0; private final Graph graph; private final Map vertexWeightMap; /** * Constructs a new GreedyVCImpl instance where all vertices have uniform weights. * * @param graph input graph */ public GreedyVCImpl(Graph graph) { this.graph = GraphTests.requireUndirected(graph); this.vertexWeightMap = graph .vertexSet().stream().collect(Collectors.toMap(Function.identity(), vertex -> 1.0)); } /** * Constructs a new GreedyVCImpl instance * * @param graph input graph * @param vertexWeightMap mapping of vertex weights */ public GreedyVCImpl(Graph graph, Map vertexWeightMap) { this.graph = GraphTests.requireUndirected(graph); this.vertexWeightMap = Objects.requireNonNull(vertexWeightMap); } /** * Finds a greedy solution to the minimum weighted vertex cover problem. At each iteration, the * algorithm picks the vertex v with the smallest ratio {@code weight(v)/degree(v)} and adds it * to the cover. Next vertex v and all edges incident to it are removed. The process repeats * until all vertices are covered. Runtime: O(|E|*log|V|) * * @return greedy solution */ @Override public VertexCoverAlgorithm.VertexCover getVertexCover() { Set cover = new LinkedHashSet<>(); double weight = 0; // Create working graph: for every vertex, create a RatioVertex which maintains its own list // of neighbors Map> vertexEncapsulationMap = new HashMap<>(); graph .vertexSet().stream().filter(v -> graph.degreeOf(v) > 0).forEach( v -> vertexEncapsulationMap .put(v, new RatioVertex<>(vertexCounter++, v, vertexWeightMap.get(v)))); for (E e : graph.edgeSet()) { V u = graph.getEdgeSource(e); RatioVertex ux = vertexEncapsulationMap.get(u); V v = graph.getEdgeTarget(e); RatioVertex vx = vertexEncapsulationMap.get(v); ux.addNeighbor(vx); vx.addNeighbor(ux); assert (ux.neighbors.get(vx).intValue() == vx.neighbors .get(ux) .intValue()) : " in an undirected graph, if vx is a neighbor of ux, then ux must be a neighbor of vx"; } TreeSet> workingGraph = new TreeSet<>(); workingGraph.addAll(vertexEncapsulationMap.values()); assert (workingGraph.size() == vertexEncapsulationMap .size()) : "vertices in vertexEncapsulationMap: " + graph.vertexSet().size() + "vertices in working graph: " + workingGraph.size(); while (!workingGraph.isEmpty()) { // Continue until all edges are covered // Find a vertex vx for which W(vx)/degree(vx) is minimal RatioVertex vx = workingGraph.pollFirst(); assert (workingGraph .parallelStream().allMatch( ux -> vx.getRatio() <= ux .getRatio())) : "vx does not have the smallest ratio among all elements. VX: " + vx + " WorkingGraph: " + workingGraph; for (RatioVertex nx : vx.neighbors.keySet()) { if (nx == vx) // Ignore self loops continue; workingGraph.remove(nx); // Delete vx from nx' neighbor list. Delete nx from the graph and place it back, // thereby updating the ordering of the graph nx.removeNeighbor(vx); if (nx.getDegree() > 0) workingGraph.add(nx); } // Update cover cover.add(vx.v); weight += vertexWeightMap.get(vx.v); assert (workingGraph .parallelStream().noneMatch( ux -> ux.ID == vx.ID)) : "vx should no longer exist in the working graph"; } return new VertexCoverAlgorithm.VertexCoverImpl<>(cover, weight); } } RecursiveExactVCImpl.java000066400000000000000000000312751402514743400340070ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2003-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * Finds a minimum vertex cover in a undirected graph. The implementation relies on a recursive * algorithm. At each recursive step, the algorithm picks a unvisited vertex v and distinguishes two * cases: either v has to be added to the vertex cover or all of its neighbors. * * In pseudo code, the algorithm (simplified) looks like this: * *
     * 
     *  $VC(G)$:
     *  if $V = \emptyset$ then return $\emptyset$
     *  Choose an arbitrary node $v \in G$
     *  $G1 := (V − v, \left{ e \in E | v \not \in e \right})$
     *  $G2 := (V − v − N(v), \left{ e \in E | e \cap (N(v) \cup v)= \empty \right})$
     *  if $|v \cup VC(G1)| \leq |N(v) \cup VC(G2)|$ then
     *    return $v \cup VC(G1)$
     *  else
     *    return $N(v) \cup VC(G2)$
     * 
     * 
    * * To speed up the implementation, memoization and a bounding procedure are used. The current * implementation solves instances with 150-250 vertices efficiently to optimality. * * TODO JK: determine runtime complexity and add it to class description. TODO JK: run this class * through a performance profiler * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class RecursiveExactVCImpl implements VertexCoverAlgorithm { /** Input graph **/ private Graph graph; /** Number of vertices in the graph **/ private int N; /** * Neighbor cache TODO JK: It might be worth trying to replace the neighbors index by a bitset * view. As such, all operations can be simplified to bitset operations, which may improve the * algorithm's performance. **/ private NeighborCache neighborCache; /** Map for memoization **/ private Map memo; /** * Ordered list of vertices which will be iteratively considered to be included in a matching **/ private List vertices; /** Mapping of a vertex to its index in the list of vertices **/ private Map vertexIDDictionary; /** * Maximum weight of the vertex cover. In case there is no weight assigned to the vertices, the * weight of the cover equals the cover's cardinality. */ private double upperBoundOnVertexCoverWeight; /** Indicates whether we are solving a weighted or unweighted version of the problem **/ private boolean weighted; private Map vertexWeightMap = null; ///////////// /** * Constructs a new GreedyVCImpl instance * * @param graph input graph */ public RecursiveExactVCImpl(Graph graph) { this.graph = GraphTests.requireUndirected(graph); this.vertexWeightMap = graph .vertexSet().stream().collect(Collectors.toMap(Function.identity(), vertex -> 1.0)); weighted = false; } /** * Constructs a new GreedyVCImpl instance * * @param graph input graph * @param vertexWeightMap mapping of vertex weights */ public RecursiveExactVCImpl(Graph graph, Map vertexWeightMap) { this.graph = GraphTests.requireUndirected(graph); this.vertexWeightMap = Objects.requireNonNull(vertexWeightMap); weighted = true; } @Override public VertexCoverAlgorithm.VertexCover getVertexCover() { // Initialize this.graph = GraphTests.requireUndirected(graph); memo = new HashMap<>(); vertices = new ArrayList<>(graph.vertexSet()); neighborCache = new NeighborCache<>(graph); vertexIDDictionary = new HashMap<>(); N = vertices.size(); // Sort vertices based on their weight/degree ratio in ascending order // TODO JK: Are there better orderings? vertices.sort(Comparator.comparingDouble(v -> vertexWeightMap.get(v) / graph.degreeOf(v))); for (int i = 0; i < vertices.size(); i++) vertexIDDictionary.put(vertices.get(i), i); // Calculate a bound on the maximum depth using heuristics and mathematical bounding // procedures. // TODO JK: Is there a lower bounding procedure which allows us to prematurely terminate the // search once a solution is found which is equal to the lower bound? Preferably a bounding // procedure which gets better throughout the search. upperBoundOnVertexCoverWeight = this.calculateUpperBound(); // Invoke recursive algorithm BitSetCover vertexCover = this.calculateCoverRecursively(0, new BitSet(N), 0); // Build solution Set verticesInCover = new LinkedHashSet<>(); for (int i = vertexCover.bitSetCover.nextSetBit(0); i >= 0 && i < N; i = vertexCover.bitSetCover.nextSetBit(i + 1)) verticesInCover.add(vertices.get(i)); return new VertexCoverAlgorithm.VertexCoverImpl<>(verticesInCover, vertexCover.weight); } private BitSetCover calculateCoverRecursively( int indexNextCandidate, BitSet visited, double accumulatedWeight) { // Check memoization table if (memo.containsKey(visited)) { return memo.get(visited).copy(); // Cache hit } // Find the next unvisited vertex WITH neighbors (if a vertex has no neighbors, then we // don't need to select it // because it doesn't cover any edges) int indexNextVertex = -1; Set neighbors = Collections.emptySet(); for (int index = visited.nextClearBit(indexNextCandidate); index >= 0 && index < N; index = visited.nextClearBit(index + 1)) { neighbors = new LinkedHashSet<>(neighborCache.neighborsOf(vertices.get(index))); for (Iterator it = neighbors.iterator(); it.hasNext();) // Exclude all visited // vertices if (visited.get(vertexIDDictionary.get(it.next()))) it.remove(); if (!neighbors.isEmpty()) { indexNextVertex = index; break; } } // Base case 1: all vertices have been visited if (indexNextVertex == -1) { // We've visited all vertices, return the base case BitSetCover vertexCover = new BitSetCover(N, 0); if (accumulatedWeight <= upperBoundOnVertexCoverWeight) { // Found new a solution that // matches our bound. Tighten // the bound. upperBoundOnVertexCoverWeight = accumulatedWeight - 1; } return vertexCover; // Base case 2 (pruning): this vertex cover can never be better than the best cover we // already have. Return a cover with a large weight, such that the other branch will be // preferred over this branch. } else if (accumulatedWeight >= upperBoundOnVertexCoverWeight) { return new BitSetCover(N, N); } // Recursion // TODO JK: Can we use a lower bound or estimation which of these 2 branches produces a // better solution? If one of them is more likely to produce a better solution, // then that branch should be explored first! Futhermore, if the lower bound+accumulated // cost > upperBoundOnVertexCoverWeight, then we may prune. // Create 2 branches (N(v) denotes the set of neighbors of v. G_{v} indicates the graph // obtained by removing vertex v and all vertices incident to it.): // Right branch (N(v) are added to the cover, and we solve for G_{N(v) \cup v }$.): BitSet visitedRightBranch = (BitSet) visited.clone(); visitedRightBranch.set(indexNextVertex); for (V v : neighbors) visitedRightBranch.set(vertexIDDictionary.get(v)); double weight = this.getWeight(neighbors); BitSetCover rightCover = calculateCoverRecursively( indexNextVertex + 1, visitedRightBranch, accumulatedWeight + weight); List neighborsIndices = neighbors.stream().map(vertexIDDictionary::get).collect(Collectors.toList()); rightCover.addAllVertices(neighborsIndices, weight); // Left branch (vertex v is added to the cover, and we solve for G_{v}): BitSet visitedLeftBranch = (BitSet) visited.clone(); visitedLeftBranch.set(indexNextVertex); weight = vertexWeightMap.get(vertices.get(indexNextVertex)); BitSetCover leftCover = calculateCoverRecursively( indexNextVertex + 1, visitedLeftBranch, accumulatedWeight + weight); leftCover.addVertex(indexNextVertex, weight); // Delayed update of the left cover // Return the best branch if (leftCover.weight <= rightCover.weight) { memo.put(visited, leftCover.copy()); return leftCover; } else { memo.put(visited, rightCover.copy()); return rightCover; } } /** * Returns the weight of a collection of vertices. In case of the unweighted vertex cover * problem, the return value is the cardinality of the collection. In case of the weighted * version, the return value is the sum of the weights of the vertices * * @param vertices vertices * @return the total weight of the vertices in the collection. */ private double getWeight(Collection vertices) { if (weighted) { return vertices.stream().map(vertexWeightMap::get).reduce(0d, Double::sum); } else { return vertices.size(); } } /** * Calculates a cheap upper bound on the optimum solution. Currently, we return the best * solution found by either the greedy heuristic, or Clarkson's 2-approximation. Neither of * these 2 algorithms dominates the other. //TODO JK: Are there better bounding procedures? */ private double calculateUpperBound() { return Math .min( new GreedyVCImpl<>(graph, vertexWeightMap).getVertexCover().getWeight(), new ClarksonTwoApproxVCImpl<>(graph, vertexWeightMap).getVertexCover().getWeight()); } /** * Helper class which represents a vertex cover as a space efficient BitSet */ protected class BitSetCover { protected BitSet bitSetCover; protected double weight; /** * Construct a new empty vertex cover as a BitSet. * * @param size initial capacity of the BitSet * @param initialWeight the initial weight */ protected BitSetCover(int size, int initialWeight) { bitSetCover = new BitSet(size); this.weight = initialWeight; } /** * Copy constructor * * @param vertexCover the input vertex cover to copy */ protected BitSetCover(BitSetCover vertexCover) { this.bitSetCover = (BitSet) vertexCover.bitSetCover.clone(); this.weight = vertexCover.weight; } /** * Copy a vertex cover. * * @return a copy of the vertex cover */ protected BitSetCover copy() { return new BitSetCover(this); } /** * Add a vertex in the vertex cover. * * @param vertexIndex the index of the vertex * @param weight the weight of the vertex */ protected void addVertex(int vertexIndex, double weight) { bitSetCover.set(vertexIndex); this.weight += weight; } /** * Add multiple vertices in the vertex cover. * * @param vertexIndices the index of the vertices * @param totalWeight the total weight of the vertices */ protected void addAllVertices(List vertexIndices, double totalWeight) { vertexIndices.forEach(bitSetCover::set); this.weight += totalWeight; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/package-info.java000066400000000000000000000001111402514743400323640ustar00rootroot00000000000000/** * Vertex cover algorithms. */ package org.jgrapht.alg.vertexcover; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/util/000077500000000000000000000000001402514743400301615ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/util/RatioVertex.java000066400000000000000000000070051402514743400333020ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover.util; import org.jgrapht.util.*; import java.util.*; /** * Helper class for vertex covers. Guarantees that vertices can be sorted, thereby obtaining a * unique ordering. * * @param the graph vertex type * * @author Joris Kinable */ public class RatioVertex implements Comparable> { /** original vertex **/ public final V v; /** weight of the vertex **/ public double weight; /** unique id, used to guarantee that compareTo never returns 0 **/ public final int ID; /** degree of this vertex **/ protected int degree = 0; /** Map of neighbors, and a count of the number of edges to this neighbor **/ public final Map, Integer> neighbors; /** * Create a new ratio vertex * * @param ID unique id * @param v the vertex * @param weight the vertex weight */ public RatioVertex(int ID, V v, double weight) { this.ID = ID; this.v = v; this.weight = weight; neighbors = new LinkedHashMap<>(); } /** * Add a neighbor. * * @param v the neighbor */ public void addNeighbor(RatioVertex v) { if (!neighbors.containsKey(v)) neighbors.put(v, 1); else neighbors.put(v, neighbors.get(v) + 1); degree++; assert (neighbors.values().stream().mapToInt(Integer::intValue).sum() == degree); } /** * Remove a neighbor. * * @param v the neighbor to remove */ public void removeNeighbor(RatioVertex v) { degree -= neighbors.get(v); neighbors.remove(v); } /** * Returns the degree of the vertex * * @return degree of the vertex */ public int getDegree() { return degree; } /** * Returns the ratio between the vertex' weight and its degree * * @return the ratio between the vertex' weight and its degree */ public double getRatio() { return weight / degree; } @Override public int compareTo(RatioVertex other) { if (this.ID == other.ID) // Same vertex return 0; int result = Double.compare(this.getRatio(), other.getRatio()); if (result == 0) // If vertices have the same value, resolve tie by an ID comparison return Integer.compare(this.ID, other.ID); else return result; } @Override public int hashCode() { return ID; } @Override public boolean equals(Object o) { if (this == o) return true; else if (!(o instanceof RatioVertex)) return false; RatioVertex other = TypeUtil.uncheckedCast(o); return this.ID == other.ID; } @Override public String toString() { return "v" + ID + "(" + degree + ")"; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/alg/vertexcover/util/package-info.java000066400000000000000000000001341402514743400333460ustar00rootroot00000000000000/** * Utilities for vertex cover algorithms. */ package org.jgrapht.alg.vertexcover.util; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/000077500000000000000000000000001402514743400252065ustar00rootroot00000000000000ConnectedComponentTraversalEvent.java000066400000000000000000000032641402514743400344520ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; import java.util.*; /** * A traversal event with respect to a connected component. * * @author Barak Naveh */ public class ConnectedComponentTraversalEvent extends EventObject { private static final long serialVersionUID = 3834311717709822262L; /** * Connected component traversal started event. */ public static final int CONNECTED_COMPONENT_STARTED = 31; /** * Connected component traversal finished event. */ public static final int CONNECTED_COMPONENT_FINISHED = 32; /** * The type of this event. */ private int type; /** * Creates a new ConnectedComponentTraversalEvent. * * @param eventSource the source of the event. * @param type the type of event. */ public ConnectedComponentTraversalEvent(Object eventSource, int type) { super(eventSource); this.type = type; } /** * Returns the event type. * * @return the event type. */ public int getType() { return type; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/EdgeTraversalEvent.java000066400000000000000000000026261402514743400316110ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; import java.util.*; /** * A traversal event for a graph edge. * * @param the graph edge type * * @author Barak Naveh */ public class EdgeTraversalEvent extends EventObject { private static final long serialVersionUID = 4050768173789820979L; /** * The traversed edge. */ protected E edge; /** * Creates a new EdgeTraversalEvent. * * @param eventSource the source of the event. * @param edge the traversed edge. */ public EdgeTraversalEvent(Object eventSource, E edge) { super(eventSource); this.edge = edge; } /** * Returns the traversed edge. * * @return the traversed edge. */ public E getEdge() { return edge; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/GraphChangeEvent.java000066400000000000000000000026771402514743400312360ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; import java.util.*; /** * An event which indicates that a graph has changed. This class is a root for graph change events. * * @author Barak Naveh */ public class GraphChangeEvent extends EventObject { private static final long serialVersionUID = 3834592106026382391L; /** * The type of graph change this event indicates. */ protected int type; /** * Creates a new graph change event. * * @param eventSource the source of the event. * @param type the type of event. */ public GraphChangeEvent(Object eventSource, int type) { super(eventSource); this.type = type; } /** * Returns the event type. * * @return the event type. */ public int getType() { return type; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/GraphEdgeChangeEvent.java000066400000000000000000000104601402514743400320100ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; import org.jgrapht.*; /** * An event which indicates that a graph edge has changed, or is about to change. The event can be * used either as an indication after the edge has been added or removed, or before it * is added. The type of the event can be tested using the * {@link org.jgrapht.event.GraphChangeEvent#getType()} method. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public class GraphEdgeChangeEvent extends GraphChangeEvent { private static final long serialVersionUID = -4421610303769803253L; /** * Before edge added event. This event is fired before an edge is added to a graph. */ public static final int BEFORE_EDGE_ADDED = 21; /** * Before edge removed event. This event is fired before an edge is removed from a graph. */ public static final int BEFORE_EDGE_REMOVED = 22; /** * Edge added event. This event is fired after an edge is added to a graph. */ public static final int EDGE_ADDED = 23; /** * Edge removed event. This event is fired after an edge is removed from a graph. */ public static final int EDGE_REMOVED = 24; /** * Edge weight updated event. This event is fired after an edge weight is updated in a graph. */ public static final int EDGE_WEIGHT_UPDATED = 25; /** * The edge that this event is related to. */ protected E edge; /** * The source vertex of the edge that this event is related to. */ protected V edgeSource; /** * The target vertex of the edge that this event is related to. */ protected V edgeTarget; /** * The weight of the edge that this event is related to. */ protected double edgeWeight; /** * Constructor for GraphEdgeChangeEvent. * * @param eventSource the source of this event. * @param type the event type of this event. * @param edge the edge that this event is related to. * @param edgeSource edge source vertex * @param edgeTarget edge target vertex */ public GraphEdgeChangeEvent(Object eventSource, int type, E edge, V edgeSource, V edgeTarget) { this(eventSource, type, edge, edgeSource, edgeTarget, Graph.DEFAULT_EDGE_WEIGHT); } /** * Constructor for GraphEdgeChangeEvent. * * @param eventSource the source of this event. * @param type the event type of this event. * @param edge the edge that this event is related to. * @param edgeSource edge source vertex * @param edgeTarget edge target vertex * @param edgeWeight edge weight */ public GraphEdgeChangeEvent( Object eventSource, int type, E edge, V edgeSource, V edgeTarget, double edgeWeight) { super(eventSource, type); this.edge = edge; this.edgeSource = edgeSource; this.edgeTarget = edgeTarget; this.edgeWeight = edgeWeight; } /** * Returns the edge that this event is related to. * * @return event edge */ public E getEdge() { return edge; } /** * Returns the source vertex that this event is related to. * * @return event source vertex */ public V getEdgeSource() { return edgeSource; } /** * Returns the target vertex that this event is related to. * * @return event target vertex */ public V getEdgeTarget() { return edgeTarget; } /** * Returns the weight of the edge that this event is related to. * * @return event edge weight */ public double getEdgeWeight() { return edgeWeight; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/GraphListener.java000066400000000000000000000030631402514743400306220ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; /** * A listener that is notified when the graph changes. * *

    * If only notifications on vertex set changes are required it is more efficient to use the * VertexSetListener. *

    * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh * @see VertexSetListener */ public interface GraphListener extends VertexSetListener { /** * Notifies that an edge has been added to the graph. * * @param e the edge event. */ void edgeAdded(GraphEdgeChangeEvent e); /** * Notifies that an edge has been removed from the graph. * * @param e the edge event. */ void edgeRemoved(GraphEdgeChangeEvent e); /** * Notifies that an edge weight has been updated. * * @param e the edge event. */ default void edgeWeightUpdated(GraphEdgeChangeEvent e) { } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/GraphVertexChangeEvent.java000066400000000000000000000047241402514743400324270ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; /** * An event which indicates that a graph vertex has changed, or is about to change. The event can be * used either as an indication after the vertex has been added or removed, or before * it is added. The type of the event can be tested using the * {@link org.jgrapht.event.GraphChangeEvent#getType()} method. * * @param the graph vertex type * * @author Barak Naveh */ public class GraphVertexChangeEvent extends GraphChangeEvent { private static final long serialVersionUID = 3690189962679104053L; /** * Before vertex added event. This event is fired before a vertex is added to a graph. */ public static final int BEFORE_VERTEX_ADDED = 11; /** * Before vertex removed event. This event is fired before a vertex is removed from a graph. */ public static final int BEFORE_VERTEX_REMOVED = 12; /** * Vertex added event. This event is fired after a vertex is added to a graph. */ public static final int VERTEX_ADDED = 13; /** * Vertex removed event. This event is fired after a vertex is removed from a graph. */ public static final int VERTEX_REMOVED = 14; /** * The vertex that this event is related to. */ protected V vertex; /** * Creates a new GraphVertexChangeEvent object. * * @param eventSource the source of the event. * @param type the type of the event. * @param vertex the vertex that the event is related to. */ public GraphVertexChangeEvent(Object eventSource, int type, V vertex) { super(eventSource, type); this.vertex = vertex; } /** * Returns the vertex that this event is related to. * * @return the vertex that this event is related to. */ public V getVertex() { return vertex; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListener.java000066400000000000000000000044561402514743400315330ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; /** * A listener on graph iterator or on a graph traverser. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public interface TraversalListener { /** * Called to inform listeners that the traversal of the current connected component has * finished. * * @param e the traversal event. */ void connectedComponentFinished(ConnectedComponentTraversalEvent e); /** * Called to inform listeners that a traversal of a new connected component has started. * * @param e the traversal event. */ void connectedComponentStarted(ConnectedComponentTraversalEvent e); /** * Called to inform the listener that the specified edge have been visited during the graph * traversal. Depending on the traversal algorithm, edge might be visited more than once. * * @param e the edge traversal event. */ void edgeTraversed(EdgeTraversalEvent e); /** * Called to inform the listener that the specified vertex have been visited during the graph * traversal. Depending on the traversal algorithm, vertex might be visited more than once. * * @param e the vertex traversal event. */ void vertexTraversed(VertexTraversalEvent e); /** * Called to inform the listener that the specified vertex have been finished during the graph * traversal. Exact meaning of "finish" is algorithm-dependent; e.g. for DFS, it means that all * vertices reachable via the vertex have been visited as well. * * @param e the vertex traversal event. */ void vertexFinished(VertexTraversalEvent e); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/TraversalListenerAdapter.java000066400000000000000000000035451402514743400330320ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; /** * An empty do-nothing implementation of the {@link TraversalListener} interface used for * subclasses. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public class TraversalListenerAdapter implements TraversalListener { /** * @see TraversalListener#connectedComponentFinished(ConnectedComponentTraversalEvent) */ @Override public void connectedComponentFinished(ConnectedComponentTraversalEvent e) { } /** * @see TraversalListener#connectedComponentStarted(ConnectedComponentTraversalEvent) */ @Override public void connectedComponentStarted(ConnectedComponentTraversalEvent e) { } /** * @see TraversalListener#edgeTraversed(EdgeTraversalEvent) */ @Override public void edgeTraversed(EdgeTraversalEvent e) { } /** * @see TraversalListener#vertexTraversed(VertexTraversalEvent) */ @Override public void vertexTraversed(VertexTraversalEvent e) { } /** * @see TraversalListener#vertexFinished(VertexTraversalEvent) */ @Override public void vertexFinished(VertexTraversalEvent e) { } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/VertexSetListener.java000066400000000000000000000026431402514743400315150ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; import java.util.*; /** * A listener that is notified when the graph's vertex set changes. It should be used when * only notifications on vertex-set changes are of interest. If all graph notifications are * of interest better use * GraphListener. * * @param the graph vertex type * * @author Barak Naveh * @see GraphListener */ public interface VertexSetListener extends EventListener { /** * Notifies that a vertex has been added to the graph. * * @param e the vertex event. */ void vertexAdded(GraphVertexChangeEvent e); /** * Notifies that a vertex has been removed from the graph. * * @param e the vertex event. */ void vertexRemoved(GraphVertexChangeEvent e); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/VertexTraversalEvent.java000066400000000000000000000026661402514743400322260ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.event; import java.util.*; /** * A traversal event for a graph vertex. * * @param the graph vertex type * * @author Barak Naveh */ public class VertexTraversalEvent extends EventObject { private static final long serialVersionUID = 3688790267213918768L; /** * The traversed vertex. */ protected V vertex; /** * Creates a new VertexTraversalEvent. * * @param eventSource the source of the event. * @param vertex the traversed vertex. */ public VertexTraversalEvent(Object eventSource, V vertex) { super(eventSource); this.vertex = vertex; } /** * Returns the traversed vertex. * * @return the traversed vertex. */ public V getVertex() { return vertex; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/event/package-info.java000066400000000000000000000002361402514743400303760ustar00rootroot00000000000000/** * Event classes and listener interfaces, used to provide a change notification mechanism on graph * modification events. */ package org.jgrapht.event; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/000077500000000000000000000000001402514743400256575ustar00rootroot00000000000000BarabasiAlbertForestGenerator.java000066400000000000000000000107561402514743400343440ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Barabási-Albert growth and preferential attachment forest generator. * *

    * The general graph generator is described in the paper: A.-L. Barabási and R. Albert. Emergence of * scaling in random networks. Science, 286:509-512, 1999. * *

    * The generator starts with a $t$ isolated nodes and grows the network by adding $n - t$ additional * nodes. The additional nodes are added one by one and each of them is connected to one previously * added node, where the probability of connecting to a node is proportional to its degree. * *

    * Note that this Barabàsi-Albert generator only works on undirected graphs. For a version that * works on both directed and undirected graphs and generates only connected graphs see * {@link BarabasiAlbertGraphGenerator}. * * @author Alexandru Valeanu * * @param the graph vertex type * @param the graph edge type */ public class BarabasiAlbertForestGenerator implements GraphGenerator { private final Random rng; private final int t; private final int n; /** * Constructor * * @param t number of trees * @param n final number of nodes * @throws IllegalArgumentException in case of invalid parameters */ public BarabasiAlbertForestGenerator(int t, int n) { this(t, n, new Random()); } /** * Constructor * * @param t number of trees * @param n final number of nodes * @param seed seed for the random number generator * @throws IllegalArgumentException in case of invalid parameters */ public BarabasiAlbertForestGenerator(int t, int n, long seed) { this(t, n, new Random(seed)); } /** * Constructor * * @param t number of trees * @param n final number of nodes * @param rng the random number generator to use * @throws IllegalArgumentException in case of invalid parameters */ public BarabasiAlbertForestGenerator(int t, int n, Random rng) { if (t < 1) { throw new IllegalArgumentException("invalid number of trees (" + t + " < 1)"); } this.t = t; if (n < t) { throw new IllegalArgumentException( "total number of nodes must be at least equal to the number of trees"); } this.n = n; this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); } /** * Generates an instance. * *

    * Note: An exception will be thrown if the target graph is not empty (i.e. contains at least * one vertex) *

    * * @param target the target graph * @param resultMap not used by this generator, can be null * @throws NullPointerException if {@code target} is {@code null} * @throws IllegalArgumentException if {@code target} is not undirected * @throws IllegalArgumentException if {@code target} is not empty */ @Override public void generateGraph(Graph target, Map resultMap) { GraphTests.requireUndirected(target); if (!target.vertexSet().isEmpty()) { throw new IllegalArgumentException("target graph is not empty"); } List nodes = new ArrayList<>(); /* * Add t roots, one for each tree in the forest */ for (int i = 0; i < t; i++) { nodes.add(target.addVertex()); } /* * Grow forest with preferential attachment */ for (int i = t; i < n; i++) { V v = target.addVertex(); V u = nodes.get(rng.nextInt(nodes.size())); assert !target.containsEdge(v, u); target.addEdge(v, u); nodes.add(v); if (i > 1) { nodes.add(u); } } } } BarabasiAlbertGraphGenerator.java000066400000000000000000000127041402514743400341360ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Barabási-Albert growth and preferential attachment graph generator. * *

    * The generator is described in the paper: A.-L. Barabási and R. Albert. Emergence of scaling in * random networks. Science, 286:509-512, 1999. * *

    * The generator starts with a complete graph of $m_0$ nodes and grows the network by adding $n - * m_0$ additional nodes. The additional nodes are added one by one and each of them is connected to * $m$ previously added nodes, where the probability of connecting to a node is proportional to its * degree. * *

    * Note that the Barabàsi-Albert model is designed for undirected networks. Nevertheless, this * generator also works with directed networks where the probabilities are proportional to the sum * of incoming and outgoing degrees. For a more general discussion see the paper: M. E. J. Newman. * The Structure and Function of Complex Networks. SIAM Rev., 45(2):167--256, 2003. * *

    * For a version that generates trees/forests see {@link BarabasiAlbertForestGenerator}. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class BarabasiAlbertGraphGenerator implements GraphGenerator { private final Random rng; private final int m0; private final int m; private final int n; /** * Constructor * * @param m0 number of initial nodes * @param m number of edges of each new node added during the network growth * @param n final number of nodes * @throws IllegalArgumentException in case of invalid parameters */ public BarabasiAlbertGraphGenerator(int m0, int m, int n) { this(m0, m, n, new Random()); } /** * Constructor * * @param m0 number of initial nodes * @param m number of edges of each new node added during the network growth * @param n final number of nodes * @param seed seed for the random number generator * @throws IllegalArgumentException in case of invalid parameters */ public BarabasiAlbertGraphGenerator(int m0, int m, int n, long seed) { this(m0, m, n, new Random(seed)); } /** * Constructor * * @param m0 number of initial nodes * @param m number of edges of each new node added during the network growth * @param n final number of nodes * @param rng the random number generator to use * @throws IllegalArgumentException in case of invalid parameters */ public BarabasiAlbertGraphGenerator(int m0, int m, int n, Random rng) { if (m0 < 1) { throw new IllegalArgumentException("invalid initial nodes (" + m0 + " < 1)"); } this.m0 = m0; if (m <= 0) { throw new IllegalArgumentException("invalid edges per node (" + m + " <= 0"); } if (m > m0) { throw new IllegalArgumentException("invalid edges per node (" + m + " > " + m0 + ")"); } this.m = m; if (n < m0) { throw new IllegalArgumentException( "total number of nodes must be at least equal to the initial set"); } this.n = n; this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); } /** * Generates an instance. * * @param target the target graph * @param resultMap not used by this generator, can be null */ @Override public void generateGraph(Graph target, Map resultMap) { /* * Create complete graph with m0 nodes */ Set oldNodes = new HashSet<>(target.vertexSet()); Set newNodes = new HashSet<>(); new CompleteGraphGenerator(m0).generateGraph(target, resultMap); target.vertexSet().stream().filter(v -> !oldNodes.contains(v)).forEach(newNodes::add); List nodes = new ArrayList<>(n * m); nodes.addAll(newNodes); /* * Augment node list to have node multiplicity equal to min(1,m0-1). */ for (int i = 0; i < m0 - 2; i++) { nodes.addAll(newNodes); } /* * Grow network with preferential attachment */ for (int i = m0; i < n; i++) { V v = target.addVertex(); List newEndpoints = new ArrayList<>(); int added = 0; while (added < m) { V u = nodes.get(rng.nextInt(nodes.size())); if (!target.containsEdge(v, u)) { target.addEdge(v, u); added++; newEndpoints.add(v); if (i > 1) { newEndpoints.add(u); } } } nodes.addAll(newEndpoints); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/ComplementGraphGenerator.java000066400000000000000000000075671402514743400334750ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generator which produces the * complement graph of a given input * graph. The complement $\overline{G}$ of a graph $G$ consists of the same vertices as $G$, but * whose edge set consists of the edges not in $G$. *

    * More formally, let $G = (V, E)$ be a graph and let $K$ consist of all 2-element subsets of $V$. * Then $\overline{G} = (V, K \setminus E)$ is the complement of $G$, where $K \setminus E$ is the * relative complement of $E$ in $K$. For directed graphs, the complement can be defined in the same * way, as a directed graph on the same vertex set, using the set of all 2-element ordered pairs of * $V$ in place of the set $K$ in the formula above. *

    * The complement is not defined for multigraphs. If a multigraph is provided as input to this * generator, it will be treated as if it is a simple graph. * * @author Joris Kinable * * * @param vertex type * @param edge type */ public class ComplementGraphGenerator implements GraphGenerator { private final Graph graph; private final boolean generateSelfLoops; /** * Complement Graph Generator * * @param graph input graph */ public ComplementGraphGenerator(Graph graph) { this(graph, false); } /** * Complement Graph Generator. If the target graph allows self-loops the complement of $G$ may * be defined by adding a self-loop to every vertex that does not have one in $G$. This behavior * can be controlled using the boolean generateSelfLoops. * * @param graph input graph * @param generateSelfLoops indicator whether self loops should be generated. If false, no * self-loops are generated, independent of whether the target graph supports self-loops. */ public ComplementGraphGenerator(Graph graph, boolean generateSelfLoops) { this.graph = GraphTests.requireDirectedOrUndirected(graph); this.generateSelfLoops = generateSelfLoops; } @Override public void generateGraph(Graph target, Map resultMap) { Graphs.addAllVertices(target, graph.vertexSet()); if (graph.getType().isDirected()) { for (V u : graph.vertexSet()) for (V v : graph.vertexSet()) if (u == v) continue; else if (!graph.containsEdge(u, v)) target.addEdge(u, v); } else { // undirected graph List vertices = new ArrayList<>(graph.vertexSet()); for (int i = 0; i < vertices.size() - 1; i++) { for (int j = i + 1; j < vertices.size(); j++) { V u = vertices.get(i); V v = vertices.get(j); if (!graph.containsEdge(u, v)) target.addEdge(u, v); } } } if (generateSelfLoops && target.getType().isAllowingSelfLoops()) { for (V v : graph.vertexSet()) { if (!graph.containsEdge(v, v)) target.addEdge(v, v); } } } } CompleteBipartiteGraphGenerator.java000066400000000000000000000066211402514743400347150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2008-2021, by Andrew Newell and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * Generates a complete bipartite * graph of any size. This is a graph with two partitions; two vertices will contain an edge if * and only if they belong to different partitions. * * @param the graph vertex type * @param the graph edge type * * @author Andrew Newell */ public class CompleteBipartiteGraphGenerator implements GraphGenerator { private final int sizeA, sizeB; private final Set partitionA, partitionB; /** * Creates a new CompleteBipartiteGraphGenerator object. * * @param partitionA number of vertices in the first partition * @param partitionB number of vertices in the second partition */ public CompleteBipartiteGraphGenerator(int partitionA, int partitionB) { if (partitionA < 0 || partitionB < 0) { throw new IllegalArgumentException("partition sizes must be non-negative"); } this.sizeA = partitionA; this.sizeB = partitionB; this.partitionA = CollectionUtil.newLinkedHashSetWithExpectedSize(sizeA); this.partitionB = CollectionUtil.newLinkedHashSetWithExpectedSize(sizeB); } /** * Creates a new CompleteBipartiteGraphGenerator object. A complete bipartite graph is generated * on the vertices provided between the vertices provided in the two partitions. Note that * all vertices in both {@code partitionA} and {@code partitionB} must be present in the * graph or an exception will be thrown during the invocation of * {@link #generateGraph(Graph, Map)} * * @param partitionA first partition * @param partitionB second partition */ public CompleteBipartiteGraphGenerator(Set partitionA, Set partitionB) { if (partitionA.isEmpty() || partitionB.isEmpty()) { throw new IllegalArgumentException("partitions must be non-empty"); } this.sizeA = 0; this.sizeB = 0; this.partitionA = partitionA; this.partitionB = partitionB; } /** * Construct a complete bipartite graph */ @Override public void generateGraph(Graph target, Map resultMap) { // Create vertices in each of the partitions for (int i = 0; i < sizeA; i++) { partitionA.add(target.addVertex()); } for (int i = 0; i < sizeB; i++) { partitionB.add(target.addVertex()); } // Add an edge for each pair of vertices in different partitions for (V u : partitionA) { for (V v : partitionB) { target.addEdge(u, v); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/CompleteGraphGenerator.java000066400000000000000000000057331402514743400331330ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Tim Shearouse and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generates a complete graph of any size. * *

    * A complete graph is a graph where every vertex shares an edge with every other vertex. If it is a * directed graph, then edges must always exist in both directions. * * @param the graph vertex type * @param the graph edge type * * @author Tim Shearouse */ public class CompleteGraphGenerator implements GraphGenerator { private final int size; /** * Construct a new CompleteGraphGenerator. * * The generator will first add {@code size} nodes to the target graph when invoking * {@link #generateGraph(Graph, Map)}. Next, a complete graph is generated on all nodes * present in the target graph, including any nodes that were already present in the target * graph. * * @param size number of vertices that will be added to the graph * @throws IllegalArgumentException if the specified size is negative */ public CompleteGraphGenerator(int size) { if (size < 0) throw new IllegalArgumentException("size must be non-negative"); this.size = size; } /** * Construct a new CompleteGraphGenerator. * * A complete graph will be generated using the vertices already present in the target graph * when invoking {@link #generateGraph(Graph, Map)} */ public CompleteGraphGenerator() { size = 0; } /** * {@inheritDoc} */ @Override public void generateGraph(Graph target, Map resultMap) { /* * Ensure directed or undirected */ GraphTests.requireDirectedOrUndirected(target); boolean isDirected = target.getType().isDirected(); /* * Add vertices */ for (int i = 0; i < size; i++) target.addVertex(); /* * Add edges */ List nodes = new ArrayList<>(target.vertexSet()); for (int i = 0; i < nodes.size(); i++) { for (int j = i + 1; j < nodes.size(); j++) { V v = nodes.get(i); V u = nodes.get(j); target.addEdge(v, u); if (isDirected) { target.addEdge(u, v); } } } } } DirectedScaleFreeGraphGenerator.java000066400000000000000000000541021402514743400345730ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2019-2021, by Amr ALHOSSARY and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * A generator for directed scale-free graphs. *

    * This generator creates a directed scale-free graph according to a power law, as described in * Bollobás et al. The paper can be cited as * Béla Bollobás, Christian Borgs, Jennifer Chayes, and Oliver Riordan. "Directed scale-free * graphs." Proceedings of the fourteenth annual ACM-SIAM symposium on Discrete algorithms. Society * for Industrial and Applied Mathematics, 2003. *

    * In This generator, the graph continues to grow one edge per step, according to the probabilities * alpha, beta, and gamma (which sum up to 1).
    *

      *
    • alpha is the probability that the new edge is from a new vertex v to an existing * vertex w, where w is chosen according to d_in + delta_in. *
    • beta is the probability that the new edge is from an existing vertex v to an existing * vertex w, where v and w are chosen independently, v according to d_out + delta_out, and w * according to d_in + delta_in. *
    • gamma is the probability that the new edge is from an existing vertex v to a new * vertex w, where v is chosen according to d_out + delta_out. *
    * *

    * In their original paper, the graph continues to grow according to a certain power law until a * certain number of edges is reached irrespective to the number of nodes.
    * However, because the target number of edges is not known beforehand, in this implementation, we * added another feature that enables the user to grow the curve according to that power law until * the target number of edges or target number of nodes is reached. * * @author Amr ALHOSSARY * * @param the graph vertex type * @param the graph edge type */ public class DirectedScaleFreeGraphGenerator implements GraphGenerator { private final Random rng; /** * probability that the new edge is from a new vertex v to an existing vertex w, where w is * chosen according to d_in + delta_in criterion. */ private final float alpha; /** * The probability that the new edge is (from a new vertex v to an existing vertex w) plus the * probability that the new edge is (from an existing vertex v to an existing vertex w), where v * and w are chosen independently, v according to d_out + delta_out, and w according to d_in + * delta_in criteria. This equals 1 - gamma. Gamma refers to the probability that the new edge * is from an existing vertex v to a new vertex w. */ private final float alphaPlusBeta; /** In-degree bias used for Alpha and Beta */ private final float deltaIn; /** Out-degree bias used for Beta and Gamma */ private final float deltaOut; /** * Target total number of edges to reach. It has a higher priority than {@link #targetNodes}. * Zero is a valid value.
    * If negative number, the user does not care about the total number of edges and is interested * only in the number of nodes, therefore, {@link #targetNodes} will be considered instead. * Otherwise, {@link #targetEdges} will be considered and {@link #targetEdges} will be ignored. */ private final int targetEdges; /** * Target total number of targetNodes to reach.
    * This has lower priority than {@link #targetEdges}. It will not be used unless * {@link #targetEdges} given is a negative number. */ private final int targetNodes; /** * An enum to indicate the vertex selection using its inDegree or outDegree */ private enum Direction { IN, OUT } /** * Maximum number of consecutive failed attempts to add an edge. */ private int maxFailures = 1000; /** * Control whether the generated graph may contain loops. */ private boolean allowingMultipleEdges = true; /** * Control whether the generated graph many contain multiple (parallel) edges between the same * two vertices */ private boolean allowingSelfLoops = true; /** * Constructs a Generator. * * @param alpha The probability that the new edge is from a new vertex v to an existing vertex * w, where w is chosen according to d_in + delta_in. * @param gamma The probability that the new edge is from an existing vertex v to a new vertex * w, where v is chosen according to d_out + delta_out. * @param deltaIn The in-degree bias used for Alpha and Beta. * @param deltaOut The out-degree bias used for Beta and Gamma. * @param targetEdges Target total number of edges to reach. It has a higher priority than * {@link #targetNodes}. Zero is a valid value.
    * If negative number, the user does not care about the total number of edges and is * interested only in the number of nodes, therefore, {@link #targetNodes} will be * considered instead. Otherwise, {@link #targetEdges} will be considered and * {@link #targetEdges} will be ignored. * @param targetNodes Target number of nodes to reach. Zero is a valid value.
    * This parameter has lower priority than {@link #targetEdges} and will be used only if * {@link #targetEdges} given is a negative number. */ public DirectedScaleFreeGraphGenerator( float alpha, float gamma, float deltaIn, float deltaOut, int targetEdges, int targetNodes) { this(alpha, gamma, deltaIn, deltaOut, targetEdges, targetNodes, new Random()); } /** * Constructs a Generator using a seed for the random number generator. * * @param alpha The probability that the new edge is from a new vertex v to an existing vertex * w, where w is chosen according to d_in + delta_in. * @param gamma The probability that the new edge is from an existing vertex v to a new vertex * w, where v is chosen according to d_out + delta_out. * @param deltaIn The in-degree bias used for Alpha and Beta. * @param deltaOut The out-degree bias used for Beta and Gamma. * @param targetEdges Target total number of edges to reach. It has a higher priority than * {@link #targetNodes}. Zero is a valid value.
    * If negative number, the user does not care about the total number of edges and is * interested only in the number of nodes, therefore, {@link #targetNodes} will be * considered instead. Otherwise, {@link #targetEdges} will be considered and * {@link #targetEdges} will be ignored. * @param targetNodes Target number of nodes to reach. Zero is a valid value.
    * This parameter has lower priority than {@link #targetEdges} and will be used only if * {@link #targetEdges} given is a negative number. * @param seed The seed to feed to the random number generator. */ public DirectedScaleFreeGraphGenerator( float alpha, float gamma, float deltaIn, float deltaOut, int targetEdges, int targetNodes, long seed) { this(alpha, gamma, deltaIn, deltaOut, targetEdges, targetNodes, new Random(seed)); } /** * Constructs a Generator using a seed for the random number generator and sets the two * relaxation options allowingMultipleEdges and allowingSelfLoops. * * @param alpha The probability that the new edge is from a new vertex v to an existing vertex * w, where w is chosen according to d_in + delta_in. * @param gamma The probability that the new edge is from an existing vertex v to a new vertex * w, where v is chosen according to d_out + delta_out. * @param deltaIn The in-degree bias used for Alpha and Beta. * @param deltaOut The out-degree bias used for Beta and Gamma. * @param targetEdges Target total number of edges to reach. It has a higher priority than * {@link #targetNodes}. Zero is a valid value.
    * If negative number, the user does not care about the total number of edges and is * interested only in the number of nodes, therefore, {@link #targetNodes} will be * considered instead. Otherwise, {@link #targetEdges} will be considered and * {@link #targetEdges} will be ignored. * @param targetNodes Target number of nodes to reach. Zero is a valid value.
    * This parameter has lower priority than {@link #targetEdges} and will be used only if * {@link #targetEdges} given is a negative number. * @param seed The seed to feed to the random number generator. * @param allowingMultipleEdges whether the generator allows multiple parallel edges between the * same two vertices (v, w). * @param allowingSelfLoops whether the generator allows self loops from the a vertex to itself. */ public DirectedScaleFreeGraphGenerator( float alpha, float gamma, float deltaIn, float deltaOut, int targetEdges, int targetNodes, long seed, boolean allowingMultipleEdges, boolean allowingSelfLoops) { this(alpha, gamma, deltaIn, deltaOut, targetEdges, targetNodes, seed); this.allowingMultipleEdges = allowingMultipleEdges; this.allowingSelfLoops = allowingSelfLoops; } /** * Construct a new generator using the provided random number generator. * * @param alpha The probability that the new edge is from a new vertex v to an existing vertex * w, where w is chosen according to d_in + delta_in. * @param gamma The probability that the new edge is from an existing vertex v to a new vertex * w, where v is chosen according to d_out + delta_out. * @param deltaIn The in-degree bias used for Alpha and Beta. * @param deltaOut The out-degree bias used for Beta and Gamma. * @param targetEdges Target total number of edges to reach. It has a higher priority than * {@link #targetNodes}. Zero is a valid value.
    * If negative number, the user does not care about the total number of edges and is * interested only in the number of nodes, therefore, {@link #targetNodes} will be * considered instead. Otherwise, {@link #targetEdges} will be considered and * {@link #targetEdges} will be ignored. * @param targetNodes Target number of nodes to reach. Zero is a valid value.
    * This parameter has lower priority than {@link #targetEdges} and will be used only if * {@link #targetEdges} given is a negative number. * @param rng The {@link Random} object to use. */ public DirectedScaleFreeGraphGenerator( float alpha, float gamma, float deltaIn, float deltaOut, int targetEdges, int targetNodes, Random rng) { this.alpha = alpha; this.alphaPlusBeta = 1.0f - gamma; this.deltaIn = deltaIn; this.deltaOut = deltaOut; this.targetEdges = targetEdges; this.targetNodes = targetNodes; this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); // Do several checks on the parameters if (alpha < 0 || gamma < 0 || alpha + gamma > 1) { throw new IllegalArgumentException( String.format("alpha and gamma values of (%f, %f) are invalid", alpha, gamma)); } if (deltaIn < 0 || deltaOut < 0) { throw new IllegalArgumentException( String .format( "deltaIn and deltaOut values of (%f, %f) are invalid", deltaIn, deltaOut)); } if (targetEdges < 0 && targetNodes < 0) { throw new IllegalArgumentException( "can not have both targetEdges and targetNodes not set."); } } /** * Construct a new generator using the provided random number generator and sets the two * relaxation options allowingMultipleEdges and allowingSelfLoops. * * @param alpha The probability that the new edge is from a new vertex v to an existing vertex * w, where w is chosen according to d_in + delta_in. * @param gamma The probability that the new edge is from an existing vertex v to a new vertex * w, where v is chosen according to d_out + delta_out. * @param deltaIn The in-degree bias used for Alpha and Beta. * @param deltaOut The out-degree bias used for Beta and Gamma. * @param targetEdges Target total number of edges to reach. It has a higher priority than * {@link #targetNodes}. Zero is a valid value.
    * If negative number, the user does not care about the total number of edges and is * interested only in the number of nodes, therefore, {@link #targetNodes} will be * considered instead. Otherwise, {@link #targetEdges} will be considered and * {@link #targetEdges} will be ignored. * @param targetNodes Target number of nodes to reach. Zero is a valid value.
    * This parameter has lower priority than {@link #targetEdges} and will be used only if * {@link #targetEdges} given is a negative number. * @param rng The {@link Random} object to use. * @param allowingMultipleEdges whether the generator allows multiple parallel edges between the * same two vertices (v, w). * @param allowingSelfLoops whether the generator allows self loops from the a vertex to itself. */ public DirectedScaleFreeGraphGenerator( float alpha, float gamma, float deltaIn, float deltaOut, int targetEdges, int targetNodes, Random rng, boolean allowingMultipleEdges, boolean allowingSelfLoops) { this(alpha, gamma, deltaIn, deltaOut, targetEdges, targetNodes, rng); this.allowingMultipleEdges = allowingMultipleEdges; this.allowingSelfLoops = allowingSelfLoops; } /** * Generates an instance of the {@link Graph}. * * @param target the target graph * @param resultMap not used by this generator, can be null * * @throws TooManyFailuresException When the method fails {@link #maxFailures} times to add a * new edge to the growing graph. * @throws IllegalArgumentException When the graph does not support Multiple edges or self loop * while the generator does. */ @Override public void generateGraph(Graph target, Map resultMap) { if (this.allowingMultipleEdges && !target.getType().isAllowingMultipleEdges()) { throw new IllegalArgumentException( "Generator allows Multiple Edges while graph does not. Consider changing this generator parameters or the target graph type."); } if (this.allowingSelfLoops && !target.getType().isAllowingSelfLoops()) { throw new IllegalArgumentException( "Generator allows Self loops while graph does not. Consider changing this generator parameters or the target graph type."); } Set newNodesSet = new HashSet<>(); Set newEdgesSet = new HashSet<>(); if (targetEdges == 0 || (targetEdges < 0 && targetNodes == 0)) return; V initV = target.addVertex(); newNodesSet.add(initV); int failuresCounter = 0; // grow network now, edge by edge. If the number of edges is unlocked, continue growing not // only until targetNodes is reached, but until adding any more edges would add another // node. i.e. allow more beta growth but not alpha nor gamma. while (targetEdges >= 0 ? targetEdges > newEdgesSet.size() : targetNodes >= newNodesSet.size()) { if (failuresCounter >= maxFailures) { throw new TooManyFailuresException( failuresCounter + " consecutive failures is more than maximum allowed number (" + maxFailures + ")."); } V v = null, w = null; boolean newV = false, newW = false; E e; float tributaries = rng.nextFloat(); if (tributaries <= alpha) { // stop adding nodes if you will exceed the target if (targetEdges < 0 && newNodesSet.size() == targetNodes) break; newV = true; w = pickAVertex(target, newNodesSet, newEdgesSet, Direction.IN, deltaIn); } else if (tributaries <= alphaPlusBeta) { v = pickAVertex(target, newNodesSet, newEdgesSet, Direction.OUT, deltaOut); w = pickAVertex(target, newNodesSet, newEdgesSet, Direction.IN, deltaIn); } else {// gamma // stop adding nodes if you will exceed the target if (targetEdges < 0 && newNodesSet.size() == targetNodes) break; v = pickAVertex(target, newNodesSet, newEdgesSet, Direction.OUT, deltaOut); newW = true; } if ((newV && w == null) || (newW && v == null)) { failuresCounter++; continue; } // check for self loops if (!allowingSelfLoops && v == w) { failuresCounter++; continue; } // check for multiple parallel targetEdges if (!allowingMultipleEdges && target.containsEdge(v, w)) { failuresCounter++; continue; } if (newV) { v = target.addVertex(); } if (newW) { w = target.addVertex(); } e = target.addEdge(v, w); failuresCounter = 0; newNodesSet.add(v); newNodesSet.add(w); newEdgesSet.add(e); } } /** * Select a vertex from the currently available vertices, using the passed bias. * * @param target The target graph * @param allNewNodes All (new) nodes in the target graph * @param allNewEdgesSet All (new) edges in the target graph * @param direction {@link Direction#IN} for inDegree or {@link Direction#IN} for outDegree * @param bias deltaIn or deltaOut value according to #directioIn * @return the selected node. */ private V pickAVertex( Graph target, Set allNewNodes, Set allNewEdgesSet, Direction direction, float bias) { final int allNewNodesSize = allNewNodes.size(); if (allNewNodesSize == 0) { return null; } else if (allNewNodesSize == 1) { return allNewNodes.iterator().next(); } float indicatorAccumulator = 0; V ret; float denominator = allNewEdgesSet.size() + allNewNodesSize * bias; float numerator; float r = rng.nextFloat(); // multiply r by denominator instead of dividing all individual values by it. r *= denominator; Iterator verticesIterator = allNewNodes.iterator(); do { ret = verticesIterator.next(); numerator = (direction == Direction.IN) ? (target.inDegreeOf(ret) + bias) : (target.outDegreeOf(ret) + bias); indicatorAccumulator += numerator; } while (verticesIterator.hasNext() && indicatorAccumulator < r); return ret; } /** * Returns the maximum allowed number of consecutive failed attempts to add an edge. * * @return maxFailure field. */ public int getMaxFailures() { return maxFailures; } /** * Sets the maximum allowed number of consecutive failed attempts to add an edge (must be non * negative). * * @param maxFailures Maximum allowed (non negative) number of consecutive failed attempts to * add an edge. */ public void setMaxFailures(int maxFailures) { if (maxFailures < 0) { throw new IllegalArgumentException("value must be non negative"); } this.maxFailures = maxFailures; } /** * Returns whether the generated graph may contain multiple (parallel) edges between the same * two vertices. * * @return whether the generated graph may contain multiple (parallel) edges between the same * two vertices */ public boolean isAllowingMultipleEdges() { return allowingMultipleEdges; } /** * Sets whether the generated graph may contain multiple (parallel) edges between the same two * vertices * * @param allowingMultipleEdges whether the generated graph may contain multiple (parallel) * edges between the same two vertices */ public void setAllowingMultipleEdges(boolean allowingMultipleEdges) { this.allowingMultipleEdges = allowingMultipleEdges; } /** * Returns whether the generated graph may contain multiple (parallel) edges between the same * two vertices * * @return whether the generated graph many contain multiple (parallel) edges between the same * two vertices */ public boolean isAllowingSelfLoops() { return allowingSelfLoops; } /** * Sets whether the generated graph may contain multiple (parallel) edges between the same two * vertices * * @param allowingSelfLoops whether the generated graph many contain multiple (parallel) edges * between the same two vertices */ public void setAllowingSelfLoops(boolean allowingSelfLoops) { this.allowingSelfLoops = allowingSelfLoops; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/EmptyGraphGenerator.java000066400000000000000000000032371402514743400324560ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generates an empty graph of any size. * An empty graph is a graph that has no edges. * * @param the graph vertex type * @param the graph edge type * * @author John V. Sichi */ public class EmptyGraphGenerator implements GraphGenerator { private final int size; /** * Construct a new EmptyGraphGenerator. * * @param size number of vertices to be generated * @throws IllegalArgumentException if the specified size is negative. */ public EmptyGraphGenerator(int size) { if (size < 0) { throw new IllegalArgumentException("size must be non-negative"); } this.size = size; } /** * {@inheritDoc} */ @Override public void generateGraph(Graph target, Map resultMap) { for (int i = 0; i < size; ++i) { target.addVertex(); } } } GeneralizedPetersenGraphGenerator.java000066400000000000000000000071061402514743400352370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generator for Generalized * Petersen Graphs The Generalized Petersen graphs $GP(n,k)$ are a family of cubic graphs formed * by connecting the vertices of a regular polygon (cycle graph $C_n$) to the corresponding vertices * of a star polygon ${n,k}$. Several special cases of the generalized Petersen graph are predefined * in the {@link NamedGraphGenerator}. * * @author Joris Kinable * * @param graph vertex type * @param graph edge type */ public class GeneralizedPetersenGraphGenerator implements GraphGenerator> { private final int n; private final int k; /** * Key used to access the star polygon vertices in the resultMap */ public final String STAR = "star"; /** * Key used to access the regular polygon vertices in the resultMap */ public final String REGULAR = "regular"; /** * Constructs a GeneralizedPetersenGraphGenerator used to generate a Generalized Petersen graphs * $GP(n,k)$. * * @param n size of the regular polygon (cycle graph $C_n$) * @param k size of the star polygon ${n,k}$ */ public GeneralizedPetersenGraphGenerator(int n, int k) { if (n < 3) throw new IllegalArgumentException("n must be larger or equal than 3"); if (k < 1 || k > Math.floor((n - 1) / 2.0)) throw new IllegalArgumentException("k must be in the range [1, floor((n-1)/2.0)]"); this.n = n; this.k = k; } /** * Generates the Generalized Petersen Graph * * @param target receives the generated edges and vertices; if this is non-empty on entry, the * result will be a disconnected graph since generated elements will not be connected to * existing elements * @param resultMap if non-null, the resultMap contains a mapping from the key "star" to a list * of vertices constituting the star polygon, as well as a key "regular" which maps to a * list of vertices constituting the regular polygon. */ @Override public void generateGraph(Graph target, Map> resultMap) { List verticesU = new ArrayList<>(n); // Regular polygon vertices List verticesV = new ArrayList<>(n); // Star polygon vertices for (int i = 0; i < n; i++) { verticesU.add(target.addVertex()); verticesV.add(target.addVertex()); } for (int i = 0; i < n; i++) { target.addEdge(verticesU.get(i), verticesU.get((i + 1) % n)); target.addEdge(verticesU.get(i), verticesV.get(i)); target.addEdge(verticesV.get(i), verticesV.get((i + k) % n)); } if (resultMap != null) { resultMap.put(REGULAR, verticesU); resultMap.put(STAR, verticesV); } } } GnmRandomBipartiteGraphGenerator.java000066400000000000000000000171641402514743400350330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2004-2021, by Michael Behrisch, Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Create a random bipartite graph based on the $G(n, M)$ Erdős–Rényi model. See the Wikipedia * article for details and references about * Random Graphs and the * Erdős–Rényi model * . * * The user provides the sizes $n_1$ and $n_2$ of the two partitions $(n_1+n_2=n)$ and a number $m$ * which is the total number of edges to create. The generator supports both directed and undirected * graphs. * * @author Michael Behrisch * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type * * @see GnpRandomBipartiteGraphGenerator */ public class GnmRandomBipartiteGraphGenerator implements GraphGenerator { private final Random rng; private final int n1; private final int n2; private final int m; private List partitionA; private List partitionB; /** * Create a new random bipartite graph generator. The generator uses the $G(n, m)$ model when $n * = n1 + n2$ and the bipartite graph has one partition with size $n_1$ and one partition with * size $n_2$. In this model a graph is chosen uniformly at random from the collection of * bipartite graphs whose partitions have sizes $n_1$ and $n_2$ respectively and $m$ edges. * * @param n1 number of vertices of the first partition * @param n2 number of vertices of the second partition * @param m the number of edges */ public GnmRandomBipartiteGraphGenerator(int n1, int n2, int m) { this(n1, n2, m, new Random()); } /** * Create a new random bipartite graph generator. The generator uses the $G(n, m)$ model when $n * = n1 + n2$ and the bipartite graph has one partition with size $n_1$ and one partition with * size $n_2$. In this model a graph is chosen uniformly at random from the collection of * bipartite graphs whose partitions have sizes $n_1$ and $n_2$ respectively and m edges. * * @param n1 number of vertices of the first partition * @param n2 number of vertices of the second partition * @param m the number of edges * @param seed seed for the random number generator */ public GnmRandomBipartiteGraphGenerator(int n1, int n2, int m, long seed) { this(n1, n2, m, new Random(seed)); } /** * Create a new random bipartite graph generator. The generator uses the $G(n, m)$ model when $n * = n_1 + n_2$ and the bipartite graph has one partition with size $n_1$ and one partition with * size $n_2$. In this model a graph is chosen uniformly at random from the collection of * bipartite graphs whose partitions have sizes $n_1$ and $n_2$ respectively and $m$ edges. * * @param n1 number of vertices of the first partition * @param n2 number of vertices of the second partition * @param m the number of edges * @param rng random number generator */ public GnmRandomBipartiteGraphGenerator(int n1, int n2, int m, Random rng) { if (n1 < 0) { throw new IllegalArgumentException("number of vertices must be non-negative"); } this.n1 = n1; if (n2 < 0) { throw new IllegalArgumentException("number of vertices must be non-negative"); } this.n2 = n2; if (m < 0) { throw new IllegalArgumentException("number of edges must be non-negative"); } this.m = m; this.rng = Objects.requireNonNull(rng); } /** * Generates a random bipartite graph. * * @param target the target graph * @param resultMap not used by this generator, can be null */ @Override public void generateGraph(Graph target, Map resultMap) { if (n1 + n2 == 0) { return; } // create vertices int previousVertexSetSize = target.vertexSet().size(); partitionA = new ArrayList<>(n1); for (int i = 0; i < n1; i++) { partitionA.add(target.addVertex()); } partitionB = new ArrayList<>(n2); for (int i = 0; i < n2; i++) { partitionB.add(target.addVertex()); } if (target.vertexSet().size() != previousVertexSetSize + n1 + n2) { throw new IllegalArgumentException( "Vertex factory did not produce " + (n1 + n2) + " distinct vertices."); } // check if graph is directed final boolean isDirected = target.getType().isDirected(); int maxAllowedEdges; try { if (isDirected) { maxAllowedEdges = Math.multiplyExact(2, Math.multiplyExact(n1, n2)); } else { // assume undirected maxAllowedEdges = Math.multiplyExact(n1, n2); } } catch (ArithmeticException e) { maxAllowedEdges = Integer.MAX_VALUE; } if (m > maxAllowedEdges) { throw new IllegalArgumentException( "number of edges not valid for bipartite graph with " + n1 + " and " + n2 + " vertices"); } // create edges int edgesCounter = 0; while (edgesCounter < m) { // find random edge V s = partitionA.get(rng.nextInt(n1)); V t = partitionB.get(rng.nextInt(n2)); // if directed, maybe reverse direction if (isDirected && rng.nextBoolean()) { V tmp = s; s = t; t = tmp; } // check whether to add the edge if (!target.containsEdge(s, t)) { try { E resultEdge = target.addEdge(s, t); if (resultEdge != null) { edgesCounter++; } } catch (IllegalArgumentException e) { // do nothing, just ignore the edge } } } } /** * Returns the first partition of vertices in the bipartite graph. This partition is guaranteed * to be smaller than or equal in size to the second partition. * * @return one partition of the bipartite graph */ public Set getFirstPartition() { if (partitionA.size() <= partitionB.size()) return new LinkedHashSet<>(partitionA); else return new LinkedHashSet<>(partitionB); } /** * Returns the second partitions of vertices in the bipartite graph. This partition is * guaranteed to be larger than or equal in size to the first partition. * * @return one partition of the bipartite graph */ public Set getSecondPartition() { if (partitionB.size() >= partitionA.size()) return new LinkedHashSet<>(partitionB); else return new LinkedHashSet<>(partitionA); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/GnmRandomGraphGenerator.java000066400000000000000000000241531402514743400332420ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by Assaf Lehr, Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Create a random graph based on the $G(n, M)$ Erdős–Rényi model. See the Wikipedia article for * details and references about Random * Graphs and the * Erdős–Rényi model * . * *

    * In the $G(n, M)$ model, a graph is chosen uniformly at random from the collection of all graphs * which have $n$ nodes and $M$ edges. For example, in the $G(3, 2)$ model, each of the three * possible graphs on three vertices and two edges are included with probability $\frac{1}{3}$. * *

    * The implementation creates the vertices and then randomly chooses an edge and tries to add it. If * the add fails for any reason (an edge already exists and multiple (parallel) edges are not * allowed) it will just choose another and try again. The performance therefore varies * significantly based on the probability of successfully constructing an acceptable edge. * *

    * The implementation tries to guess the number of allowed edges based on the following. If * self-loops or multiple edges are allowed and requested, the maximum number of edges is * {@link Integer#MAX_VALUE}. Otherwise the maximum for undirected graphs with n vertices is * $\frac{n(n-1)}{2}$ while for directed $n(n-1)$. * *

    * For the $G(n, p)$ model please see {@link GnpRandomGraphGenerator}. * * @author Assaf Lehr * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type * * @see GnpRandomGraphGenerator */ public class GnmRandomGraphGenerator implements GraphGenerator { private static final boolean DEFAULT_ALLOW_LOOPS = false; private static final boolean DEFAULT_ALLOW_MULTIPLE_EDGES = false; private final Random rng; private final int n; private final int m; private final boolean loops; private final boolean multipleEdges; /** * Create a new $G(n, M)$ random graph generator. The generator does not create self-loops or * multiple (parallel) edges between the same two vertices. * * @param n the number of nodes * @param m the number of edges */ public GnmRandomGraphGenerator(int n, int m) { this(n, m, new Random(), DEFAULT_ALLOW_LOOPS, DEFAULT_ALLOW_MULTIPLE_EDGES); } /** * Create a new $G(n, M)$ random graph generator. The generator does not create self-loops or * multiple (parallel) edges between the same two vertices. * * @param n the number of nodes * @param m the number of edges * @param seed seed for the random number generator */ public GnmRandomGraphGenerator(int n, int m, long seed) { this(n, m, new Random(seed), DEFAULT_ALLOW_LOOPS, DEFAULT_ALLOW_MULTIPLE_EDGES); } /** * Create a new $G(n, M)$ random graph generator * * @param n the number of nodes * @param m the number of edges * @param seed seed for the random number generator * @param loops whether the generated graph may contain loops * @param multipleEdges whether the generated graph many contain multiple (parallel) edges * between the same two vertices */ public GnmRandomGraphGenerator(int n, int m, long seed, boolean loops, boolean multipleEdges) { this(n, m, new Random(seed), loops, multipleEdges); } /** * Create a new $G(n, M)$ random graph generator * * @param n the number of nodes * @param m the number of edges * @param rng the random number generator * @param loops whether the generated graph may contain loops * @param multipleEdges whether the generated graph many contain multiple (parallel) edges * between the same two vertices */ public GnmRandomGraphGenerator(int n, int m, Random rng, boolean loops, boolean multipleEdges) { if (n < 0) { throw new IllegalArgumentException("number of vertices must be non-negative"); } this.n = n; if (m < 0) { throw new IllegalArgumentException("number of edges must be non-negative"); } this.m = m; this.rng = Objects.requireNonNull(rng); this.loops = loops; this.multipleEdges = multipleEdges; } /** * Generates a random graph based on the $G(n, M)$ model * * @param target the target graph * @param resultMap not used by this generator, can be null * * @throws IllegalArgumentException if the number of edges, passed in the constructor, cannot be * created on a graph of the concrete type with the specified number of vertices * @throws IllegalArgumentException if the graph does not support a requested feature such as * self-loops or multiple (parallel) edges */ @Override public void generateGraph(Graph target, Map resultMap) { // special case if (n == 0) { return; } // check whether to create loops if (loops && !target.getType().isAllowingSelfLoops()) { throw new IllegalArgumentException("Provided graph does not support self-loops"); } // check whether to create multiple edges if (multipleEdges && !target.getType().isAllowingMultipleEdges()) { throw new IllegalArgumentException( "Provided graph does not support multiple edges between the same vertices"); } // compute maximum allowed edges if (m > computeMaximumAllowedEdges( n, target.getType().isDirected(), loops, multipleEdges)) { throw new IllegalArgumentException( "number of edges is not valid for the graph type " + "\n-> invalid number of edges=" + m + " for:" + " graph type=" + target.getType() + ", number of vertices=" + n); } // create vertices List vertices = new ArrayList<>(n); int previousVertexSetSize = target.vertexSet().size(); for (int i = 0; i < n; i++) { vertices.add(target.addVertex()); } if (target.vertexSet().size() != previousVertexSetSize + n) { throw new IllegalArgumentException( "Vertex factory did not produce " + n + " distinct vertices."); } // create edges int edgesCounter = 0; while (edgesCounter < m) { int sIndex = rng.nextInt(n); int tIndex = rng.nextInt(n); // lazy to avoid lookups V s = null; V t = null; // check whether to add the edge boolean addEdge = false; if (sIndex == tIndex) { // self-loop if (loops) { addEdge = true; } } else { if (multipleEdges) { addEdge = true; } else { s = vertices.get(sIndex); t = vertices.get(tIndex); if (!target.containsEdge(s, t)) { addEdge = true; } } } // if yes, add it if (addEdge) { try { if (s == null) { s = vertices.get(sIndex); t = vertices.get(tIndex); } E resultEdge = target.addEdge(s, t); if (resultEdge != null) { edgesCounter++; } } catch (IllegalArgumentException e) { // do nothing, just ignore the edge } } } } /** * Return the number of allowed edges based on the graph type. * * @param n number of nodes * @param isDirected whether the graph is directed or not * @param createLoops if loops are allowed * @param createMultipleEdges if multiple (parallel) edges are allowed * @return the number of maximum edges */ static int computeMaximumAllowedEdges( int n, boolean isDirected, boolean createLoops, boolean createMultipleEdges) { if (n == 0) { return 0; } int maxAllowedEdges; try { if (isDirected) { maxAllowedEdges = Math.multiplyExact(n, n - 1); } else { // assume undirected if (n % 2 == 0) { maxAllowedEdges = Math.multiplyExact(n / 2, n - 1); } else { maxAllowedEdges = Math.multiplyExact(n, (n - 1) / 2); } } if (createLoops) { if (createMultipleEdges) { return Integer.MAX_VALUE; } else { if (isDirected) { maxAllowedEdges = Math.addExact(maxAllowedEdges, Math.multiplyExact(2, n)); } else { // assume undirected maxAllowedEdges = Math.addExact(maxAllowedEdges, n); } } } else { if (createMultipleEdges) { if (n > 1) { return Integer.MAX_VALUE; } } } } catch (ArithmeticException e) { return Integer.MAX_VALUE; } return maxAllowedEdges; } } GnpRandomBipartiteGraphGenerator.java000066400000000000000000000150161402514743400350300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Create a random bipartite graph based on the $G(n, p)$ Erdős–Rényi model. See the Wikipedia * article for details and references about * Random Graphs and the * Erdős–Rényi model * . * * The user provides the sizes $n_1$ and $n_2$ of the two partitions $(n1+n2=n)$ and the probability * $p$ of the existence of an edge. The generator supports both directed and undirected graphs. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type * * @see GnmRandomBipartiteGraphGenerator */ public class GnpRandomBipartiteGraphGenerator implements GraphGenerator { private final Random rng; private final int n1; private final int n2; private final double p; private List partitionA; private List partitionB; /** * Create a new random bipartite graph generator. The generator uses the $G(n, p)$ model when $n * = n_1 + n_2$ and the bipartite graph has one partition with size $n_1$ and one partition with * size $n_2$. An edge between two vertices of different partitions is included with probability * $p$ independent of all other edges. * * @param n1 number of vertices of the first partition * @param n2 number of vertices of the second partition * @param p edge probability */ public GnpRandomBipartiteGraphGenerator(int n1, int n2, double p) { this(n1, n2, p, new Random()); } /** * Create a new random bipartite graph generator. The generator uses the $G(n, p)$ model when $n * = n_1 + n_2$, the bipartite graph has partition with size $n_1$ and a partition with size * $n_2$. An edge between two vertices of different partitions is included with probability $p$ * independent of all other edges. * * @param n1 number of vertices of the first partition * @param n2 number of vertices of the second partition * @param p edge probability * @param seed seed for the random number generator */ public GnpRandomBipartiteGraphGenerator(int n1, int n2, double p, long seed) { this(n1, n2, p, new Random(seed)); } /** * Create a new random bipartite graph generator. The generator uses the $G(n, p)$ model when $n * = n_1 + n_2$, the bipartite graph has partition with size $n_1$ and a partition with size * $n_2$. An edge between two vertices of different partitions is included with probability $p$ * independent of all other edges. * * @param n1 number of vertices of the first partition * @param n2 number of vertices of the second partition * @param p edge probability * @param rng random number generator */ public GnpRandomBipartiteGraphGenerator(int n1, int n2, double p, Random rng) { if (n1 < 0) { throw new IllegalArgumentException("number of vertices must be non-negative"); } this.n1 = n1; if (n2 < 0) { throw new IllegalArgumentException("number of vertices must be non-negative"); } this.n2 = n2; if (p < 0.0 || p > 1.0) { throw new IllegalArgumentException("not valid probability of edge existence"); } this.p = p; this.rng = Objects.requireNonNull(rng); } /** * Generates a random bipartite graph. * * @param target the target graph * @param resultMap not used by this generator, can be null */ @Override public void generateGraph(Graph target, Map resultMap) { if (n1 + n2 == 0) { return; } // create vertices int previousVertexSetSize = target.vertexSet().size(); partitionA = new ArrayList<>(n1); for (int i = 0; i < n1; i++) { partitionA.add(target.addVertex()); } partitionB = new ArrayList<>(n2); for (int i = 0; i < n2; i++) { partitionB.add(target.addVertex()); } if (target.vertexSet().size() != previousVertexSetSize + n1 + n2) { throw new IllegalArgumentException( "Vertex factory did not produce " + (n1 + n2) + " distinct vertices."); } // check if graph is directed boolean isDirected = target.getType().isDirected(); // create edges for (int i = 0; i < n1; i++) { V s = partitionA.get(i); for (int j = 0; j < n2; j++) { V t = partitionB.get(j); // s->t if (rng.nextDouble() < p) { target.addEdge(s, t); } if (isDirected) { // t->s if (rng.nextDouble() < p) { target.addEdge(t, s); } } } } } /** * Returns the first partition of vertices in the bipartite graph. This partition is guaranteed * to be smaller than or equal in size to the second partition. * * @return one partition of the bipartite graph */ public Set getFirstPartition() { if (partitionA.size() <= partitionB.size()) return new LinkedHashSet<>(partitionA); else return new LinkedHashSet<>(partitionB); } /** * Returns the second partitions of vertices in the bipartite graph. This partition is * guaranteed to be larger than or equal in size to the first partition. * * @return one partition of the bipartite graph */ public Set getSecondPartition() { if (partitionB.size() >= partitionA.size()) return new LinkedHashSet<>(partitionB); else return new LinkedHashSet<>(partitionA); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/GnpRandomGraphGenerator.java000066400000000000000000000132211402514743400332370ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Create a random graph based on the $G(n, p)$ Erdős–Rényi model. See the Wikipedia article for * details and references about Random * Graphs and the * Erdős–Rényi model * . * *

    * In the $G(n, p)$ model, a graph is constructed by connecting nodes randomly. Each edge is * included in the graph with probability $p$ independent from every other edge. The complexity of * the generator is $O(n^2)$ where $n$ is the number of vertices. * *

    * For the $G(n, M)$ model please see {@link GnmRandomGraphGenerator}. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type * * @see GnmRandomGraphGenerator */ public class GnpRandomGraphGenerator implements GraphGenerator { private static final boolean DEFAULT_ALLOW_LOOPS = false; private final Random rng; private final int n; private final double p; private final boolean createLoops; /** * Create a new $G(n, p)$ random graph generator. The generator does not create self-loops. * * @param n the number of nodes * @param p the edge probability */ public GnpRandomGraphGenerator(int n, double p) { this(n, p, new Random(), DEFAULT_ALLOW_LOOPS); } /** * Create a new $G(n, p)$ random graph generator. The generator does not create self-loops. * * @param n the number of nodes * @param p the edge probability * @param seed seed for the random number generator */ public GnpRandomGraphGenerator(int n, double p, long seed) { this(n, p, new Random(seed), DEFAULT_ALLOW_LOOPS); } /** * Create a new $G(n, p)$ random graph generator. * * @param n the number of nodes * @param p the edge probability * @param seed seed for the random number generator * @param createLoops whether the generated graph may create loops */ public GnpRandomGraphGenerator(int n, double p, long seed, boolean createLoops) { this(n, p, new Random(seed), createLoops); } /** * Create a new $G(n, p)$ random graph generator. * * @param n the number of nodes * @param p the edge probability * @param rng the random number generator to use * @param createLoops whether the generated graph may create loops */ public GnpRandomGraphGenerator(int n, double p, Random rng, boolean createLoops) { if (n < 0) { throw new IllegalArgumentException("number of vertices must be non-negative"); } this.n = n; if (p < 0.0 || p > 1.0) { throw new IllegalArgumentException("not valid probability of edge existence"); } this.p = p; this.rng = Objects.requireNonNull(rng); this.createLoops = createLoops; } /** * Generates a random graph based on the $G(n, p)$ model. * * @param target the target graph * @param resultMap not used by this generator, can be null */ @Override public void generateGraph(Graph target, Map resultMap) { // special case if (n == 0) { return; } // check whether to also create loops if (createLoops && !target.getType().isAllowingSelfLoops()) { throw new IllegalArgumentException("Provided graph does not support self-loops"); } // create vertices int previousVertexSetSize = target.vertexSet().size(); List vertices = new ArrayList<>(n); for (int i = 0; i < n; i++) { vertices.add(target.addVertex()); } if (target.vertexSet().size() != previousVertexSetSize + n) { throw new IllegalArgumentException( "Vertex factory did not produce " + n + " distinct vertices."); } // check if graph is directed boolean isDirected = target.getType().isDirected(); // create edges for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { if (i == j) { if (!createLoops) { // no self-loops continue; } } V s = null; V t = null; // s->t if (rng.nextDouble() < p) { s = vertices.get(i); t = vertices.get(j); target.addEdge(s, t); } if (isDirected) { // t->s if (rng.nextDouble() < p) { if (s == null) { s = vertices.get(i); t = vertices.get(j); } target.addEdge(t, s); } } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/GraphGenerator.java000066400000000000000000000056341402514743400314420ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * An interface for generating new graph structures. * * @param the graph vertex type * @param the graph edge type * @param type for returning implementation-specific mappings from String roles to graph * elements * * @author John V. Sichi */ public interface GraphGenerator { /** * Generate a graph structure. The topology of the generated graph is dependent on the * implementation. For graphs in which not all vertices share the same automorphism equivalence * class, the generator may produce a labeling indicating the roles played by generated * elements. This is the purpose of the resultMap parameter. For example, a generator for a * wheel graph would designate a hub vertex. Role names used as keys in resultMap should be * declared as public static final Strings by implementation classes. * * @param target receives the generated edges and vertices; if this is non-empty on entry, the * result will be a disconnected graph since generated elements will not be connected to * existing elements * @param resultMap if non-null, receives implementation-specific mappings from String roles to * graph elements (or collections of graph elements) * * @throws UnsupportedOperationException if the graph does not have appropriate vertex and edge * suppliers, in order to be able to create new vertices and edges. Methods * {@link Graph#getEdgeSupplier()} and {@link Graph#getVertexSupplier()} must not return * null. */ void generateGraph(Graph target, Map resultMap); /** * Generate a graph structure. * * @param target receives the generated edges and vertices; if this is non-empty on entry, the * result will be a disconnected graph since generated elements will not be connected to * existing elements * @throws UnsupportedOperationException if the graph does not have appropriate vertex and edge * suppliers, in order to be able to create new vertices and edges */ default void generateGraph(Graph target) { generateGraph(target, null); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/GridGraphGenerator.java000066400000000000000000000070501402514743400322420ustar00rootroot00000000000000/* * (C) Copyright 2011-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generates a bidirectional grid graph of * any size. A grid graph is a two dimensional graph whose vertices correspond to the points in the * plane with integer coordinates, x-coordinates being in the range 0,..., n, y-coordinates being in * the range 1,...m, and two vertices are connected by an edge whenever the corresponding points are * at distance 1. Vertices are created from left to right and from top to bottom. * * @param the graph vertex type * @param the graph edge type * * @author Assaf Mizrachi */ public class GridGraphGenerator implements GraphGenerator { /** * Role for the vertices at the corners. */ public static final String CORNER_VERTEX = "Corner Vertex"; private final int rows; private final int cols; /** * Creates a new GridGraphGenerator object with rows x cols dimension. * * @param rows the number of rows * @param cols the number of columns */ public GridGraphGenerator(int rows, int cols) { if (rows < 2) { throw new IllegalArgumentException( "illegal number of rows (" + rows + "). there must be at least two."); } if (cols < 2) { throw new IllegalArgumentException( "illegal number of columns (" + cols + "). there must be at least two."); } this.rows = rows; this.cols = cols; } /** * {@inheritDoc} */ @Override public void generateGraph(Graph target, Map resultMap) { List vertices = new ArrayList<>(rows * cols); // Adding all vertices to the set int cornerCtr = 0; for (int i = 0; i < rows * cols; i++) { V vertex = target.addVertex(); vertices.add(vertex); boolean isCorner = (i == 0) || (i == (cols - 1)) || (i == (cols * (rows - 1))) || (i == ((rows * cols) - 1)); if (isCorner && (resultMap != null)) { resultMap.put(CORNER_VERTEX + ' ' + ++cornerCtr, vertex); } } // Iterating twice over the key set, for undirected graph edges are // added from upper vertices to lower, and from left to right. The // second addEdge call will return nothing; it will not add a the edge // at the opposite direction. For directed graph, edges in opposite // direction are also added. for (int i = 1; i <= vertices.size(); i++) { for (int j = 1; j <= vertices.size(); j++) { if ((((i % cols) > 0) && ((i + 1) == j)) || ((i + cols) == j)) { target.addEdge(vertices.get(i - 1), vertices.get(j - 1)); target.addEdge(vertices.get(j - 1), vertices.get(i - 1)); } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/HyperCubeGraphGenerator.java000066400000000000000000000051501402514743400332420ustar00rootroot00000000000000/* * (C) Copyright 2008-2021, by Andrew Newell and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generates a hyper cube graph of * any size. This is a graph that can be represented by bit strings, so for an n-dimensional * hypercube each vertex resembles an n-length bit string. Then, two vertices are adjacent if and * only if their bitstring differ by exactly one element. * * @param the graph vertex type * @param the graph edge type * * @author Andrew Newell */ public class HyperCubeGraphGenerator implements GraphGenerator { private int dim; /** * Creates a new generator * * @param dim the dimension of the hypercube */ public HyperCubeGraphGenerator(int dim) { this.dim = dim; } @Override public void generateGraph(Graph target, Map resultMap) { // Vertices are created, and they are included in the resultmap as their // bitstring representation int order = (int) Math.pow(2, dim); LinkedList vertices = new LinkedList<>(); for (int i = 0; i < order; i++) { V newVertex = target.addVertex(); vertices.add(newVertex); if (resultMap != null) { StringBuilder s = new StringBuilder(Integer.toBinaryString(i)); while (s.length() < dim) { s.insert(0, "0"); } resultMap.put(s.toString(), newVertex); } } // Two vertices will have an edge if their bitstrings differ by exactly // 1 element for (int i = 0; i < order; i++) { for (int j = i + 1; j < order; j++) { for (int z = 0; z < dim; z++) { if ((j ^ i) == (1 << z)) { target.addEdge(vertices.get(i), vertices.get(j)); break; } } } } } } KleinbergSmallWorldGraphGenerator.java000066400000000000000000000173711402514743400352100ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.alg.util.*; import java.util.*; /** * Kleinberg's small-world graph generator. * *

    * The generator is described in the paper: J. Kleinberg, The Small-World Phenomenon: An Algorithmic * Perspective, in Proc. 32nd ACM Symp. Theory of Comp., 163-170, 2000. * *

    * The basic structure is a a two-dimensional grid and allows for edges to be directed. It begins * with a set of nodes (representing individuals in the social network) that are identified with the * set of lattice points in an $n \times n$ square. For a universal constant $p \geq 1$, the node * $u$ has a directed edge to every other node within lattice distance $p$ (these are its local * contacts). For universal constants $q \geq 0$ and $r \geq 0$, we also construct directed edges * from $u$ to $q$ other nodes (the long-range contacts) using independent random trials; the i-th * directed edge from $u$ has endpoint $v$ with probability proportional to \frac{1}{d(u,v)^r}$ * where $d(u,v)$ is the lattice distance from $u$ to $v$. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class KleinbergSmallWorldGraphGenerator implements GraphGenerator { private final Random rng; private final int n; private final int p; private final int q; private final int r; /** * Constructor * * @param n generate set of lattice points in a $n$ by $n$ square * @param p lattice distance for which each node is connected to every other node in the lattice * (local connections) * @param q how many long-range contacts to add for each node * @param r probability distribution parameter which is a basic structural parameter measuring * how widely "networked" the underlying society of nodes is * @throws IllegalArgumentException in case of invalid parameters */ public KleinbergSmallWorldGraphGenerator(int n, int p, int q, int r) { this(n, p, q, r, new Random()); } /** * Constructor * * @param n generate set of lattice points in a $n$ by $n$ square * @param p lattice distance for which each node is connected to every other node in the lattice * (local connections) * @param q how many long-range contacts to add for each node * @param r probability distribution parameter which is a basic structural parameter measuring * how widely "networked" the underlying society of nodes is * @param seed seed for the random number generator * @throws IllegalArgumentException in case of invalid parameters */ public KleinbergSmallWorldGraphGenerator(int n, int p, int q, int r, long seed) { this(n, p, q, r, new Random(seed)); } /** * Constructor * * @param n generate set of lattice points in a $n \times n$ square * @param p lattice distance for which each node is connected to every other node in the lattice * (local connections) * @param q how many long-range contacts to add for each node * @param r probability distribution parameter which is a basic structural parameter measuring * how widely "networked" the underlying society of nodes is * @param rng the random number generator to use * @throws IllegalArgumentException in case of invalid parameters */ public KleinbergSmallWorldGraphGenerator(int n, int p, int q, int r, Random rng) { if (n < 1) { throw new IllegalArgumentException("parameter n must be positive"); } this.n = n; if (p < 1) { throw new IllegalArgumentException("parameter p must be positive"); } if (p > 2 * n - 2) { throw new IllegalArgumentException("lattice distance too large"); } this.p = p; if (q < 0) { throw new IllegalArgumentException("parameter q must be non-negative"); } this.q = q; if (r < 0) { throw new IllegalArgumentException("parameter r must be non-negative"); } this.r = r; this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); } /** * Generates a small-world graph. * * @param target the target graph * @param resultMap not used by this generator, can be null */ @Override public void generateGraph(Graph target, Map resultMap) { /* * Special cases */ if (n == 0) { return; } else if (n == 1) { target.addVertex(); return; } /* * Ensure directed or undirected */ GraphTests.requireDirectedOrUndirected(target); boolean isDirected = target.getType().isDirected(); /* * Create vertices */ List nodes = new ArrayList<>(n * n); for (int i = 0; i < n * n; i++) { nodes.add(target.addVertex()); } /* * Add local-contacts */ for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { int vi = i * n + j; V v = nodes.get(vi); // lookup neighborhood for (int di = -p; di <= p; di++) { for (int dj = -p; dj <= p; dj++) { int t = (i + di) * n + (j + dj); if (t < 0 || t == vi || t >= n * n) { continue; } if (Math.abs(di) + Math.abs(dj) <= p && (isDirected || t > i * n + j)) { target.addEdge(v, nodes.get(t)); } } } } } /* * Add long-range contacts */ double[] p = new double[n * n]; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { V v = nodes.get(i * n + j); /* * Create inverse r power distribution */ double sum = 0d; for (int oi = 0; oi < n; oi++) { for (int oj = 0; oj < n; oj++) { if (oi != i || oj != j) { double weight = Math.pow(Math.abs(i - oi) + Math.abs(j - oj), -r); p[oi * n + oj] = weight; sum += weight; } } } p[i * n + j] = 0d; for (int k = 0; k < n * n; k++) { p[k] /= sum; } /* * Sample from distribution and add long-range edges */ AliasMethodSampler sampler = new AliasMethodSampler(p, rng); for (int k = 0; k < q; k++) { V u = nodes.get(sampler.next()); if (!u.equals(v) && !target.containsEdge(v, u)) { target.addEdge(v, u); } } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/LinearGraphGenerator.java000066400000000000000000000044701402514743400325720ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generates a linear graph of any size. For a directed graph, the edges are oriented from * START_VERTEX to END_VERTEX. * * @param the graph vertex type * @param the graph edge type * * @author John V. Sichi */ public class LinearGraphGenerator implements GraphGenerator { /** * Role for the first vertex generated. */ public static final String START_VERTEX = "Start Vertex"; /** * Role for the last vertex generated. */ public static final String END_VERTEX = "End Vertex"; private int size; /** * Construct a new LinearGraphGenerator. * * @param size number of vertices to be generated * * @throws IllegalArgumentException if the specified size is negative. */ public LinearGraphGenerator(int size) { if (size < 0) { throw new IllegalArgumentException("must be non-negative"); } this.size = size; } /** * {@inheritDoc} */ @Override public void generateGraph(Graph target, Map resultMap) { V lastVertex = null; for (int i = 0; i < size; ++i) { V newVertex = target.addVertex(); if (lastVertex == null) { if (resultMap != null) { resultMap.put(START_VERTEX, newVertex); } } else { target.addEdge(lastVertex, newVertex); } lastVertex = newVertex; } if ((resultMap != null) && (lastVertex != null)) { resultMap.put(END_VERTEX, lastVertex); } } } LinearizedChordDiagramGraphGenerator.java000066400000000000000000000115531402514743400356340ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; /** * The linearized chord diagram graph model generator. * *

    * The generator makes precise several unspecified mathematical details of the Barabási-Albert * model, such as the initial configuration of the first nodes, and whether the $m$ links assigned * to a new node are added one by one, or simultaneously, etc. The generator is described in the * paper: Bélaa Bollobás and Oliver Riordan. The Diameter of a Scale-Free Random Graph. Journal * Combinatorica, 24(1): 5--34, 2004. * *

    * In contrast with the Barabási-Albert model, the model of Bollobás and Riordan allows for multiple * edges (parallel-edges) and self-loops. They show, however, that their number will be small. This * means that this generator works only on graphs which allow multiple edges (parallel-edges) such * as {@link Pseudograph} or {@link DirectedPseudograph}. * *

    * The generator starts with a graph of one node and grows the network by adding $n-1$ additional * nodes. The additional nodes are added one by one and each of them is connected to $m$ previously * added nodes (or to itself with a small probability), where the probability of connecting to a * node is proportional to its degree. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class LinearizedChordDiagramGraphGenerator implements GraphGenerator { private final Random rng; private final int m; private final int n; /** * Constructor * * @param n number of nodes * @param m number of edges of each new node added during the network growth * @throws IllegalArgumentException in case of invalid parameters */ public LinearizedChordDiagramGraphGenerator(int n, int m) { this(n, m, new Random()); } /** * Constructor * * @param n number of nodes * @param m number of edges of each new node added during the network growth * @param seed seed for the random number generator * @throws IllegalArgumentException in case of invalid parameters */ public LinearizedChordDiagramGraphGenerator(int n, int m, long seed) { this(n, m, new Random(seed)); } /** * Constructor * * @param n number of nodes * @param m number of edges of each new node added during the network growth * @param rng the random number generator to use * @throws IllegalArgumentException in case of invalid parameters */ public LinearizedChordDiagramGraphGenerator(int n, int m, Random rng) { if (n <= 0) { throw new IllegalArgumentException("invalid number of nodes: must be positive"); } this.n = n; if (m <= 0) { throw new IllegalArgumentException("invalid edges per node (" + m + " <= 0"); } this.m = m; this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); } /** * Generates an instance. * * @param target the target graph, which must allow self-loops and parallel edges * @param resultMap not used by this generator, can be null * @throws IllegalArgumentException if the graph does not allow self-loops or parallel edges */ @Override public void generateGraph(Graph target, Map resultMap) { /* * Add nodes by maintaining a list with vertex multiplicity equal to its degree for sampling * purposes. */ List nodes = new ArrayList<>(2 * n * m); for (int t = 0; t < n; t++) { // add node V vt = target.addVertex(); // add edges for (int j = 0; j < m; j++) { // add outward half degree before sampling nodes.add(vt); // sample V vs = nodes.get(rng.nextInt(nodes.size())); if (target.addEdge(vt, vs) == null) { throw new IllegalArgumentException("Graph does not permit parallel-edges."); } // add inward half-degree after sampling nodes.add(vs); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/NamedGraphGenerator.java000066400000000000000000002516151402514743400324110ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.*; /** * Collection of commonly used named graphs * * @author Joris Kinable * * @param graph vertex type * @param graph edge type */ public class NamedGraphGenerator { private Map vertexMap; /** * Constructs a new generator for named graphs */ public NamedGraphGenerator() { vertexMap = new HashMap<>(); } // -------------Doyle Graph-----------// /** * Generate the Doyle Graph * * @see #generateDoyleGraph * @return Doyle Graph */ public static Graph doyleGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateDoyleGraph(g); return g; } /** * Generates a Doyle Graph. The Doyle * graph, sometimes also known as the Holt graph (Marušič et al. 2005), is the quartic symmetric * graph on 27 nodes * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateDoyleGraph(Graph targetGraph) { vertexMap.clear(); for (int i = 0; i < 9; i++) for (int j = 0; j < 3; j++) { this .addEdge( targetGraph, doyleHash(i, j), doyleHash(mod(4 * i + 1, 9), mod(j - 1, 3))); this .addEdge( targetGraph, doyleHash(i, j), doyleHash(mod(4 * i - 1, 9), mod(j - 1, 3))); this .addEdge( targetGraph, doyleHash(i, j), doyleHash(mod(7 * i + 7, 9), mod(j + 1, 3))); this .addEdge( targetGraph, doyleHash(i, j), doyleHash(mod(7 * i - 7, 9), mod(j + 1, 3))); } } private int doyleHash(int u, int v) { return u * 19 + v; } private int mod(int u, int m) { int r = u % m; return r < 0 ? r + m : r; } // -------------Generalized Petersen Graph-----------// /** * @see GeneralizedPetersenGraphGenerator * @param n Generalized Petersen graphs $GP(n,k)$ * @param k Generalized Petersen graphs $GP(n,k)$ * @return Generalized Petersen Graph */ public static Graph generalizedPetersenGraph(int n, int k) { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateGeneralizedPetersenGraph(g, n, k); return g; } private void generateGeneralizedPetersenGraph(Graph targetGraph, int n, int k) { GeneralizedPetersenGraphGenerator gpgg = new GeneralizedPetersenGraphGenerator<>(n, k); gpgg.generateGraph(targetGraph); } // -------------Petersen Graph-----------// /** * @see #generatePetersenGraph * @return Petersen Graph */ public static Graph petersenGraph() { return generalizedPetersenGraph(5, 2); } /** * Generates a Petersen Graph. The * Petersen Graph is a named graph that consists of 10 vertices and 15 edges, usually drawn as a * five-point star embedded in a pentagon. It is the generalized Petersen graph $GP(5,2)$ * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generatePetersenGraph(Graph targetGraph) { generateGeneralizedPetersenGraph(targetGraph, 5, 2); } // -------------Dürer Graph-----------// /** * Generates a Dürer Graph. The * Dürer graph is the skeleton of Dürer's solid, which is the generalized Petersen graph * $GP(6,2)$. * * @return the Dürer Graph */ public static Graph dürerGraph() { return generalizedPetersenGraph(6, 2); } /** * Generates a Dürer Graph. The * Dürer graph is the skeleton of Dürer's solid, which is the generalized Petersen graph * $GP(6,2)$. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateDürerGraph(Graph targetGraph) { generateGeneralizedPetersenGraph(targetGraph, 6, 2); } // -------------Dodecahedron Graph-----------// /** * @see #generateDodecahedronGraph * @return Dodecahedron Graph */ public static Graph dodecahedronGraph() { return generalizedPetersenGraph(10, 2); } /** * Generates a Dodecahedron * Graph. The skeleton of the dodecahedron (the vertices and edges) form a graph. It is one * of 5 Platonic graphs, each a skeleton of its Platonic solid. It is the generalized Petersen * graph $GP(10,2)$ * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateDodecahedronGraph(Graph targetGraph) { generateGeneralizedPetersenGraph(targetGraph, 10, 2); } // -------------Desargues Graph-----------// /** * @see #generateDesarguesGraph * @return Desargues Graph */ public static Graph desarguesGraph() { return generalizedPetersenGraph(10, 3); } /** * Generates a Desargues Graph. * The Desargues graph is a cubic symmetric graph distance-regular graph on 20 vertices and 30 * edges. It is the generalized Petersen graph $GP(10,3)$ * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateDesarguesGraph(Graph targetGraph) { generateGeneralizedPetersenGraph(targetGraph, 10, 3); } // -------------Nauru Graph-----------// /** * @see #generateNauruGraph * @return Nauru Graph */ public static Graph nauruGraph() { return generalizedPetersenGraph(12, 5); } /** * Generates a Nauru Graph. The Nauru * graph is a symmetric bipartite cubic graph with 24 vertices and 36 edges. It is the * generalized Petersen graph $GP(12,5)$ * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateNauruGraph(Graph targetGraph) { generateGeneralizedPetersenGraph(targetGraph, 12, 5); } // -------------Möbius-Kantor Graph-----------// /** * Generates a Möbius-Kantor * Graph. The unique cubic symmetric graph on 16 nodes. It is the generalized Petersen graph * $GP(8,3)$ * * @return the Möbius-Kantor Graph */ public static Graph möbiusKantorGraph() { return generalizedPetersenGraph(8, 3); } /** * Generates a Möbius-Kantor * Graph. The unique cubic symmetric graph on 16 nodes. It is the generalized Petersen graph * $GP(8,3)$ * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateMöbiusKantorGraph(Graph targetGraph) { generateGeneralizedPetersenGraph(targetGraph, 8, 3); } // -------------Bull Graph-----------// /** * @see #generateBullGraph * @return Bull Graph */ public static Graph bullGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateBullGraph(g); return g; } /** * Generates a Bull Graph. The bull * graph is a simple graph on 5 nodes and 5 edges whose name derives from its resemblance to a * schematic illustration of a bull or ram * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateBullGraph(Graph targetGraph) { vertexMap.clear(); this.addEdge(targetGraph, 0, 1); this.addEdge(targetGraph, 1, 2); this.addEdge(targetGraph, 2, 3); this.addEdge(targetGraph, 1, 3); this.addEdge(targetGraph, 3, 4); } // -------------Butterfly Graph-----------// /** * @see #generateButterflyGraph * @return Butterfly Graph */ public static Graph butterflyGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateButterflyGraph(g); return g; } /** * Generates a Butterfly Graph. * This graph is also known as the "bowtie graph" (West 2000, p. 12). It is isomorphic to the * friendship graph $F_2$. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateButterflyGraph(Graph targetGraph) { new WindmillGraphsGenerator(WindmillGraphsGenerator.Mode.DUTCHWINDMILL, 2, 3) .generateGraph(targetGraph); } // -------------Claw Graph-----------// /** * @see #generateClawGraph * @return Claw Graph */ public static Graph clawGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateClawGraph(g); return g; } /** * Generates a Claw Graph. The * complete bipartite graph $K_{1,3}$ is a tree known as the "claw." * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateClawGraph(Graph targetGraph) { new StarGraphGenerator(4).generateGraph(targetGraph); } // -------------Bucky ball Graph-----------// /** * @see #generateBuckyBallGraph * @return Bucky ball Graph */ public static Graph buckyBallGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateBuckyBallGraph(g); return g; } /** * Generates a Bucky ball Graph. This * graph is a 3-regular 60-vertex planar graph. Its vertices and edges correspond precisely to * the carbon atoms and bonds in buckminsterfullerene. When embedded on a sphere, its 12 * pentagon and 20 hexagon faces are arranged exactly as the sections of a soccer ball. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateBuckyBallGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 2 }, { 0, 48 }, { 0, 59 }, { 1, 3 }, { 1, 9 }, { 1, 58 }, { 2, 3 }, { 2, 36 }, { 3, 17 }, { 4, 6 }, { 4, 8 }, { 4, 12 }, { 5, 7 }, { 5, 9 }, { 5, 16 }, { 6, 7 }, { 6, 20 }, { 7, 21 }, { 8, 9 }, { 8, 56 }, { 10, 11 }, { 10, 12 }, { 10, 20 }, { 11, 27 }, { 11, 47 }, { 12, 13 }, { 13, 46 }, { 13, 54 }, { 14, 15 }, { 14, 16 }, { 14, 21 }, { 15, 25 }, { 15, 41 }, { 16, 17 }, { 17, 40 }, { 18, 19 }, { 18, 20 }, { 18, 26 }, { 19, 21 }, { 19, 24 }, { 22, 23 }, { 22, 31 }, { 22, 34 }, { 23, 25 }, { 23, 38 }, { 24, 25 }, { 24, 30 }, { 26, 27 }, { 26, 30 }, { 27, 29 }, { 28, 29 }, { 28, 31 }, { 28, 35 }, { 29, 44 }, { 30, 31 }, { 32, 34 }, { 32, 39 }, { 32, 50 }, { 33, 35 }, { 33, 45 }, { 33, 51 }, { 34, 35 }, { 36, 37 }, { 36, 40 }, { 37, 39 }, { 37, 52 }, { 38, 39 }, { 38, 41 }, { 40, 41 }, { 42, 43 }, { 42, 46 }, { 42, 55 }, { 43, 45 }, { 43, 53 }, { 44, 45 }, { 44, 47 }, { 46, 47 }, { 48, 49 }, { 48, 52 }, { 49, 53 }, { 49, 57 }, { 50, 51 }, { 50, 52 }, { 51, 53 }, { 54, 55 }, { 54, 56 }, { 55, 57 }, { 56, 58 }, { 57, 59 }, { 58, 59 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Clebsch Graph-----------// /** * @see #generateClebschGraph * @return Clebsch Graph */ public static Graph clebschGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateClebschGraph(g); return g; } /** * Generates a Clebsch Graph. The * Clebsch graph, also known as the Greenwood-Gleason graph (Read and Wilson, 1998, p. 284), is * a strongly regular quintic graph on 16 vertices and 40 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateClebschGraph(Graph targetGraph) { vertexMap.clear(); int x = 0; for (int i = 0; i < 8; i++) { addEdge(targetGraph, x % 16, (x + 1) % 16); addEdge(targetGraph, x % 16, (x + 6) % 16); addEdge(targetGraph, x % 16, (x + 8) % 16); x++; addEdge(targetGraph, x % 16, (x + 3) % 16); addEdge(targetGraph, x % 16, (x + 2) % 16); addEdge(targetGraph, x % 16, (x + 8) % 16); x++; } } // -------------Grötzsch Graph-----------// /** * Generates a Grötzsch Graph. * The Grötzsch graph is smallest triangle-free graph with chromatic number four. * * @return the Grötzsch Graph */ public static Graph grötzschGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateGrötzschGraph(g); return g; } /** * Generates a Grötzsch Graph. * The Grötzsch graph is smallest triangle-free graph with chromatic number four. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateGrötzschGraph(Graph targetGraph) { vertexMap.clear(); for (int i = 1; i < 6; i++) addEdge(targetGraph, 0, i); addEdge(targetGraph, 10, 6); for (int i = 6; i < 10; i++) { addEdge(targetGraph, i, i + 1); addEdge(targetGraph, i, i - 4); } addEdge(targetGraph, 10, 1); for (int i = 7; i < 11; i++) addEdge(targetGraph, i, i - 6); addEdge(targetGraph, 6, 5); } // -------------Bidiakis cube Graph-----------// /** * @see #generateBidiakisCubeGraph * @return Bidiakis cube Graph */ public static Graph bidiakisCubeGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateBidiakisCubeGraph(g); return g; } /** * Generates a Bidiakis cube Graph. * The 12-vertex graph consisting of a cube in which two opposite faces (say, top and bottom) * have edges drawn across them which connect the centers of opposite sides of the faces in such * a way that the orientation of the edges added on top and bottom are perpendicular to each * other. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateBidiakisCubeGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 6 }, { 0, 11 }, { 1, 2 }, { 1, 5 }, { 2, 3 }, { 2, 10 }, { 3, 4 }, { 3, 9 }, { 4, 5 }, { 4, 8 }, { 5, 6 }, { 6, 7 }, { 7, 8 }, { 7, 11 }, { 8, 9 }, { 9, 10 }, { 10, 11 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------First Blanusa Snark Graph-----------// /** * @see #generateBlanusaFirstSnarkGraph * @return First Blanusa Snark Graph */ public static Graph blanusaFirstSnarkGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateBlanusaFirstSnarkGraph(g); return g; } /** * Generates the First Blanusa Snark * Graph. The Blanusa graphs are two snarks on 18 vertices and 27 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateBlanusaFirstSnarkGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 5 }, { 0, 16 }, { 1, 2 }, { 1, 17 }, { 2, 3 }, { 2, 14 }, { 3, 4 }, { 3, 8 }, { 4, 5 }, { 4, 17 }, { 5, 6 }, { 6, 7 }, { 6, 11 }, { 7, 8 }, { 7, 17 }, { 8, 9 }, { 9, 10 }, { 9, 13 }, { 10, 11 }, { 10, 15 }, { 11, 12 }, { 12, 13 }, { 12, 16 }, { 13, 14 }, { 14, 15 }, { 15, 16 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Second Blanusa Snark Graph-----------// /** * @see #generateBlanusaSecondSnarkGraph * @return Second Blanusa Snark Graph */ public static Graph blanusaSecondSnarkGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateBlanusaSecondSnarkGraph(g); return g; } /** * Generates the Second Blanusa Snark * Graph. The Blanusa graphs are two snarks on 18 vertices and 27 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateBlanusaSecondSnarkGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 2 }, { 0, 14 }, { 1, 5 }, { 1, 11 }, { 2, 3 }, { 2, 6 }, { 3, 4 }, { 3, 9 }, { 4, 5 }, { 4, 7 }, { 5, 6 }, { 6, 8 }, { 7, 8 }, { 7, 17 }, { 8, 9 }, { 9, 15 }, { 10, 11 }, { 10, 14 }, { 10, 16 }, { 11, 12 }, { 12, 13 }, { 12, 17 }, { 13, 14 }, { 13, 15 }, { 15, 16 }, { 16, 17 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Double Star Snark Graph-----------// /** * @see #generateDoubleStarSnarkGraph * @return Double Star Snark Graph */ public static Graph doubleStarSnarkGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateDoubleStarSnarkGraph(g); return g; } /** * Generates the Double Star Snark * Graph. A snark on 30 vertices with edge chromatic number 4. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateDoubleStarSnarkGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 14 }, { 0, 15 }, { 1, 2 }, { 1, 11 }, { 2, 3 }, { 2, 7 }, { 3, 4 }, { 3, 18 }, { 4, 5 }, { 4, 14 }, { 5, 6 }, { 5, 10 }, { 6, 7 }, { 6, 21 }, { 7, 8 }, { 8, 9 }, { 8, 13 }, { 9, 10 }, { 9, 24 }, { 10, 11 }, { 11, 12 }, { 12, 13 }, { 12, 27 }, { 13, 14 }, { 15, 16 }, { 15, 29 }, { 16, 20 }, { 16, 23 }, { 17, 18 }, { 17, 25 }, { 17, 28 }, { 18, 19 }, { 19, 23 }, { 19, 26 }, { 20, 21 }, { 20, 28 }, { 21, 22 }, { 22, 26 }, { 22, 29 }, { 23, 24 }, { 24, 25 }, { 25, 29 }, { 26, 27 }, { 27, 28 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Brinkmann Graph-----------// /** * @see #generateBrinkmannGraph * @return Brinkmann Graph */ public static Graph brinkmannGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateBrinkmannGraph(g); return g; } /** * Generates the Brinkmann Graph. * The Brinkmann graph is a weakly regular quartic graph on 21 vertices and 42 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateBrinkmannGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 2 }, { 0, 5 }, { 0, 7 }, { 0, 13 }, { 1, 3 }, { 1, 6 }, { 1, 7 }, { 1, 8 }, { 2, 4 }, { 2, 8 }, { 2, 9 }, { 3, 5 }, { 3, 9 }, { 3, 10 }, { 4, 6 }, { 4, 10 }, { 4, 11 }, { 5, 11 }, { 5, 12 }, { 6, 12 }, { 6, 13 }, { 7, 15 }, { 7, 20 }, { 8, 14 }, { 8, 16 }, { 9, 15 }, { 9, 17 }, { 10, 16 }, { 10, 18 }, { 11, 17 }, { 11, 19 }, { 12, 18 }, { 12, 20 }, { 13, 14 }, { 13, 19 }, { 14, 17 }, { 14, 18 }, { 15, 18 }, { 15, 19 }, { 16, 19 }, { 16, 20 }, { 17, 20 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Gosset Graph-----------// /** * @see #generateGossetGraph * @return Gosset Graph */ public static Graph gossetGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateGossetGraph(g); return g; } /** * Generates the Gosset Graph. The * Gosset graph is a 27-regular graph on 56 vertices which is the skeleton of the Gosset * polytope $3_{21}$. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateGossetGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 0, 4 }, { 0, 5 }, { 0, 6 }, { 0, 7 }, { 0, 8 }, { 0, 9 }, { 0, 10 }, { 0, 11 }, { 0, 12 }, { 0, 13 }, { 0, 14 }, { 0, 15 }, { 0, 16 }, { 0, 17 }, { 0, 18 }, { 0, 19 }, { 0, 20 }, { 0, 21 }, { 0, 28 }, { 0, 29 }, { 0, 30 }, { 0, 31 }, { 0, 32 }, { 0, 33 }, { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, { 1, 6 }, { 1, 7 }, { 1, 8 }, { 1, 9 }, { 1, 10 }, { 1, 11 }, { 1, 12 }, { 1, 13 }, { 1, 14 }, { 1, 15 }, { 1, 16 }, { 1, 22 }, { 1, 23 }, { 1, 24 }, { 1, 25 }, { 1, 26 }, { 1, 28 }, { 1, 34 }, { 1, 35 }, { 1, 36 }, { 1, 37 }, { 1, 38 }, { 2, 3 }, { 2, 4 }, { 2, 5 }, { 2, 6 }, { 2, 7 }, { 2, 8 }, { 2, 9 }, { 2, 10 }, { 2, 11 }, { 2, 12 }, { 2, 17 }, { 2, 18 }, { 2, 19 }, { 2, 20 }, { 2, 22 }, { 2, 23 }, { 2, 24 }, { 2, 25 }, { 2, 27 }, { 2, 29 }, { 2, 34 }, { 2, 39 }, { 2, 40 }, { 2, 41 }, { 2, 42 }, { 3, 4 }, { 3, 5 }, { 3, 6 }, { 3, 7 }, { 3, 8 }, { 3, 9 }, { 3, 13 }, { 3, 14 }, { 3, 15 }, { 3, 17 }, { 3, 18 }, { 3, 19 }, { 3, 21 }, { 3, 22 }, { 3, 23 }, { 3, 24 }, { 3, 26 }, { 3, 27 }, { 3, 30 }, { 3, 35 }, { 3, 39 }, { 3, 43 }, { 3, 44 }, { 3, 45 }, { 4, 5 }, { 4, 6 }, { 4, 7 }, { 4, 10 }, { 4, 11 }, { 4, 13 }, { 4, 14 }, { 4, 16 }, { 4, 17 }, { 4, 18 }, { 4, 20 }, { 4, 21 }, { 4, 22 }, { 4, 23 }, { 4, 25 }, { 4, 26 }, { 4, 27 }, { 4, 31 }, { 4, 36 }, { 4, 40 }, { 4, 43 }, { 4, 46 }, { 4, 47 }, { 5, 6 }, { 5, 8 }, { 5, 10 }, { 5, 12 }, { 5, 13 }, { 5, 15 }, { 5, 16 }, { 5, 17 }, { 5, 19 }, { 5, 20 }, { 5, 21 }, { 5, 22 }, { 5, 24 }, { 5, 25 }, { 5, 26 }, { 5, 27 }, { 5, 32 }, { 5, 37 }, { 5, 41 }, { 5, 44 }, { 5, 46 }, { 5, 48 }, { 6, 9 }, { 6, 11 }, { 6, 12 }, { 6, 14 }, { 6, 15 }, { 6, 16 }, { 6, 18 }, { 6, 19 }, { 6, 20 }, { 6, 21 }, { 6, 23 }, { 6, 24 }, { 6, 25 }, { 6, 26 }, { 6, 27 }, { 6, 33 }, { 6, 38 }, { 6, 42 }, { 6, 45 }, { 6, 47 }, { 6, 48 }, { 7, 8 }, { 7, 9 }, { 7, 10 }, { 7, 11 }, { 7, 13 }, { 7, 14 }, { 7, 17 }, { 7, 18 }, { 7, 22 }, { 7, 23 }, { 7, 28 }, { 7, 29 }, { 7, 30 }, { 7, 31 }, { 7, 34 }, { 7, 35 }, { 7, 36 }, { 7, 39 }, { 7, 40 }, { 7, 43 }, { 7, 49 }, { 7, 50 }, { 8, 9 }, { 8, 10 }, { 8, 12 }, { 8, 13 }, { 8, 15 }, { 8, 17 }, { 8, 19 }, { 8, 22 }, { 8, 24 }, { 8, 28 }, { 8, 29 }, { 8, 30 }, { 8, 32 }, { 8, 34 }, { 8, 35 }, { 8, 37 }, { 8, 39 }, { 8, 41 }, { 8, 44 }, { 8, 49 }, { 8, 51 }, { 9, 11 }, { 9, 12 }, { 9, 14 }, { 9, 15 }, { 9, 18 }, { 9, 19 }, { 9, 23 }, { 9, 24 }, { 9, 28 }, { 9, 29 }, { 9, 30 }, { 9, 33 }, { 9, 34 }, { 9, 35 }, { 9, 38 }, { 9, 39 }, { 9, 42 }, { 9, 45 }, { 9, 50 }, { 9, 51 }, { 10, 11 }, { 10, 12 }, { 10, 13 }, { 10, 16 }, { 10, 17 }, { 10, 20 }, { 10, 22 }, { 10, 25 }, { 10, 28 }, { 10, 29 }, { 10, 31 }, { 10, 32 }, { 10, 34 }, { 10, 36 }, { 10, 37 }, { 10, 40 }, { 10, 41 }, { 10, 46 }, { 10, 49 }, { 10, 52 }, { 11, 12 }, { 11, 14 }, { 11, 16 }, { 11, 18 }, { 11, 20 }, { 11, 23 }, { 11, 25 }, { 11, 28 }, { 11, 29 }, { 11, 31 }, { 11, 33 }, { 11, 34 }, { 11, 36 }, { 11, 38 }, { 11, 40 }, { 11, 42 }, { 11, 47 }, { 11, 50 }, { 11, 52 }, { 12, 15 }, { 12, 16 }, { 12, 19 }, { 12, 20 }, { 12, 24 }, { 12, 25 }, { 12, 28 }, { 12, 29 }, { 12, 32 }, { 12, 33 }, { 12, 34 }, { 12, 37 }, { 12, 38 }, { 12, 41 }, { 12, 42 }, { 12, 48 }, { 12, 51 }, { 12, 52 }, { 13, 14 }, { 13, 15 }, { 13, 16 }, { 13, 17 }, { 13, 21 }, { 13, 22 }, { 13, 26 }, { 13, 28 }, { 13, 30 }, { 13, 31 }, { 13, 32 }, { 13, 35 }, { 13, 36 }, { 13, 37 }, { 13, 43 }, { 13, 44 }, { 13, 46 }, { 13, 49 }, { 13, 53 }, { 14, 15 }, { 14, 16 }, { 14, 18 }, { 14, 21 }, { 14, 23 }, { 14, 26 }, { 14, 28 }, { 14, 30 }, { 14, 31 }, { 14, 33 }, { 14, 35 }, { 14, 36 }, { 14, 38 }, { 14, 43 }, { 14, 45 }, { 14, 47 }, { 14, 50 }, { 14, 53 }, { 15, 16 }, { 15, 19 }, { 15, 21 }, { 15, 24 }, { 15, 26 }, { 15, 28 }, { 15, 30 }, { 15, 32 }, { 15, 33 }, { 15, 35 }, { 15, 37 }, { 15, 38 }, { 15, 44 }, { 15, 45 }, { 15, 48 }, { 15, 51 }, { 15, 53 }, { 16, 20 }, { 16, 21 }, { 16, 25 }, { 16, 26 }, { 16, 28 }, { 16, 31 }, { 16, 32 }, { 16, 33 }, { 16, 36 }, { 16, 37 }, { 16, 38 }, { 16, 46 }, { 16, 47 }, { 16, 48 }, { 16, 52 }, { 16, 53 }, { 17, 18 }, { 17, 19 }, { 17, 20 }, { 17, 21 }, { 17, 22 }, { 17, 27 }, { 17, 29 }, { 17, 30 }, { 17, 31 }, { 17, 32 }, { 17, 39 }, { 17, 40 }, { 17, 41 }, { 17, 43 }, { 17, 44 }, { 17, 46 }, { 17, 49 }, { 17, 54 }, { 18, 19 }, { 18, 20 }, { 18, 21 }, { 18, 23 }, { 18, 27 }, { 18, 29 }, { 18, 30 }, { 18, 31 }, { 18, 33 }, { 18, 39 }, { 18, 40 }, { 18, 42 }, { 18, 43 }, { 18, 45 }, { 18, 47 }, { 18, 50 }, { 18, 54 }, { 19, 20 }, { 19, 21 }, { 19, 24 }, { 19, 27 }, { 19, 29 }, { 19, 30 }, { 19, 32 }, { 19, 33 }, { 19, 39 }, { 19, 41 }, { 19, 42 }, { 19, 44 }, { 19, 45 }, { 19, 48 }, { 19, 51 }, { 19, 54 }, { 20, 21 }, { 20, 25 }, { 20, 27 }, { 20, 29 }, { 20, 31 }, { 20, 32 }, { 20, 33 }, { 20, 40 }, { 20, 41 }, { 20, 42 }, { 20, 46 }, { 20, 47 }, { 20, 48 }, { 20, 52 }, { 20, 54 }, { 21, 26 }, { 21, 27 }, { 21, 30 }, { 21, 31 }, { 21, 32 }, { 21, 33 }, { 21, 43 }, { 21, 44 }, { 21, 45 }, { 21, 46 }, { 21, 47 }, { 21, 48 }, { 21, 53 }, { 21, 54 }, { 22, 23 }, { 22, 24 }, { 22, 25 }, { 22, 26 }, { 22, 27 }, { 22, 34 }, { 22, 35 }, { 22, 36 }, { 22, 37 }, { 22, 39 }, { 22, 40 }, { 22, 41 }, { 22, 43 }, { 22, 44 }, { 22, 46 }, { 22, 49 }, { 22, 55 }, { 23, 24 }, { 23, 25 }, { 23, 26 }, { 23, 27 }, { 23, 34 }, { 23, 35 }, { 23, 36 }, { 23, 38 }, { 23, 39 }, { 23, 40 }, { 23, 42 }, { 23, 43 }, { 23, 45 }, { 23, 47 }, { 23, 50 }, { 23, 55 }, { 24, 25 }, { 24, 26 }, { 24, 27 }, { 24, 34 }, { 24, 35 }, { 24, 37 }, { 24, 38 }, { 24, 39 }, { 24, 41 }, { 24, 42 }, { 24, 44 }, { 24, 45 }, { 24, 48 }, { 24, 51 }, { 24, 55 }, { 25, 26 }, { 25, 27 }, { 25, 34 }, { 25, 36 }, { 25, 37 }, { 25, 38 }, { 25, 40 }, { 25, 41 }, { 25, 42 }, { 25, 46 }, { 25, 47 }, { 25, 48 }, { 25, 52 }, { 25, 55 }, { 26, 27 }, { 26, 35 }, { 26, 36 }, { 26, 37 }, { 26, 38 }, { 26, 43 }, { 26, 44 }, { 26, 45 }, { 26, 46 }, { 26, 47 }, { 26, 48 }, { 26, 53 }, { 26, 55 }, { 27, 39 }, { 27, 40 }, { 27, 41 }, { 27, 42 }, { 27, 43 }, { 27, 44 }, { 27, 45 }, { 27, 46 }, { 27, 47 }, { 27, 48 }, { 27, 54 }, { 27, 55 }, { 28, 29 }, { 28, 30 }, { 28, 31 }, { 28, 32 }, { 28, 33 }, { 28, 34 }, { 28, 35 }, { 28, 36 }, { 28, 37 }, { 28, 38 }, { 28, 49 }, { 28, 50 }, { 28, 51 }, { 28, 52 }, { 28, 53 }, { 29, 30 }, { 29, 31 }, { 29, 32 }, { 29, 33 }, { 29, 34 }, { 29, 39 }, { 29, 40 }, { 29, 41 }, { 29, 42 }, { 29, 49 }, { 29, 50 }, { 29, 51 }, { 29, 52 }, { 29, 54 }, { 30, 31 }, { 30, 32 }, { 30, 33 }, { 30, 35 }, { 30, 39 }, { 30, 43 }, { 30, 44 }, { 30, 45 }, { 30, 49 }, { 30, 50 }, { 30, 51 }, { 30, 53 }, { 30, 54 }, { 31, 32 }, { 31, 33 }, { 31, 36 }, { 31, 40 }, { 31, 43 }, { 31, 46 }, { 31, 47 }, { 31, 49 }, { 31, 50 }, { 31, 52 }, { 31, 53 }, { 31, 54 }, { 32, 33 }, { 32, 37 }, { 32, 41 }, { 32, 44 }, { 32, 46 }, { 32, 48 }, { 32, 49 }, { 32, 51 }, { 32, 52 }, { 32, 53 }, { 32, 54 }, { 33, 38 }, { 33, 42 }, { 33, 45 }, { 33, 47 }, { 33, 48 }, { 33, 50 }, { 33, 51 }, { 33, 52 }, { 33, 53 }, { 33, 54 }, { 34, 35 }, { 34, 36 }, { 34, 37 }, { 34, 38 }, { 34, 39 }, { 34, 40 }, { 34, 41 }, { 34, 42 }, { 34, 49 }, { 34, 50 }, { 34, 51 }, { 34, 52 }, { 34, 55 }, { 35, 36 }, { 35, 37 }, { 35, 38 }, { 35, 39 }, { 35, 43 }, { 35, 44 }, { 35, 45 }, { 35, 49 }, { 35, 50 }, { 35, 51 }, { 35, 53 }, { 35, 55 }, { 36, 37 }, { 36, 38 }, { 36, 40 }, { 36, 43 }, { 36, 46 }, { 36, 47 }, { 36, 49 }, { 36, 50 }, { 36, 52 }, { 36, 53 }, { 36, 55 }, { 37, 38 }, { 37, 41 }, { 37, 44 }, { 37, 46 }, { 37, 48 }, { 37, 49 }, { 37, 51 }, { 37, 52 }, { 37, 53 }, { 37, 55 }, { 38, 42 }, { 38, 45 }, { 38, 47 }, { 38, 48 }, { 38, 50 }, { 38, 51 }, { 38, 52 }, { 38, 53 }, { 38, 55 }, { 39, 40 }, { 39, 41 }, { 39, 42 }, { 39, 43 }, { 39, 44 }, { 39, 45 }, { 39, 49 }, { 39, 50 }, { 39, 51 }, { 39, 54 }, { 39, 55 }, { 40, 41 }, { 40, 42 }, { 40, 43 }, { 40, 46 }, { 40, 47 }, { 40, 49 }, { 40, 50 }, { 40, 52 }, { 40, 54 }, { 40, 55 }, { 41, 42 }, { 41, 44 }, { 41, 46 }, { 41, 48 }, { 41, 49 }, { 41, 51 }, { 41, 52 }, { 41, 54 }, { 41, 55 }, { 42, 45 }, { 42, 47 }, { 42, 48 }, { 42, 50 }, { 42, 51 }, { 42, 52 }, { 42, 54 }, { 42, 55 }, { 43, 44 }, { 43, 45 }, { 43, 46 }, { 43, 47 }, { 43, 49 }, { 43, 50 }, { 43, 53 }, { 43, 54 }, { 43, 55 }, { 44, 45 }, { 44, 46 }, { 44, 48 }, { 44, 49 }, { 44, 51 }, { 44, 53 }, { 44, 54 }, { 44, 55 }, { 45, 47 }, { 45, 48 }, { 45, 50 }, { 45, 51 }, { 45, 53 }, { 45, 54 }, { 45, 55 }, { 46, 47 }, { 46, 48 }, { 46, 49 }, { 46, 52 }, { 46, 53 }, { 46, 54 }, { 46, 55 }, { 47, 48 }, { 47, 50 }, { 47, 52 }, { 47, 53 }, { 47, 54 }, { 47, 55 }, { 48, 51 }, { 48, 52 }, { 48, 53 }, { 48, 54 }, { 48, 55 }, { 49, 50 }, { 49, 51 }, { 49, 52 }, { 49, 53 }, { 49, 54 }, { 49, 55 }, { 50, 51 }, { 50, 52 }, { 50, 53 }, { 50, 54 }, { 50, 55 }, { 51, 52 }, { 51, 53 }, { 51, 54 }, { 51, 55 }, { 52, 53 }, { 52, 54 }, { 52, 55 }, { 53, 54 }, { 53, 55 }, { 54, 55 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Chvatal Graph-----------// /** * @see #generateChvatalGraph * @return Chvatal Graph */ public static Graph chvatalGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateChvatalGraph(g); return g; } /** * Generates the Chvatal Graph. The * Chvátal graph is an undirected graph with 12 vertices and 24 edges, discovered by Václav * Chvátal (1970) * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateChvatalGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 4 }, { 0, 6 }, { 0, 9 }, { 1, 2 }, { 1, 5 }, { 1, 7 }, { 2, 3 }, { 2, 6 }, { 2, 8 }, { 3, 4 }, { 3, 7 }, { 3, 9 }, { 4, 5 }, { 4, 8 }, { 5, 10 }, { 5, 11 }, { 6, 10 }, { 6, 11 }, { 7, 8 }, { 7, 11 }, { 8, 10 }, { 9, 10 }, { 9, 11 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Kittell Graph-----------// /** * @see #generateKittellGraph * @return Kittell Graph */ public static Graph kittellGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateKittellGraph(g); return g; } /** * Generates the Kittell Graph. The * Kittell graph is a planar graph on 23 nodes and 63 edges that tangles the Kempe chains in * Kempe's algorithm and thus provides an example of how Kempe's supposed proof of the * four-color theorem fails. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateKittellGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 2 }, { 0, 4 }, { 0, 5 }, { 0, 6 }, { 0, 7 }, { 1, 2 }, { 1, 7 }, { 1, 10 }, { 1, 11 }, { 1, 13 }, { 2, 4 }, { 2, 11 }, { 2, 14 }, { 3, 4 }, { 3, 5 }, { 3, 12 }, { 3, 14 }, { 3, 16 }, { 4, 5 }, { 4, 14 }, { 5, 6 }, { 5, 16 }, { 6, 7 }, { 6, 15 }, { 6, 16 }, { 6, 17 }, { 6, 18 }, { 7, 8 }, { 7, 13 }, { 7, 18 }, { 8, 9 }, { 8, 13 }, { 8, 18 }, { 8, 19 }, { 9, 10 }, { 9, 13 }, { 9, 19 }, { 9, 20 }, { 10, 11 }, { 10, 13 }, { 10, 20 }, { 10, 21 }, { 11, 12 }, { 11, 14 }, { 11, 15 }, { 11, 21 }, { 12, 14 }, { 12, 15 }, { 12, 16 }, { 15, 16 }, { 15, 17 }, { 15, 21 }, { 15, 22 }, { 17, 18 }, { 17, 19 }, { 17, 22 }, { 18, 19 }, { 19, 20 }, { 19, 22 }, { 20, 21 }, { 20, 22 }, { 21, 22 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Coxeter Graph-----------// /** * @see #generateCoxeterGraph * @return Coxeter Graph */ public static Graph coxeterGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateCoxeterGraph(g); return g; } /** * Generates the Coxeter Graph. The * Coxeter graph is a nonhamiltonian cubic symmetric graph on 28 vertices and 42 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateCoxeterGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 23 }, { 0, 24 }, { 1, 2 }, { 1, 12 }, { 2, 3 }, { 2, 25 }, { 3, 4 }, { 3, 21 }, { 4, 5 }, { 4, 17 }, { 5, 6 }, { 5, 11 }, { 6, 7 }, { 6, 27 }, { 7, 8 }, { 7, 24 }, { 8, 9 }, { 8, 25 }, { 9, 10 }, { 9, 20 }, { 10, 11 }, { 10, 26 }, { 11, 12 }, { 12, 13 }, { 13, 14 }, { 13, 19 }, { 14, 15 }, { 14, 27 }, { 15, 16 }, { 15, 25 }, { 16, 17 }, { 16, 26 }, { 17, 18 }, { 18, 19 }, { 18, 24 }, { 19, 20 }, { 20, 21 }, { 21, 22 }, { 22, 23 }, { 22, 27 }, { 23, 26 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Diamond Graph-----------// /** * @see #generateDiamondGraph * @return Diamond Graph */ public static Graph diamondGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateDiamondGraph(g); return g; } /** * Generates the Diamond Graph. The * Diamond graph has 4 vertices and 5 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateDiamondGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 2 }, { 2, 3 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Ellingham-Horton 54 Graph-----------// /** * @see #generateEllinghamHorton54Graph * @return Ellingham-Horton 54 Graph */ public static Graph ellinghamHorton54Graph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateEllinghamHorton54Graph(g); return g; } /** * Generates the * Ellingham-Horton 54 * Graph. The Ellingham–Horton graph is a 3-regular bicubic graph of 54 vertices * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateEllinghamHorton54Graph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 11 }, { 0, 15 }, { 1, 2 }, { 1, 47 }, { 2, 3 }, { 2, 13 }, { 3, 4 }, { 3, 8 }, { 4, 5 }, { 4, 15 }, { 5, 6 }, { 5, 10 }, { 6, 7 }, { 6, 30 }, { 7, 8 }, { 7, 12 }, { 8, 9 }, { 9, 10 }, { 9, 29 }, { 10, 11 }, { 11, 12 }, { 12, 13 }, { 13, 14 }, { 14, 15 }, { 14, 48 }, { 16, 17 }, { 16, 21 }, { 16, 28 }, { 17, 24 }, { 17, 29 }, { 18, 19 }, { 18, 23 }, { 18, 30 }, { 19, 20 }, { 19, 31 }, { 20, 21 }, { 20, 32 }, { 21, 33 }, { 22, 23 }, { 22, 27 }, { 22, 28 }, { 23, 29 }, { 24, 25 }, { 24, 30 }, { 25, 26 }, { 25, 31 }, { 26, 27 }, { 26, 32 }, { 27, 33 }, { 28, 31 }, { 32, 52 }, { 33, 53 }, { 34, 35 }, { 34, 39 }, { 34, 46 }, { 35, 42 }, { 35, 47 }, { 36, 37 }, { 36, 41 }, { 36, 48 }, { 37, 38 }, { 37, 49 }, { 38, 39 }, { 38, 50 }, { 39, 51 }, { 40, 41 }, { 40, 45 }, { 40, 46 }, { 41, 47 }, { 42, 43 }, { 42, 48 }, { 43, 44 }, { 43, 49 }, { 44, 45 }, { 44, 50 }, { 45, 51 }, { 46, 49 }, { 50, 52 }, { 51, 53 }, { 52, 53 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Ellingham-Horton 78 Graph-----------// /** * @see #generateEllinghamHorton78Graph * @return Ellingham-Horton 78 Graph */ public static Graph ellinghamHorton78Graph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateEllinghamHorton78Graph(g); return g; } /** * Generates the * Ellingham-Horton 78 * Graph. The Ellingham–Horton graph is a 3-regular graph of 78 vertices * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateEllinghamHorton78Graph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 5 }, { 0, 60 }, { 1, 2 }, { 1, 12 }, { 2, 3 }, { 2, 7 }, { 3, 4 }, { 3, 14 }, { 4, 5 }, { 4, 9 }, { 5, 6 }, { 6, 7 }, { 6, 11 }, { 7, 15 }, { 8, 9 }, { 8, 13 }, { 8, 22 }, { 9, 10 }, { 10, 11 }, { 10, 72 }, { 11, 12 }, { 12, 13 }, { 13, 14 }, { 14, 72 }, { 15, 16 }, { 15, 20 }, { 16, 17 }, { 16, 27 }, { 17, 18 }, { 17, 22 }, { 18, 19 }, { 18, 29 }, { 19, 20 }, { 19, 24 }, { 20, 21 }, { 21, 22 }, { 21, 26 }, { 23, 24 }, { 23, 28 }, { 23, 72 }, { 24, 25 }, { 25, 26 }, { 25, 71 }, { 26, 27 }, { 27, 28 }, { 28, 29 }, { 29, 69 }, { 30, 31 }, { 30, 35 }, { 30, 52 }, { 31, 32 }, { 31, 42 }, { 32, 33 }, { 32, 37 }, { 33, 34 }, { 33, 43 }, { 34, 35 }, { 34, 39 }, { 35, 36 }, { 36, 41 }, { 36, 63 }, { 37, 65 }, { 37, 66 }, { 38, 39 }, { 38, 59 }, { 38, 74 }, { 39, 40 }, { 40, 41 }, { 40, 44 }, { 41, 42 }, { 42, 74 }, { 43, 44 }, { 43, 74 }, { 44, 45 }, { 45, 46 }, { 45, 50 }, { 46, 47 }, { 46, 57 }, { 47, 48 }, { 47, 52 }, { 48, 49 }, { 48, 75 }, { 49, 50 }, { 49, 54 }, { 50, 51 }, { 51, 52 }, { 51, 56 }, { 53, 54 }, { 53, 58 }, { 53, 73 }, { 54, 55 }, { 55, 56 }, { 55, 59 }, { 56, 57 }, { 57, 58 }, { 58, 75 }, { 59, 75 }, { 60, 61 }, { 60, 64 }, { 61, 62 }, { 61, 71 }, { 62, 63 }, { 62, 77 }, { 63, 67 }, { 64, 65 }, { 64, 69 }, { 65, 77 }, { 66, 70 }, { 66, 73 }, { 67, 68 }, { 67, 73 }, { 68, 69 }, { 68, 76 }, { 70, 71 }, { 70, 76 }, { 76, 77 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Errera Graph-----------// /** * @see #generateErreraGraph * @return Errera Graph */ public static Graph erreraGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateErreraGraph(g); return g; } /** * Generates the Errera Graph. The * Errera graph is the 17-node planar graph * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateErreraGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 7 }, { 0, 14 }, { 0, 15 }, { 0, 16 }, { 1, 2 }, { 1, 9 }, { 1, 14 }, { 1, 15 }, { 2, 3 }, { 2, 8 }, { 2, 9 }, { 2, 10 }, { 2, 14 }, { 3, 4 }, { 3, 9 }, { 3, 10 }, { 3, 11 }, { 4, 5 }, { 4, 10 }, { 4, 11 }, { 4, 12 }, { 5, 6 }, { 5, 11 }, { 5, 12 }, { 5, 13 }, { 6, 7 }, { 6, 8 }, { 6, 12 }, { 6, 13 }, { 6, 16 }, { 7, 13 }, { 7, 15 }, { 7, 16 }, { 8, 10 }, { 8, 12 }, { 8, 14 }, { 8, 16 }, { 9, 11 }, { 9, 13 }, { 9, 15 }, { 10, 12 }, { 11, 13 }, { 13, 15 }, { 14, 16 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Folkman Graph-----------// /** * @see #generateFolkmanGraph * @return Folkman Graph */ public static Graph folkmanGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateFolkmanGraph(g); return g; } /** * Generates the Folkman Graph. The * Folkman graph is the 20-vertex 4-regular graph. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateFolkmanGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 3 }, { 0, 13 }, { 0, 15 }, { 1, 2 }, { 1, 6 }, { 1, 8 }, { 2, 3 }, { 2, 17 }, { 2, 19 }, { 3, 6 }, { 3, 8 }, { 4, 5 }, { 4, 7 }, { 4, 17 }, { 4, 19 }, { 5, 6 }, { 5, 10 }, { 5, 12 }, { 6, 7 }, { 7, 10 }, { 7, 12 }, { 8, 9 }, { 8, 11 }, { 9, 10 }, { 9, 14 }, { 9, 16 }, { 10, 11 }, { 11, 14 }, { 11, 16 }, { 12, 13 }, { 12, 15 }, { 13, 14 }, { 13, 18 }, { 14, 15 }, { 15, 18 }, { 16, 17 }, { 16, 19 }, { 17, 18 }, { 18, 19 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Franklin Graph-----------// /** * @see #generateFranklinGraph * @return Franklin Graph */ public static Graph franklinGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateFranklinGraph(g); return g; } /** * Generates the Franklin Graph. * The Franklin graph is the 12-vertex cubic graph. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateFranklinGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 5 }, { 0, 6 }, { 1, 2 }, { 1, 7 }, { 2, 3 }, { 2, 8 }, { 3, 4 }, { 3, 9 }, { 4, 5 }, { 4, 10 }, { 5, 11 }, { 6, 7 }, { 6, 9 }, { 7, 10 }, { 8, 9 }, { 8, 11 }, { 10, 11 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Frucht Graph-----------// /** * @see #generateFruchtGraph * @return Frucht Graph */ public static Graph fruchtGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateFruchtGraph(g); return g; } /** * Generates the Frucht Graph. The * Frucht graph is smallest cubic identity graph. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateFruchtGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 6 }, { 0, 7 }, { 1, 2 }, { 1, 7 }, { 2, 3 }, { 2, 8 }, { 3, 4 }, { 3, 9 }, { 4, 5 }, { 4, 9 }, { 5, 6 }, { 5, 10 }, { 6, 10 }, { 7, 11 }, { 8, 9 }, { 8, 11 }, { 10, 11 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Goldner-Harary Graph-----------// /** * @see #generateGoldnerHararyGraph * @return Goldner-Harary Graph */ public static Graph goldnerHararyGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateGoldnerHararyGraph(g); return g; } /** * Generates the Goldner-Harary * Graph. The Goldner-Harary graph is a graph on 11 vertices and 27. It is a simplicial * graph, meaning that it is polyhedral and consists of only triangular faces. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateGoldnerHararyGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 3 }, { 0, 4 }, { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, { 1, 6 }, { 1, 7 }, { 1, 10 }, { 2, 3 }, { 2, 7 }, { 3, 4 }, { 3, 7 }, { 3, 8 }, { 3, 9 }, { 3, 10 }, { 4, 5 }, { 4, 9 }, { 4, 10 }, { 5, 10 }, { 6, 7 }, { 6, 10 }, { 7, 8 }, { 7, 10 }, { 8, 10 }, { 9, 10 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Heawood Graph-----------// /** * @see #generateHeawoodGraph * @return Heawood Graph */ public static Graph heawoodGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateHeawoodGraph(g); return g; } /** * Generates the Heawood Graph. * Heawood graph is an undirected graph with 14 vertices and 21 edges, named after Percy John * Heawood. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateHeawoodGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 5 }, { 0, 13 }, { 1, 2 }, { 1, 10 }, { 2, 3 }, { 2, 7 }, { 3, 4 }, { 3, 12 }, { 4, 5 }, { 4, 9 }, { 5, 6 }, { 6, 7 }, { 6, 11 }, { 7, 8 }, { 8, 9 }, { 8, 13 }, { 9, 10 }, { 10, 11 }, { 11, 12 }, { 12, 13 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Herschel Graph-----------// /** * @see #generateHerschelGraph * @return Herschel Graph */ public static Graph herschelGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateHerschelGraph(g); return g; } /** * Generates the Herschel Graph. * The Herschel graph is the smallest nonhamiltonian polyhedral graph (Coxeter 1973, p. 8). It * is the unique such graph on 11 nodes and 18 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateHerschelGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 3 }, { 0, 4 }, { 1, 2 }, { 1, 5 }, { 1, 6 }, { 2, 3 }, { 2, 7 }, { 3, 8 }, { 3, 9 }, { 4, 5 }, { 4, 9 }, { 5, 10 }, { 6, 7 }, { 6, 10 }, { 7, 8 }, { 8, 10 }, { 9, 10 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Hoffman Graph-----------// /** * @see #generateHoffmanGraph * @return Hoffman Graph */ public static Graph hoffmanGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateHoffmanGraph(g); return g; } /** * Generates the Hoffman Graph. The * Hoffman graph is the bipartite graph on 16 nodes and 32 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateHoffmanGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 7 }, { 0, 8 }, { 0, 13 }, { 1, 2 }, { 1, 9 }, { 1, 14 }, { 2, 3 }, { 2, 8 }, { 2, 10 }, { 3, 4 }, { 3, 9 }, { 3, 15 }, { 4, 5 }, { 4, 10 }, { 4, 11 }, { 5, 6 }, { 5, 12 }, { 5, 14 }, { 6, 7 }, { 6, 11 }, { 6, 13 }, { 7, 12 }, { 7, 15 }, { 8, 12 }, { 8, 14 }, { 9, 11 }, { 9, 13 }, { 10, 12 }, { 10, 15 }, { 11, 14 }, { 13, 15 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Krackhardt kite Graph-----------// /** * @see #generateKrackhardtKiteGraph * @return Krackhardt kite Graph */ public static Graph krackhardtKiteGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateKrackhardtKiteGraph(g); return g; } /** * Generates the Krackhardt kite * Graph. The Krackhardt kite is the simple graph on 10 nodes and 18 edges. It arises in * social network theory. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateKrackhardtKiteGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 0, 5 }, { 1, 3 }, { 1, 4 }, { 1, 6 }, { 2, 3 }, { 2, 5 }, { 3, 4 }, { 3, 5 }, { 3, 6 }, { 4, 6 }, { 5, 6 }, { 5, 7 }, { 6, 7 }, { 7, 8 }, { 8, 9 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Klein 3-regular Graph-----------// /** * @see #generateKlein3RegularGraph * @return Klein 3-regular Graph */ public static Graph klein3RegularGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateKlein3RegularGraph(g); return g; } /** * Generates the Klein 3-regular Graph. * This graph is a 3-regular graph with 56 vertices and 84 edges, named after Felix Klein. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateKlein3RegularGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 3 }, { 0, 53 }, { 0, 55 }, { 1, 4 }, { 1, 30 }, { 1, 42 }, { 2, 6 }, { 2, 44 }, { 2, 55 }, { 3, 7 }, { 3, 10 }, { 4, 15 }, { 4, 22 }, { 5, 8 }, { 5, 13 }, { 5, 50 }, { 6, 9 }, { 6, 14 }, { 7, 12 }, { 7, 18 }, { 8, 9 }, { 8, 33 }, { 9, 12 }, { 10, 17 }, { 10, 29 }, { 11, 16 }, { 11, 25 }, { 11, 53 }, { 12, 19 }, { 13, 18 }, { 13, 54 }, { 14, 21 }, { 14, 37 }, { 15, 16 }, { 15, 17 }, { 16, 23 }, { 17, 20 }, { 18, 40 }, { 19, 20 }, { 19, 24 }, { 20, 27 }, { 21, 22 }, { 21, 24 }, { 22, 26 }, { 23, 28 }, { 23, 47 }, { 24, 31 }, { 25, 26 }, { 25, 44 }, { 26, 32 }, { 27, 28 }, { 27, 35 }, { 28, 33 }, { 29, 30 }, { 29, 46 }, { 30, 54 }, { 31, 34 }, { 31, 36 }, { 32, 34 }, { 32, 51 }, { 33, 39 }, { 34, 40 }, { 35, 36 }, { 35, 38 }, { 36, 43 }, { 37, 42 }, { 37, 48 }, { 38, 41 }, { 38, 46 }, { 39, 41 }, { 39, 44 }, { 40, 49 }, { 41, 51 }, { 42, 50 }, { 43, 45 }, { 43, 48 }, { 45, 47 }, { 45, 49 }, { 46, 52 }, { 47, 50 }, { 48, 52 }, { 49, 53 }, { 51, 54 }, { 52, 55 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Klein 7-regular Graph-----------// /** * @see #generateKlein7RegularGraph * @return Klein 7-regular Graph */ public static Graph klein7RegularGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateKlein7RegularGraph(g); return g; } /** * Generates the Klein 7-regular Graph. * This graph is a 7-regular graph with 24 vertices and 84 edges, named after Felix Klein. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateKlein7RegularGraph(Graph targetGraph) { vertexMap.clear(); int arr[] = { 0, 1, 2, 3, 4, 5, 6 }; addCycle(targetGraph, arr); int[][] edges = { { 0, 2 }, { 0, 6 }, { 0, 10 }, { 0, 11 }, { 0, 12 }, { 0, 18 }, { 1, 3 }, { 1, 9 }, { 1, 11 }, { 1, 20 }, { 1, 22 }, { 2, 4 }, { 2, 10 }, { 2, 15 }, { 2, 19 }, { 3, 5 }, { 3, 7 }, { 3, 14 }, { 3, 22 }, { 4, 6 }, { 4, 8 }, { 4, 19 }, { 4, 21 }, { 5, 7 }, { 5, 11 }, { 5, 17 }, { 5, 23 }, { 6, 8 }, { 6, 11 }, { 6, 16 }, { 6, 18 }, { 7, 9 }, { 7, 14 }, { 7, 15 }, { 7, 16 }, { 7, 17 }, { 8, 10 }, { 8, 13 }, { 8, 14 }, { 8, 16 }, { 8, 21 }, { 9, 11 }, { 9, 13 }, { 9, 15 }, { 9, 16 }, { 9, 20 }, { 10, 12 }, { 10, 13 }, { 10, 14 }, { 10, 15 }, { 11, 13 }, { 11, 23 }, { 12, 14 }, { 12, 17 }, { 12, 18 }, { 12, 22 }, { 12, 23 }, { 13, 15 }, { 13, 21 }, { 13, 23 }, { 14, 16 }, { 14, 22 }, { 15, 17 }, { 15, 19 }, { 16, 18 }, { 16, 20 }, { 17, 18 }, { 17, 19 }, { 17, 23 }, { 18, 19 }, { 18, 20 }, { 19, 20 }, { 19, 21 }, { 20, 21 }, { 20, 22 }, { 21, 22 }, { 21, 23 }, { 22, 23 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Moser spindle Graph-----------// /** * @see #generateMoserSpindleGraph * @return Moser spindle Graph */ public static Graph moserSpindleGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateMoserSpindleGraph(g); return g; } /** * Generates the Moser spindle * Graph. The Moser spindle is the 7-node unit-distance graph. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateMoserSpindleGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 4 }, { 0, 5 }, { 0, 6 }, { 1, 2 }, { 1, 5 }, { 2, 3 }, { 2, 5 }, { 3, 4 }, { 3, 6 }, { 4, 6 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Pappus Graph-----------// /** * @see #generatePappusGraph * @return Pappus Graph */ public static Graph pappusGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generatePappusGraph(g); return g; } /** * Generates the Pappus Graph. The * Pappus Graph is a bipartite 3-regular undirected graph with 18 vertices and 27 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generatePappusGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 5 }, { 0, 6 }, { 1, 2 }, { 1, 7 }, { 2, 3 }, { 2, 8 }, { 3, 4 }, { 3, 9 }, { 4, 5 }, { 4, 10 }, { 5, 11 }, { 6, 13 }, { 6, 17 }, { 7, 12 }, { 7, 14 }, { 8, 13 }, { 8, 15 }, { 9, 14 }, { 9, 16 }, { 10, 15 }, { 10, 17 }, { 11, 12 }, { 11, 16 }, { 12, 15 }, { 13, 16 }, { 14, 17 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Poussin Graph-----------// /** * @see #generatePoussinGraph * @return Poussin Graph */ public static Graph poussinGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generatePoussinGraph(g); return g; } /** * Generates the Poussin Graph. The * Poussin graph is the 15-node planar graph. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generatePoussinGraph(Graph targetGraph) { vertexMap.clear(); int arr[] = { 0, 1, 2, 3, 4, 5, 6 }; addCycle(targetGraph, arr); int arr1[] = { 9, 10, 11, 12, 13, 14 }; addCycle(targetGraph, arr1); int[][] edges = { { 0, 2 }, { 0, 4 }, { 0, 5 }, { 1, 6 }, { 1, 7 }, { 2, 4 }, { 2, 7 }, { 2, 8 }, { 3, 5 }, { 3, 8 }, { 3, 9 }, { 3, 13 }, { 5, 9 }, { 5, 10 }, { 6, 7 }, { 6, 10 }, { 6, 11 }, { 7, 8 }, { 7, 11 }, { 7, 12 }, { 8, 12 }, { 8, 13 }, { 9, 13 }, { 10, 14 }, { 11, 14 }, { 12, 14 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Schläfli Graph-----------// /** * Generates the Schläfli Graph. * The Schläfli graph is a strongly regular graph on 27 nodes * * @return the Schläfli Graph */ public static Graph schläfliGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateSchläfliGraph(g); return g; } /** * Generates the Schläfli Graph. * The Schläfli graph is a strongly regular graph on 27 nodes * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateSchläfliGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 11 }, { 0, 12 }, { 0, 13 }, { 0, 14 }, { 0, 15 }, { 0, 16 }, { 0, 17 }, { 0, 18 }, { 0, 19 }, { 0, 20 }, { 0, 21 }, { 0, 22 }, { 0, 23 }, { 0, 24 }, { 0, 25 }, { 0, 26 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, { 1, 6 }, { 1, 7 }, { 1, 8 }, { 1, 9 }, { 1, 10 }, { 1, 19 }, { 1, 20 }, { 1, 21 }, { 1, 22 }, { 1, 23 }, { 1, 24 }, { 1, 25 }, { 1, 26 }, { 2, 3 }, { 2, 4 }, { 2, 5 }, { 2, 6 }, { 2, 7 }, { 2, 8 }, { 2, 9 }, { 2, 10 }, { 2, 11 }, { 2, 12 }, { 2, 13 }, { 2, 14 }, { 2, 15 }, { 2, 16 }, { 2, 17 }, { 2, 18 }, { 3, 5 }, { 3, 6 }, { 3, 7 }, { 3, 8 }, { 3, 9 }, { 3, 10 }, { 3, 15 }, { 3, 16 }, { 3, 17 }, { 3, 18 }, { 3, 23 }, { 3, 24 }, { 3, 25 }, { 3, 26 }, { 4, 5 }, { 4, 6 }, { 4, 7 }, { 4, 8 }, { 4, 9 }, { 4, 10 }, { 4, 11 }, { 4, 12 }, { 4, 13 }, { 4, 14 }, { 4, 19 }, { 4, 20 }, { 4, 21 }, { 4, 22 }, { 5, 7 }, { 5, 8 }, { 5, 9 }, { 5, 10 }, { 5, 13 }, { 5, 14 }, { 5, 17 }, { 5, 18 }, { 5, 21 }, { 5, 22 }, { 5, 25 }, { 5, 26 }, { 6, 7 }, { 6, 8 }, { 6, 9 }, { 6, 10 }, { 6, 11 }, { 6, 12 }, { 6, 15 }, { 6, 16 }, { 6, 19 }, { 6, 20 }, { 6, 23 }, { 6, 24 }, { 7, 9 }, { 7, 10 }, { 7, 12 }, { 7, 14 }, { 7, 16 }, { 7, 18 }, { 7, 20 }, { 7, 22 }, { 7, 24 }, { 7, 26 }, { 8, 9 }, { 8, 10 }, { 8, 11 }, { 8, 13 }, { 8, 15 }, { 8, 17 }, { 8, 19 }, { 8, 21 }, { 8, 23 }, { 8, 25 }, { 9, 12 }, { 9, 13 }, { 9, 15 }, { 9, 18 }, { 9, 19 }, { 9, 22 }, { 9, 24 }, { 9, 25 }, { 10, 11 }, { 10, 14 }, { 10, 16 }, { 10, 17 }, { 10, 20 }, { 10, 21 }, { 10, 23 }, { 10, 26 }, { 11, 12 }, { 11, 13 }, { 11, 14 }, { 11, 15 }, { 11, 16 }, { 11, 17 }, { 11, 19 }, { 11, 20 }, { 11, 21 }, { 11, 23 }, { 12, 13 }, { 12, 14 }, { 12, 15 }, { 12, 16 }, { 12, 18 }, { 12, 19 }, { 12, 20 }, { 12, 22 }, { 12, 24 }, { 13, 14 }, { 13, 15 }, { 13, 17 }, { 13, 18 }, { 13, 19 }, { 13, 21 }, { 13, 22 }, { 13, 25 }, { 14, 16 }, { 14, 17 }, { 14, 18 }, { 14, 20 }, { 14, 21 }, { 14, 22 }, { 14, 26 }, { 15, 16 }, { 15, 17 }, { 15, 18 }, { 15, 19 }, { 15, 23 }, { 15, 24 }, { 15, 25 }, { 16, 17 }, { 16, 18 }, { 16, 20 }, { 16, 23 }, { 16, 24 }, { 16, 26 }, { 17, 18 }, { 17, 21 }, { 17, 23 }, { 17, 25 }, { 17, 26 }, { 18, 22 }, { 18, 24 }, { 18, 25 }, { 18, 26 }, { 19, 20 }, { 19, 21 }, { 19, 22 }, { 19, 23 }, { 19, 24 }, { 19, 25 }, { 20, 21 }, { 20, 22 }, { 20, 23 }, { 20, 24 }, { 20, 26 }, { 21, 22 }, { 21, 23 }, { 21, 25 }, { 21, 26 }, { 22, 24 }, { 22, 25 }, { 22, 26 }, { 23, 24 }, { 23, 25 }, { 23, 26 }, { 24, 25 }, { 24, 26 }, { 25, 26 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Tietze Graph-----------// /** * @see #generateTietzeGraph * @return Tietze Graph */ public static Graph tietzeGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateTietzeGraph(g); return g; } /** * Generates the Tietze Graph. The * Tietze Graph is an undirected cubic graph with 12 vertices and 18 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateTietzeGraph(Graph targetGraph) { vertexMap.clear(); int arr[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 }; addCycle(targetGraph, arr); int[][] edges = { { 0, 9 }, { 1, 5 }, { 2, 7 }, { 3, 10 }, { 4, 8 }, { 6, 11 }, { 9, 10 }, { 9, 11 }, { 10, 11 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Thomsen Graph-----------// /** * @see #generateThomsenGraph * @return Thomsen Graph */ public static Graph thomsenGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateThomsenGraph(g); return g; } /** * Generates the Thomsen Graph. The * Thomsen Graph is complete bipartite graph consisting of 6 vertices (3 vertices in each * bipartite partition. It is also called the Utility graph. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateThomsenGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 3 }, { 0, 4 }, { 0, 5 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, { 2, 3 }, { 2, 4 }, { 2, 5 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Tutte Graph-----------// /** * @see #generateTutteGraph * @return Tutte Graph */ public static Graph tutteGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(DefaultEdge.class) .buildGraph(); new NamedGraphGenerator().generateTutteGraph(g); return g; } /** * Generates the Tutte Graph. The Tutte * Graph is a 3-regular graph with 46 vertices and 69 edges. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateTutteGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 16 }, { 0, 31 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 5 }, { 3, 4 }, { 3, 7 }, { 4, 9 }, { 5, 6 }, { 5, 10 }, { 6, 7 }, { 6, 11 }, { 7, 8 }, { 8, 9 }, { 8, 12 }, { 9, 15 }, { 10, 11 }, { 10, 13 }, { 11, 12 }, { 12, 14 }, { 13, 14 }, { 13, 30 }, { 14, 15 }, { 15, 43 }, { 16, 17 }, { 16, 19 }, { 17, 18 }, { 17, 20 }, { 18, 19 }, { 18, 22 }, { 19, 24 }, { 20, 21 }, { 20, 25 }, { 21, 22 }, { 21, 26 }, { 22, 23 }, { 23, 24 }, { 23, 27 }, { 24, 30 }, { 25, 26 }, { 25, 28 }, { 26, 27 }, { 27, 29 }, { 28, 29 }, { 28, 45 }, { 29, 30 }, { 31, 32 }, { 31, 34 }, { 32, 33 }, { 32, 35 }, { 33, 34 }, { 33, 37 }, { 34, 39 }, { 35, 36 }, { 35, 40 }, { 36, 37 }, { 36, 41 }, { 37, 38 }, { 38, 39 }, { 38, 42 }, { 39, 45 }, { 40, 41 }, { 40, 43 }, { 41, 42 }, { 42, 44 }, { 43, 44 }, { 44, 45 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // -------------Zachary's Karate Club Graph-----------// /** * Generates the Zachary's * karate club Graph. * * @param targetGraph receives the generated edges and vertices; if this is non-empty on entry, * the result will be a disconnected graph since generated elements will not be connected * to existing elements */ public void generateZacharyKarateClubGraph(Graph targetGraph) { vertexMap.clear(); int[][] edges = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 0, 4 }, { 0, 5 }, { 0, 6 }, { 0, 7 }, { 0, 8 }, { 0, 10 }, { 0, 11 }, { 0, 12 }, { 0, 13 }, { 0, 17 }, { 0, 19 }, { 0, 21 }, { 0, 31 }, { 1, 2 }, { 1, 3 }, { 1, 7 }, { 1, 13 }, { 1, 17 }, { 1, 19 }, { 1, 21 }, { 1, 30 }, { 2, 3 }, { 2, 7 }, { 2, 8 }, { 2, 9 }, { 2, 13 }, { 2, 27 }, { 2, 28 }, { 2, 32 }, { 3, 7 }, { 3, 12 }, { 3, 13 }, { 4, 6 }, { 4, 10 }, { 5, 6 }, { 5, 10 }, { 5, 16 }, { 6, 16 }, { 8, 30 }, { 8, 32 }, { 8, 33 }, { 9, 33 }, { 13, 33 }, { 14, 32 }, { 14, 33 }, { 15, 32 }, { 15, 33 }, { 18, 32 }, { 18, 33 }, { 19, 33 }, { 20, 32 }, { 20, 33 }, { 22, 32 }, { 22, 33 }, { 23, 25 }, { 23, 27 }, { 23, 29 }, { 23, 32 }, { 23, 33 }, { 24, 25 }, { 24, 27 }, { 24, 31 }, { 25, 31 }, { 26, 29 }, { 26, 33 }, { 27, 33 }, { 28, 31 }, { 28, 33 }, { 29, 32 }, { 29, 33 }, { 30, 32 }, { 30, 33 }, { 31, 32 }, { 31, 33 }, { 32, 33 } }; for (int[] edge : edges) addEdge(targetGraph, edge[0], edge[1]); } // --------------Helper methods-----------------/ private V addVertex(Graph targetGraph, int i) { if (!vertexMap.containsKey(i)) { vertexMap.put(i, targetGraph.addVertex()); } return vertexMap.get(i); } private void addEdge(Graph targetGraph, int i, int j) { V u = addVertex(targetGraph, i); V v = addVertex(targetGraph, j); targetGraph.addEdge(u, v); } private void addCycle(Graph targetGraph, int array[]) { for (int i = 0; i < array.length; i++) addEdge(targetGraph, array[i], array[(i + 1) % array.length]); } } PlantedPartitionGraphGenerator.java000066400000000000000000000265451402514743400345710ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2018-2021, by Emilio Cruciani and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * Create a random $l$-planted partition graph. An $l$-planted partition graph is a random graph on * $n = l \cdot k$ vertices subdivided in $l$ groups with $k$ vertices each. Vertices within the * same group are connected by an edge with probability $p$, while vertices belonging to different * groups are connected by an edge with probability $q$. * *

    * The $l$-planted partition model is a special case of the * Stochastic Block Model. If the * probability matrix is a constant, in the sense that $P_{ij}=p$ for all $i,j$, then the result is * the Erdős–Rényi model $\mathcal G(n,p)$. This case is degenerate—the partition into communities * becomes irrelevant— but it illustrates a close relationship to the Erdős–Rényi model. * * For more information on planted graphs, refer to: *

      *
    1. Condon, A. Karp, R.M. Algorithms for graph partitioning on the planted partition model, * Random Structures and Algorithms, Volume 18, Issue 2, p.116-140, 2001
    2. *
    3. Fortunato, S. Community Detection in Graphs, Physical Reports Volume 486, Issue 3-5 p. * 75-174, 2010
    4. *
    * * @param the graph vertex type * @param the graph edge type * * @author Emilio Cruciani */ public class PlantedPartitionGraphGenerator implements GraphGenerator { private static final boolean DEFAULT_ALLOW_SELFLOOPS = false; private final int l; private final int k; private final double p; private final double q; private final Random rng; private final boolean selfLoops; private boolean fired; private List> communities; /** * Construct a new PlantedPartitionGraphGenerator. * * @param l number of groups * @param k number of nodes in each group * @param p probability of connecting vertices within a group * @param q probability of connecting vertices between groups * @throws IllegalArgumentException if number of groups is negative * @throws IllegalArgumentException if number of nodes in each group is negative * @throws IllegalArgumentException if p is not in [0,1] * @throws IllegalArgumentException if q is not in [0,1] */ public PlantedPartitionGraphGenerator(int l, int k, double p, double q) { this(l, k, p, q, new Random(), DEFAULT_ALLOW_SELFLOOPS); } /** * Construct a new PlantedPartitionGraphGenerator. * * @param l number of groups * @param k number of nodes in each group * @param p probability of connecting vertices within a group * @param q probability of connecting vertices between groups * @param selfLoops true if the graph allows self loops * @throws IllegalArgumentException if number of groups is negative * @throws IllegalArgumentException if number of nodes in each group is negative * @throws IllegalArgumentException if p is not in [0,1] * @throws IllegalArgumentException if q is not in [0,1] */ public PlantedPartitionGraphGenerator(int l, int k, double p, double q, boolean selfLoops) { this(l, k, p, q, new Random(), selfLoops); } /** * Construct a new PlantedPartitionGraphGenerator. * * @param l number of groups * @param k number of nodes in each group * @param p probability of connecting vertices within a group * @param q probability of connecting vertices between groups * @param seed seed for the random number generator * @throws IllegalArgumentException if number of groups is negative * @throws IllegalArgumentException if number of nodes in each group is negative * @throws IllegalArgumentException if p is not in [0,1] * @throws IllegalArgumentException if q is not in [0,1] */ public PlantedPartitionGraphGenerator(int l, int k, double p, double q, long seed) { this(l, k, p, q, new Random(seed), DEFAULT_ALLOW_SELFLOOPS); } /** * Construct a new PlantedPartitionGraphGenerator. * * @param l number of groups * @param k number of nodes in each group * @param p probability of connecting vertices within a group * @param q probability of connecting vertices between groups * @param seed seed for the random number generator * @param selfLoops true if the graph allows self loops * @throws IllegalArgumentException if number of groups is negative * @throws IllegalArgumentException if number of nodes in each group is negative * @throws IllegalArgumentException if p is not in [0,1] * @throws IllegalArgumentException if q is not in [0,1] */ public PlantedPartitionGraphGenerator( int l, int k, double p, double q, long seed, boolean selfLoops) { this(l, k, p, q, new Random(seed), selfLoops); } /** * Construct a new PlantedPartitionGraphGenerator. * * @param l number of groups * @param k number of nodes in each group * @param p probability of connecting vertices within a group * @param q probability of connecting vertices between groups * @param rng random number generator * @param selfLoops true if the graph allows self loops * @throws IllegalArgumentException if number of groups is negative * @throws IllegalArgumentException if number of nodes in each group is negative * @throws IllegalArgumentException if p is not in [0,1] * @throws IllegalArgumentException if q is not in [0,1] */ public PlantedPartitionGraphGenerator( int l, int k, double p, double q, Random rng, boolean selfLoops) { if (l < 0) { throw new IllegalArgumentException("number of groups must be non-negative"); } if (k < 0) { throw new IllegalArgumentException( "number of nodes in each group must be non-negative"); } if (p < 0 || p > 1) { throw new IllegalArgumentException("invalid probability p"); } if (q < 0 || q > 1) { throw new IllegalArgumentException("invalid probability q"); } this.l = l; this.k = k; this.p = p; this.q = q; this.rng = rng; this.selfLoops = selfLoops; this.fired = false; } /** * Generate an $l$-planted partition graph. * * Note that the method can be called only once. Must instantiate another * PlantedPartitionGraphGenerator object in order to generate another $l$-planted partition * graph. * * @param target target graph * @param resultMap result map * @throws IllegalArgumentException if target is directed * @throws IllegalArgumentException if self loops are requested but target does not allow them * @throws IllegalStateException if generateGraph() is called more than once */ @Override public void generateGraph(Graph target, Map resultMap) { if (fired) { throw new IllegalStateException("generateGraph() can be only called once"); } this.fired = true; // instantiate community structure communities = new ArrayList<>(this.l); for (int i = 0; i < this.l; i++) { communities.add(CollectionUtil.newLinkedHashSetWithExpectedSize(this.k)); } // empty graph case if (this.l == 0 || this.k == 0) { return; } // number of nodes int n = this.k * this.l; // integer to vertices List vertices = new ArrayList<>(n); for (int i = 0; i < n; i++) { V vertex = target.addVertex(); vertices.add(vertex); // populate community structure int lv = i / this.k; // group of node v communities.get(lv).add(vertex); } // add self loops if (this.selfLoops) { if (target.getType().isAllowingSelfLoops()) { for (V v : vertices) { if (this.rng.nextDouble() < this.p) { target.addEdge(v, v); } } } else { throw new IllegalArgumentException("target graph must allow self-loops"); } } // undirected edges if (target.getType().isUndirected()) { for (int i = 0; i < n; i++) { int li = i / this.k; // group of node i for (int j = i + 1; j < n; j++) { int lj = j / this.k; // group of node j // edge within partition if (li == lj) { if (this.rng.nextDouble() < this.p) { target.addEdge(vertices.get(i), vertices.get(j)); } } // edge between partitions else { if (this.rng.nextDouble() < this.q) { target.addEdge(vertices.get(i), vertices.get(j)); } } } } } // directed edges else { for (int i = 0; i < n; i++) { int li = i / this.k; // group of node i for (int j = i + 1; j < n; j++) { int lj = j / this.k; // group of node j // edge within partition if (li == lj) { if (this.rng.nextDouble() < this.p) { target.addEdge(vertices.get(i), vertices.get(j)); } if (this.rng.nextDouble() < this.p) { target.addEdge(vertices.get(j), vertices.get(i)); } } // edge between partitions else { if (this.rng.nextDouble() < this.q) { target.addEdge(vertices.get(i), vertices.get(j)); } if (this.rng.nextDouble() < this.q) { target.addEdge(vertices.get(j), vertices.get(i)); } } } } } } /** * Get the community structure of the graph. The method returns a list of communities, * represented as sets of nodes. * * @throws IllegalStateException if getCommunities() is called before generating the graph * @return the community structure of the graph */ public List> getCommunities() { if (communities == null) throw new IllegalStateException( "must generate graph before getting community structure"); return communities; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/PruferTreeGenerator.java000066400000000000000000000151151402514743400324570ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generates a random tree using Prüfer sequences. * *

    * A Prüfer sequence of length $n$ is randomly generated and converted into the corresponding tree. *

    * *

    * This implementation is inspired by "X. Wang, L. Wang and Y. Wu, "An Optimal Algorithm for Prufer * Codes," Journal of Software Engineering and Applications, Vol. 2 No. 2, 2009, pp. 111-115. doi: * 10.4236/jsea.2009.22016." and has a running time of $O(n)$. *

    * * @param the graph vertex type * @param the graph edge type * * @author Alexandru Valeanu */ public class PruferTreeGenerator implements GraphGenerator { // number of vertices private final int n; // random number generator private final Random rng; // input Prufer sequence private final int[] inputPruferSeq; /** * Construct a new PruferTreeGenerator from an input Prüfer sequence. Note that the size of the * generated tree will be $l+2$ where $l$ is the length of the input sequence. The Prüfer * sequence must contain integers between $0$ and $l+1$ (inclusive). * * Note: In this case, the same tree will be generated every time. * * @param pruferSequence the input Prüfer sequence * @throws IllegalArgumentException if {@code n} is ≤ 0 * @throws IllegalArgumentException if {@code pruferSequence} is {@code null} * @throws IllegalArgumentException if {@code pruferSequence} is invalid. */ public PruferTreeGenerator(int[] pruferSequence) { if (Objects.isNull(pruferSequence)) { throw new IllegalArgumentException("pruferSequence cannot be null"); } this.n = pruferSequence.length + 2; this.rng = null; this.inputPruferSeq = pruferSequence.clone(); if (n <= 0) { throw new IllegalArgumentException("n must be greater than 0"); } for (int i = 0; i < n - 2; i++) { if (pruferSequence[i] < 0 || pruferSequence[i] >= n) { throw new IllegalArgumentException("invalid pruferSequence"); } } } /** * Construct a new PruferTreeGenerator. * * @param n number of vertices to be generated * @throws IllegalArgumentException if {@code n} is ≤ 0 */ public PruferTreeGenerator(int n) { this(n, new Random()); } /** * Construct a new PruferTreeGenerator. * * @param n number of vertices to be generated * @param seed seed for the random number generator * @throws IllegalArgumentException if {@code n} is ≤ 0 */ public PruferTreeGenerator(int n, long seed) { this(n, new Random(seed)); } /** * Construct a new PruferTreeGenerator * * @param n number of vertices to be generated * @param rng the random number generator to use * @throws IllegalArgumentException if {@code n} is ≤ 0 * @throws NullPointerException if {@code rng} is {@code null} */ public PruferTreeGenerator(int n, Random rng) { if (n <= 0) { throw new IllegalArgumentException("n must be greater than 0"); } this.n = n; this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); this.inputPruferSeq = null; } /** * Generates a tree. * *

    * Note: An exception will be thrown if the target graph is not empty (i.e. contains at least * one vertex) *

    * * @param target the target graph * @param resultMap not used by this generator, can be null * @throws NullPointerException if {@code target} is {@code null} * @throws IllegalArgumentException if {@code target} is not undirected * @throws IllegalArgumentException if {@code target} is not empty */ @Override public void generateGraph(Graph target, Map resultMap) { GraphTests.requireUndirected(target); if (!target.vertexSet().isEmpty()) { throw new IllegalArgumentException("target graph is not empty"); } List vertexList = new ArrayList<>(n); // add vertices for (int i = 0; i < n; i++) { vertexList.add(target.addVertex()); } // base case if (n == 1) { return; } // degree stores the remaining degree (plus one) for each node. The // degree of a node in the decoded tree is one more than the number // of times it appears in the code. int[] degree = new int[n]; Arrays.fill(degree, 1); int[] pruferSeq; if (inputPruferSeq == null) { pruferSeq = new int[n - 2]; for (int i = 0; i < n - 2; i++) { pruferSeq[i] = rng.nextInt(n); ++degree[pruferSeq[i]]; } } else { pruferSeq = inputPruferSeq; } int index = -1; for (int k = 0; k < n; k++) { if (degree[k] == 1) { index = k; break; } } assert index != -1; int x = index; // set of nodes without a parent Set orphans = new HashSet<>(target.vertexSet()); for (int i = 0; i < n - 2; i++) { int y = pruferSeq[i]; orphans.remove(vertexList.get(x)); target.addEdge(vertexList.get(x), vertexList.get(y)); --degree[y]; if (y < index && degree[y] == 1) { x = y; } else { for (int k = index + 1; k < n; k++) { if (degree[k] == 1) { index = x = k; break; } } } } assert orphans.size() == 2; Iterator iterator = orphans.iterator(); V u = iterator.next(); V v = iterator.next(); target.addEdge(u, v); } } RandomRegularGraphGenerator.java000066400000000000000000000221141402514743400340360ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2018-2021, by Emilio Cruciani and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * Generate a random $d$-regular undirected graph with $n$ vertices. A regular graph is a graph * where each vertex has the same degree, i.e. the same number of neighbors. * *

    * The algorithm for the simple case, proposed in [SW99] and extending the one for the non-simple * case [W99], runs in expected $\mathcal{O}(nd^2)$ time. It has been proved in [KV03] to sample * from the space of random d-regular graphs in a way which is asymptotically uniform at random when * $d = \mathcal{O}(n^{1/3 - \epsilon})$. * *

    * [KV03] Kim, Jeong Han, and Van H. Vu. "Generating random regular graphs." Proceedings of the * thirty-fifth annual ACM symposium on Theory of computing. ACM, 2003. * * [SW99] Steger, Angelika, and Nicholas C. Wormald. "Generating random regular graphs quickly." * Combinatorics, Probability and Computing 8.4 (1999): 377-396. * * [W99] Wormald, Nicholas C. "Models of random regular graphs." London Mathematical Society Lecture * Note Series (1999): 239-298. * * @author Emilio Cruciani * * @param graph node type * @param graph edge type */ public class RandomRegularGraphGenerator implements GraphGenerator { private final int n; private final int d; private final Random rng; /** * Construct a new RandomRegularGraphGenerator. * * @param n number of nodes * @param d degree of nodes * @throws IllegalArgumentException if number of nodes is negative * @throws IllegalArgumentException if degree is negative * @throws IllegalArgumentException if degree is greater than number of nodes * @throws IllegalArgumentException if the value "n * d" is odd */ public RandomRegularGraphGenerator(int n, int d) { this(n, d, new Random()); } /** * Construct a new RandomRegularGraphGenerator. * * @param n number of nodes * @param d degree of nodes * @param seed seed for the random number generator * @throws IllegalArgumentException if number of nodes is negative * @throws IllegalArgumentException if degree is negative * @throws IllegalArgumentException if degree is greater than number of nodes * @throws IllegalArgumentException if the value "n * d" is odd */ public RandomRegularGraphGenerator(int n, int d, long seed) { this(n, d, new Random(seed)); } /** * Construct a new RandomRegularGraphGenerator. * * @param n number of nodes * @param d degree of nodes * @param rng the random number generator to use * @throws IllegalArgumentException if number of nodes is negative * @throws IllegalArgumentException if degree is negative * @throws IllegalArgumentException if degree is greater than number of nodes * @throws IllegalArgumentException if the value "n * d" is odd */ public RandomRegularGraphGenerator(int n, int d, Random rng) { if (n < 0) { throw new IllegalArgumentException("number of nodes must be non-negative"); } if (d < 0) { throw new IllegalArgumentException("degree of nodes must be non-negative"); } if (d > n) { throw new IllegalArgumentException( "degree of nodes must be smaller than or equal to number of nodes"); } if ((n * d) % 2 != 0) { throw new IllegalArgumentException("value 'n * d' must be even"); } this.n = n; this.d = d; this.rng = rng; } /** * Generate a random regular graph. * * @param target the target graph * @param resultMap the result map * @throws IllegalArgumentException if target is not an undirected graph * @throws IllegalArgumentException if "n == d" and the graph is simple */ @Override public void generateGraph(Graph target, Map resultMap) { if (!target.getType().isUndirected()) { throw new IllegalArgumentException("target graph must be undirected"); } if (target.getType().isSimple()) { // simple case if (n == 0 || d == 0) { // no nodes or zero degree case new EmptyGraphGenerator(n).generateGraph(target); } else if (d == n) { throw new IllegalArgumentException("target graph must be simple if 'n == d'"); } else if (d == n - 1) { // complete case new CompleteGraphGenerator(n).generateGraph(target); } else { // general case generateSimpleRegularGraph(target); } } else { // non-simple case generateNonSimpleRegularGraph(target); } } /* * Auxiliary method to check if there are remaining suitable edges, in the simple regular graph * generator. */ private boolean suitable( Set> edges, Map potentialEdges) { if (potentialEdges.isEmpty()) { return true; } Integer[] keys = potentialEdges.keySet().toArray(new Integer[0]); Arrays.sort(keys); for (int i = 0; i < keys.length; i++) { int s2 = keys[i]; for (int j = 0; j < i; j++) { int s1 = keys[j]; Map.Entry e = new AbstractMap.SimpleImmutableEntry<>(s1, s2); if (!edges.contains(e)) { return true; } } } return false; } /* * Generate simple regular graph */ private void generateSimpleRegularGraph(Graph target) { // integers to vertices List vertices = new ArrayList<>(n); for (int i = 0; i < n; i++) { vertices.add(target.addVertex()); } // set of final edges to add to target graph Set> edges = CollectionUtil.newHashSetWithExpectedSize(n * d); do { List stubs = new ArrayList<>(n * d); for (int i = 0; i < n * d; i++) { stubs.add(i % n); } while (!stubs.isEmpty()) { Map potentialEdges = new HashMap<>(); Collections.shuffle(stubs, rng); for (int i = 0; i < stubs.size() - 1; i += 2) { int s1 = stubs.get(i); int s2 = stubs.get(i + 1); // s1 < s2 has to be true if (s1 > s2) { int temp = s1; s1 = s2; s2 = temp; } Map.Entry edge = new AbstractMap.SimpleImmutableEntry<>(s1, s2); if (s1 != s2 && !edges.contains(edge)) { edges.add(edge); } else { potentialEdges.put(s1, potentialEdges.getOrDefault(s1, 0) + 1); potentialEdges.put(s2, potentialEdges.getOrDefault(s2, 0) + 1); } } if (!suitable(edges, potentialEdges)) { edges.clear(); break; } stubs.clear(); for (Map.Entry e : potentialEdges.entrySet()) { int node = e.getKey(); int potential = e.getValue(); for (int i = 0; i < potential; i++) { stubs.add(node); } } } } while (edges.isEmpty()); // add edges to target for (Map.Entry e : edges) { target.addEdge(vertices.get(e.getKey()), vertices.get(e.getValue())); } } /* * Generate non-simple regular graph. */ private void generateNonSimpleRegularGraph(Graph target) { List vertices = new ArrayList<>(n * d); for (int i = 0; i < n; i++) { V vertex = target.addVertex(); for (int j = 0; j < d; j++) { vertices.add(vertex); } } Collections.shuffle(vertices, rng); for (int i = 0; i < (n * d) / 2; i++) { V u = vertices.get(2 * i); V v = vertices.get(2 * i + 1); target.addEdge(u, v); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/RingGraphGenerator.java000066400000000000000000000040401402514743400322500ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generates a ring graph of any size. A ring graph is a graph that contains a single cycle that * passes through all its vertices exactly once. For a directed graph, the generated edges are * oriented consistently around the ring. * * @param the graph vertex type * @param the graph edge type * * @author John V. Sichi */ public class RingGraphGenerator implements GraphGenerator { private final int size; /** * Construct a new RingGraphGenerator. * * @param size number of vertices to be generated * * @throws IllegalArgumentException if the specified size is negative. */ public RingGraphGenerator(int size) { if (size < 0) { throw new IllegalArgumentException("must be non-negative"); } this.size = size; } /** * {@inheritDoc} */ @Override public void generateGraph(Graph target, Map resultMap) { if (size < 1) { return; } Map privateMap = new HashMap<>(); new LinearGraphGenerator(size).generateGraph(target, privateMap); V startVertex = privateMap.get(LinearGraphGenerator.START_VERTEX); V endVertex = privateMap.get(LinearGraphGenerator.END_VERTEX); target.addEdge(endVertex, startVertex); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/ScaleFreeGraphGenerator.java000066400000000000000000000072541402514743400332140ustar00rootroot00000000000000/* * (C) Copyright 2008-2021, by Ilya Razenshteyn and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generates directed or undirected * scale-free network of any * size. Scale-free network is a connected graph, where degrees of vertices are distributed in * unusual way. There are many vertices with small degrees and only small amount of vertices with * big degrees. * * @param the graph vertex type * @param the graph edge type * * @author Ilya Razenshteyn */ public class ScaleFreeGraphGenerator implements GraphGenerator { private final int size; private final Random rng; /** * Constructor * * @param size number of vertices to be generated */ public ScaleFreeGraphGenerator(int size) { this(size, new Random()); } /** * Constructor * * @param size number of vertices to be generated * @param seed initial seed for the random generator */ public ScaleFreeGraphGenerator(int size, long seed) { this(size, new Random(seed)); } /** * Constructor * * @param size number of vertices to be generated * @param rng the random number generator */ public ScaleFreeGraphGenerator(int size, Random rng) { if (size < 0) { throw new IllegalArgumentException("invalid size: " + size + " (must be non-negative)"); } this.size = size; this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); } /** * Generates scale-free network with size passed to the constructor. * * @param target receives the generated edges and vertices; if this is non-empty on entry, the * result will be a disconnected graph since generated elements will not be connected to * existing elements * @param resultMap unused parameter, can be null */ @Override public void generateGraph(Graph target, Map resultMap) { List vertexList = new ArrayList<>(); List degrees = new ArrayList<>(); int degreeSum = 0; for (int i = 0; i < size; i++) { V newVertex = target.addVertex(); int newDegree = 0; while ((newDegree == 0) && (i != 0)) // we want our graph to be connected { for (int j = 0; j < vertexList.size(); j++) { if ((degreeSum == 0) || (rng.nextInt(degreeSum) < degrees.get(j))) { degrees.set(j, degrees.get(j) + 1); newDegree++; degreeSum += 2; if (rng.nextBoolean()) { target.addEdge(vertexList.get(j), newVertex); } else { target.addEdge(newVertex, vertexList.get(j)); } } } } vertexList.add(newVertex); degrees.add(newDegree); } } } SimpleWeightedBipartiteGraphMatrixGenerator.java000066400000000000000000000056461402514743400372520ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2016-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * A simple weighted bipartite graph matrix generator. * * @param the graph vertex type * @param the graph edge type */ public class SimpleWeightedBipartiteGraphMatrixGenerator implements GraphGenerator { protected List first; protected List second; protected double[][] weights; /** * Set the first partition of the generator. * * @param first the first partition * @return the generator */ public SimpleWeightedBipartiteGraphMatrixGenerator first(List first) { this.first = new ArrayList<>(first); return this; } /** * Set the second partition of the generator. * * @param second the second partition * @return the generator */ public SimpleWeightedBipartiteGraphMatrixGenerator second(List second) { this.second = new ArrayList<>(second); return this; } /** * Set the weights of the generator. * * @param weights the weights * @return the generator */ public SimpleWeightedBipartiteGraphMatrixGenerator weights(double[][] weights) { this.weights = weights; return this; } /** * {@inheritDoc} */ @Override public void generateGraph(Graph target, Map resultMap) { if (weights == null) { throw new IllegalArgumentException( "Graph may not be constructed without weight-matrix specified"); } if ((first == null) || (second == null)) { throw new IllegalArgumentException( "Graph may not be constructed without either of vertex-set partitions specified"); } assert second.size() == weights.length; for (V vertex : first) { target.addVertex(vertex); } for (V vertex : second) { target.addVertex(vertex); } for (int i = 0; i < first.size(); ++i) { assert first.size() == weights[i].length; for (int j = 0; j < second.size(); ++j) { target.setEdgeWeight(target.addEdge(first.get(i), second.get(j)), weights[i][j]); } } } } SimpleWeightedGraphMatrixGenerator.java000066400000000000000000000047471402514743400354070ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2016-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * A simple weighted graph matrix generator. * * @param the graph vertex type * @param the graph edge type */ public class SimpleWeightedGraphMatrixGenerator implements GraphGenerator { protected List vertices; protected double[][] weights; /** * Set the generator vertices. * * @param vertices the graph vertices * @return the generator */ public SimpleWeightedGraphMatrixGenerator vertices(List vertices) { this.vertices = vertices; return this; } /** * Set the weights of the generator. * * @param weights the weights * @return the generator */ public SimpleWeightedGraphMatrixGenerator weights(double[][] weights) { this.weights = weights; return this; } @Override public void generateGraph(Graph target, Map resultMap) { if (weights == null) { throw new IllegalArgumentException( "Graph may not be constructed without weight-matrix specified"); } if (vertices == null) { throw new IllegalArgumentException( "Graph may not be constructed without vertex-set specified"); } assert vertices.size() == weights.length; for (V vertex : vertices) { target.addVertex(vertex); } for (int i = 0; i < vertices.size(); ++i) { assert vertices.size() == weights[i].length; for (int j = 0; j < vertices.size(); ++j) { if (i != j) { target .setEdgeWeight( target.addEdge(vertices.get(i), vertices.get(j)), weights[i][j]); } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/StarGraphGenerator.java000066400000000000000000000041671402514743400322740ustar00rootroot00000000000000/* * (C) Copyright 2008-2021, by Andrew Newell and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generates a star graph of any size. * This is a graph where every vertex has exactly one edge with a center vertex. * * @param the graph vertex type * @param the graph edge type * * @author Andrew Newell */ public class StarGraphGenerator implements GraphGenerator { public static final String CENTER_VERTEX = "Center Vertex"; private final int order; /** * Creates a new StarGraphGenerator object. * * @param order number of total vertices including the center vertex * @throws IllegalArgumentException if the order is negative */ public StarGraphGenerator(int order) { if (order < 0) { throw new IllegalArgumentException("Order must be non-negative"); } this.order = order; } /** * Generates a star graph with the designated order from the constructor */ @Override public void generateGraph(Graph target, Map resultMap) { if (order < 1) { return; } // Create center vertex V centerVertex = target.addVertex(); if (resultMap != null) { resultMap.put(CENTER_VERTEX, centerVertex); } // Create other vertices for (int i = 0; i < (order - 1); i++) { target.addEdge(target.addVertex(), centerVertex); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/TooManyFailuresException.java000066400000000000000000000044201402514743400334620ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Amr ALHOSSARY and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; /** * Raised when the generator fails, too many times in a row, to grow a graph. * * @author Amr ALHOSSARY * */ public class TooManyFailuresException extends RuntimeException { /** Serial Version ID */ private static final long serialVersionUID = 7986467967127358163L; /** * Constructs a new too many failures Exception with null as its detail message. The cause is * not initialized, and may subsequently be initialized by a call to initCause. */ public TooManyFailuresException() { super(); } /** * Constructs a new exception with the specified detail message. The cause is not initialized, * and may subsequently be initialized by a call to initCause. * * @param message the detail message (which is saved for later retrieval by the getMessage() * method). */ public TooManyFailuresException(String message) { super(message); } /** * Constructs a new too Many Failures exception with the specified detail message and cause. * Note that the detail message associated with cause is not automatically incorporated in this * runtime exception's detail message. * * @param message the detail message (which is saved for later retrieval by the getMessage() * method). * @param cause the cause (which is saved for later retrieval by the getCause() method). (A null * value is permitted, and indicates that the cause is nonexistent or unknown.) */ public TooManyFailuresException(String message, Throwable cause) { super(message, cause); } } WattsStrogatzGraphGenerator.java000066400000000000000000000160511402514743400341370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * Watts-Strogatz small-world graph generator. * *

    * The generator is described in the paper: D. J. Watts and S. H. Strogatz. Collective dynamics of * small-world networks. Nature 393(6684):440--442, 1998. * *

    * The following paragraph from the paper describes the construction. * *

    * "The generator starts with a ring of $n$ vertices, each connected to its $k$ nearest neighbors * ($k$ must be even). Then it chooses a vertex and the edge that connects it to its nearest * neighbor in a clockwise sense. With probability $p$, it reconnects this edge to a vertex chosen * uniformly at random over the entire ring with duplicate edges forbidden; otherwise it leaves the * edge in place. The process is repeated by moving clock-wise around the ring, considering each * vertex in turn until one lap is completed. Next, it considers the edges that connect vertices to * their second-nearest neighbors clockwise. As before, it randomly rewires each of these edges with * probability $p$, and continues this process, circulating around the ring and proceeding outward * to more distant neighbors after each lap, until each edge in the original lattice has been * considered once. As there are $\frac{nk}{2}$ edges in the entire graph, the rewiring process * stops after $\frac{k}{2}$ laps. For $p = 0$, the original ring is unchanged; as $p$ increases, * the graph becomes increasingly disordered until for $p = 1$, all edges are rewired randomly. For * intermediate values of $p$, the graph is a small-world network: highly clustered like a regular * graph, yet with small characteristic path length, like a random graph." * *

    * The authors require $n \gg k \gg \ln(n) \gg 1$ and specifically $k \gg \ln(n)$ guarantees that a * random graph will be connected. * *

    * Through the constructor parameter the model can be slightly changed into adding shortcut edges * instead of re-wiring. This variation was proposed in the paper: M. E. J. Newman and D. J. Watts, * Renormalization group analysis of the small-world network model, Physics Letters A, 263, 341, * 1999. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class WattsStrogatzGraphGenerator implements GraphGenerator { private static final boolean DEFAULT_ADD_INSTEAD_OF_REWIRE = false; private final Random rng; private final int n; private final int k; private final double p; private final boolean addInsteadOfRewire; /** * Constructor * * @param n the number of nodes * @param k connect each node to its k nearest neighbors in a ring * @param p the probability of re-wiring each edge * @throws IllegalArgumentException in case of invalid parameters */ public WattsStrogatzGraphGenerator(int n, int k, double p) { this(n, k, p, DEFAULT_ADD_INSTEAD_OF_REWIRE, new Random()); } /** * Constructor * * @param n the number of nodes * @param k connect each node to its k nearest neighbors in a ring * @param p the probability of re-wiring each edge * @param seed seed for the random number generator * @throws IllegalArgumentException in case of invalid parameters */ public WattsStrogatzGraphGenerator(int n, int k, double p, long seed) { this(n, k, p, DEFAULT_ADD_INSTEAD_OF_REWIRE, new Random(seed)); } /** * Constructor * * @param n the number of nodes * @param k connect each node to its k nearest neighbors in a ring * @param p the probability of re-wiring each edge * @param addInsteadOfRewire whether to add shortcut edges instead of re-wiring * @param rng the random number generator to use * @throws IllegalArgumentException in case of invalid parameters */ public WattsStrogatzGraphGenerator( int n, int k, double p, boolean addInsteadOfRewire, Random rng) { if (n < 3) { throw new IllegalArgumentException("number of vertices must be at least 3"); } this.n = n; if (k < 1) { throw new IllegalArgumentException("number of k-nearest neighbors must be positive"); } if (k % 2 == 1) { throw new IllegalArgumentException("number of k-nearest neighbors must be even"); } if (k > n - 2 + (n % 2)) { throw new IllegalArgumentException("invalid k-nearest neighbors"); } this.k = k; if (p < 0.0 || p > 1.0) { throw new IllegalArgumentException("invalid probability"); } this.p = p; this.rng = Objects.requireNonNull(rng, "Random number generator cannot be null"); this.addInsteadOfRewire = addInsteadOfRewire; } /** * Generates a small-world graph based on the Watts-Strogatz model. * * @param target the target graph * @param resultMap not used by this generator, can be null */ @Override public void generateGraph(Graph target, Map resultMap) { // special cases if (n == 0) { return; } else if (n == 1) { target.addVertex(); return; } // create ring lattice List ring = new ArrayList<>(n); Map> adj = CollectionUtil.newLinkedHashMapWithExpectedSize(n); for (int i = 0; i < n; i++) { V v = target.addVertex(); ring.add(v); adj.put(v, new ArrayList<>(k)); } for (int i = 0; i < n; i++) { V vi = ring.get(i); List viAdj = adj.get(vi); for (int j = 1; j <= k / 2; j++) { viAdj.add(target.addEdge(vi, ring.get((i + j) % n))); } } // re-wire edges for (int r = 0; r < k / 2; r++) { for (int i = 0; i < n; i++) { if (rng.nextDouble() < p) { V v = ring.get(i); E e = adj.get(v).get(r); V other = ring.get(rng.nextInt(n)); if (!other.equals(v) && !target.containsEdge(v, other)) { if (!addInsteadOfRewire) { target.removeEdge(e); } target.addEdge(v, other); } } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/WheelGraphGenerator.java000066400000000000000000000072361402514743400324270ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; import java.util.function.*; /** * Generates a wheel graph of any size. * Reminding a bicycle wheel, a wheel graph has a hub vertex in the center and a rim of vertices * around it that are connected to each other (as a ring). The rim vertices are also connected to * the hub with edges that are called "spokes". * * @param the graph vertex type * @param the graph edge type * * @author John V. Sichi */ public class WheelGraphGenerator implements GraphGenerator { /** * Role for the hub vertex. */ public static final String HUB_VERTEX = "Hub Vertex"; private boolean inwardSpokes; private int size; /** * Creates a new WheelGraphGenerator object. This constructor is more suitable for undirected * graphs, where spokes' direction is meaningless. In the directed case, spokes will be oriented * from rim to hub. * * @param size number of vertices to be generated. */ public WheelGraphGenerator(int size) { this(size, true); } /** * Construct a new WheelGraphGenerator. * * @param size number of vertices to be generated. * @param inwardSpokes if true and graph is directed, spokes are oriented from rim * to hub; else from hub to rim. * * @throws IllegalArgumentException in case the number of vertices is negative */ public WheelGraphGenerator(int size, boolean inwardSpokes) { if (size < 0) { throw new IllegalArgumentException("must be non-negative"); } this.size = size; this.inwardSpokes = inwardSpokes; } /** * {@inheritDoc} */ @Override public void generateGraph(Graph target, Map resultMap) { if (size < 1) { return; } // A little trickery to intercept the rim generation. This is // necessary since target may be initially non-empty, meaning we can't // rely on its vertex set after the rim is generated. final Collection rim = new ArrayList<>(); final Supplier initialSupplier = target.getVertexSupplier(); Supplier rimVertexSupplier = () -> { V vertex = initialSupplier.get(); rim.add(vertex); return vertex; }; Graph targetWithRimVertexSupplier = new GraphDelegator<>(target, rimVertexSupplier, null); new RingGraphGenerator(size - 1) .generateGraph(targetWithRimVertexSupplier, resultMap); V hubVertex = target.addVertex(); if (resultMap != null) { resultMap.put(HUB_VERTEX, hubVertex); } for (V rimVertex : rim) { if (inwardSpokes) { target.addEdge(rimVertex, hubVertex); } else { target.addEdge(hubVertex, rimVertex); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/WindmillGraphsGenerator.java000066400000000000000000000074431402514743400333250ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import java.util.*; /** * Generator for Windmill Graphs, * Dutch Windmill Graphs and * Friendship Graphs. *

    * The windmill graph $W_n^{(m)}$ is the graph obtained by taking $m$ copies of the complete graph * $K_n$ with a vertex in common. The Dutch windmill graph $D_n^{(m)}$, is the graph obtained by * taking $m$ copies of the cycle graph $C_3$ with a vertex in common. For the special case where * $n=3$, $D_n^{(m)}$ and $W_n^{(m)}$ are identical. The class of graphs $D_3^{(m)}$ is sometimes * referred to as the Friendship graph, denoted by $F_m$. * * @author Joris Kinable * * @param graph vertex type * @param graph edge type */ public class WindmillGraphsGenerator implements GraphGenerator { /** * WINDMILL and DUTCHWINDMILL Modes for the Constructor */ public enum Mode { WINDMILL, DUTCHWINDMILL } private final Mode mode; private final int m; private final int n; /** * Constructs a GeneralizedPetersenGraphGenerator used to generate a Generalized Petersen graphs * $GP(n,k)$. * * @param mode indicate whether the generator should generate Windmill graphs or Dutch Windmill * graphs * @param m number of copies of $C_n$ (Dutch Windmill graph) or $K_n$ (Windmill graph) * @param n size of $C_n$ (Dutch Windmill graph) or $K_n$ (Windmill graph). To generate * friendship graphs, set $n=3$ (the mode is irrelevant). */ public WindmillGraphsGenerator(Mode mode, int m, int n) { if (m < 2) throw new IllegalArgumentException("m must be larger or equal than 2"); if (n < 3) throw new IllegalArgumentException("n must be larger or equal than 3"); this.mode = mode; this.m = m; this.n = n; } @Override public void generateGraph(Graph target, Map resultMap) { V center = target.addVertex(); List sub = new ArrayList<>(n); if (mode == Mode.DUTCHWINDMILL) { // Generate Dutch windmill graph for (int i = 0; i < m; i++) { // m copies of cycle graph Cn sub.clear(); sub.add(center); for (int j = 1; j < n; j++) { sub.add(target.addVertex()); } for (int r = 0; r < sub.size(); r++) target.addEdge(sub.get(r), sub.get((r + 1) % n)); } } else { // Generate windmill graph for (int i = 0; i < m; i++) { // m copies of complete graph Kn sub.clear(); sub.add(center); for (int j = 1; j < n; j++) { sub.add(target.addVertex()); } for (int r = 0; r < sub.size() - 1; r++) for (int s = r + 1; s < sub.size(); s++) target.addEdge(sub.get(r), sub.get(s)); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/000077500000000000000000000000001402514743400271375ustar00rootroot00000000000000BipartiteMatchingProblem.java000066400000000000000000000127221402514743400346460ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; import org.jgrapht.Graph; import java.util.Set; import java.util.function.Function; /** * This class represents a bipartite matching problem. The problem can be weighted or unweighted * depending on the {@link BipartiteMatchingProblem#isWeighted()}. *

    * The minimum weight (minimum cost) perfect bipartite matching problem is defined as follows: \[ * \begin{align} \mbox{minimize}~& \sum_{e \in E}c_e\cdot x_e &\\ \mbox{s.t. * }&\sum_{e\in \delta(v)} x_e = 1 & \forall v\in V\\ &x_e \in \{0,1\} & \forall * e\in E \end{align} \] Here $\delta(v)$ denotes the set of edges incident to the vertex $v$. The * parameters $c_{e}$ define a cost of adding the edge $e$ to the matching. If the problem is * unweighted, the values $c_e$ are equal to 1 in the problem formulation. *

    * This class can define bipartite matching problems without the requirement that every edge must be * matched, i.e. non-perfect matching problems. These problems are called maximum cardinality * bipartite matching problems. The goal of the maximum cardinality matching problem is to find a * matching with maximum number of edges. If the cost function is used in this setup, the goal is to * find the cheapest matching among all matchings of maximum cardinality. * * @param the graph vertex types * @param the graph edge type * @author Timofey Chudakov * @see org.jgrapht.alg.interfaces.MatchingAlgorithm */ public interface BipartiteMatchingProblem { /** * Returns the graph, which defines the problem * * @return the graph, which defines the problem */ Graph getGraph(); /** * Returns one of the 2 partitions of the graph (no 2 vertices in this set share an edge) * * @return one of the 2 partitions of the graph */ Set getPartition1(); /** * Returns one of the 2 partitions of the graph (no 2 vertices in this set share an edge) * * @return one of the 2 partitions of the graph */ Set getPartition2(); /** * Returns a cost function of this problem. This function must be defined for all edges of the * graph. In the case the problem is unweighted, the function must return any constant value for * all edges. * * @return a cost function of this problem */ Function getCosts(); /** * Determines if this problem is weighted or not. * * @return {@code true} is the problem is weighted, {@code false} otherwise */ boolean isWeighted(); /** * Dumps the problem edge costs to the underlying graph. */ default void dumpCosts() { Graph graph = getGraph(); Function costs = getCosts(); for (E edge : graph.edgeSet()) { graph.setEdgeWeight(edge, costs.apply(edge)); } } /** * Default implementation of a Bipartite Matching Problem * * @param the graph vertex type * @param the graph edge type */ class BipartiteMatchingProblemImpl implements BipartiteMatchingProblem { private final Graph graph; private final Set partition1; private final Set partition2; private final Function costs; private final boolean weighted; /** * Constructs a new bipartite matching problem * * @param graph a graph, which defines the problem * @param partition1 one of the partitions of the graph * @param partition2 one of the partitions of the graph * @param costs problem cost function * @param weighted is the problem is weighted or not */ public BipartiteMatchingProblemImpl( Graph graph, Set partition1, Set partition2, Function costs, boolean weighted) { this.graph = graph; this.partition1 = partition1; this.partition2 = partition2; this.costs = costs; this.weighted = weighted; } /** * {@inheritDoc} */ @Override public Graph getGraph() { return graph; } /** * {@inheritDoc} */ @Override public Function getCosts() { return costs; } /** * {@inheritDoc} */ @Override public Set getPartition1() { return partition1; } /** * {@inheritDoc} */ @Override public Set getPartition2() { return partition2; } /** * {@inheritDoc} */ @Override public boolean isWeighted() { return weighted; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/Distributor.java000066400000000000000000000161021402514743400323140ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; import org.jgrapht.alg.util.Pair; import java.util.*; import java.util.function.Function; /** * Distributes value units among keys given lower and upper bound constraints. *

    * Let's define a set of elements $\{k_1, k_2, \dots, k_n\}$. For every element a set of lower * bounds $\{l_1, l_2, \dots, l_t\}$ and upper bounds $\{u_1, u_2, \dots, u_p\}$ is specified. The * problem is to randomly distribute a number of abstract value units $V$ among keys such that the * lower bound and upper bound constraints are satisfied. This class solves this problem. * * @param the element type. * @author Timofey Chudakov * @see NetworkGenerator */ public class Distributor { /** * Random number generator used by this distributor. */ private final Random rng; /** * Lower bounds. */ private final List> lowerBounds; /** * Upper bounds. */ private final List> upperBounds; /** * Creates a Distributor using random seed. */ public Distributor() { this(System.nanoTime()); } /** * Creates a distributor using the specified {@code seed}. * * @param seed the seed for the random number generator. */ public Distributor(long seed) { this(new Random(seed)); } /** * Creates a distributor which uses the random number generatow {@code rng}. * * @param rng a random number generator to use. */ public Distributor(Random rng) { this.rng = rng; this.lowerBounds = new ArrayList<>(); this.upperBounds = new ArrayList<>(); } /** * Adds an upper bounding function. This function must be defined for all keys. * * @param upperBound an upper bound function. */ public void addUpperBound(Function upperBound) { this.upperBounds.add(upperBound); } /** * Adds a lower bound function. This function must be defined for all keys. * * @param lowerBound a lower bound function. */ public void addLowerBound(Function lowerBound) { this.lowerBounds.add(lowerBound); } /** * Finds a maximum lower bound for every key. * * @param keys list of keys. * @return the computed key lower bounds. */ private List computeLowerBounds(List keys) { List keyLowerBounds = new ArrayList<>(keys.size()); for (K key : keys) { int lowerBound = 0; for (Function lowerBoundFunction : lowerBounds) { lowerBound = Math.max(lowerBound, lowerBoundFunction.apply(key)); } keyLowerBounds.add(lowerBound); } return keyLowerBounds; } /** * Finds a minimum lower bound for every key. * * @param keys a list of keys. * @return the computed key upper bound. */ private List computeUpperBounds(List keys) { List keyUpperBounds = new ArrayList<>(keys.size()); for (K key : keys) { int upperBound = Integer.MAX_VALUE; for (Function upperBoundFunction : upperBounds) { upperBound = Math.min(upperBound, upperBoundFunction.apply(key)); } keyUpperBounds.add(upperBound); } return keyUpperBounds; } /** * Computes a suffix sum of the {@code bounds}. Returns computed suffix sum and the sum of all * elements in the {@code bounds list}. * * @param bounds list of integers. * @return computed pair of suffix sum list and a sum of all elements. */ private Pair, Long> computeSuffixSum(List bounds) { List suffixSum = new ArrayList<>(Collections.nCopies(bounds.size(), 0)); long sum = 0; for (int i = bounds.size() - 1; i >= 0; i--) { suffixSum.set(i, (int) Math.min(Integer.MAX_VALUE, sum)); sum += bounds.get(i); } return Pair.of(suffixSum, sum); } /** * Computes and returns a value distribution for the list of keys. The resulting distribution * will satisfy the (possibly empty) sets of lower and upper bound constraints. Distributed * values will be in the same order as the keys in the key list. * * @param keys the list of keys. * @param valueNum the number of abstract value units to distribute. * @return the computed value distribution. */ public List getDistribution(List keys, final int valueNum) { List keyLowerBounds = computeLowerBounds(keys); List keyUpperBounds = computeUpperBounds(keys); Pair, Long> lbSufSumP = computeSuffixSum(keyLowerBounds); Pair, Long> ubSufSumP = computeSuffixSum(keyUpperBounds); List lbSufSum = lbSufSumP.getFirst(); List ubSufSum = ubSufSumP.getFirst(); long lbSum = lbSufSumP.getSecond(); long ubSum = ubSufSumP.getSecond(); if (lbSum > valueNum) { throw new IllegalArgumentException( "Can't distribute values among keys: the sum of lower bounds is greater than the number of values"); } else if (ubSum < valueNum) { throw new IllegalArgumentException( "Can't distribute values among keys: the sum of upper bounds is smaller than the number of values"); } int remainingValues = valueNum; List resultingDistribution = new ArrayList<>(); for (int i = 0; i < keyLowerBounds.size(); i++) { int lowerBound = keyLowerBounds.get(i); int upperBound = keyUpperBounds.get(i); int valueNumUpperBound = remainingValues - lbSufSum.get(i); int valueNumLowerBound = remainingValues - ubSufSum.get(i); lowerBound = Math.max(lowerBound, valueNumLowerBound); upperBound = Math.min(upperBound, valueNumUpperBound); if (lowerBound > upperBound) { throw new IllegalArgumentException( "Infeasible bound specified for the key: " + keys.get(i)); } int allocatedValues = rng.nextInt(upperBound - lowerBound + 1) + lowerBound; resultingDistribution.add(allocatedValues); remainingValues -= allocatedValues; } return resultingDistribution; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/MaximumFlowProblem.java000066400000000000000000000202741402514743400335750ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; import org.jgrapht.Graph; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.Function; /** * This class represents a maximum flow problem. Use this class for both directed and undirected * maximum flow problems. *

    * The single-source, single-sink maximum flow problem is defined as follows: \[ \begin{align} * \mbox{maximize}~& \sum_{e \in \delta^+(s)}f_e - \sum_{e \in \delta^-(s)}f_e &\\ * \mbox{s.t. }&\sum_{e\in \delta^-(v)} f_e = \sum_{e\in \delta^+(v)} f_e & \forall v\in * V\setminus \{s, t\} \\ &0 \leq f_e \leq c_e & \forall e\in E \end{align} \] Here * $\delta^+(v)$ and $\delta^-(v)$ denote the outgoing and incoming edges of vertex $v$ * respectively. The value $f_e$ denotes the flow on edge $e$, which is bounded by $c_e$ - the * capacity of the edge $e$. The vertex $s$ is a network source, the vertex $t$ - network sink. The * edge capacities can be retrieved using {@link MaximumFlowProblem#getCapacities()}. The problem * formulation above defines a canonical maximum flow problem, i.e. with only one source and one * sink. *

    * A maximum flow problem can be defined on a network with multiple sources and sinks. This problem * can be reduced to the above problem definition as follows: *

      *
    • Two special vertices are added to the graph: a super source and a super sink;
    • *
    • Edges with infinite capacity are added from the super source to every source and from every * sink to the super sink
    • *
    * To use this reduction, see {@link MaximumFlowProblem#toSingleSourceSingleSinkProblem()}. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see org.jgrapht.alg.interfaces.MaximumFlowAlgorithm */ public interface MaximumFlowProblem { double CAPACITY_INF = Integer.MAX_VALUE; /** * Returns the network the problem is defined on. * * @return the network the problem is defined on. */ Graph getGraph(); /** * Returns the source set of this problem. * * @return the source set of this problem. */ Set getSources(); /** * Returns the sink set of this problem. * * @return the sink set of this problem. */ Set getSinks(); /** * Returns one source of this problem (a problem is guaranteed to have at least one source). Use * this method if the problem is in canonical form (only one source and one sink). * * @return one source of this problem. */ default V getSource() { return getSources().iterator().next(); } /** * Returns one sink of this problem (a problem is guaranteed to have at least one sink). Use * this method if the problem is in canonical form (only one source and one sink). * * @return one sink of this problem. */ default V getSink() { return getSinks().iterator().next(); } /** * Returns the capacity function of this problem. This function is defined for all edges of the * underlying network. * * @return the capacity function of this problem. */ Function getCapacities(); /** * Converts this problem to the canonical form. Resulting problem is equivalent to the previous * one. * * @return a problem in the canonical form. */ MaximumFlowProblem toSingleSourceSingleSinkProblem(); /** * Checks if this problem is in the canonical form. * * @return {@code true} if this problem is in the canonical form, {@code false} otherwise. */ default boolean isSingleSourceSingleSinkProblem() { return getSources().size() == 1 && getSinks().size() == 1; } /** * Dumps the network edge capacities to the underlying graph. */ default void dumpCapacities() { Graph graph = getGraph(); Function capacities = getCapacities(); for (E edge : graph.edgeSet()) { graph.setEdgeWeight(edge, capacities.apply(edge)); } } /** * Default implementation of a Maximum Flow Problem. * * @param the graph vertex type * @param the graph edge type */ class MaximumFlowProblemImpl implements MaximumFlowProblem { private final Graph graph; private final Set sources; private final Set sinks; private final Function capacities; /** * Constructs a new maximum flow problem. * * @param graph flow network * @param sources set of network sources * @param sinks set of network sinks * @param capacities network capacity function */ public MaximumFlowProblemImpl( Graph graph, Set sources, Set sinks, Function capacities) { this.graph = graph; this.sources = sources; this.sinks = sinks; this.capacities = capacities; } /** * {@inheritDoc} */ @Override public Graph getGraph() { return graph; } /** * {@inheritDoc} */ @Override public Set getSources() { return sources; } /** * {@inheritDoc} */ @Override public Set getSinks() { return sinks; } /** * {@inheritDoc} */ @Override public Function getCapacities() { return capacities; } /** * {@inheritDoc} */ @Override public MaximumFlowProblem toSingleSourceSingleSinkProblem() { Set newEdges = new HashSet<>(); Set sourceSet = convert(sources, newEdges, true); Set sinkSet = convert(sinks, newEdges, false); Function updatedCapacities = e -> { if (newEdges.contains(e)) { return CAPACITY_INF; } else { return capacities.apply(e); } }; return new MaximumFlowProblemImpl<>(graph, sourceSet, sinkSet, updatedCapacities); } /** * Adds a new super vertex and connects it to all vertices in {@code vertices}. Depending on * the value of {@code sources}, the edges are directed from super vertex or to super * vertex. New edges are added to {@code newEdges}. * * @param vertices set of vertices to connect super vertex to * @param newEdges container to add new edges to * @param sources {@code true} if super vertex is super source, {@code false} if it's super * sink * @return 1 element set containing the super vertex */ private Set convert(Set vertices, Set newEdges, boolean sources) { if (vertices.size() == 1) { return vertices; } V superVertex = graph.addVertex(); Set newSourceSet = Collections.singleton(superVertex); for (V vertex : vertices) { E edge; if (sources) { edge = graph.addEdge(superVertex, vertex); } else { edge = graph.addEdge(vertex, superVertex); } newEdges.add(edge); } return newSourceSet; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkGenerator.java000066400000000000000000001143451402514743400333120ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; import org.jgrapht.Graph; import org.jgrapht.GraphTests; import org.jgrapht.Graphs; import org.jgrapht.alg.flow.mincost.MinimumCostFlowProblem; import org.jgrapht.alg.util.Pair; import org.jgrapht.util.CollectionUtil; import org.jgrapht.util.ElementsSequenceGenerator; import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * NETGEN-style network generator. This generator is capable of generating bipartite matching * problems (both weighted and unweighted), maximum flow problems and minimum cost flow problems. * Note, that this generator works only with directed graphs. The algorithm is originally described * in: D. Klingman, A. Napier, and J. Shutz, "NETGEN - A program for generating large scale * (un)capacitated assignment, transportation, and minimum cost flow network problems", Management * Science 20, 5, 814-821 (1974) *

    * This generator is not completely equivalent to the original implementation. A number of changes * has been made to remove bugs and ensure parameter constraints. For a complete parameter * description and constraints on them, see {@link NetworkGeneratorConfig}. Under an assumption that * this generator receives a valid config, the following properties of the resulting minimum cost * flow network are guaranteed: *

      *
    1. Network has exactly the same number of nodes, network sources, transshipment sources, network * sinks, transshipment sinks, and transshipment nodes;
    2. *
    3. Network has exactly the same number of arcs;
    4. *
    5. Pure network sources don't have incoming arcs; pure network sinks don't have outgoing * arcs;
    6. *
    7. Capacity lower and upper bounds are satisfied for all arcs except for a subset of skeleton * arcs, for which the capacity lower bound is equal to the supply of the source arc's chain is * originating from. The description of the skeleton network and source chains follows. This is done * to ensure that the generated network is feasible with respect to the node supplies. You can find * out which arcs belong to the skeleton network by using {@link NetworkInfo}. For example, if there * is only one network source, network supply is equal to 10, minCap = 1, maxCap = 5, then some of * the arcs will have the capacity equal to 10;
    8. *
    9. If percentCapacitated is 100, then all arcs have finite capacity (which is bounded by minCap * and maxCap). If percent capacitated is 0, every arc is uncapacitated;
    10. *
    11. Cost lower and upper bound are satisfied;
    12. *
    13. If percentWithInfCost is 100, then all arcs have infinite cost. If percentWithInfCost is 0, * then every arc has finite cost (which is bounded by minCost and maxCost).
    14. *
    15. Every source's supply is at least 1;
    16. *
    17. Every sink's supply is at most -1 (equivalently, demand is at least 1);
    18. *
    19. The resulting network is feasible meaning that there exist a network flow satisfying the * source supply, sink demand and arc capacity constraints.
    20. *
    * Note, that transshipment sources and transshipment sinks can have incoming and outgoing arcs * respectively, but this property is optional. *

    * The maximum flow networks, that are generated by this algorithm, are guaranteed to have following * properties: *

      *
    1. Properties 1-5 are equivalent to the properties of the generated minimum cost flow * networks;
    2. *
    3. The maximum flow is greater that or equal to the value of the total supply specified * in the network config.
    4. *
    *

    * The bipartite matching problems, that are generated by this algorithm, are guaranteed to * following properties: *

      *
    1. Properties 1, 2, 6, 7 are equivalent to the properties of the generated minimum cost flow * networks;
    2. *
    3. For the generated problem, there exist a perfect matching meaning that every vertex can be * matched.
    4. *
    *

    * Now a brief description of the algorithm will be provided. The generator begins by distributing * supply among network sources. Every source gets at least 1 unit of supply. After that, * approximately 60% of transshipment nodes are evenly separated between sources. For every source, * an initial chain is built using these transshipment nodes. A chain is effectively a path. * Remaining 40% of transshipment nodes are randomly distributed among source chains. *

    * Now every chain has to be connected to at least one sink and every sink has to be connected to at * least on chain. For every chain a random number of arcs is generated. This number is at least 1. * The total number of generated arcs is max(sourceNum, sinkNum). Every chain is connected to random * sinks such that above constraints are satisfied. The network source supply is distributed among * network sinks such that every sink received at least 1 unit of supply (with negative sign). *

    * After the skeleton network is generated, the network is guaranteed to be feasible. The remaining * arcs are randomly distributed between remaining pairs of vertices. The algorithm tries to * distribute them evenly to avoid large arc clusters. * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see NetworkGeneratorConfig * @see NetworkInfo */ public class NetworkGenerator { /** * Upper bound on the number of nodes in the network this generator can work with. */ public static final int MAX_NODE_NUM = 100 * 1000 * 1000; /** * Upper bound on the number of supply units in the network this generator can work with. */ public static final int MAX_SUPPLY = 200 * 1000 * 1000; /** * Upper bound on the number of arcs in the network this generator can work with. */ public static final int MAX_ARC_NUM = 2 * 1000 * 1000 * 1000; /** * Upper bound on the arc capacities and costs values in the network this generator can work * with. */ public static final int CAPACITY_COST_BOUND = 2 * 1000 * 1000 * 1000; /** * User-provided network configuration. */ private final NetworkGeneratorConfig config; /** * Random number generator used to create a network. */ private final Random rng; /** * A network that is being generated. */ private Graph graph; /** * Network structure information obtained during generation process. */ private NetworkInfo networkInfo; /** * Network nodes stored in a list. Nodes of the same type are located in the continuous * segments. There are 5 segments in this list: *

    * [ pureSources | tSources | tNodes | tSinks | pureSinks ] *

    * - [ 0, pureSourceNum ) - pure source nodes - [ pureSourceNum, sourceNum ) - transshipment * source nodes - [ sourceNum, sourceNum + transshipNodeNum ) - transshipment nodes - [ * sourceNum + transshipNodeNum, nodeNum - pureSinkNum ) - transshipment sink nodes - [ nodeNum * - pureSinkNum, nodeNum ) - pure sink nodes */ private List nodes; /** * Mapping for converting graph vertices to their internal representation as nodes. */ private Map graphVertexMapping; /** * Supply vertex mapping which is used to define node supplies. */ private Map supplyMap; /** * Arc capacity mapping which is used to define arc capacity function. */ private Map capacityMap; /** * Arc cost mapping which is used to define arc cost function. */ private Map costMap; /** * Maximum number of arcs a network can contain between source nodes and t-source nodes. */ private long source2TSourceUB; /** * Maximum number of arcs a network can contain between source nodes and t-nodes. This value is * decreased during skeleton network generation whenever an arc between corresponding pair of * nodes is generated. */ private long source2TNodeUB; /** * Maximum number of arcs a network can contain between source nodes and sink nodes. This value * is decreased during skeleton network generation whenever an arc between corresponding pair of * nodes is generated. */ private long source2SinkUB; /** * Maximum number of arcs a network can contain between t-nodes and t-sources. */ private long tNode2TSourceUB; /** * Maximum number of arcs a network can contain between t-nodes. This value is decreased during * skeleton network generation whenever an arc between corresponding pair of nodes is generated. */ private long tNode2TNodeUB; /** * Maximum number of arcs a network can contain between t-nodes and sink nodes. This value is * decreased during skeleton network generation whenever an arc between corresponding pair of * nodes is generated. */ private long tNode2SinkUB; /** * Maximum number of arcs a network can contain between t-sinks and t-sources. */ private long tSink2TSourceUB; /** * Maximum number of arcs a network can contain between t-sinks and t-nodes. */ private long tSink2TNodeUB; /** * Maximum number of arcs a network can contain between t-sinks and sink nodes. */ private long tSink2SinkUB; /** * Creates a new network generator using specified {@code config}. The created generator uses * random seed for the random number generator. Thus the code using this generator won't produce * the same networks between different invocations. * * @param config the network configuration for this generator. */ public NetworkGenerator(NetworkGeneratorConfig config) { this(config, System.nanoTime()); } /** * Creates a new network generator using specified {@code config} and {@code seed}. As the seed * for the random number generator is fixed, the code using this generator will produce the same * networks between different invocations. * * @param config the network configuration for this generator. * @param seed the seed for the random number generator. */ public NetworkGenerator(NetworkGeneratorConfig config, long seed) { this(config, new Random(seed)); } /** * Creates a new network generator using specified {@code config} and random number generator * {@code rng}. The network generated by this algorithm depends entirely on the random number * sequences produced by {@code rng} given a fixed network config. * * @param config the network configuration for this generator. * @param rng the random number generator for this algorithm. */ public NetworkGenerator(NetworkGeneratorConfig config, Random rng) { this.config = config; this.rng = rng; } /** * Generates a bipartite matching problem satisfying the parameters specified in the config * provided to this generator. The provided network config must specify a bipartite matching * problem, otherwise an exception will be throws by this method. For a description of the * bipartite matching problem, see {@link BipartiteMatchingProblem}. * * @param graph the target graph which will represent the generated problem. * @return generated bipartite matching problem. */ public BipartiteMatchingProblem generateBipartiteMatchingProblem(Graph graph) { if (!config.isAssignmentProblem()) { throw new IllegalArgumentException( "Input config doesn't specify a bipartite matching problem"); } GraphTests.requireDirected(graph); generate(graph); return new BipartiteMatchingProblem.BipartiteMatchingProblemImpl<>( graph, new HashSet<>(networkInfo.getSources()), new HashSet<>(networkInfo.getSinks()), e -> (double) costMap.get(e), config.isCostWeighted()); } /** * Generates a maximum flow problem satisfying the parameters specified in the config provided * to this generator. The provided network config must specify a maximum flow problem, otherwise * an exception will be throws by this method. For a description of the maximum flow problem, * see {@link MaximumFlowProblem}. * * @param graph the target graph which will represent the generated problem. * @return generated maximum flow problem. */ public MaximumFlowProblem generateMaxFlowProblem(Graph graph) { if (!config.isMaxFlowProblem()) { throw new IllegalArgumentException( "Input config doesn't specify a maximum flow problem"); } GraphTests.requireDirected(graph); generate(graph); // calling network info to get unmodifiable source and sink lists return new MaximumFlowProblem.MaximumFlowProblemImpl<>( graph, new HashSet<>(networkInfo.getSources()), new HashSet<>(networkInfo.getSinks()), e -> (double) capacityMap.get(e)); } /** * Generates a minimum cost flow problem satisfying the parameters specified in the config * provided to this generator. For a description of the minimum cost flow problem, see * {@link MinimumCostFlowProblem}. * * @param graph the target graph which will represent the generated problem. * @return generated minimum cost flow problem. */ public MinimumCostFlowProblem generateMinimumCostFlowProblem(Graph graph) { GraphTests.requireDirected(graph); generate(graph); return new MinimumCostFlowProblem.MinimumCostFlowProblemImpl<>( graph, v -> supplyMap.getOrDefault(v, 0), e -> capacityMap.get(e), e -> costMap.get(e)); } /** * Runs all the steps of the generator algorithm. For the brief description of the algorithm, * see the class documentation. The complete NETGEN algorithm description is given in the * original paper. * * @param graph the target graph which will represent the generated problem. */ private void generate(Graph graph) { init(graph); createSupply(); // generating skeleton network initChains(); generateChains(); connectChainsToSinks(); addAllRemainingArcs(); networkInfo.vertices = nodes.stream().map(n -> n.graphVertex).collect(Collectors.toList()); } /** * Initializes internal datastructures. This method gets called during every invocation of the * {@link NetworkGenerator#generate(Graph)} to clear information from previous invocation. * * @param graph the target graph which will represent the generated problem. */ private void init(Graph graph) { this.graph = Objects.requireNonNull(graph); this.nodes = new ArrayList<>(); this.graphVertexMapping = CollectionUtil.newHashMapWithExpectedSize(config.getNodeNum()); this.supplyMap = new HashMap<>(); this.capacityMap = CollectionUtil.newHashMapWithExpectedSize(config.getArcNum()); this.costMap = CollectionUtil.newHashMapWithExpectedSize(config.getArcNum()); this.networkInfo = new NetworkInfo<>(config); this.source2TSourceUB = config.getMaxSource2TSourceArcNum(); this.source2TNodeUB = config.getMaxSource2TNodeArcNum(); this.source2SinkUB = config.getMaxSource2SinkArcNum(); this.tNode2TSourceUB = config.getMaxTNode2TSourceArcNum(); this.tNode2TNodeUB = config.getMaxTNode2TNodeArcNum(); this.tNode2SinkUB = config.getMaxTNode2SinkArcNum(); this.tSink2TSourceUB = config.getMaxTSink2TSourceArcNum(); this.tSink2TNodeUB = config.getMaxTSink2TNodeArcNum(); this.tSink2SinkUB = config.getMaxTSink2SinkArcNum(); createNodes(config.getPureSourceNum(), NodeType.PURE_SOURCE); createNodes(config.getTransshipSourceNum(), NodeType.TRANSSHIP_SOURCE); createNodes(config.getTransshipNodeNum(), NodeType.TRANSSHIP_NODE); createNodes(config.getTransshipSinkNum(), NodeType.TRANSSHIP_SINK); createNodes(config.getPureSinkNum(), NodeType.PURE_SINK); } /** * Creates {@code num} nodes of the specified {@code type}. * * @param num the number of nodes to generate. * @param type the type of nodes to generate. */ private void createNodes(int num, NodeType type) { for (int i = 0; i < num; i++) { V vertex = graph.addVertex(); Node node = new Node(vertex, type); nodes.add(node); graphVertexMapping.put(vertex, node); } } /** * Distributes supply units among source nodes. *

    * The precondition for this method is that totalSupply >= max(sourceNum, sinkNum). This method * guarantees that every sourceNode received at least one unit of supply. */ private void createSupply() { // supply per source is guaranteed to be at least 1 int supplyPerSource = config.getTotalSupply() / config.getSourceNum(); for (int sourceId = 0; sourceId < config.getSourceNum(); sourceId++) { // every source's supply is guaranteed to be at least one int partialSupply = generatePositiveRandom(supplyPerSource); nodes.get(sourceId).supply += partialSupply; // remaining supply is given to a random source node int randomSourceId = generateRandom(config.getSourceNum()); nodes.get(randomSourceId).supply += supplyPerSource - partialSupply; } // assign the rest of the supply to a random source int randomSourceId = generateRandom(config.getSourceNum()); nodes.get(randomSourceId).supply += config.getTotalSupply() % config.getSourceNum(); // save the result in the supply map nodes.forEach(node -> { if (node.supply != 0) { supplyMap.put(node.graphVertex, node.supply); } }); } /** * Initializes source chains by adding source nodes as 1-st nodes of their chains. */ private void initChains() { for (Node node : getSources()) { node.chainNodes.add(node); } } /** * Generates source chains using all t-nodes. The generated chains are disjoint and not yet * connected to sinks. */ private void generateChains() { int transshipmentNodeNum = config.getTransshipNodeNum(); int sixtyPercent = (6 * transshipmentNodeNum) / 10; ElementsSequenceGenerator tNodesGenerator = new ElementsSequenceGenerator<>(getTransshipNodes(), rng); // generating chains from source nodes using ~60% of pure transshipment nodes for (int i = 0, chainSourceId = 0; i < sixtyPercent; i++, chainSourceId++) { if (chainSourceId == config.getSourceNum()) { chainSourceId = 0; } Node arcHead = tNodesGenerator.next(); Node chainSource = nodes.get(chainSourceId); addSkeletonArc(chainSource, chainSource.getLastInChain(), arcHead); } // randomly extending generated chains using remaining ~40% of pure transhipment nodes for (Node arcHead : tNodesGenerator) { int sourceId = rng.nextInt(config.getSourceNum()); Node chainSource = nodes.get(sourceId); addSkeletonArc(chainSource, chainSource.getLastInChain(), arcHead); } } /** * Connects generated chains to sinks and distributes network supply among sinks. This method * guarantees that: *

    * 1. Every source chain is connected to at least one sink. 2. Every sink is connected to at * least one source chain. 3. Every sink's supply is at most -1 (or its demand is at least 1). */ private void connectChainsToSinks() { int remainingArcs = config.getArcNum() - graph.edgeSet().size(); assert remainingArcs >= config.getSinkNum(); /* * First, we have to compute the number of arcs to use to connect source chains to sinks. * Our "guess" is 2 * max(sourceNum, sinkNum). At the same time we have to take the * following upper bounds into account: 1. this value is bounded by #remaining_arcs from * above. 2. this value is bounded by source2SinkUB + tNode2SinkUB from above. * * We have to take one more bound into account to ensure that every sink's demand is at * least 1. A source's supply is distributed among sinks it's connected to such that every * sink get's at least 1 unit of demand. Thus, for every source we don't generate more arcs * that the number of supply units it has. */ int chainToSinkArcs = Math.min(remainingArcs, 2 * Math.max(config.getSourceNum(), config.getSinkNum())); int chainToSinkArcUB = (int) Math.min(source2SinkUB + tNode2SinkUB, MAX_ARC_NUM); chainToSinkArcs = Math.min(chainToSinkArcUB, chainToSinkArcs); List sources = getSources(); // this sum is at least max(sourceNum, sinkNum) // because config.getTotalSupply() >= max(sourceNum, sinkNum) int supplyAndSinkNumUB = 0; for (Node source : sources) { supplyAndSinkNumUB += Math.min(config.getSinkNum(), source.supply); } chainToSinkArcs = Math.min(chainToSinkArcs, supplyAndSinkNumUB); // distributing sinks among sources Distributor sinkDistributor = new Distributor<>(rng); sinkDistributor.addLowerBound(source -> 1); sinkDistributor.addUpperBound(source -> source.supply); sinkDistributor.addUpperBound(source -> config.getSinkNum()); List sinksPerSourceDist = sinkDistributor.getDistribution(sources, chainToSinkArcs); List sinks = getSinks(); /* * Generate the assigned number of source chain to sink arcs from every source. This process * cycles through the sink list ensuring that every sink gets at least or arc from some * source chain. */ for (int i = 0, sinkId = 0; i < sources.size(); i++) { Node chainSource = sources.get(i); int sinksPerSource = sinksPerSourceDist.get(i); // taking a needed portion of sinks from the sink list. List chainSinks = new ArrayList<>(); for (int j = 0; j < sinksPerSource; j++, sinkId++) { if (sinkId == sinks.size()) { sinkId = 0; } chainSinks.add(sinks.get(sinkId)); } /* * Randomly distribute supply units among target sinks such that every sink gets at * least 1 unit of demand. */ Distributor sinkSupplyDistributor = new Distributor<>(rng); sinkSupplyDistributor.addLowerBound(sink -> 1); List supplyDist = sinkSupplyDistributor.getDistribution(chainSinks, chainSource.supply); for (int j = 0; j < sinksPerSource; j++) { Node sink = chainSinks.get(j); int sinkSupply = supplyDist.get(j); int arcTailIndex = generateRandom(chainSource.getChainLength()); Node arcTail = chainSource.chainNodes.get(arcTailIndex); addSkeletonArc(chainSource, arcTail, sink); supplyMap .put( sink.graphVertex, supplyMap.getOrDefault(sink.graphVertex, 0) - sinkSupply); } } } /** * Generates remaining arcs to satisfy the arcNum constraint. */ private void addAllRemainingArcs() { final int remainingArcs = config.getArcNum() - graph.edgeSet().size(); assert remainingArcs >= 0; /* * Upper bounds for every class of arcs. */ List upperBounds = new ArrayList<>( List .of( source2TSourceUB, source2TNodeUB, source2SinkUB, tNode2TSourceUB, tNode2TNodeUB, tNode2SinkUB, tSink2TSourceUB, tSink2TNodeUB, tSink2SinkUB)); long classBoundsSum = upperBounds.stream().mapToLong(l -> l).sum(); if (classBoundsSum == 0) { return; } /* * Distribute remaining arcs among every arc class. Upper bounds of the number of arcs for * every class are taken into account. Additionally, as for large networks these upperbounds * are large, we introduce weight bounds to distribute arcs evenly among arc classes. */ Distributor arcNumDistributor = new Distributor<>(rng); arcNumDistributor .addUpperBound(classId -> (int) Math.min(upperBounds.get(classId), MAX_ARC_NUM)); arcNumDistributor.addUpperBound(classId -> { double classWeight = (double) upperBounds.get(classId) / classBoundsSum; int weightBound = (int) (2.0 * classWeight * remainingArcs); return weightBound + 1; // make this bound positive }); List arcNumDistribution = arcNumDistributor .getDistribution( IntStream.range(0, upperBounds.size()).boxed().collect(Collectors.toList()), remainingArcs); generateArcs(getSources(), getTransshipSources(), arcNumDistribution.get(0)); generateArcs(getSources(), getTransshipNodes(), arcNumDistribution.get(1)); generateArcs(getSources(), getSinks(), arcNumDistribution.get(2)); generateArcs(getTransshipNodes(), getTransshipSources(), arcNumDistribution.get(3)); generateArcs(getTransshipNodes(), getTransshipNodes(), arcNumDistribution.get(4)); generateArcs(getTransshipNodes(), getSinks(), arcNumDistribution.get(5)); generateArcs(getTransshipSinks(), getTransshipSources(), arcNumDistribution.get(6)); generateArcs(getTransshipSinks(), getTransshipNodes(), arcNumDistribution.get(7)); generateArcs(getTransshipSinks(), getSinks(), arcNumDistribution.get(8)); assert config.getArcNum() - graph.edgeSet().size() == 0; } /** * Generates {@code arcsToGenerate} number of arcs between nodes from {@code tails} and * {@code heads}. A node can belong to both lists at the same time. * * @param tails list of possible arc tails. * @param heads list of possible arc heads. * @param arcsToGenerate number of arcs to generate */ private void generateArcs(List tails, List heads, int arcsToGenerate) { // For every tail, compute an upper bound on the number arcs it's // possible to generate from it. Set headsSet = new HashSet<>(heads); List outDegrees = tails .stream().map(node -> getPossibleArcNum(node, headsSet)).collect(Collectors.toList()); long degreeSum = outDegrees.stream().mapToLong(i -> i).sum(); // Add weight bounds as well to make the distribution more uniform. Distributor arcNumDistributor = new Distributor<>(rng); arcNumDistributor.addUpperBound(outDegrees::get); arcNumDistributor.addUpperBound(tailId -> { double tailWeight = (double) outDegrees.get(tailId) / degreeSum; int tailArcWeightBound = (int) (2 * tailWeight * arcsToGenerate); return tailArcWeightBound + 1; }); List arcNumDistribution = arcNumDistributor .getDistribution( IntStream.range(0, tails.size()).boxed().collect(Collectors.toList()), arcsToGenerate); // For every tail, generate the assigned number of arcs. for (int i = 0; i < tails.size(); i++) { Node tail = tails.get(i); int tailArcNum = arcNumDistribution.get(i); ElementsSequenceGenerator headGenerator = new ElementsSequenceGenerator<>(heads, rng); while (tailArcNum > 0 && headGenerator.hasNext()) { Node currentHead = headGenerator.next(); if (isValidArc(tail, currentHead)) { --tailArcNum; addArc(tail, currentHead); } } assert tailArcNum == 0; } } /** * Returns the number of arcs it is possible to generate from {@code node} to the {@code nodes} * set. * * @param node an arc tail. * @param nodes set of possible arc heads. * @return the computed number of arcs it's possible to generate. */ private int getPossibleArcNum(Node node, Set nodes) { int possibleArcNum = nodes.size(); if (nodes.contains(node)) { possibleArcNum--; } for (E arc : graph.outgoingEdgesOf(node.graphVertex)) { Node arcHead = graphVertexMapping.get(Graphs.getOppositeVertex(graph, arc, node.graphVertex)); if (nodes.contains(arcHead)) { possibleArcNum--; } } return possibleArcNum; } /** * Returns the network information computed for the last generated problem. Call this method * only after the first invocation of any generating method. * * @return network information. */ public NetworkInfo getNetworkInfo() { return networkInfo; } /** * Checks if it is possible to add an arc between {@code tail} and {@code head} to the network. * * @param tail arc tail. * @param head arc head. * @return {@code true} if it's possible to add an arc, {@code false} otherwise. */ private boolean isValidArc(Node tail, Node head) { return tail != head && !graph.containsEdge(tail.graphVertex, head.graphVertex); } /** * Adds an arc between the {@code tail} and {@code head}. The added arc is registered to update * upper bounds on the number of possible arcs to generate. * * @param chainSource the source of the chain. * @param tail arc tail. * @param head arc head. */ private void addSkeletonArc(Node chainSource, Node tail, Node head) { assert isValidArc(tail, head); E arc = graph.addEdge(tail.graphVertex, head.graphVertex); capacityMap.put(arc, Math.max(getCapacity(), chainSource.supply)); costMap.put(arc, getCost()); registerSkeletonArc(tail, head); networkInfo.registerChainArc(arc); if (head.type == NodeType.TRANSSHIP_NODE) { chainSource.chainNodes.add(head); } } /** * Adds a simple arc to the network. The arc isn't registered. * * @param tail arc tail. * @param head arc head. */ private void addArc(Node tail, Node head) { assert isValidArc(tail, head); E edge = graph.addEdge(tail.graphVertex, head.graphVertex); capacityMap.put(edge, getCapacity()); costMap.put(edge, getCost()); } /** * Registers an arc between {@code tail} and {@code head} by decreasing one of the upper bounds * by 1. * * @param tail arc tail. * @param head arc head. */ private void registerSkeletonArc(Node tail, Node head) { switch (tail.type) { case PURE_SOURCE: case TRANSSHIP_SOURCE: switch (head.type) { case TRANSSHIP_NODE: source2TNodeUB--; break; case TRANSSHIP_SINK: case PURE_SINK: source2SinkUB--; break; default: // should never happen throw new RuntimeException(); } break; case TRANSSHIP_NODE: switch (head.type) { case TRANSSHIP_NODE: tNode2TNodeUB--; break; case TRANSSHIP_SINK: case PURE_SINK: tNode2SinkUB--; break; default: // should never happen throw new RuntimeException(); } break; default: // should never happen throw new RuntimeException(); } } /** * Generates an arc capacity. This capacity can be infinite. * * @return the generated arc capacity. */ private int getCapacity() { int percent = generateBetween(1, 100); if (percent <= config.getPercentCapacitated()) { return generateBetween(config.getMinCap(), config.getMaxCap()); } else { return Integer.MAX_VALUE; } } /** * Generates an arc cost. This cost can be infinite. * * @return the generated arc cost. */ private int getCost() { int percent = generateBetween(1, 100); if (percent <= config.getPercentWithInfCost()) { return Integer.MAX_VALUE; } else { return generateBetween(config.getMinCost(), config.getMaxCost()); } } private int generatePositiveRandom(int boundInclusive) { return rng.nextInt(boundInclusive) + 1; } /** * Generates a random number using random number generator between {@code startInclusive} and * {@code endInclusive}. * * @param startInclusive lower bound * @param endInclusive upper bound * @return the generated number */ private int generateBetween(int startInclusive, int endInclusive) { return rng.nextInt(endInclusive - startInclusive + 1) + startInclusive; } /** * Generates a random number using random number generator between 0 and {@code endExclusive}. * * @param endExclusive upper bound. * @return the generated number. */ private int generateRandom(int endExclusive) { return rng.nextInt(endExclusive); } /** * Returns a list containing generated transshipment sources. * * @return a list containing generated transshipment sources. */ private List getTransshipSources() { return nodes.subList(config.getPureSourceNum(), config.getSourceNum()); } /** * Returns a list containing generated source (pure sources + t-sources). * * @return a list containing generated sources. */ private List getSources() { return nodes.subList(0, config.getSourceNum()); } /** * Returns a list containing generated t-nodes. * * @return a list containing generated t-nodes. */ private List getTransshipNodes() { return nodes .subList(config.getSourceNum(), config.getSourceNum() + config.getTransshipNodeNum()); } /** * Returns a list containing generated transshipment sinks. * * @return a list containing generated transshipment sinks. */ private List getTransshipSinks() { return nodes .subList( config.getSourceNum() + config.getTransshipNodeNum(), nodes.size() - config.getPureSinkNum()); } /** * Returns a list containing generated sinks (pure sinks + t-sinks). * * @return a list containing generated sinks. */ private List getSinks() { return nodes.subList(config.getSourceNum() + config.getTransshipNodeNum(), nodes.size()); } /** * Enum specifying the nodes type. */ private enum NodeType { PURE_SOURCE { @Override public String toString() { return "Pure source"; } }, TRANSSHIP_SOURCE { @Override public String toString() { return "Transship source"; } }, TRANSSHIP_NODE { @Override public String toString() { return "Transship node"; } }, TRANSSHIP_SINK { @Override public String toString() { return "Transship sink"; } }, PURE_SINK { @Override public String toString() { return "Pure sink"; } }; /** * {@inheritDoc} */ @Override public abstract String toString(); } /** * Internal representation of network nodes. This class is used to store auxiliary information * during generation process. */ private class Node { /** * Graph vertex counterpart of this node. */ V graphVertex; /** * Supply units of this node. This value is 0 for t-nodes. */ int supply; /** * Type of this node. */ NodeType type; /** * List of chain nodes. This list is empty for t-nodes and sinks. */ List chainNodes; /** * Creates a new node using {@code graphVertex} and {@code type}. * * @param graphVertex network vertex. * @param type type of this node. */ Node(V graphVertex, NodeType type) { this.graphVertex = graphVertex; this.type = type; chainNodes = new ArrayList<>(); } /** * Returns the last node of this node's chain. * * @return the last node of this node's chain. */ Node getLastInChain() { return chainNodes.get(chainNodes.size() - 1); } /** * Returns the length of this node's chain. * * @return the length of this node's chain. */ int getChainLength() { return chainNodes.size(); } /** * {@inheritDoc} */ @Override public String toString() { return String.format("{%s}: type = %s, supply = %d", graphVertex, type, supply); } } } NetworkGeneratorConfig.java000066400000000000000000000522561402514743400343630ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; /** * Configuration class to specify network parameters for the {@link NetworkGenerator}. Any valid * configuration specifies a minimum cost flow network to generate. Under additional constraints the * minimum cost flow networks can be interpreted as maximum flow problems or bipartite matching * problems. *

    * In the following parameter definition the term transshipment is used for nodes that have * both incoming and outgoing arcs. This config is used to configure the following parameters: *

      *
    • nodeNum - number of all nodes in the network;
    • *
    • arcNum - number of all arcs in the network;
    • *
    • sourceNum - number of source nodes in the network. Source node is node that has positive * supply value;
    • *
    • sinkNum - number of sink nodes in the network. Sink node is a node that has negative supply * value (i.e. it has demand);
    • *
    • transshipSourceNum - number of transshipment sources. These source nodes compose a subtype of * all source nodes, which means that the number of these nodes must not exceed the number of * sources. This parameter can be called tSourceNum as well, the transshipment sources can be called * t-sources;
    • *
    • transshipSinkNum - number of transshipment sinks. As with transshipment sources, these sinks * are a subtype of all sinks and thus their number must not exceed the number of all sinks. This * parameter can be called tSinkNum as well, the transshipment sinks can be called t-sinks;
    • *
    • totalSupply - the sum of supplies od all source nodes. This value is distributed among source * nodes. The same amount is distributed among sink nodes with negative sign;
    • *
    • minCap - a lower bound on the arc capacities;
    • *
    • maxCap - an upper bound on the arc capacities;
    • *
    • minCost - a lower bound on the arc costs;
    • *
    • maxCost - an upper bound on the arc costs;
    • *
    • percentCapacitated - a value between 0 and 100 which specifies an approximate ratio of arcs * which have finite capacity. Other arcs will have infinite capacity;
    • *
    • percentWithInfCost - a value between 0 and 100 which specifies an approximate ratio of arcs * which have infinite cost. All other arcs will have finite cost.
    • *
    *

    * This parameter set specifies certain amount of implicit parameters: *

      *
    • pureSourceNum - number of sources, which are guaranteed to have no incoming arcs. This value * is equal to the sourceNum - transshipSourceNum;
    • *
    • pureSinkNum - number of sinks, which are guaranteed to have no outcoming arcs. This value is * equal to the sinkNum - transshipSinkNum;
    • *
    • transshipNodeNum - number of nodes in the network which are neither sources now sinks. These * nodes can have both incoming and outcoming arcs and their supply values are equal to 0. The * number of these nodes is equal to the nodeNum - sourceNum - sinkNum. This parameter can be called * tNodeNum as well, transshipment nodes can be called t-nodes.
    • *
    *

    * Not every parameter combination specifies a valid config for a network generator. The following * are existing parameter constraints: *

      *
    • transshipSourceNum $\leq$ sourceNum;
    • *
    • transshipSinkNum $\leq$ sinkNum;
    • *
    • sourceNum $+$ sinkNum $\leq$ nodeNum;
    • *
    • max(sourceNum, sinkNum) $\leq$ totalSupply;
    • *
    • minArcNum $\leq$ arcNum $\leq$ maxArcNum;
    • *
    • minCap $\leq$ maxCap<;/li> *
    • minCost $\leq$ maxCost;
    • *
    • 0 $\leq$ percentCapacitated $\leq$ 100;
    • *
    • 0 $\leq$ percentWithInfCost $\leq$ 100;
    • *
    • all parameters are non-negative except for minCost and maxCost (the are costs may be * negative).
    • *
    *

    * MinArcNum is a number of arcs that is needed to make every node connected to at least one source * and one sink. This value is equal to transshipNodeNum + max(sourceNum, sinkNum). This value can * be computed using {@link NetworkGeneratorConfig#getMinimumArcNum()} for a specific network. This * value can be computes using {@link NetworkGeneratorConfig#getMinimumArcNum(long, long, long)} as * well. MaxArcNum is a number of arcs that makes it impossible to add more arcs to the network * without violating the constraints. This value consists of 3 quantities: *

      *
    • sourceArcs = pureSourceNum*tSourceNum + tSourceNum*(tSourceNum - 1) + sourceNum * (tNodeNum + * sinkNum)
    • *
    • tNodeArcs = tNodeNum*(tSourceNum + (tNodeNum - 1) + sinkNum)
    • *
    • tSinkArcs = tSinkNum*(tSourceNum + tNodeNum + (tSinkNum - 1))
    • *
    *

    * The maximum number of arcs is therefore equal to sourceArcs + tNodeArcs + tSinkArcs. This values * can be computed for a specific network configuration using * {@link NetworkGeneratorConfig#getMaximumArcNum()}, or for specified node quantity parameters * using {@link NetworkGeneratorConfig#getMaximumArcNum(long, long, long, long, long)}. *

    * The general purpose of this config is to specify parameters for the minimum cost flow network. At * the same time, this config can specify parameters for the max flow network or bipartite matching * problems if additional parameter constraints are imposed. If minCost = maxCost, then the network * is called unweighted. An unweighted network specifies a maximum flow problem, it the supply * values are additionally removed. To specify a bipartite matching problem, the parameters must * satisfy: *

      *
    • tSourceNum = tSinkNum = 0;
    • *
    • sourceNum = sinkNum = nodeNum/2 (nodeNum must be even);
    • *
    • totalSupply = sourceNum;
    • *
    • minCap = maxCap = 1.
    • *
    *

    * Note that bipartite matching problem can be both weighted and unweighted. *

    * To construct instances of the {@link NetworkGeneratorConfig}, use * {@link NetworkGeneratorConfigBuilder}. It performs all the parameter validation and provides * meaningful error messages in the cases something is going wrong. * * @author Timofey Chudakov * @see NetworkGenerator * @see NetworkGeneratorConfigBuilder * @see org.jgrapht.alg.flow.mincost.MinimumCostFlowProblem * @see MaximumFlowProblem * @see BipartiteMatchingProblem */ public class NetworkGeneratorConfig { private final int nodeNum; private final int arcNum; private final int sourceNum; private final int sinkNum; private final int transshipSourceNum; private final int transshipSinkNum; private final int totalSupply; private final int minCap; private final int maxCap; private final int minCost; private final int maxCost; private final int percentCapacitated; private final int percentWithInfCost; /** * Constructs a new {@link NetworkGeneratorConfig} * * @param nodeNum number of nodes * @param arcNum number of arcs * @param sourceNum number of network sources * @param sinkNum number of network sinks * @param transshipSourceNum number of transshipment sources * @param transshipSinkNum number of transshipment sinks * @param totalSupply total supply of all network sources * @param minCap arc capacity lower bound * @param maxCap arc capacity upper bound * @param minCost arc cost lower bound * @param maxCost arc cost upper bound * @param percentCapacitated percent of arcs to have finite capacity * @param percentWithInfCost percent of arcs to have infinite cost */ NetworkGeneratorConfig( int nodeNum, int arcNum, int sourceNum, int sinkNum, int transshipSourceNum, int transshipSinkNum, int totalSupply, int minCap, int maxCap, int minCost, int maxCost, int percentCapacitated, int percentWithInfCost) { this.nodeNum = nodeNum; this.arcNum = arcNum; this.sourceNum = sourceNum; this.sinkNum = sinkNum; this.transshipSourceNum = transshipSourceNum; this.transshipSinkNum = transshipSinkNum; this.totalSupply = totalSupply; this.minCap = minCap; this.maxCap = maxCap; this.minCost = minCost; this.maxCost = maxCost; this.percentCapacitated = percentCapacitated; this.percentWithInfCost = percentWithInfCost; } /** * Returns maximum possible number of arcs this network can contain between the source nodes. * This number is 0 if network doesn't contain transshipment sources. * * @return maximum number of arcs between network sources. */ public long getMaxSource2TSourceArcNum() { return (long) getPureSourceNum() * transshipSourceNum + (long) transshipSourceNum * (transshipSourceNum - 1); } /** * Returns maximum number of arcs this network can contain between network sources and * transshipment nodes. * * @return maximum number of arcs between network sources and transshipment nodes. */ public long getMaxSource2TNodeArcNum() { return (long) sourceNum * getTransshipNodeNum(); } /** * Returns maximum number of arcs between network sources and network sinks. * * @return maximum number of arcs between network sources and network sinks. */ public long getMaxSource2SinkArcNum() { return (long) sourceNum * sinkNum; } /** * Returns maximum number of arcs between transshipment nodes and network sources. * * @return maximum number of arcs between transshipment nodes and network sources. */ public long getMaxTNode2TSourceArcNum() { return (long) getTransshipNodeNum() * transshipSourceNum; } /** * Returns maximum number of arcs between transshipment nodes of this network * * @return maximum number of arcs between transshipment nodes of this network */ public long getMaxTNode2TNodeArcNum() { return (long) getTransshipNodeNum() * (getTransshipNodeNum() - 1); } /** * Returns maximum number of arcs between transshipment nodes and network sinks. * * @return maximum number of arcs between transshipment nodes and network sinks. */ public long getMaxTNode2SinkArcNum() { return (long) getTransshipNodeNum() * sinkNum; } /** * Returns maximum number of arcs between network sinks and network sources. * * @return maximum number of arcs between network sinks and network sources. */ public long getMaxTSink2TSourceArcNum() { return (long) transshipSinkNum * transshipSourceNum; } /** * Returns maximum number of arcs between network sinks and transshipment nodes. * * @return maximum number of arcs between network sinks and transshipment nodes. */ public long getMaxTSink2TNodeArcNum() { return (long) transshipSinkNum * getTransshipNodeNum(); } /** * Returns maximum number of arcs between network sinks. * * @return maximum number of arcs between network sinks. */ public long getMaxTSink2SinkArcNum() { return (long) transshipSinkNum * (transshipSinkNum - 1) + getPureSinkNum() * transshipSinkNum; } /** * Returns maximum number of arcs between network sources and all other nodes. * * @return maximum number of arcs between network sources and all other nodes. */ public long getMaxSource2AllArcNum() { return getMaxSource2TSourceArcNum() + getMaxSource2TNodeArcNum() + getMaxSource2SinkArcNum(); } /** * Returns maximum number of arcs between transshipment nodes and all other nodes. * * @return maximum number of arcs between transshipment nodes and all other nodes. */ public long getMaxTransshipNode2AllArcNum() { return getMaxTNode2TSourceArcNum() + getMaxTNode2TNodeArcNum() + getMaxTNode2SinkArcNum(); } /** * Returns maximum number of arcs between network sinks and all other nodes. * * @return maximum number of arcs between network sinks and all other nodes. */ public long getMaxSink2ALlArcNum() { return getMaxTSink2TSourceArcNum() + getMaxTSink2TNodeArcNum() + getMaxTSink2SinkArcNum(); } /** * Returns minimum number of nodes this network can contain. * * @return minimum number of nodes this network can contain. */ public long getMinimumArcNum() { return getTransshipNodeNum() + Math.max(getSourceNum(), getSinkNum()); } /** * Returns maximum number of nodes this network can contain. * * @return maximum number of nodes this network can contain. */ public long getMaximumArcNum() { return getMaxSource2AllArcNum() + getMaxTransshipNode2AllArcNum() + getMaxSink2ALlArcNum(); } /** * Returns minimum number of arcs a network with specifies node parameters can contain. Note, * that the number of transshipment sources and sinks doesn't affect this quantity. * * @param sourceNum number of sources in the network * @param tNodeNum number of transshipment nodes in the network * @param sinkNum number of sinks in the network * @return minimum number of arcs a network with specifies nodes parameters can contain. */ public static long getMinimumArcNum(long sourceNum, long tNodeNum, long sinkNum) { return tNodeNum + Math.max(sourceNum, sinkNum); } /** * Returns maximum number of arcs a network with specified node parameters can contain. Use this * network in situation when number of transshipment sources and sinks is zero. * * @param sourceNum number of sources in the network * @param tNodeNum number of transshipment nodes in the network * @param sinkNum number of sinks in the network * @return maximum number of arcs a network with specified node parameters can contain. */ public static long getMaximumArcNum(long sourceNum, long tNodeNum, long sinkNum) { return getMaximumArcNum(sourceNum, 0, tNodeNum, 0, sinkNum); } /** * Returns maximum number of arcs a network with specified node parameters can contain. * * @param sourceNum number of sources in the network * @param tSourceNum number of transshipment sources in the network * @param tNodeNum number of transshipment nodes in the network * @param tSinkNum number of transshipment sinks in the network * @param sinkNum number of sinks in the network * @return maximum number of arcs a network with specified node parameters can contain. */ public static long getMaximumArcNum( long sourceNum, long tSourceNum, long tNodeNum, long tSinkNum, long sinkNum) { long pureSourceNum = sourceNum - tSourceNum; long sourceArcs = pureSourceNum * tSourceNum + tSourceNum * (tSourceNum - 1) + sourceNum * (tNodeNum + sinkNum); long tNodeArcs = tNodeNum * (tSourceNum + (tNodeNum - 1) + sinkNum); long sinkArcs = tSinkNum * (tSourceNum + tNodeNum + (sinkNum - 1)); return sourceArcs + tNodeArcs + sinkArcs; } /** * Returns number of pure sources in the network. Pure sources are network sources which can't * have incoming arcs. * * @return number of pure sources in the network. */ public int getPureSourceNum() { return sourceNum - transshipSourceNum; } /** * Returns number of pure sinks in the network. Pure sinks are network sinks which can't have * outgoing arcs. which can't have outgoing arcs. * * @return number of pure sinks in the network. */ public int getPureSinkNum() { return sinkNum - transshipSinkNum; } /** * Checks if the network allows different arc costs. * * @return {@code true} if the network allows different arc costs, {@code false} otherwise. */ public boolean isCostWeighted() { return minCost != maxCost; } /** * Returns the number of transshipment nodes in the network. * * @return the number of transshipment nodes in the network. */ public int getTransshipNodeNum() { return nodeNum - sourceNum - sinkNum; } /** * Checks if the network satisfies the transportation problem conditions. *

    * In transportation problem the sum of network sources and network sinks equals to the number * of nodes (no transshipment nodes) and the network doesn't contain transshipment sources and * sinks. In essence, the network is a bipartite graph. * * @return {@code true} if the network specifies a transportation problem, {@code false} * otherwise. */ private boolean transportationProblemCondition() { return sourceNum + sinkNum == nodeNum && transshipSourceNum == 0 && transshipSinkNum == 0; } /** * Checks if the transportation network is a bipartite matching problem. *

    * A transportation problem is a bipartite matching problem, if the bipartite graph partitions * are of equal size, every source supply is equal to 1 (thus the demand of every sink is equal * to 1 as well), and the capacity of every arc is 1. * * @return {@code true} if the transportation problem is a bipartite matching problem, * {@code false} otherwise. */ private boolean assignmentProblemCondition() { return sourceNum == sinkNum && totalSupply == sourceNum && minCap == 1 && maxCap == 1; } /** * Checks if a network can be interpreted as a maximum flow problem. *

    * The only condition for a minimum cost flow to be interpreted as a maximum flow problem is * that the arc costs are constant for all arcs. * * @return {@code true} if the network can be interpreted as a max flow problem, {@code false} * otherwise. */ public boolean isMaxFlowProblem() { return !isCostWeighted(); } /** * Checks if the network is a bipartite matching problem (assignment problem). The problem can * we both weighted and unweighted. * * @return {@code true} if the network specifies a bipartite matching problem, {@code false} * otherwise. */ public boolean isAssignmentProblem() { return transportationProblemCondition() && assignmentProblemCondition(); } /** * Returns the number of nodes in the network. * * @return the number of nodes in the network. */ public int getNodeNum() { return nodeNum; } /** * Returns the number of arcs in the network. * * @return the number of arcs in the network. */ public int getArcNum() { return arcNum; } /** * Returns the number of sources in the network. * * @return the number of sources in the network. */ public int getSourceNum() { return sourceNum; } /** * Returns the number of sinks in the network. * * @return the number of sinks in the network. */ public int getSinkNum() { return sinkNum; } /** * Returns the number of transshipment sources in the network. * * @return the number of transshipment sources in the network. */ public int getTransshipSourceNum() { return transshipSourceNum; } /** * Returns the number of transshipment sinks in the network. * * @return the number of transshipment sinks in the network. */ public int getTransshipSinkNum() { return transshipSinkNum; } /** * Returns the total supply of the network. * * @return the total supply of the network. */ public int getTotalSupply() { return totalSupply; } /** * Returns arc capacity lower bound. * * @return arc capacity lower bound. */ public int getMinCap() { return minCap; } /** * Returns arc capacity upper bound. * * @return arc capacity upper bound. */ public int getMaxCap() { return maxCap; } /** * Returns arc cost lower bound. * * @return arc cost lower bound. */ public int getMinCost() { return minCost; } /** * Returns arc cost upper bound. * * @return arc cost upper bound. */ public int getMaxCost() { return maxCost; } /** * Returns percent of arcs that have finite capacity. * * @return percent of arcs that have finite capacity. */ public int getPercentCapacitated() { return percentCapacitated; } /** * Returns percent of arcs that have infinite cost. * * @return percent of arcs that have infinite cost. */ public int getPercentWithInfCost() { return percentWithInfCost; } } NetworkGeneratorConfigBuilder.java000066400000000000000000000420551402514743400356660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; /** * Builder class for the {@link NetworkGeneratorConfig}. This class perform all the necessary * parameter validation and provides meaningful error messages. For the network parameter * description and a complete list of parameter constrants, see {@link NetworkGeneratorConfig}. Use * this class to construct instances of the {@link NetworkGeneratorConfig}. * * @author Timofey Chudakov * @see NetworkGenerator * @see NetworkGeneratorConfig */ public class NetworkGeneratorConfigBuilder { int nodeNum = 0; int arcNum = 0; int sourceNum = 0; int sinkNum = 0; int tSourceNum = 0; int tSinkNum = 0; int totalSupply = 0; int minCap = 0; int maxCap = 0; int minCost = 0; int maxCost = 0; int percentCapacitated = 100; int percentWithInfCost = 0; /** * Builds the {@link NetworkGeneratorConfig}. This method performs remaining parameter * validation. * * @return the constructed {@link NetworkGeneratorConfig}. */ public NetworkGeneratorConfig build() { if (nodeNum <= 0) { invalidParam("Number of nodes must be positive"); } else if (arcNum <= 0) { invalidParam("Number of arcs must be positive"); } else if (sourceNum <= 0) { invalidParam("Number of sources must be positive"); } else if (sinkNum <= 0) { invalidParam("Number of sinks must be positive"); } else if (sourceNum + sinkNum > nodeNum) { invalidParam("Number of sources and sinks must not exceed the number of nodes"); } else if (tSourceNum > sourceNum) { invalidParam( "Number of transhipment sources must not exceed the overall number of sources"); } else if (tSinkNum > sinkNum) { invalidParam( "Number of transhipment sinks must not exceed the overall number of sinks"); } else if (totalSupply < Math.max(sourceNum, sinkNum)) { invalidParam( "Total supply must not be less than the number of sources and the number of sinks"); } else if (minCap > maxCap) { invalidParam("Minimum capacity must not exceed the maximum capacity"); } else if (minCap <= 0) { invalidParam("Minimum capacity must be positive"); } else if (minCost > maxCost) { invalidParam("Minimum cost must not exceed the maximum cost"); } int tNodeNum = nodeNum - sourceNum - sinkNum; long minArcNum = NetworkGeneratorConfig.getMinimumArcNum(sourceNum, tNodeNum, sinkNum); long maxArcNum = NetworkGeneratorConfig .getMaximumArcNum(sourceNum, tSourceNum, tNodeNum, tSinkNum, sinkNum); if (arcNum < minArcNum) { invalidParam("Too few arcs to generate a valid problem"); } else if (arcNum > maxArcNum) { invalidParam("Too many arcs to generate a valid problem"); } return new NetworkGeneratorConfig( nodeNum, arcNum, sourceNum, sinkNum, tSourceNum, tSinkNum, totalSupply, minCap, maxCap, minCost, maxCost, percentCapacitated, percentWithInfCost); } /** * Throws {@code IllegalArgumentException} with the specified {@code message}. * * @param message a message for the exception. */ private void invalidParam(String message) { throw new IllegalArgumentException(message); } /** * Perform node parameter validation. * * @param value the value of a node parameter. * @return {@code value} */ private int checkNodeConstraint(int value) { if (value > NetworkGenerator.MAX_NODE_NUM) { invalidParam( String.format("Number of nodes must not exceed %d", NetworkGenerator.MAX_NODE_NUM)); } return value; } /** * Performs capacity and cost parameter valiation. * * @param value the value of the capacity or cost parameter * @return {@code value} */ private int checkCapacityCostConstraint(int value) { if (Math.abs(value) > NetworkGenerator.CAPACITY_COST_BOUND) { invalidParam( String .format( "Arcs capacities and cost must be between -%d and %d", NetworkGenerator.CAPACITY_COST_BOUND, NetworkGenerator.CAPACITY_COST_BOUND)); } return value; } /** * Sets all the network parameters. * * @param nodeNum number of nodes in the network * @param arcNum number of arcs in the network * @param sourceNum number of sources in the network * @param sinkNum number of sinks in the network * @param transshipSourceNum number of transshipment sources in the network * @param transshipSinkNum number of transshipment sinks in the network * @param totalSupply total supply of the network * @param minCap arc capacity lower bound * @param maxCap arc capacity upper bound * @param minCost arc cost lower bound * @param maxCost arc cost upper bound * @param percentCapacitated percent of arcs to have finite capacity * @param percentWithInfCost percent of arcs to have infinite cost * @return this object */ public NetworkGeneratorConfigBuilder setParams( int nodeNum, int arcNum, int sourceNum, int sinkNum, int transshipSourceNum, int transshipSinkNum, int totalSupply, int minCap, int maxCap, int minCost, int maxCost, int percentCapacitated, int percentWithInfCost) { setNodeNum(nodeNum); setArcNum(arcNum); setSourceNum(sourceNum); setSinkNum(sinkNum); setTSourceNum(transshipSourceNum); setTSinkNum(transshipSinkNum); setTotalSupply(totalSupply); setMinCap(minCap); setMaxCap(maxCap); setMinCost(minCost); setMaxCost(maxCost); setPercentCapacitated(percentCapacitated); setPercentWithInfCost(percentWithInfCost); return this; } /** * Sets maximum flow network parameter subset. The values of minCap and maxCap are set to 1, the * values of {@code sourceNum} and {@code sinkNum} are set to 1 and the value of the * {@code percentCapacitated} is set to 100. * * @param nodeNum number of nodes in the network * @param arcNum number of arcs in the network * @param supply total supply of the network * @return this object */ public NetworkGeneratorConfigBuilder setMaximumFlowProblemParams( int nodeNum, int arcNum, int supply) { setMaximumFlowProblemParams(nodeNum, arcNum, supply, 1, 1); return this; } /** * Sets maximum flow network parameter subset. The values of {@code sourceNum} and * {@code sinkNum} are set to 1 and the value of the {@code percentCapacitated} is set to 100. * * @param nodeNum number of nodes in the network * @param arcNum number of arcs in the network * @param supply total supply of the network * @param minCap arc capacity lower bound * @param maxCap arc capacity upper bound * @return this object */ public NetworkGeneratorConfigBuilder setMaximumFlowProblemParams( int nodeNum, int arcNum, int supply, int minCap, int maxCap) { setMaximumFlowProblemParams(nodeNum, arcNum, supply, minCap, maxCap, 1, 1); return this; } /** * Sets maximum flow network parameter subset. The value of the {@code percentCapacitated} is * set to 100. * * @param nodeNum number of nodes in the network * @param arcNum number of arcs in the network * @param supply total supply of the network * @param minCap arc capacity lower bound * @param maxCap arc capacity upper bound * @param sourceNum number of source in the network * @param sinkNum number of sinks in the network * @return this object */ public NetworkGeneratorConfigBuilder setMaximumFlowProblemParams( int nodeNum, int arcNum, int supply, int minCap, int maxCap, int sourceNum, int sinkNum) { setMaximumFlowProblemParams( nodeNum, arcNum, supply, minCap, maxCap, sourceNum, sinkNum, 100); return this; } /** * Sets maximum flow network parameter subset. * * @param nodeNum number of nodes in the network * @param arcNum number of arcs in the network * @param supply total supply of the network * @param minCap arc capacity lower bound * @param maxCap arc capacity upper bound * @param sourceNum number of source in the network * @param sinkNum number of sinks in the network * @param percentCapacitated percent of arcs to have finite capacity * @return this object */ public NetworkGeneratorConfigBuilder setMaximumFlowProblemParams( int nodeNum, int arcNum, int supply, int minCap, int maxCap, int sourceNum, int sinkNum, int percentCapacitated) { setParams( nodeNum, arcNum, sourceNum, sinkNum, 0, 0, supply, minCap, maxCap, 1, 1, percentCapacitated, 0); return this; } /** * Sets bipartite matching parameter subset. The values of the {@code minCost} and * {@code maxCost} are set to 1, the value of the {@code percentWithInfCost} is set to 0. * * @param nodeNum number of nodes in the network * @param arcNum number of arcs in the network * @return this object */ public NetworkGeneratorConfigBuilder setBipartiteMatchingProblemParams(int nodeNum, int arcNum) { setBipartiteMatchingProblemParams(nodeNum, arcNum, 1, 1); return this; } /** * Sets bipartite matching parameter subset. The value of the {@code percentWithInfCost} is set * to 0. * * @param nodeNum number of nodes in the network * @param arcNum number of arcs in the network * @param minCost arc cost lower bound * @param maxCost arc cost upper bound * @return this object */ public NetworkGeneratorConfigBuilder setBipartiteMatchingProblemParams( int nodeNum, int arcNum, int minCost, int maxCost) { setBipartiteMatchingProblemParams(nodeNum, arcNum, minCost, maxCost, 0); return this; } /** * Sets bipartite matching parameter subset. * * @param nodeNum number of nodes in the network * @param arcNum number of arcs in the network * @param minCost arc cost lower bound * @param maxCost arc cost upper bound * @param percentWithInfCost percent of arcs to have infinite cost * @return this object */ public NetworkGeneratorConfigBuilder setBipartiteMatchingProblemParams( int nodeNum, int arcNum, int minCost, int maxCost, int percentWithInfCost) { if ((nodeNum & 1) != 0) { invalidParam("Assignment problem must have even number of nodes"); } setParams( nodeNum, arcNum, nodeNum / 2, nodeNum / 2, 0, 0, nodeNum / 2, 1, 1, minCost, maxCost, 100, percentWithInfCost); return this; } /** * Sets the number of nodes in the network. * * @param nodeNum the number of nodes in the network. * @return this object. */ public NetworkGeneratorConfigBuilder setNodeNum(int nodeNum) { if (nodeNum <= 0) { invalidParam("Number of nodes must be positive"); } this.nodeNum = checkNodeConstraint(nodeNum); return this; } /** * Sets the number of arcs in the network. * * @param arcNum the number of arcs in the network. * @return this object. */ public NetworkGeneratorConfigBuilder setArcNum(int arcNum) { if (arcNum > NetworkGenerator.MAX_ARC_NUM) { invalidParam(String.format("Number of arcs must not exceed %d", arcNum)); } this.arcNum = arcNum; return this; } /** * Sets the number of sources in the network. * * @param sourceNum the number of sources in the network. * @return this object */ public NetworkGeneratorConfigBuilder setSourceNum(int sourceNum) { if (sourceNum <= 0) { invalidParam("Number of sources must be positive"); } this.sourceNum = checkNodeConstraint(sourceNum); return this; } /** * Sets the number of sinks in the network. * * @param sinkNum the number of sinks in the network. * @return this object. */ public NetworkGeneratorConfigBuilder setSinkNum(int sinkNum) { if (sinkNum <= 0) { invalidParam("Number of sinks must be positive"); } this.sinkNum = checkNodeConstraint(sinkNum); return this; } /** * Sets the number of transshipment sources in the network. * * @param tSourceNum the number of transshipment sources in the network. * @return this object. */ public NetworkGeneratorConfigBuilder setTSourceNum(int tSourceNum) { if (tSourceNum < 0) { invalidParam("Number of transshipment sources must be non-negative"); } this.tSourceNum = checkNodeConstraint(tSourceNum); return this; } /** * Sets the number of transshipment sinks in the network. * * @param tSinkNum the number of transshipment sinks in the network. * @return this object. */ public NetworkGeneratorConfigBuilder setTSinkNum(int tSinkNum) { if (tSinkNum < 0) { invalidParam("Number of transshipment sinks must be non-negative"); } this.tSinkNum = checkNodeConstraint(tSinkNum); return this; } /** * Sets the total supply of the network. * * @param totalSupply the total supply of the network. * @return this object. */ public NetworkGeneratorConfigBuilder setTotalSupply(int totalSupply) { if (totalSupply > NetworkGenerator.MAX_SUPPLY) { invalidParam( String.format("Total supply must not exceed %d", NetworkGenerator.MAX_NODE_NUM)); } this.totalSupply = totalSupply; return this; } /** * Sets the arc capacity lower bound. * * @param minCap the arc capacity lower bound. * @return this object. */ public NetworkGeneratorConfigBuilder setMinCap(int minCap) { if (minCap < 0) { invalidParam("Minimum arc capacity must be non-negative"); } this.minCap = checkCapacityCostConstraint(minCap); return this; } /** * Sets the arc capacity upper bound. * * @param maxCap the arc capacity upper bound. * @return this object. */ public NetworkGeneratorConfigBuilder setMaxCap(int maxCap) { if (maxCap < 0) { invalidParam("Maximum arc capacity must be non-negative"); } this.maxCap = checkCapacityCostConstraint(maxCap); return this; } /** * Sets the arc cost lower bound. * * @param minCost the arc cost lower bound. * @return this object. */ public NetworkGeneratorConfigBuilder setMinCost(int minCost) { this.minCost = checkCapacityCostConstraint(minCost); return this; } /** * Sets the arc cost upper bound. * * @param maxCost the arc cost upper bound. * @return this object. */ public NetworkGeneratorConfigBuilder setMaxCost(int maxCost) { this.maxCost = checkCapacityCostConstraint(maxCost); return this; } /** * Sets the percent of arcs to have finite capacity. * * @param percentCapacitated the percent of arcs to have finite capacity. * @return this object. */ public NetworkGeneratorConfigBuilder setPercentCapacitated(int percentCapacitated) { if (percentCapacitated < 0 || percentCapacitated > 100) { invalidParam("Percent of capacitated arcs must be between 0 and 100 inclusive"); } this.percentCapacitated = percentCapacitated; return this; } /** * Sets the percent of arcs to have infinite cost. * * @param percentWithInfCost the percent of arcs to have infinite cost. * @return this object. */ public NetworkGeneratorConfigBuilder setPercentWithInfCost(int percentWithInfCost) { if (percentWithInfCost < 0 || percentWithInfCost > 100) { invalidParam("Percent of arcs with infinite cost must be between 0 and 100 inclusive"); } this.percentWithInfCost = percentWithInfCost; return this; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/NetworkInfo.java000066400000000000000000000107251402514743400322540ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; import java.util.*; /** * Represents network auxiliary information. This information is produced by the * {@link NetworkGenerator}. *

    * Using the network information instance, you can find out: *

      *
    • Which network vertices belong to which class.
    • *
    • Which network arcs belong to the skeleton network.
    • *
    * * @param the graph vertex type * @param the graph edge type * @author Timofey Chudakov * @see NetworkGenerator */ public class NetworkInfo { /** * Network configuration. */ NetworkGeneratorConfig config; /** * List of network vertices. */ List vertices; /** * List of network skeleton arcs. */ List skeletonArcs; /** * Creates a new network information instance. * * @param config network configuration. */ NetworkInfo(NetworkGeneratorConfig config) { this.config = config; this.vertices = new ArrayList<>(); this.skeletonArcs = new ArrayList<>(); } /** * Saves information about the arc {@code chainArc}. * * @param chainArc chain arc. */ void registerChainArc(E chainArc) { skeletonArcs.add(chainArc); } /** * Returns a list containing network pure sources. * * @return a list containing network pure sources. */ public List getPureSources() { return Collections.unmodifiableList(vertices.subList(0, config.getPureSourceNum())); } /** * Returns a list containing network t-sources. * * @return a list containing network t-sources. */ public List getTransshipmentSources() { return Collections .unmodifiableList(vertices.subList(config.getPureSourceNum(), config.getSourceNum())); } /** * Returns a list containing network sources (pure sources + t-sources). * * @return a list containing network sources. */ public List getSources() { return Collections.unmodifiableList(vertices.subList(0, config.getSourceNum())); } /** * Returns a list containing network t-nodes. * * @return a list containing network t-nodes. */ public List getTransshipmentNodes() { return Collections .unmodifiableList( vertices .subList( config.getSourceNum(), config.getSourceNum() + config.getTransshipNodeNum())); } /** * Returns a list containing network pure sinks. * * @return a list containing network pure sinks. */ public List getPureSinks() { return Collections .unmodifiableList( vertices .subList(config.getNodeNum() - config.getPureSinkNum(), config.getNodeNum())); } /** * Return a list containing network t-sinks. * * @return a list containing network t-sinks. */ public List getTransshipmentSinks() { return Collections .unmodifiableList( vertices .subList( config.getNodeNum() - config.getSinkNum(), config.getNodeNum() - config.getPureSinkNum())); } /** * Returns a list containing network sinks (pure sinks + t-sinks). * * @return a list containing network sinks. */ public List getSinks() { return Collections .unmodifiableList( vertices.subList(config.getNodeNum() - config.getSinkNum(), config.getNodeNum())); } /** * Return a list of network skeleton arcs. * * @return a list of network skeleton arcs. */ public List getSkeletonArcs() { return Collections.unmodifiableList(skeletonArcs); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/netgen/package-info.java000066400000000000000000000001151402514743400323230ustar00rootroot00000000000000/** * Network generator components */ package org.jgrapht.generate.netgen; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/generate/package-info.java000066400000000000000000000001261402514743400310450ustar00rootroot00000000000000/** * Generators for graphs of various topologies. */ package org.jgrapht.generate; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/000077500000000000000000000000001402514743400251665ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractBaseGraph.java000066400000000000000000000436231402514743400313610ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.graph.specifics.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * The most general implementation of the {@link org.jgrapht.Graph} interface. * *

    * Its subclasses add various restrictions to get more specific graphs. The decision whether it is * directed or undirected is decided at construction time and cannot be later modified (see * constructor for details). * *

    * The behavior of this class can be adjusted by changing the {@link GraphSpecificsStrategy} that is * provided from the constructor. All implemented strategies guarantee deterministic vertex and edge * set ordering (via {@link LinkedHashMap} and {@link LinkedHashSet}). The defaults are reasonable * for most use-cases, only change if you know what you are doing. * *

    * The default graph implementations are not safe for concurrent reads and writes from different * threads. If an application attempts to modify a graph in one thread while another thread is * reading or writing the same graph, undefined behavior will result. However, concurrent reads * against the same graph from different threads are safe. (Note that the {@link org.jgrapht.Graph * Graph interface} itself makes no such guarantee, so for non-default implementations, different * rules may apply.) * *

    * If you need support for concurrent reads and writes, consider using the * {@link org.jgrapht.graph.concurrent.AsSynchronizedGraph AsSynchronizedGraph wrapper}. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh * @author Dimitrios Michail */ public abstract class AbstractBaseGraph extends AbstractGraph implements Graph, Cloneable, Serializable { private static final long serialVersionUID = -3582386521833998627L; private static final String LOOPS_NOT_ALLOWED = "loops not allowed"; private static final String GRAPH_SPECIFICS_MUST_NOT_BE_NULL = "Graph specifics must not be null"; private static final String INVALID_VERTEX_SUPPLIER_DOES_NOT_RETURN_UNIQUE_VERTICES_ON_EACH_CALL = "Invalid vertex supplier (does not return unique vertices on each call)."; private static final String MIXED_GRAPH_NOT_SUPPORTED = "Mixed graph not supported"; private static final String GRAPH_SPECIFICS_STRATEGY_REQUIRED = "Graph specifics strategy required"; private static final String THE_GRAPH_CONTAINS_NO_VERTEX_SUPPLIER = "The graph contains no vertex supplier"; private static final String THE_GRAPH_CONTAINS_NO_EDGE_SUPPLIER = "The graph contains no edge supplier"; private transient Set unmodifiableVertexSet = null; private Supplier vertexSupplier; private Supplier edgeSupplier; private GraphType type; private Specifics specifics; private IntrusiveEdgesSpecifics intrusiveEdgesSpecifics; private GraphSpecificsStrategy graphSpecificsStrategy; private transient GraphIterables graphIterables = null; /** * Construct a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param type the graph type * * @throws IllegalArgumentException if the graph type is mixed */ protected AbstractBaseGraph( Supplier vertexSupplier, Supplier edgeSupplier, GraphType type) { this(vertexSupplier, edgeSupplier, type, new FastLookupGraphSpecificsStrategy<>()); } /** * Construct a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param type the graph type * @param graphSpecificsStrategy strategy for constructing low-level graph specifics * * @throws IllegalArgumentException if the graph type is mixed */ protected AbstractBaseGraph( Supplier vertexSupplier, Supplier edgeSupplier, GraphType type, GraphSpecificsStrategy graphSpecificsStrategy) { this.vertexSupplier = vertexSupplier; this.edgeSupplier = edgeSupplier; this.type = Objects.requireNonNull(type); if (type.isMixed()) { throw new IllegalArgumentException(MIXED_GRAPH_NOT_SUPPORTED); } this.graphSpecificsStrategy = Objects.requireNonNull(graphSpecificsStrategy, GRAPH_SPECIFICS_STRATEGY_REQUIRED); this.specifics = Objects .requireNonNull( graphSpecificsStrategy.getSpecificsFactory().apply(this, type), GRAPH_SPECIFICS_MUST_NOT_BE_NULL); this.intrusiveEdgesSpecifics = Objects .requireNonNull( graphSpecificsStrategy.getIntrusiveEdgesSpecificsFactory().apply(type), GRAPH_SPECIFICS_MUST_NOT_BE_NULL); } /** * {@inheritDoc} */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { return specifics.getAllEdges(sourceVertex, targetVertex); } @Override public Supplier getEdgeSupplier() { return edgeSupplier; } /** * Set the edge supplier that the graph uses whenever it needs to create new edges. * *

    * A graph uses the edge supplier to create new edge objects whenever a user calls method * {@link Graph#addEdge(Object, Object)}. Users can also create the edge in user code and then * use method {@link Graph#addEdge(Object, Object, Object)} to add the edge. * *

    * In contrast with the {@link Supplier} interface, the edge supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new edge to be added in a graph e must not be equal to * any other edge in the graph (even if the graph allows edge-multiplicity). More formally, the * graph must not contain any edge e2 such that e2.equals(e). * * @param edgeSupplier the edge supplier */ public void setEdgeSupplier(Supplier edgeSupplier) { this.edgeSupplier = edgeSupplier; } @Override public Supplier getVertexSupplier() { return vertexSupplier; } /** * Set the vertex supplier that the graph uses whenever it needs to create new vertices. * *

    * A graph uses the vertex supplier to create new vertex objects whenever a user calls method * {@link Graph#addVertex()}. Users can also create the vertex in user code and then use method * {@link Graph#addVertex(Object)} to add the vertex. * *

    * In contrast with the {@link Supplier} interface, the vertex supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new vertex to be added in a graph v must not be equal * to any other vertex in the graph. More formally, the graph must not contain any vertex * v2 such that v2.equals(v). * *

    * Care must also be taken when interchanging calls to methods {@link Graph#addVertex(Object)} * and {@link Graph#addVertex()}. In such a case the user must make sure never to add vertices * in the graph using method {@link Graph#addVertex(Object)}, which are going to be returned in * the future by the supplied vertex supplier. Such a sequence will result into an * {@link IllegalArgumentException} when calling method {@link Graph#addVertex()}. * * @param vertexSupplier the vertex supplier */ public void setVertexSupplier(Supplier vertexSupplier) { this.vertexSupplier = vertexSupplier; } /** * {@inheritDoc} */ @Override public E getEdge(V sourceVertex, V targetVertex) { return specifics.getEdge(sourceVertex, targetVertex); } /** * {@inheritDoc} */ @Override public E addEdge(V sourceVertex, V targetVertex) { assertVertexExist(sourceVertex); assertVertexExist(targetVertex); if (!type.isAllowingSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } if (edgeSupplier == null) { throw new UnsupportedOperationException(THE_GRAPH_CONTAINS_NO_EDGE_SUPPLIER); } if (!type.isAllowingMultipleEdges()) { E e = specifics .createEdgeToTouchingVerticesIfAbsent(sourceVertex, targetVertex, edgeSupplier); if (e != null) { boolean edgeAdded = false; try { edgeAdded = intrusiveEdgesSpecifics.add(e, sourceVertex, targetVertex); } finally { if (!edgeAdded) { // edge was already present or adding threw an exception -> revert add specifics.removeEdgeFromTouchingVertices(sourceVertex, targetVertex, e); } } if (edgeAdded) { return e; } } } else { E e = edgeSupplier.get(); if (intrusiveEdgesSpecifics.add(e, sourceVertex, targetVertex)) { specifics.addEdgeToTouchingVertices(sourceVertex, targetVertex, e); return e; } } return null; } /** * {@inheritDoc} */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { if (e == null) { throw new NullPointerException(); } assertVertexExist(sourceVertex); assertVertexExist(targetVertex); if (!type.isAllowingSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } if (!type.isAllowingMultipleEdges()) { if (!specifics.addEdgeToTouchingVerticesIfAbsent(sourceVertex, targetVertex, e)) { return false; } boolean edgeAdded = false; try { edgeAdded = intrusiveEdgesSpecifics.add(e, sourceVertex, targetVertex); } finally { if (!edgeAdded) { // edge was already present or adding threw an exception -> revert add specifics.removeEdgeFromTouchingVertices(sourceVertex, targetVertex, e); } } return edgeAdded; } else { if (intrusiveEdgesSpecifics.add(e, sourceVertex, targetVertex)) { specifics.addEdgeToTouchingVertices(sourceVertex, targetVertex, e); return true; } return false; } } @Override public V addVertex() { if (vertexSupplier == null) { throw new UnsupportedOperationException(THE_GRAPH_CONTAINS_NO_VERTEX_SUPPLIER); } V v = vertexSupplier.get(); if (!specifics.addVertex(v)) { throw new IllegalArgumentException( INVALID_VERTEX_SUPPLIER_DOES_NOT_RETURN_UNIQUE_VERTICES_ON_EACH_CALL); } return v; } /** * {@inheritDoc} */ @Override public boolean addVertex(V v) { if (v == null) { throw new NullPointerException(); } else if (containsVertex(v)) { return false; } else { specifics.addVertex(v); return true; } } /** * {@inheritDoc} */ @Override public V getEdgeSource(E e) { return intrusiveEdgesSpecifics.getEdgeSource(e); } /** * {@inheritDoc} */ @Override public V getEdgeTarget(E e) { return intrusiveEdgesSpecifics.getEdgeTarget(e); } /** * Returns a shallow copy of this graph instance. Neither edges nor vertices are cloned. * * @return a shallow copy of this graph. * * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ @Override public Object clone() { try { AbstractBaseGraph newGraph = TypeUtil.uncheckedCast(super.clone()); newGraph.vertexSupplier = this.vertexSupplier; newGraph.edgeSupplier = this.edgeSupplier; newGraph.type = type; newGraph.unmodifiableVertexSet = null; newGraph.graphSpecificsStrategy = this.graphSpecificsStrategy; // NOTE: it's important for this to happen in an object // method so that the new inner class instance gets associated with // the right outer class instance newGraph.specifics = newGraph.graphSpecificsStrategy .getSpecificsFactory().apply(newGraph, newGraph.type); newGraph.intrusiveEdgesSpecifics = newGraph.graphSpecificsStrategy .getIntrusiveEdgesSpecificsFactory().apply(newGraph.type); newGraph.graphIterables = null; Graphs.addGraph(newGraph, this); return newGraph; } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new RuntimeException(); } } /** * {@inheritDoc} */ @Override public boolean containsEdge(E e) { return intrusiveEdgesSpecifics.containsEdge(e); } /** * {@inheritDoc} */ @Override public boolean containsVertex(V v) { return specifics.getVertexSet().contains(v); } /** * {@inheritDoc} */ @Override public int degreeOf(V vertex) { assertVertexExist(vertex); return specifics.degreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set edgeSet() { return intrusiveEdgesSpecifics.getEdgeSet(); } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { assertVertexExist(vertex); return specifics.edgesOf(vertex); } /** * {@inheritDoc} */ @Override public int inDegreeOf(V vertex) { assertVertexExist(vertex); return specifics.inDegreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { assertVertexExist(vertex); return specifics.incomingEdgesOf(vertex); } /** * {@inheritDoc} */ @Override public int outDegreeOf(V vertex) { assertVertexExist(vertex); return specifics.outDegreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { assertVertexExist(vertex); return specifics.outgoingEdgesOf(vertex); } /** * {@inheritDoc} */ @Override public E removeEdge(V sourceVertex, V targetVertex) { E e = getEdge(sourceVertex, targetVertex); if (e != null) { specifics.removeEdgeFromTouchingVertices(sourceVertex, targetVertex, e); intrusiveEdgesSpecifics.remove(e); } return e; } /** * {@inheritDoc} */ @Override public boolean removeEdge(E e) { if (containsEdge(e)) { V sourceVertex = getEdgeSource(e); V targetVertex = getEdgeTarget(e); specifics.removeEdgeFromTouchingVertices(sourceVertex, targetVertex, e); intrusiveEdgesSpecifics.remove(e); return true; } else { return false; } } /** * {@inheritDoc} */ @Override public boolean removeVertex(V v) { if (containsVertex(v)) { Set touchingEdgesList = edgesOf(v); // cannot iterate over list - will cause // ConcurrentModificationException removeAllEdges(new ArrayList<>(touchingEdgesList)); specifics.getVertexSet().remove(v); // remove the vertex itself return true; } else { return false; } } /** * {@inheritDoc} */ @Override public Set vertexSet() { if (unmodifiableVertexSet == null) { unmodifiableVertexSet = Collections.unmodifiableSet(specifics.getVertexSet()); } return unmodifiableVertexSet; } /** * {@inheritDoc} */ @Override public double getEdgeWeight(E e) { if (e == null) { throw new NullPointerException(); } return intrusiveEdgesSpecifics.getEdgeWeight(e); } /** * Set an edge weight. * * @param e the edge * @param weight the weight * @throws UnsupportedOperationException if the graph is not weighted */ @Override public void setEdgeWeight(E e, double weight) { if (e == null) { throw new NullPointerException(); } intrusiveEdgesSpecifics.setEdgeWeight(e, weight); } /** * {@inheritDoc} */ @Override public GraphType getType() { return type; } @Override public GraphIterables iterables() { // override interface to avoid instantiating frequently if (graphIterables == null) { graphIterables = new DefaultGraphIterables<>(this); } return graphIterables; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/AbstractGraph.java000066400000000000000000000206671402514743400305710ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * A skeletal implementation of the Graph interface, to minimize the effort required to * implement graph interfaces. This implementation is applicable to both: directed graphs and * undirected graphs. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh * @see Graph */ public abstract class AbstractGraph implements Graph { /** * Construct a new empty graph object. */ protected AbstractGraph() { } /** * @see Graph#containsEdge(Object, Object) */ @Override public boolean containsEdge(V sourceVertex, V targetVertex) { return getEdge(sourceVertex, targetVertex) != null; } /** * @see Graph#removeAllEdges(Collection) */ @Override public boolean removeAllEdges(Collection edges) { boolean modified = false; for (E e : edges) { modified |= removeEdge(e); } return modified; } /** * @see Graph#removeAllEdges(Object, Object) */ @Override public Set removeAllEdges(V sourceVertex, V targetVertex) { Set removed = getAllEdges(sourceVertex, targetVertex); if (removed == null) { return null; } removeAllEdges(removed); return removed; } /** * @see Graph#removeAllVertices(Collection) */ @Override public boolean removeAllVertices(Collection vertices) { boolean modified = false; for (V v : vertices) { modified |= removeVertex(v); } return modified; } /** * Returns a string of the parenthesized pair (V, E) representing this G=(V,E) graph. 'V' is the * string representation of the vertex set, and 'E' is the string representation of the edge * set. * * @return a string representation of this graph. */ @Override public String toString() { return toStringFromSets(vertexSet(), edgeSet(), this.getType().isDirected()); } /** * Ensures that the specified vertex exists in this graph, or else throws exception. * * @param v vertex * * @return true if this assertion holds. * * @throws NullPointerException if specified vertex is null. * @throws IllegalArgumentException if specified vertex does not exist in this graph. */ protected boolean assertVertexExist(V v) { if (containsVertex(v)) { return true; } else if (v == null) { throw new NullPointerException(); } else { throw new IllegalArgumentException("no such vertex in graph: " + v.toString()); } } /** * Removes all the edges in this graph that are also contained in the specified edge array. * After this call returns, this graph will contain no edges in common with the specified edges. * This method will invoke the {@link Graph#removeEdge(Object)} method. * * @param edges edges to be removed from this graph. * * @return true if this graph changed as a result of the call. * * @see Graph#removeEdge(Object) * @see Graph#containsEdge(Object) */ protected boolean removeAllEdges(E[] edges) { boolean modified = false; for (E edge : edges) { modified |= removeEdge(edge); } return modified; } /** * Helper for subclass implementations of toString( ). * * @param vertexSet the vertex set V to be printed * @param edgeSet the edge set E to be printed * @param directed true to use parens for each edge (representing directed); false to use curly * braces (representing undirected) * * @return a string representation of (V,E) */ protected String toStringFromSets( Collection vertexSet, Collection edgeSet, boolean directed) { List renderedEdges = new ArrayList<>(); StringBuilder sb = new StringBuilder(); for (E e : edgeSet) { if ((e.getClass() != DefaultEdge.class) && (e.getClass() != DefaultWeightedEdge.class)) { sb.append(e.toString()); sb.append("="); } if (directed) { sb.append("("); } else { sb.append("{"); } sb.append(getEdgeSource(e)); sb.append(","); sb.append(getEdgeTarget(e)); if (directed) { sb.append(")"); } else { sb.append("}"); } // REVIEW jvs 29-May-2006: dump weight somewhere? renderedEdges.add(sb.toString()); sb.setLength(0); } return "(" + vertexSet + ", " + renderedEdges + ")"; } /** * Returns a hash code value for this graph. The hash code of a graph is defined to be the sum * of the hash codes of vertices and edges in the graph. It is also based on graph topology and * edges weights. * * @return the hash code value this graph * * @see Object#hashCode() */ @Override public int hashCode() { int hash = vertexSet().hashCode(); final boolean isDirected = getType().isDirected(); for (E e : edgeSet()) { int part = e.hashCode(); int source = getEdgeSource(e).hashCode(); int target = getEdgeTarget(e).hashCode(); int pairing = source + target; if (isDirected) { // see http://en.wikipedia.org/wiki/Pairing_function (VK); pairing = ((pairing) * (pairing + 1) / 2) + target; } part = (31 * part) + pairing; part = (31 * part) + Double.hashCode(getEdgeWeight(e)); hash += part; } return hash; } /** * Indicates whether some other object is "equal to" this graph. Returns true if * the given object is also a graph, the two graphs are instances of the same graph class, have * identical vertices and edges sets with the same weights. * * @param obj object to be compared for equality with this graph * * @return true if the specified object is equal to this graph * * @see Object#equals(Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if ((obj == null) || (getClass() != obj.getClass())) { return false; } Graph g = TypeUtil.uncheckedCast(obj); if (!vertexSet().equals(g.vertexSet())) { return false; } if (edgeSet().size() != g.edgeSet().size()) { return false; } final boolean isDirected = getType().isDirected(); for (E e : edgeSet()) { V source = getEdgeSource(e); V target = getEdgeTarget(e); if (!g.containsEdge(e)) { return false; } V gSource = g.getEdgeSource(e); V gTarget = g.getEdgeTarget(e); if (isDirected) { if (!gSource.equals(source) || !gTarget.equals(target)) { return false; } } else { if ((!gSource.equals(source) || !gTarget.equals(target)) && (!gSource.equals(target) || !gTarget.equals(source))) { return false; } } if (Double.compare(getEdgeWeight(e), g.getEdgeWeight(e)) != 0) { return false; } } return true; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/AsGraphUnion.java000066400000000000000000000312561402514743400303760ustar00rootroot00000000000000/* * (C) Copyright 2009-2021, by Ilya Razenshteyn and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Read-only union of two graphs. * *

    * Read-only union of two graphs: G1 and G2. If G1 = * (V1, E1) and G2 = (V2, E2) then their * union G = (V, E), where V is the union of V1 and V2, and E is the union of * E1 and E2. A {@link WeightCombiner} in order to calculate edge weights. * * @param the vertex type * @param the edge type * * @author Ilya Razenshteyn */ public class AsGraphUnion extends AbstractGraph implements Serializable { private static final long serialVersionUID = -3848082143382987713L; private static final String READ_ONLY = "union of graphs is read-only"; private final Graph g1; private final GraphType type1; private final Graph g2; private final GraphType type2; private final GraphType type; private final WeightCombiner operator; /** * Construct a new graph union. * * @param g1 the first graph * @param g2 the second graph * @param operator the weight combiner (policy for edge weight calculation) */ public AsGraphUnion(Graph g1, Graph g2, WeightCombiner operator) { this.g1 = GraphTests.requireDirectedOrUndirected(g1); this.type1 = g1.getType(); this.g2 = GraphTests.requireDirectedOrUndirected(g2); this.type2 = g2.getType(); if (g1 == g2) { throw new IllegalArgumentException("g1 is equal to g2"); } this.operator = Objects.requireNonNull(operator, "Weight combiner cannot be null"); // compute result type DefaultGraphType.Builder builder = new DefaultGraphType.Builder(); if (type1.isDirected() && type2.isDirected()) { builder = builder.directed(); } else if (type1.isUndirected() && type2.isUndirected()) { builder = builder.undirected(); } else { builder = builder.mixed(); } this.type = builder .allowSelfLoops(type1.isAllowingSelfLoops() || type2.isAllowingSelfLoops()) .allowMultipleEdges(true).weighted(true).modifiable(false).build(); } /** * Construct a new graph union. The union will use the {@link WeightCombiner#SUM} weight * combiner. * * @param g1 the first graph * @param g2 the second graph */ public AsGraphUnion(Graph g1, Graph g2) { this(g1, g2, WeightCombiner.SUM); } /** * {@inheritDoc} */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { boolean inG1 = g1.containsVertex(sourceVertex) && g1.containsVertex(targetVertex); boolean inG2 = g2.containsVertex(sourceVertex) && g2.containsVertex(targetVertex); if (inG1 && inG2) { return new UnmodifiableUnionSet<>( g1.getAllEdges(sourceVertex, targetVertex), g2.getAllEdges(sourceVertex, targetVertex)); } else if (inG1) { return Collections.unmodifiableSet(g1.getAllEdges(sourceVertex, targetVertex)); } else if (inG2) { return Collections.unmodifiableSet(g2.getAllEdges(sourceVertex, targetVertex)); } return Collections.emptySet(); } /** * {@inheritDoc} */ @Override public E getEdge(V sourceVertex, V targetVertex) { E res = null; if (g1.containsVertex(sourceVertex) && g1.containsVertex(targetVertex)) { res = g1.getEdge(sourceVertex, targetVertex); } if ((res == null) && g2.containsVertex(sourceVertex) && g2.containsVertex(targetVertex)) { res = g2.getEdge(sourceVertex, targetVertex); } return res; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public Supplier getVertexSupplier() { throw new UnsupportedOperationException(READ_ONLY); } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public Supplier getEdgeSupplier() { throw new UnsupportedOperationException(READ_ONLY); } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public E addEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(READ_ONLY); } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { throw new UnsupportedOperationException(READ_ONLY); } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public V addVertex() { throw new UnsupportedOperationException(READ_ONLY); } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public boolean addVertex(V v) { throw new UnsupportedOperationException(READ_ONLY); } /** * {@inheritDoc} */ @Override public boolean containsEdge(E e) { return g1.containsEdge(e) || g2.containsEdge(e); } /** * {@inheritDoc} */ @Override public boolean containsVertex(V v) { return g1.containsVertex(v) || g2.containsVertex(v); } /** * {@inheritDoc} */ @Override public Set edgeSet() { return new UnmodifiableUnionSet<>(g1.edgeSet(), g2.edgeSet()); } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { boolean inG1 = g1.containsVertex(vertex); boolean inG2 = g2.containsVertex(vertex); if (inG1 && inG2) { return new UnmodifiableUnionSet<>(g1.edgesOf(vertex), g2.edgesOf(vertex)); } else if (inG1) { return Collections.unmodifiableSet(g1.edgesOf(vertex)); } else if (inG2) { return Collections.unmodifiableSet(g2.edgesOf(vertex)); } else { throw new IllegalArgumentException("no such vertex in graph: " + vertex.toString()); } } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { boolean inG1 = g1.containsVertex(vertex); boolean inG2 = g2.containsVertex(vertex); if (inG1 && inG2) { return new UnmodifiableUnionSet<>( g1.incomingEdgesOf(vertex), g2.incomingEdgesOf(vertex)); } else if (inG1) { return Collections.unmodifiableSet(g1.incomingEdgesOf(vertex)); } else if (inG2) { return Collections.unmodifiableSet(g2.incomingEdgesOf(vertex)); } else { throw new IllegalArgumentException("no such vertex in graph: " + vertex.toString()); } } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { boolean inG1 = g1.containsVertex(vertex); boolean inG2 = g2.containsVertex(vertex); if (inG1 && inG2) { return new UnmodifiableUnionSet<>( g1.outgoingEdgesOf(vertex), g2.outgoingEdgesOf(vertex)); } else if (inG1) { return Collections.unmodifiableSet(g1.outgoingEdgesOf(vertex)); } else if (inG2) { return Collections.unmodifiableSet(g2.outgoingEdgesOf(vertex)); } else { throw new IllegalArgumentException("no such vertex in graph: " + vertex.toString()); } } /** * {@inheritDoc} */ @Override public int degreeOf(V vertex) { if (type.isMixed()) { int d = 0; if (g1.containsVertex(vertex)) { d += g1.degreeOf(vertex); } if (g2.containsVertex(vertex)) { d += g2.degreeOf(vertex); } return d; } else if (type.isUndirected()) { int degree = 0; Iterator it = edgesOf(vertex).iterator(); while (it.hasNext()) { E e = it.next(); degree++; if (getEdgeSource(e).equals(getEdgeTarget(e))) { degree++; } } return degree; } else { return incomingEdgesOf(vertex).size() + outgoingEdgesOf(vertex).size(); } } /** * {@inheritDoc} */ @Override public int inDegreeOf(V vertex) { if (type.isMixed()) { int d = 0; if (g1.containsVertex(vertex)) { d += g1.inDegreeOf(vertex); } if (g2.containsVertex(vertex)) { d += g2.inDegreeOf(vertex); } return d; } else if (type.isUndirected()) { return degreeOf(vertex); } else { return incomingEdgesOf(vertex).size(); } } /** * {@inheritDoc} */ @Override public int outDegreeOf(V vertex) { if (type.isMixed()) { int d = 0; if (g1.containsVertex(vertex)) { d += g1.outDegreeOf(vertex); } if (g2.containsVertex(vertex)) { d += g2.outDegreeOf(vertex); } return d; } else if (type.isUndirected()) { return degreeOf(vertex); } else { return outgoingEdgesOf(vertex).size(); } } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public E removeEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(READ_ONLY); } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public boolean removeEdge(E e) { throw new UnsupportedOperationException(READ_ONLY); } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public boolean removeVertex(V v) { throw new UnsupportedOperationException(READ_ONLY); } /** * {@inheritDoc} */ @Override public Set vertexSet() { return new UnmodifiableUnionSet<>(g1.vertexSet(), g2.vertexSet()); } /** * {@inheritDoc} */ @Override public V getEdgeSource(E e) { if (g1.containsEdge(e)) { return g1.getEdgeSource(e); } if (g2.containsEdge(e)) { return g2.getEdgeSource(e); } return null; } /** * {@inheritDoc} */ @Override public V getEdgeTarget(E e) { if (g1.containsEdge(e)) { return g1.getEdgeTarget(e); } if (g2.containsEdge(e)) { return g2.getEdgeTarget(e); } return null; } /** * {@inheritDoc} */ @Override public double getEdgeWeight(E e) { if (g1.containsEdge(e) && g2.containsEdge(e)) { return operator.combine(g1.getEdgeWeight(e), g2.getEdgeWeight(e)); } if (g1.containsEdge(e)) { return g1.getEdgeWeight(e); } if (g2.containsEdge(e)) { return g2.getEdgeWeight(e); } throw new IllegalArgumentException("no such edge in the union"); } /** * {@inheritDoc} */ @Override public GraphType getType() { return type; } /** * Throws {@link UnsupportedOperationException} since graph union is read-only. */ @Override public void setEdgeWeight(E e, double weight) { throw new UnsupportedOperationException(READ_ONLY); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/AsSubgraph.java000066400000000000000000000443101402514743400300720ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.event.*; import java.io.*; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * A subgraph is a graph that has a subset of vertices and a subset of edges with respect to some * base graph. More formally, a subgraph G(V,E) that is based on a base graph Gb(Vb,Eb) satisfies * the following subgraph property: V is a subset of Vb and E is a subset of Eb. Other * than this property, a subgraph is a graph with any respect and fully complies with the * Graph interface. * *

    * If the base graph is a {@link org.jgrapht.ListenableGraph}, the subgraph listens on the base * graph and guarantees the subgraph property. If an edge or a vertex is removed from the base * graph, it is automatically removed from the subgraph. Subgraph listeners are informed on such * removal only if it results in a cascaded removal from the subgraph. If the subgraph has been * created as an induced subgraph it also keeps track of edges being added to its vertices. If * vertices are added to the base graph, the subgraph remains unaffected. *

    * *

    * If the base graph is not a ListenableGraph, then the subgraph property cannot be * guaranteed. If edges or vertices are removed from the base graph, they are not removed * from the subgraph. *

    * *

    * Modifications to Subgraph are allowed as long as the subgraph property is maintained. Addition of * vertices or edges are allowed as long as they also exist in the base graph. Removal of vertices * or edges is always allowed. The base graph is never affected by any modification made to * the subgraph. *

    * *

    * A subgraph may provide a "live-window" on a base graph, so that changes made to its vertices or * edges are immediately reflected in the base graph, and vice versa. For that to happen, vertices * and edges added to the subgraph must be identical (that is, reference-equal and not only * value-equal) to their respective ones in the base graph. Previous versions of this class enforced * such identity, at a severe performance cost. Currently it is no longer enforced. If you want to * achieve a "live-window" functionality, your safest tactics would be to NOT override the * equals() methods of your vertices and edges. If you use a class that has already * overridden the equals() method, such as String, then you can use a * wrapper around it, or else use it directly but exercise a great care to avoid having * different-but-equal instances in the subgraph and the base graph. *

    * *

    * This graph implementation guarantees deterministic vertex and edge set ordering (via * {@link LinkedHashSet}). *

    * *

    * Note that this implementation tries to maintain a "live-window" on the base graph, which has * implications in the performance of the various operations. For example iterating over the * adjacent edges of a vertex takes time proportional to the number of adjacent edges of the vertex * in the base graph even if the subgraph contains only a small subset of those edges. Therefore, * the user must be aware that using this implementation for certain algorithms might come with * computational overhead. For certain algorithms it is better to maintain a subgraph by hand * instead of using this implementation as a black box. * * @param the vertex type * @param the edge type * * @author Barak Naveh * @see Graph * @see Set */ public class AsSubgraph extends AbstractGraph implements Serializable { private static final long serialVersionUID = -1471811754881775298L; private static final String NO_SUCH_EDGE_IN_BASE = "no such edge in base graph"; private static final String NO_SUCH_VERTEX_IN_BASE = "no such vertex in base graph"; private static final String CANNOT_CREATE_NEW_VERTICES_FROM_SUBGRAPH = "Cannot create new vertices from subgraph"; protected final Set edgeSet = new LinkedHashSet<>(); protected final Set vertexSet = new LinkedHashSet<>(); protected final Graph base; protected final GraphType baseType; protected final boolean isInduced; private transient Set unmodifiableEdgeSet = null; private transient Set unmodifiableVertexSet = null; /** * Creates a new subgraph. * * @param base the base (backing) graph on which the subgraph will be based. * @param vertexSubset vertices to include in the subgraph. If null then all * vertices are included. * @param edgeSubset edges to in include in the subgraph. If null then all the * edges whose vertices found in the graph are included. */ public AsSubgraph(Graph base, Set vertexSubset, Set edgeSubset) { super(); this.base = GraphTests.requireDirectedOrUndirected(base); this.baseType = base.getType(); this.isInduced = edgeSubset == null; if (base instanceof ListenableGraph) { ((ListenableGraph) base).addGraphListener(new BaseGraphListener()); } initialize(vertexSubset, edgeSubset); } /** * Creates a new induced subgraph. The subgraph will keep track of edges being added to its * vertex subset as well as deletion of edges and vertices. If base it not listenable, this is * identical to the call Subgraph(base, vertexSubset, null). * * @param base the base (backing) graph on which the subgraph will be based. * @param vertexSubset vertices to include in the subgraph. If null then all * vertices are included. */ public AsSubgraph(Graph base, Set vertexSubset) { this(base, vertexSubset, null); } /** * Creates a new induced Subgraph with all vertices included. The subgraph will keep track of * edges being added to its vertex subset as well as deletion of edges and vertices. If base is * not listenable, this is identical to the call Subgraph(base, null, null). * * @param base the base (backing) graph on which the subgraph will be based. */ public AsSubgraph(Graph base) { this(base, null, null); } /** * {@inheritDoc} */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { if (containsVertex(sourceVertex) && containsVertex(targetVertex)) { return base .getAllEdges(sourceVertex, targetVertex).stream().filter(edgeSet::contains) .collect(Collectors.toCollection(LinkedHashSet::new)); } else { return null; } } /** * {@inheritDoc} */ @Override public E getEdge(V sourceVertex, V targetVertex) { Set edges = getAllEdges(sourceVertex, targetVertex); if (edges == null) { return null; } else { return edges.stream().findAny().orElse(null); } } /** * {@inheritDoc} */ @Override public Supplier getVertexSupplier() { return base.getVertexSupplier(); } /** * {@inheritDoc} */ @Override public Supplier getEdgeSupplier() { return base.getEdgeSupplier(); } /** * Add an edge to the subgraph. The end-points must exist in the subgraph and the edge must * exist in the base graph. In case multiple such edges exist in the base graph, one that is not * already in the subgraph is chosen arbitrarily and added to the subgraph. In case all such * edges already exist in the subgraph, the method returns null. * * @param sourceVertex the source vertex * @param targetVertex the source vertex * @return the added edge or null if all such edges from the base graph already belong in the * subgraph * @throws IllegalArgumentException if the source or target vertex does not belong to the * subgraph * @throws IllegalArgumentException if the base graph does not contain any edge between the two * end-points */ @Override public E addEdge(V sourceVertex, V targetVertex) { assertVertexExist(sourceVertex); assertVertexExist(targetVertex); if (!base.containsEdge(sourceVertex, targetVertex)) { throw new IllegalArgumentException(NO_SUCH_EDGE_IN_BASE); } Set edges = base.getAllEdges(sourceVertex, targetVertex); for (E e : edges) { if (!containsEdge(e)) { edgeSet.add(e); return e; } } return null; } /** * {@inheritDoc} */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { if (e == null) { throw new NullPointerException(); } if (!base.containsEdge(e)) { throw new IllegalArgumentException(NO_SUCH_EDGE_IN_BASE); } assertVertexExist(sourceVertex); assertVertexExist(targetVertex); assert (base.getEdgeSource(e) == sourceVertex); assert (base.getEdgeTarget(e) == targetVertex); return edgeSet.add(e); } @Override public V addVertex() { throw new UnsupportedOperationException(CANNOT_CREATE_NEW_VERTICES_FROM_SUBGRAPH); } /** * Adds the specified vertex to this subgraph. * * @param v the vertex to be added. * * @return true if the vertex was added, otherwise * false. * * @throws NullPointerException if v is null * @throws IllegalArgumentException if the base graph does not contain the vertex * * @see AsSubgraph * @see Graph#addVertex(Object) */ @Override public boolean addVertex(V v) { if (v == null) { throw new NullPointerException(); } if (!base.containsVertex(v)) { throw new IllegalArgumentException(NO_SUCH_VERTEX_IN_BASE); } return vertexSet.add(v); } /** * {@inheritDoc} */ @Override public boolean containsEdge(E e) { return edgeSet.contains(e); } /** * {@inheritDoc} */ @Override public boolean containsVertex(V v) { return vertexSet.contains(v); } /** * {@inheritDoc} */ @Override public Set edgeSet() { if (unmodifiableEdgeSet == null) { unmodifiableEdgeSet = Collections.unmodifiableSet(edgeSet); } return unmodifiableEdgeSet; } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { assertVertexExist(vertex); return base .edgesOf(vertex).stream().filter(edgeSet::contains) .collect(Collectors.toCollection(LinkedHashSet::new)); } /** * {@inheritDoc} * *

    * By default this method returns the sum of in-degree and out-degree. The exact value returned * depends on the types of the underlying graph. */ @Override public int degreeOf(V vertex) { assertVertexExist(vertex); if (baseType.isUndirected()) { int degree = 0; Iterator it = base.edgesOf(vertex).stream().filter(edgeSet::contains).iterator(); while (it.hasNext()) { E e = it.next(); degree++; if (getEdgeSource(e).equals(getEdgeTarget(e))) { degree++; } } return degree; } else { return inDegreeOf(vertex) + outDegreeOf(vertex); } } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { assertVertexExist(vertex); return base .incomingEdgesOf(vertex).stream().filter(edgeSet::contains) .collect(Collectors.toCollection(LinkedHashSet::new)); } /** * {@inheritDoc} */ @Override public int inDegreeOf(V vertex) { if (baseType.isUndirected()) { return degreeOf(vertex); } else { return incomingEdgesOf(vertex).size(); } } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { assertVertexExist(vertex); return base .outgoingEdgesOf(vertex).stream().filter(edgeSet::contains) .collect(Collectors.toCollection(LinkedHashSet::new)); } /** * {@inheritDoc} */ @Override public int outDegreeOf(V vertex) { if (baseType.isUndirected()) { return degreeOf(vertex); } else { return outgoingEdgesOf(vertex).size(); } } /** * {@inheritDoc} */ @Override public boolean removeEdge(E e) { return edgeSet.remove(e); } /** * {@inheritDoc} */ @Override public E removeEdge(V sourceVertex, V targetVertex) { E e = getEdge(sourceVertex, targetVertex); return edgeSet.remove(e) ? e : null; } /** * {@inheritDoc} */ @Override public boolean removeVertex(V v) { // If the base graph does NOT contain v it means we are here in // response to removal of v from the base. In such case we don't need // to remove all the edges of v as they were already removed. if (containsVertex(v) && base.containsVertex(v)) { removeAllEdges(edgesOf(v)); } return vertexSet.remove(v); } /** * {@inheritDoc} */ @Override public Set vertexSet() { if (unmodifiableVertexSet == null) { unmodifiableVertexSet = Collections.unmodifiableSet(vertexSet); } return unmodifiableVertexSet; } /** * {@inheritDoc} */ @Override public V getEdgeSource(E e) { return base.getEdgeSource(e); } /** * {@inheritDoc} */ @Override public V getEdgeTarget(E e) { return base.getEdgeTarget(e); } /** * {@inheritDoc} */ @Override public GraphType getType() { return base.getType(); } /** * {@inheritDoc} */ @Override public double getEdgeWeight(E e) { return base.getEdgeWeight(e); } /** * {@inheritDoc} */ @Override public void setEdgeWeight(E e, double weight) { base.setEdgeWeight(e, weight); } private void initialize(Set vertexFilter, Set edgeFilter) { if (vertexFilter == null && edgeFilter == null) { vertexSet.addAll(base.vertexSet()); edgeSet.addAll(base.edgeSet()); return; } // add vertices if (vertexFilter == null) { vertexSet.addAll(base.vertexSet()); } else { if (vertexFilter.size() > base.vertexSet().size()) { base.vertexSet().stream().filter(vertexFilter::contains).forEach(vertexSet::add); } else { vertexFilter .stream().filter(v -> v != null && base.containsVertex(v)) .forEach(vertexSet::add); } } // add edges if (edgeFilter == null) { base .edgeSet().stream() .filter( e -> vertexSet.contains(base.getEdgeSource(e)) && vertexSet.contains(base.getEdgeTarget(e))) .forEach(edgeSet::add); } else { if (edgeFilter.size() > base.edgeSet().size()) { base .edgeSet().stream() .filter( e -> edgeFilter.contains(e) && vertexSet.contains(base.getEdgeSource(e)) && vertexSet.contains(base.getEdgeTarget(e))) .forEach(edgeSet::add); } else { edgeFilter .stream() .filter( e -> e != null && base.containsEdge(e) && vertexSet.contains(base.getEdgeSource(e)) && vertexSet.contains(base.getEdgeTarget(e))) .forEach(edgeSet::add); } } } /** * An internal listener on the base graph. * * @author Barak Naveh */ private class BaseGraphListener implements GraphListener, Serializable { private static final long serialVersionUID = 4343535244243546391L; /** * {@inheritDoc} */ @Override public void edgeAdded(GraphEdgeChangeEvent e) { if (isInduced) { E edge = e.getEdge(); V source = e.getEdgeSource(); V target = e.getEdgeTarget(); if (containsVertex(source) && containsVertex(target)) { addEdge(source, target, edge); } } } /** * {@inheritDoc} */ @Override public void edgeRemoved(GraphEdgeChangeEvent e) { E edge = e.getEdge(); removeEdge(edge); } /** * {@inheritDoc} */ @Override public void vertexAdded(GraphVertexChangeEvent e) { // we don't care } /** * {@inheritDoc} */ @Override public void vertexRemoved(GraphVertexChangeEvent e) { V vertex = e.getVertex(); removeVertex(vertex); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/AsUndirectedGraph.java000066400000000000000000000121451402514743400313700ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; /** * An undirected view of the backing directed graph specified in the constructor. This graph allows * modules to apply algorithms designed for undirected graphs to a directed graph by simply ignoring * edge direction. If the backing directed graph is an * oriented graph, then the view will * be a simple graph; otherwise, it will be a multigraph. Query operations on this graph "read * through" to the backing graph. Attempts to add edges will result in an * UnsupportedOperationException, but vertex addition/removal and edge removal are all * supported (and immediately reflected in the backing graph). * *

    * Note that edges returned by this graph's accessors are really just the edges of the underlying * directed graph. Since there is no interface distinction between directed and undirected edges, * this detail should be irrelevant to algorithms. *

    * *

    * This graph does not pass the hashCode and equals operations through to the backing graph, * but relies on Object's equals and hashCode methods. This * graph will be serializable if the backing graph is serializable. *

    * * @param the graph vertex type * @param the graph edge type * * @author John V. Sichi */ public class AsUndirectedGraph extends GraphDelegator implements Serializable, Graph { private static final long serialVersionUID = 325983813283133557L; private static final String NO_EDGE_ADD = "this graph does not support edge addition"; /** * Constructor for AsUndirectedGraph. * * @param g the backing directed graph over which an undirected view is to be created. * @throws IllegalArgumentException if the graph is not directed */ public AsUndirectedGraph(Graph g) { super(g); GraphTests.requireDirected(g); } /** * {@inheritDoc} */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { Set forwardList = super.getAllEdges(sourceVertex, targetVertex); if (sourceVertex.equals(targetVertex)) { // avoid duplicating loops return forwardList; } Set reverseList = super.getAllEdges(targetVertex, sourceVertex); Set list = new ArrayUnenforcedSet<>(forwardList.size() + reverseList.size()); list.addAll(forwardList); list.addAll(reverseList); return list; } /** * {@inheritDoc} */ @Override public E getEdge(V sourceVertex, V targetVertex) { E edge = super.getEdge(sourceVertex, targetVertex); if (edge != null) { return edge; } // try the other direction return super.getEdge(targetVertex, sourceVertex); } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public E addEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(NO_EDGE_ADD); } /** * {@inheritDoc} * * @throws UnsupportedOperationException always, since operation is unsupported */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { throw new UnsupportedOperationException(NO_EDGE_ADD); } /** * {@inheritDoc} */ @Override public int degreeOf(V vertex) { return super.degreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { return super.edgesOf(vertex); } /** * {@inheritDoc} */ @Override public int inDegreeOf(V vertex) { return super.degreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { return super.edgesOf(vertex); } /** * {@inheritDoc} */ @Override public int outDegreeOf(V vertex) { return super.degreeOf(vertex); } /** * {@inheritDoc} */ @Override public GraphType getType() { return super.getType().asUndirected(); } /** * {@inheritDoc} */ @Override public String toString() { return super.toStringFromSets(vertexSet(), edgeSet(), false); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnmodifiableGraph.java000066400000000000000000000100101402514743400316650ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.io.*; import java.util.*; /** * An unmodifiable view of the backing graph specified in the constructor. This graph allows modules * to provide users with "read-only" access to internal graphs. Query operations on this graph "read * through" to the backing graph, and attempts to modify this graph result in an * UnsupportedOperationException. * *

    * This graph does not pass the hashCode and equals operations through to the backing graph, * but relies on Object's equals and hashCode methods. This * graph will be serializable if the backing graph is serializable. *

    * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public class AsUnmodifiableGraph extends GraphDelegator implements Serializable { private static final long serialVersionUID = -8186686968362705760L; private static final String UNMODIFIABLE = "this graph is unmodifiable"; /** * Creates a new unmodifiable graph based on the specified backing graph. * * @param g the backing graph on which an unmodifiable graph is to be created. */ public AsUnmodifiableGraph(Graph g) { super(g); } /** * @see Graph#addEdge(Object, Object) */ @Override public E addEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * @see Graph#addEdge(Object, Object, Object) */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * @see Graph#addVertex() */ @Override public V addVertex() { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * @see Graph#addVertex(Object) */ @Override public boolean addVertex(V v) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * @see Graph#removeAllEdges(Collection) */ @Override public boolean removeAllEdges(Collection edges) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * @see Graph#removeAllEdges(Object, Object) */ @Override public Set removeAllEdges(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * @see Graph#removeAllVertices(Collection) */ @Override public boolean removeAllVertices(Collection vertices) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * @see Graph#removeEdge(Object) */ @Override public boolean removeEdge(E e) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * @see Graph#removeEdge(Object, Object) */ @Override public E removeEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * @see Graph#removeVertex(Object) */ @Override public boolean removeVertex(V v) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public GraphType getType() { return super.getType().asUnmodifiable(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/AsUnweightedGraph.java000066400000000000000000000047401402514743400314070ustar00rootroot00000000000000/* * (C) Copyright 2018, by Lukas Harzenetter and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.io.*; import java.util.*; /** * Provides an unweighted view on a graph. * * Algorithms designed for unweighted graphs should also work on weighted graphs. This class * emulates an unweighted graph based on a weighted one by returning Graph.DEFAULT_EDGE_WEIGHT * for each edge weight. The underlying weighted graph is provided at the constructor. * Modifying operations (adding/removing vertexes/edges) are also passed through to the underlying * weighted graph. As edge weight, Graph.DEFAULT_EDGE_WEIGHT is used. Setting an edge weight is not * supported. The edges are not modified. So, if an edge is asked for, the one from the underlying * weighted graph is returned. In case the underlying graph is serializable, this one is * serializable, too. * * @param the graph vertex type * @param the graph edge type */ public class AsUnweightedGraph extends GraphDelegator implements Serializable, Graph { private static final long serialVersionUID = -5186421272597767751L; private static final String EDGE_WEIGHT_IS_NOT_SUPPORTED = "Edge weight is not supported"; /** * Constructor for AsUnweightedGraph. * * @param g the backing directed graph over which an undirected view is to be created. * @throws NullPointerException if the graph is null */ public AsUnweightedGraph(Graph g) { super(Objects.requireNonNull(g)); } @Override public double getEdgeWeight(E e) { return Graph.DEFAULT_EDGE_WEIGHT; } @Override public void setEdgeWeight(E e, double weight) { throw new UnsupportedOperationException(EDGE_WEIGHT_IS_NOT_SUPPORTED); } @Override public GraphType getType() { return super.getType().asUnweighted(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/AsWeightedGraph.java000066400000000000000000000215311402514743400310410ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Lukas Harzenetter and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Provides a weighted view of a graph. The class stores edge weights internally. * All @link{getEdgeWeight} calls are handled by this view; all other graph operations are * propagated to the graph backing this view. * *

    * This class can be used to make an unweighted graph weighted, to override the weights of a * weighted graph, or to provide different weighted views of the same underlying graph. For * instance, the edges of a graph representing a road network might have two weights associated with * them: a travel time and a travel distance. Instead of creating two weighted graphs of the same * network, one would simply create two weighted views of the same underlying graph. * *

    * This class offers two ways to associate a weight with an edge: *

      *
    1. Explicitly through a map which contains a mapping from an edge to a weight
    2. *
    3. Implicitly through a function which computes a weight for a given edge
    4. *
    * In the first way, the map is used to lookup edge weights. In the second way, a function is * provided to calculate the weight of an edge. If the map does not contain a particular edge, or * the function does not provide a weight for a particular edge, the @link{getEdgeWeight} call is * propagated to the backing graph. * * Finally, the view provides a @link{setEdgeWeight} method. This method behaves differently * depending on how the view is constructed. See @link{setEdgeWeight} for details. * * @param the graph vertex type * @param the graph edge type */ public class AsWeightedGraph extends GraphDelegator implements Serializable, Graph { private static final long serialVersionUID = -6838132233557L; private final Function weightFunction; private final Map weights; private final boolean writeWeightsThrough; private final boolean cacheWeights; /** * Constructor for AsWeightedGraph where the weights are provided through a map. Invocations of * the @link{setEdgeWeight} method will update the map. Moreover, calls to @link{setEdgeWeight} * are propagated to the underlying graph. * * @param graph the backing graph over which a weighted view is to be created. * @param weights the map containing the edge weights. * @throws NullPointerException if the graph or the weights are null. */ public AsWeightedGraph(Graph graph, Map weights) { this(graph, weights, graph.getType().isWeighted()); } /** * Constructor for AsWeightedGraph which allows weight write propagation to be requested * explicitly. * * @param graph the backing graph over which an weighted view is to be created * @param weights the map containing the edge weights * @param writeWeightsThrough if set to true, the weights will get propagated to the backing * graph in the setEdgeWeight() method. * @throws NullPointerException if the graph or the weights are null * @throws IllegalArgumentException if writeWeightsThrough is set to true and * graph is not a weighted graph */ public AsWeightedGraph(Graph graph, Map weights, boolean writeWeightsThrough) { super(graph); this.weights = Objects.requireNonNull(weights); this.weightFunction = null; this.cacheWeights = false; this.writeWeightsThrough = writeWeightsThrough; if (this.writeWeightsThrough) GraphTests.requireWeighted(graph); } /** * Constructor for AsWeightedGraph which uses a weight function to compute edge weights. When * the weight of an edge is queried, the weight function is invoked. If * cacheWeights is set to true, the weight of an edge returned by the * weightFunction after its first invocation is stored in a map. The weight of an * edge returned by subsequent calls to @link{getEdgeWeight} for the same edge will then be * retrieved directly from the map, instead of re-invoking the weight function. If * cacheWeights is set to false, each invocation of * the @link{getEdgeWeight} method will invoke the weight function. Caching the edge weights is * particularly useful when pre-computing all edge weights is expensive and it is expected that * the weights of only a subset of all edges will be queried. * * @param graph the backing graph over which an weighted view is to be created * @param weightFunction function which maps an edge to a weight * @param cacheWeights if set to true, weights are cached once computed by the * weight function * @param writeWeightsThrough if set to true, the weight set directly by * the @link{setEdgeWeight} method will be propagated to the backing graph. * @throws NullPointerException if the graph or the weight function is null * @throws IllegalArgumentException if writeWeightsThrough is set to true and * graph is not a weighted graph */ public AsWeightedGraph( Graph graph, Function weightFunction, boolean cacheWeights, boolean writeWeightsThrough) { super(graph); this.weightFunction = Objects.requireNonNull(weightFunction); this.cacheWeights = cacheWeights; this.writeWeightsThrough = writeWeightsThrough; this.weights = new HashMap<>(); if (this.writeWeightsThrough) GraphTests.requireWeighted(graph); } /** * Returns the weight assigned to a given edge. If weights are provided through a map, first a * map lookup is performed. If the edge is not found, the @link{getEdgeWeight} method of the * underlying graph is invoked instead. If, on the other hand, the weights are provided through * a function, this method will first attempt to lookup the weight of an edge in the cache (that * is, if cacheWeights is set to true in the constructor). If caching * was disabled, or the edge could not be found in the cache, the weight function is invoked. If * the function does not provide a weight for a given edge, the call is again propagated to the * underlying graph. * * @param e edge of interest * @return the edge weight * @throws NullPointerException if the edge is null */ @Override public double getEdgeWeight(E e) { Double weight; if (weightFunction != null) { if (cacheWeights) // If weights are cached, check map first before invoking the weight // function weight = weights.computeIfAbsent(e, weightFunction); else weight = weightFunction.apply(e); } else { weight = weights.get(e); } if (Objects.isNull(weight)) weight = super.getEdgeWeight(e); return weight; } /** * Assigns a weight to an edge. If writeWeightsThrough is set to true, * the same weight is set in the backing graph. If this class was constructed using a weight * function, it only makes sense to invoke this method when cacheWeights is set to * true. This method can then be used to preset weights in the cache, or to overwrite existing * values. * * @param e edge on which to set weight * @param weight new weight for edge * @throws NullPointerException if the edge is null */ @Override public void setEdgeWeight(E e, double weight) { assert e != null; if (weightFunction != null && !cacheWeights) { throw new UnsupportedOperationException( "Cannot set an edge weight when a weight function is used and caching is disabled"); } this.weights.put(e, weight); if (this.writeWeightsThrough) this.getDelegate().setEdgeWeight(e, weight); } @Override public GraphType getType() { return super.getType().asWeighted(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/BaseIntrusiveEdgesSpecifics.java000066400000000000000000000110211402514743400334100ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; /** * A base implementation for the intrusive edges specifics. * * @author Barak Naveh * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type * @param the intrusive edge type */ public abstract class BaseIntrusiveEdgesSpecifics implements Serializable { private static final long serialVersionUID = -7498268216742485L; protected Map edgeMap; protected transient Set unmodifiableEdgeSet = null; /** * Constructor * * @param edgeMap the map to use for storage */ public BaseIntrusiveEdgesSpecifics(Map edgeMap) { this.edgeMap = Objects.requireNonNull(edgeMap); } /** * Check if an edge exists * * @param e the edge * @return true if the edge exists, false otherwise */ public boolean containsEdge(E e) { return edgeMap.containsKey(e); } /** * Get the edge set. * * @return an unmodifiable edge set */ public Set getEdgeSet() { if (unmodifiableEdgeSet == null) { unmodifiableEdgeSet = Collections.unmodifiableSet(edgeMap.keySet()); } return unmodifiableEdgeSet; } /** * Remove an edge. * * @param e the edge */ public void remove(E e) { edgeMap.remove(e); } /** * Get the source of an edge. * * @param e the edge * @return the source vertex of an edge */ public V getEdgeSource(E e) { IntrusiveEdge ie = getIntrusiveEdge(e); if (ie == null) { throw new IllegalArgumentException("no such edge in graph: " + e.toString()); } return TypeUtil.uncheckedCast(ie.source); } /** * Get the target of an edge. * * @param e the edge * @return the target vertex of an edge */ public V getEdgeTarget(E e) { IntrusiveEdge ie = getIntrusiveEdge(e); if (ie == null) { throw new IllegalArgumentException("no such edge in graph: " + e.toString()); } return TypeUtil.uncheckedCast(ie.target); } /** * Get the weight of an edge. * * @param e the edge * @return the weight of an edge */ public double getEdgeWeight(E e) { return Graph.DEFAULT_EDGE_WEIGHT; } /** * Set the weight of an edge * * @param e the edge * @param weight the new weight */ public void setEdgeWeight(E e, double weight) { throw new UnsupportedOperationException(); } /** * Add a new edge * * @param e the edge * @param sourceVertex the source vertex of the edge * @param targetVertex the target vertex of the edge * @return true if the edge was added, false if the edge was already present */ public abstract boolean add(E e, V sourceVertex, V targetVertex); protected boolean addIntrusiveEdge(E edge, V sourceVertex, V targetVertex, IE e) { if (e.source == null && e.target == null) { // edge not yet in any graph e.source = sourceVertex; e.target = targetVertex; } else if (e.source != sourceVertex || e.target != targetVertex) { // Edge already contained in this or another graph but with different touching // edges. Reject the edge to not reset the touching vertices of the edge. // Changing the touching vertices causes major inconsistent behavior. throw new IntrusiveEdgeException(e.source, e.target); } return edgeMap.putIfAbsent(edge, e) == null; } /** * Get the intrusive edge of an edge. * * @param e the edge * @return the intrusive edge */ protected abstract IE getIntrusiveEdge(E e); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedGraph.java000066400000000000000000000057311402514743400320510ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * The default implementation of a directed graph. A default directed graph is a non-simple directed * graph in which multiple (parallel) edges between any two vertices are not permitted, but * loops are. * * @param the graph vertex type * @param the graph edge type * */ public class DefaultDirectedGraph extends AbstractBaseGraph { private static final long serialVersionUID = -2066644490824847621L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public DefaultDirectedGraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass), false); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param weighted whether the graph is weighted or not */ public DefaultDirectedGraph( Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted) { super( vertexSupplier, edgeSupplier, new DefaultGraphType.Builder() .directed().allowMultipleEdges(false).allowSelfLoops(true).weighted(weighted) .build()); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new DefaultDirectedGraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier of the new graph * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new DefaultDirectedGraph<>(null, edgeSupplier, false)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultDirectedWeightedGraph.java000066400000000000000000000055351402514743400335340ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * The default implementation of a directed weighted graph. A default directed weighted graph is a * non-simple directed graph in which multiple (parallel) edges between any two vertices are * not permitted, but loops are. The graph has weights on its edges. * * @param the graph vertex type * @param the graph edge type * * @see DefaultDirectedGraph */ public class DefaultDirectedWeightedGraph extends DefaultDirectedGraph { private static final long serialVersionUID = -4867672646995721544L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public DefaultDirectedWeightedGraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass)); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null */ public DefaultDirectedWeightedGraph(Supplier vertexSupplier, Supplier edgeSupplier) { super(vertexSupplier, edgeSupplier, true); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new DefaultDirectedWeightedGraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new DefaultDirectedWeightedGraph<>(null, edgeSupplier)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultEdge.java000066400000000000000000000027731402514743400302130ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; /** * A default implementation for edges in a {@link Graph}. * * @author Barak Naveh */ public class DefaultEdge extends IntrusiveEdge { private static final long serialVersionUID = 3258408452177932855L; /** * Retrieves the source of this edge. This is protected, for use by subclasses only (e.g. for * implementing toString). * * @return source of this edge */ protected Object getSource() { return source; } /** * Retrieves the target of this edge. This is protected, for use by subclasses only (e.g. for * implementing toString). * * @return target of this edge */ protected Object getTarget() { return target; } @Override public String toString() { return "(" + source + " : " + target + ")"; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultEdgeFunction.java000066400000000000000000000044631402514743400317170ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import java.io.*; import java.util.*; import java.util.function.*; /** * Default implementation of an edge function which uses a map to store values. * * @author Dimitrios Michail * * @param the edge type * @param the value type */ public class DefaultEdgeFunction implements Function, Serializable { private static final long serialVersionUID = -4247429315268336855L; protected final Map map; protected final T defaultValue; /** * Create a new function * * @param defaultValue the default value */ public DefaultEdgeFunction(T defaultValue) { this(defaultValue, new HashMap<>()); } /** * Create a new function * * @param defaultValue the default value * @param map the underlying map */ public DefaultEdgeFunction(T defaultValue, Map map) { this.defaultValue = Objects.requireNonNull(defaultValue, "Default value cannot be null"); this.map = Objects.requireNonNull(map, "Map cannot be null"); } /** * Get the function value for an edge. * * @param e the edge */ @Override public T apply(E e) { return map.getOrDefault(e, defaultValue); } /** * Get the function value for an edge. * * @param e the edge * @return the function value for the edge */ public T get(E e) { return map.getOrDefault(e, defaultValue); } /** * Set the function value for an edge. * * @param e the edge * @param value the value */ public void set(E e, T value) { map.put(e, value); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphIterables.java000066400000000000000000000030311402514743400322270ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import java.util.Objects; import org.jgrapht.Graph; import org.jgrapht.GraphIterables; /** * The default implementation of the graph iterables which simply delegates to the set * implementations. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class DefaultGraphIterables implements GraphIterables { /** * The underlying graph */ protected Graph graph; /** * Create new graph iterables */ public DefaultGraphIterables() { this(null); } /** * Create new graph iterables * * @param graph the underlying graph */ public DefaultGraphIterables(Graph graph) { this.graph = Objects.requireNonNull(graph); } @Override public Graph getGraph() { return graph; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphMapping.java000066400000000000000000000060071402514743400317160ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by Assaf Lehr and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.util.*; /** * Implementation of the GraphMapping interface. The performance of * getVertex/EdgeCorrespondence is based on the performance of the concrete Map class which * is passed in the constructor. For example, using {@link HashMap} will provide expected $O(1)$ * performance. * * @param the graph vertex type * @param the graph edge type * * @author Assaf Lehr */ public class DefaultGraphMapping implements GraphMapping { private Map graphMappingForward; private Map graphMappingReverse; private Graph graph1; private Graph graph2; /** * The maps themselves are used. There is no defensive-copy. Assumption: The key and value in * the mappings are of valid graph objects. It is not checked. * * @param g1ToG2 vertex mapping from the first graph to the second * @param g2ToG1 vertex mapping from the second graph to the first * @param g1 the first graph * @param g2 the second graph */ public DefaultGraphMapping(Map g1ToG2, Map g2ToG1, Graph g1, Graph g2) { this.graph1 = g1; this.graph2 = g2; this.graphMappingForward = g1ToG2; this.graphMappingReverse = g2ToG1; } @Override public E getEdgeCorrespondence(E currEdge, boolean forward) { Graph sourceGraph, targetGraph; if (forward) { sourceGraph = this.graph1; targetGraph = this.graph2; } else { sourceGraph = this.graph2; targetGraph = this.graph1; } V mappedSourceVertex = getVertexCorrespondence(sourceGraph.getEdgeSource(currEdge), forward); V mappedTargetVertex = getVertexCorrespondence(sourceGraph.getEdgeTarget(currEdge), forward); if ((mappedSourceVertex == null) || (mappedTargetVertex == null)) { return null; } else { return targetGraph.getEdge(mappedSourceVertex, mappedTargetVertex); } } @Override public V getVertexCorrespondence(V keyVertex, boolean forward) { Map graphMapping; if (forward) { graphMapping = graphMappingForward; } else { graphMapping = graphMappingReverse; } return graphMapping.get(keyVertex); } } DefaultGraphSpecificsStrategy.java000066400000000000000000000050651402514743400337020ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.graph.specifics.*; import java.io.*; import java.util.*; import java.util.function.*; /** * A default lookup specifics strategy implementation. * *

    * Graphs constructed using this strategy require the least amount of memory, at the expense of slow * edge retrievals. Methods which depend on edge retrievals, e.g. getEdge(V u, V v), containsEdge(V * u, V v), addEdge(V u, V v), etc may be relatively slow when the average degree of a vertex is * high (dense graphs). For a fast implementation, use {@link FastLookupGraphSpecificsStrategy}. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class DefaultGraphSpecificsStrategy implements GraphSpecificsStrategy { private static final long serialVersionUID = 7615319421753562075L; @Override public Function> getIntrusiveEdgesSpecificsFactory() { return (Function> & Serializable) (type) -> { if (type.isWeighted()) { return new WeightedIntrusiveEdgesSpecifics(new LinkedHashMap<>()); } else { return new UniformIntrusiveEdgesSpecifics<>(new LinkedHashMap<>()); } }; } @Override public BiFunction, GraphType, Specifics> getSpecificsFactory() { return (BiFunction, GraphType, Specifics> & Serializable) (graph, type) -> { if (type.isDirected()) { return new DirectedSpecifics( graph, new LinkedHashMap<>(), getEdgeSetFactory()); } else { return new UndirectedSpecifics<>( graph, new LinkedHashMap<>(), getEdgeSetFactory()); } }; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultGraphType.java000066400000000000000000000304111402514743400312400ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.io.*; /** * Default implementation of the graph type. * *

    * The graph type describes various properties of a graph such as whether it is directed, undirected * or mixed, whether it contain self-loops (a self-loop is an edge where the source vertex is the * same as the target vertex), whether it contain multiple (parallel) edges (multiple edges which * connect the same pair of vertices) and whether it is weighted or not. * *

    * The type of a graph can be queried on runtime using method {@link Graph#getType()}. This way, for * example, an algorithm can have different behavior based on whether the input graph is directed or * undirected, etc. * * @author Dimitrios Michail */ public class DefaultGraphType implements GraphType, Serializable { private static final long serialVersionUID = 4291049312119347474L; private final boolean directed; private final boolean undirected; private final boolean selfLoops; private final boolean multipleEdges; private final boolean weighted; private final boolean allowsCycles; private final boolean modifiable; private DefaultGraphType( boolean directed, boolean undirected, boolean selfLoops, boolean multipleEdges, boolean weighted, boolean allowsCycles, boolean modifiable) { this.directed = directed; this.undirected = undirected; this.selfLoops = selfLoops; this.multipleEdges = multipleEdges; this.weighted = weighted; this.allowsCycles = allowsCycles; this.modifiable = modifiable; } @Override public boolean isDirected() { return directed && !undirected; } @Override public boolean isUndirected() { return undirected && !directed; } @Override public boolean isMixed() { return undirected && directed; } @Override public boolean isAllowingMultipleEdges() { return multipleEdges; } @Override public boolean isAllowingSelfLoops() { return selfLoops; } @Override public boolean isWeighted() { return weighted; } @Override public boolean isAllowingCycles() { return allowsCycles; } @Override public boolean isModifiable() { return modifiable; } @Override public boolean isSimple() { return !isAllowingMultipleEdges() && !isAllowingSelfLoops(); } @Override public boolean isPseudograph() { return isAllowingMultipleEdges() && isAllowingSelfLoops(); } @Override public boolean isMultigraph() { return isAllowingMultipleEdges() && !isAllowingSelfLoops(); } @Override public GraphType asDirected() { return new Builder(this).directed().build(); } @Override public GraphType asUndirected() { return new Builder(this).undirected().build(); } @Override public GraphType asMixed() { return new Builder(this).mixed().build(); } @Override public GraphType asUnweighted() { return new Builder(this).weighted(false).build(); } @Override public GraphType asWeighted() { return new Builder(this).weighted(true).build(); } @Override public GraphType asModifiable() { return new Builder(this).modifiable(true).build(); } @Override public GraphType asUnmodifiable() { return new Builder(this).modifiable(false).build(); } /** * A simple graph type. An undirected graph for which at most one edge connects any two * vertices, and self-loops are not permitted. * * @return a simple graph type */ public static DefaultGraphType simple() { return new Builder() .undirected().allowSelfLoops(false).allowMultipleEdges(false).weighted(false).build(); } /** * A multigraph type. A non-simple undirected graph in which no self-loops are permitted, but * multiple edges between any two vertices are. * * @return a multigraph type */ public static DefaultGraphType multigraph() { return new Builder() .undirected().allowSelfLoops(false).allowMultipleEdges(true).weighted(false).build(); } /** * A pseudograph type. A non-simple undirected graph in which both graph self-loops and multiple * edges are permitted. * * @return a pseudograph type */ public static DefaultGraphType pseudograph() { return new Builder() .undirected().allowSelfLoops(true).allowMultipleEdges(true).weighted(false).build(); } /** * A directed simple graph type. An undirected graph for which at most one edge connects any two * vertices, and self-loops are not permitted. * * @return a directed simple graph type */ public static DefaultGraphType directedSimple() { return new Builder() .directed().allowSelfLoops(false).allowMultipleEdges(false).weighted(false).build(); } /** * A directed multigraph type. A non-simple undirected graph in which no self-loops are * permitted, but multiple edges between any two vertices are. * * @return a directed multigraph type */ public static DefaultGraphType directedMultigraph() { return new Builder() .directed().allowSelfLoops(false).allowMultipleEdges(true).weighted(false).build(); } /** * A directed pseudograph type. A non-simple undirected graph in which both graph self-loops and * multiple edges are permitted. * * @return a directed pseudograph type */ public static DefaultGraphType directedPseudograph() { return new Builder() .directed().allowSelfLoops(true).allowMultipleEdges(true).weighted(false).build(); } /** * A mixed graph type. A graph having a set of undirected and a set of directed edges, which may * contain self-loops and multiple edges are permitted. * * @return a mixed graph type */ public static DefaultGraphType mixed() { return new Builder() .mixed().allowSelfLoops(true).allowMultipleEdges(true).weighted(false).build(); } /** * A directed acyclic graph. * * @return a directed acyclic graph type */ public static DefaultGraphType dag() { return new Builder() .directed().allowSelfLoops(false).allowMultipleEdges(true).allowCycles(false) .weighted(false).build(); } @Override public String toString() { return "DefaultGraphType [directed=" + directed + ", undirected=" + undirected + ", self-loops=" + selfLoops + ", multiple-edges=" + multipleEdges + ", weighted=" + weighted + ", allows-cycles=" + allowsCycles + ", modifiable=" + modifiable + "]"; } /** * A builder for {@link DefaultGraphType}. * * @author Dimitrios Michail */ public static class Builder { private boolean directed; private boolean undirected; private boolean allowSelfLoops; private boolean allowMultipleEdges; private boolean weighted; private boolean allowCycles; private boolean modifiable; /** * Construct a new Builder. */ public Builder() { this.directed = false; this.undirected = true; this.allowSelfLoops = true; this.allowMultipleEdges = true; this.weighted = false; this.allowCycles = true; this.modifiable = true; } /** * Construct a new Builder. * * @param type the type to base the builder */ public Builder(GraphType type) { this.directed = type.isDirected() || type.isMixed(); this.undirected = type.isUndirected() || type.isMixed(); this.allowSelfLoops = type.isAllowingSelfLoops(); this.allowMultipleEdges = type.isAllowingMultipleEdges(); this.weighted = type.isWeighted(); this.allowCycles = type.isAllowingCycles(); this.modifiable = type.isModifiable(); } /** * Construct a new Builder. * * @param directed whether the graph contains directed edges * @param undirected whether the graph contains undirected edges */ public Builder(boolean directed, boolean undirected) { if (!directed && !undirected) { throw new IllegalArgumentException( "At least one of directed or undirected must be true"); } this.directed = directed; this.undirected = undirected; this.allowSelfLoops = true; this.allowMultipleEdges = true; this.weighted = false; this.allowCycles = true; this.modifiable = true; } /** * Set the type as directed. * * @return the builder */ public Builder directed() { this.directed = true; this.undirected = false; return this; } /** * Set the type as undirected. * * @return the builder */ public Builder undirected() { this.directed = false; this.undirected = true; return this; } /** * Set the type as mixed. * * @return the builder */ public Builder mixed() { this.directed = true; this.undirected = true; return this; } /** * Set whether to allow self-loops. * * @param value if true self-values are allowed, otherwise not * @return the builder */ public Builder allowSelfLoops(boolean value) { this.allowSelfLoops = value; return this; } /** * Set whether to allow multiple edges. * * @param value if true multiple edges are allowed, otherwise not * @return the builder */ public Builder allowMultipleEdges(boolean value) { this.allowMultipleEdges = value; return this; } /** * Set whether the graph will be weighted. * * @param value if true the graph will be weighted, otherwise unweighted * @return the builder */ public Builder weighted(boolean value) { this.weighted = value; return this; } /** * Set whether the graph will allow cycles. * * @param value if true the graph will allow cycles, otherwise not * @return the builder */ public Builder allowCycles(boolean value) { this.allowCycles = value; return this; } /** * Set whether the graph is modifiable. * * @param value if true the graph will be modifiable, otherwise not * @return the builder */ public Builder modifiable(boolean value) { this.modifiable = value; return this; } /** * Build the type. * * @return the type */ public DefaultGraphType build() { return new DefaultGraphType( directed, undirected, allowSelfLoops, allowMultipleEdges, weighted, allowCycles, modifiable); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultListenableGraph.java000066400000000000000000000327361402514743400324150ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.event.*; import org.jgrapht.util.*; import java.util.*; /** * A graph backed by the the graph specified at the constructor, which can be listened by * GraphListener s and by * VertexSetListener s. Operations on this graph "pass through" to the to the backing graph. * Any modification made to this graph or the backing graph is reflected by the other. * *

    * This graph does not pass the hashCode and equals operations through to the backing graph, * but relies on Object's equals and hashCode methods. *

    * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh * @see GraphListener * @see VertexSetListener */ public class DefaultListenableGraph extends GraphDelegator implements ListenableGraph, Cloneable { private static final long serialVersionUID = -1156773351121025002L; private List> graphListeners = new ArrayList<>(); private List> vertexSetListeners = new ArrayList<>(); private FlyweightEdgeEvent reuseableEdgeEvent; private FlyweightVertexEvent reuseableVertexEvent; private boolean reuseEvents; /** * Creates a new listenable graph. * * @param g the backing graph. */ public DefaultListenableGraph(Graph g) { this(g, false); } /** * Creates a new listenable graph. If the reuseEvents flag is set to * true this class will reuse previously fired events and will not create a new * object for each event. This option increases performance but should be used with care, * especially in multithreaded environment. * * @param g the backing graph. * @param reuseEvents whether to reuse previously fired event objects instead of creating a new * event object for each event. * * @throws IllegalArgumentException if the backing graph is already a listenable graph. */ public DefaultListenableGraph(Graph g, boolean reuseEvents) { super(g); this.reuseEvents = reuseEvents; reuseableEdgeEvent = new FlyweightEdgeEvent<>(this, -1, null); reuseableVertexEvent = new FlyweightVertexEvent<>(this, -1, null); // the following restriction could be probably relaxed in the future. if (g instanceof ListenableGraph) { throw new IllegalArgumentException("base graph cannot be listenable"); } } /** * If the reuseEvents flag is set to true this class will reuse * previously fired events and will not create a new object for each event. This option * increases performance but should be used with care, especially in multithreaded environment. * * @param reuseEvents whether to reuse previously fired event objects instead of creating a new * event object for each event. */ public void setReuseEvents(boolean reuseEvents) { this.reuseEvents = reuseEvents; } /** * Tests whether the reuseEvents flag is set. If the flag is set to * true this class will reuse previously fired events and will not create a new * object for each event. This option increases performance but should be used with care, * especially in multithreaded environment. * * @return the value of the reuseEvents flag. */ public boolean isReuseEvents() { return reuseEvents; } @Override public E addEdge(V sourceVertex, V targetVertex) { E e = super.addEdge(sourceVertex, targetVertex); if (e != null) { fireEdgeAdded(e, sourceVertex, targetVertex, Graph.DEFAULT_EDGE_WEIGHT); } return e; } @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { boolean added = super.addEdge(sourceVertex, targetVertex, e); if (added) { fireEdgeAdded(e, sourceVertex, targetVertex, Graph.DEFAULT_EDGE_WEIGHT); } return added; } @Override public void addGraphListener(GraphListener l) { addToListenerList(graphListeners, l); } @Override public V addVertex() { V v = super.addVertex(); if (v != null) { fireVertexAdded(v); } return v; } @Override public boolean addVertex(V v) { boolean modified = super.addVertex(v); if (modified) { fireVertexAdded(v); } return modified; } @Override public void addVertexSetListener(VertexSetListener l) { addToListenerList(vertexSetListeners, l); } @Override public Object clone() { try { DefaultListenableGraph g = TypeUtil.uncheckedCast(super.clone()); g.graphListeners = new ArrayList<>(); g.vertexSetListeners = new ArrayList<>(); return g; } catch (CloneNotSupportedException e) { // should never get here since we're Cloneable e.printStackTrace(); throw new RuntimeException("internal error"); } } @Override public E removeEdge(V sourceVertex, V targetVertex) { E e = super.getEdge(sourceVertex, targetVertex); if (e != null) { double weight = super.getEdgeWeight(e); if (super.removeEdge(e)) { fireEdgeRemoved(e, sourceVertex, targetVertex, weight); } } return e; } @Override public boolean removeEdge(E e) { V sourceVertex = getEdgeSource(e); V targetVertex = getEdgeTarget(e); double weight = getEdgeWeight(e); boolean modified = super.removeEdge(e); if (modified) { fireEdgeRemoved(e, sourceVertex, targetVertex, weight); } return modified; } @Override public void removeGraphListener(GraphListener l) { graphListeners.remove(l); } @Override public boolean removeVertex(V v) { if (containsVertex(v)) { Set touchingEdgesList = edgesOf(v); // copy set to avoid ConcurrentModificationException removeAllEdges(new ArrayList<>(touchingEdgesList)); super.removeVertex(v); // remove the vertex itself fireVertexRemoved(v); return true; } else { return false; } } @Override public void setEdgeWeight(E e, double weight) { super.setEdgeWeight(e, weight); V sourceVertex = getEdgeSource(e); V targetVertex = getEdgeTarget(e); fireEdgeWeightUpdated(e, sourceVertex, targetVertex, weight); } @Override public void removeVertexSetListener(VertexSetListener l) { vertexSetListeners.remove(l); } /** * Notify listeners that the specified edge was added. * * @param edge the edge that was added. * @param source edge source * @param target edge target * @param weight edge weight */ protected void fireEdgeAdded(E edge, V source, V target, double weight) { GraphEdgeChangeEvent e = createGraphEdgeChangeEvent( GraphEdgeChangeEvent.EDGE_ADDED, edge, source, target, weight); for (GraphListener l : graphListeners) { l.edgeAdded(e); } } /** * Notify listeners that the specified edge was removed. * * @param edge the edge that was removed. * @param source edge source * @param target edge target * @param weight edge weight */ protected void fireEdgeRemoved(E edge, V source, V target, double weight) { GraphEdgeChangeEvent e = createGraphEdgeChangeEvent( GraphEdgeChangeEvent.EDGE_REMOVED, edge, source, target, weight); for (GraphListener l : graphListeners) { l.edgeRemoved(e); } } /** * Notify listeners that the weight of an edge has changed. * * @param edge the edge whose weight has changed. * @param source edge source * @param target edge target * @param weight the edge weight */ protected void fireEdgeWeightUpdated(E edge, V source, V target, double weight) { GraphEdgeChangeEvent e = createGraphEdgeChangeEvent( GraphEdgeChangeEvent.EDGE_WEIGHT_UPDATED, edge, source, target, weight); for (GraphListener l : graphListeners) { l.edgeWeightUpdated(e); } } /** * Notify listeners that the specified vertex was added. * * @param vertex the vertex that was added. */ protected void fireVertexAdded(V vertex) { GraphVertexChangeEvent e = createGraphVertexChangeEvent(GraphVertexChangeEvent.VERTEX_ADDED, vertex); for (VertexSetListener l : vertexSetListeners) { l.vertexAdded(e); } for (GraphListener l : graphListeners) { l.vertexAdded(e); } } /** * Notify listeners that the specified vertex was removed. * * @param vertex the vertex that was removed. */ protected void fireVertexRemoved(V vertex) { GraphVertexChangeEvent e = createGraphVertexChangeEvent(GraphVertexChangeEvent.VERTEX_REMOVED, vertex); for (VertexSetListener l : vertexSetListeners) { l.vertexRemoved(e); } for (GraphListener l : graphListeners) { l.vertexRemoved(e); } } private static void addToListenerList(List list, L l) { if (!list.contains(l)) { list.add(l); } } private GraphEdgeChangeEvent createGraphEdgeChangeEvent( int eventType, E edge, V source, V target, double weight) { if (reuseEvents) { reuseableEdgeEvent.setType(eventType); reuseableEdgeEvent.setEdge(edge); reuseableEdgeEvent.setEdgeSource(source); reuseableEdgeEvent.setEdgeTarget(target); reuseableEdgeEvent.setEdgeWeight(weight); return reuseableEdgeEvent; } else { return new GraphEdgeChangeEvent<>(this, eventType, edge, source, target, weight); } } private GraphVertexChangeEvent createGraphVertexChangeEvent(int eventType, V vertex) { if (reuseEvents) { reuseableVertexEvent.setType(eventType); reuseableVertexEvent.setVertex(vertex); return reuseableVertexEvent; } else { return new GraphVertexChangeEvent<>(this, eventType, vertex); } } /** * A reuseable edge event. * * @author Barak Naveh */ private static class FlyweightEdgeEvent extends GraphEdgeChangeEvent { private static final long serialVersionUID = 3907207152526636089L; /** * @see GraphEdgeChangeEvent */ public FlyweightEdgeEvent(Object eventSource, int type, EE e) { super(eventSource, type, e, null, null); } /** * Sets the edge of this event. * * @param e the edge to be set. */ protected void setEdge(EE e) { this.edge = e; } protected void setEdgeSource(VV v) { this.edgeSource = v; } protected void setEdgeTarget(VV v) { this.edgeTarget = v; } protected void setEdgeWeight(double weight) { this.edgeWeight = weight; } /** * Set the event type of this event. * * @param type the type to be set. */ protected void setType(int type) { this.type = type; } } /** * A reuseable vertex event. * * @author Barak Naveh */ private static class FlyweightVertexEvent extends GraphVertexChangeEvent { private static final long serialVersionUID = 3257848787857585716L; /** * @see GraphVertexChangeEvent#GraphVertexChangeEvent(Object, int, Object) */ public FlyweightVertexEvent(Object eventSource, int type, VV vertex) { super(eventSource, type, vertex); } /** * Set the event type of this event. * * @param type type to be set. */ protected void setType(int type) { this.type = type; } /** * Sets the vertex of this event. * * @param vertex the vertex to be set. */ protected void setVertex(VV vertex) { this.vertex = vertex; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultUndirectedGraph.java000066400000000000000000000057611402514743400324170ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * The default implementation of an undirected graph. A default undirected graph is a non-simple * undirected graph in which multiple (parallel) edges between any two vertices are not * permitted, but loops are. * * @param the graph vertex type * @param the graph edge type */ public class DefaultUndirectedGraph extends AbstractBaseGraph { private static final long serialVersionUID = -2066644490824847621L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public DefaultUndirectedGraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass), false); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param weighted whether the graph is weighted or not */ public DefaultUndirectedGraph( Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted) { super( vertexSupplier, edgeSupplier, new DefaultGraphType.Builder() .undirected().allowMultipleEdges(false).allowSelfLoops(true).weighted(weighted) .build()); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new DefaultUndirectedGraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier of the new graph * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new DefaultUndirectedGraph<>(null, edgeSupplier, false)); } } DefaultUndirectedWeightedGraph.java000066400000000000000000000056211402514743400340140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * The default implementation of an undirected weighted graph. A default undirected weighted graph * is a non-simple undirected graph in which multiple (parallel) edges between any two vertices are * not permitted, but loops are. The edges of a weighted undirected graph have weights. * * @param the graph vertex type * @param the graph edge type * * @see DefaultUndirectedGraph */ public class DefaultUndirectedWeightedGraph extends DefaultUndirectedGraph { private static final long serialVersionUID = -1008165881690129042L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public DefaultUndirectedWeightedGraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass)); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null */ public DefaultUndirectedWeightedGraph(Supplier vertexSupplier, Supplier edgeSupplier) { super(vertexSupplier, edgeSupplier, true); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new DefaultUndirectedWeightedGraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new DefaultUndirectedWeightedGraph<>(null, edgeSupplier)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DefaultWeightedEdge.java000066400000000000000000000036011402514743400316630ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; /** * A default implementation for edges in a weighted graph. All access to the weight of an edge must * go through the graph interface, which is why this class doesn't expose any public methods. * * @author John V. Sichi */ public class DefaultWeightedEdge extends IntrusiveWeightedEdge { private static final long serialVersionUID = -3259071493169286685L; /** * Retrieves the source of this edge. This is protected, for use by subclasses only (e.g. for * implementing toString). * * @return source of this edge */ protected Object getSource() { return source; } /** * Retrieves the target of this edge. This is protected, for use by subclasses only (e.g. for * implementing toString). * * @return target of this edge */ protected Object getTarget() { return target; } /** * Retrieves the weight of this edge. This is protected, for use by subclasses only (e.g. for * implementing toString). * * @return weight of this edge */ protected double getWeight() { return weight; } @Override public String toString() { return "(" + source + " : " + target + ")"; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedAcyclicGraph.java000066400000000000000000001140431402514743400320310ustar00rootroot00000000000000/* * (C) Copyright 2008-2021, by Peter Giles and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * A directed acyclic graph (DAG). * *

    * Implements a DAG that can be modified (vertices & edges added and removed), is guaranteed to * remain acyclic, and provides fast topological order iteration. An attempt to add an edge which * would induce a cycle throws an {@link IllegalArgumentException}. * *

    * This is done using a dynamic topological sort which is based on the algorithm described in "David * J. Pearce & Paul H. J. Kelly. A dynamic topological sort algorithm for directed acyclic * graphs. Journal of Experimental Algorithmics, 11, 2007." (see * paper or * ACM link for details). The * implementation differs from the algorithm specified in the above paper in some ways, perhaps most * notably in that the topological ordering is stored by default using two hash maps, which will * have some effects on the runtime, but also allow for vertex addition and removal. This storage * mechanism can be adjusted by subclasses. * *

    * The complexity of adding a new edge in the graph depends on the number of edges incident to the * "affected region", and should in general be faster than recomputing the whole topological * ordering from scratch. For details about the complexity parameters and running times, see the * previously mentioned paper. * *

    * This class makes no claims to thread safety, and concurrent usage from multiple threads will * produce undefined results. * * @param the graph vertex type * @param the graph edge type * * @author Peter Giles */ public class DirectedAcyclicGraph extends AbstractBaseGraph implements Iterable { private static final long serialVersionUID = 4522128427004938150L; private static final String EDGE_WOULD_INDUCE_A_CYCLE = "Edge would induce a cycle"; private final Comparator topoComparator; private final TopoOrderMap topoOrderMap; private int maxTopoIndex = 0; private int minTopoIndex = 0; // this update count is used to keep internal topological iterators honest private transient long topoModCount = 0; /** * The visited strategy factory to use. Subclasses can change this. */ private final VisitedStrategyFactory visitedStrategyFactory; /** * Construct a directed acyclic graph. * * @param edgeClass the edge class */ public DirectedAcyclicGraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass), false, false); } /** * Construct a directed acyclic graph. * * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param weighted if true the graph will be weighted, otherwise not */ public DirectedAcyclicGraph( Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted) { this( vertexSupplier, edgeSupplier, new VisitedBitSetImpl(), new TopoVertexBiMap<>(), weighted, false); } /** * Construct a directed acyclic graph. * * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param weighted if true the graph will be weighted, otherwise not * @param allowMultipleEdges if true the graph will allow multiple edges, otherwise not */ public DirectedAcyclicGraph( Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted, boolean allowMultipleEdges) { this( vertexSupplier, edgeSupplier, new VisitedBitSetImpl(), new TopoVertexBiMap<>(), weighted, allowMultipleEdges); } /** * Construct a directed acyclic graph. * * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param visitedStrategyFactory the visited strategy factory. Subclasses can change this * implementation to adjust the performance tradeoffs. * @param topoOrderMap the topological order map. For performance reasons, subclasses can change * the way this class stores the topological order. * @param weighted if true the graph will be weighted, otherwise not */ protected DirectedAcyclicGraph( Supplier vertexSupplier, Supplier edgeSupplier, VisitedStrategyFactory visitedStrategyFactory, TopoOrderMap topoOrderMap, boolean weighted) { this(vertexSupplier, edgeSupplier, visitedStrategyFactory, topoOrderMap, weighted, false); } /** * Construct a directed acyclic graph. * * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param visitedStrategyFactory the visited strategy factory. Subclasses can change this * implementation to adjust the performance tradeoffs. * @param topoOrderMap the topological order map. For performance reasons, subclasses can change * the way this class stores the topological order. * @param weighted if true the graph will be weighted, otherwise not * @param allowMultipleEdges if true the graph will allow multiple edges, otherwise not */ protected DirectedAcyclicGraph( Supplier vertexSupplier, Supplier edgeSupplier, VisitedStrategyFactory visitedStrategyFactory, TopoOrderMap topoOrderMap, boolean weighted, boolean allowMultipleEdges) { super( vertexSupplier, edgeSupplier, new DefaultGraphType.Builder() .directed().allowMultipleEdges(allowMultipleEdges).allowSelfLoops(false) .weighted(weighted).allowCycles(false).build()); this.visitedStrategyFactory = Objects.requireNonNull(visitedStrategyFactory, "Visited factory cannot be null"); this.topoOrderMap = Objects.requireNonNull(topoOrderMap, "Topological order map cannot be null"); this.topoComparator = new TopoComparator(); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new DirectedAcyclicGraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier edge supplier for the edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new DirectedAcyclicGraph<>(null, edgeSupplier, false)); } @Override public V addVertex() { V v = super.addVertex(); if (v != null) { // add to the topological map ++maxTopoIndex; topoOrderMap.putVertex(maxTopoIndex, v); ++topoModCount; } return v; } @Override public boolean addVertex(V v) { boolean added = super.addVertex(v); if (added) { // add to the topological map ++maxTopoIndex; topoOrderMap.putVertex(maxTopoIndex, v); ++topoModCount; } return added; } @Override public boolean removeVertex(V v) { boolean removed = super.removeVertex(v); if (removed) { /* * Depending on the topoOrderMap implementation, this can leave holes in the topological * ordering, which can degrade performance for certain operations over time. */ Integer topoIndex = topoOrderMap.removeVertex(v); // if possible contract minTopoIndex if (topoIndex == minTopoIndex) { while ((minTopoIndex < 0) && (topoOrderMap.getVertex(minTopoIndex) == null)) { ++minTopoIndex; } } // if possible contract maxTopoIndex if (topoIndex == maxTopoIndex) { while ((maxTopoIndex > 0) && (topoOrderMap.getVertex(maxTopoIndex) == null)) { --maxTopoIndex; } } ++topoModCount; } return removed; } /** * {@inheritDoc} * *

    * The complexity of adding a new edge in the graph depends on the number of edges incident to * the "affected region", and should in general be faster than recomputing the whole topological * ordering from scratch. * * @throws IllegalArgumentException if the edge would induce a cycle in the graph */ @Override public E addEdge(V sourceVertex, V targetVertex) { assertVertexExist(sourceVertex); assertVertexExist(targetVertex); E result; try { updateDag(sourceVertex, targetVertex); result = super.addEdge(sourceVertex, targetVertex); } catch (CycleFoundException e) { throw new IllegalArgumentException(EDGE_WOULD_INDUCE_A_CYCLE); } return result; } /** * {@inheritDoc} * *

    * The complexity of adding a new edge in the graph depends on the number of edges incident to * the "affected region", and should in general be faster than recomputing the whole topological * ordering from scratch. * * @throws IllegalArgumentException if the edge would induce a cycle in the graph */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { if (e == null) { throw new NullPointerException(); } else if (containsEdge(e)) { return false; } assertVertexExist(sourceVertex); assertVertexExist(targetVertex); boolean result; try { updateDag(sourceVertex, targetVertex); result = super.addEdge(sourceVertex, targetVertex, e); } catch (CycleFoundException ex) { throw new IllegalArgumentException(EDGE_WOULD_INDUCE_A_CYCLE); } return result; } /** * Get the ancestors of a vertex. * * @param vertex the vertex to get the ancestors of * @return {@link Set} of ancestors of a vertex */ public Set getAncestors(V vertex) { EdgeReversedGraph reversedGraph = new EdgeReversedGraph<>(this); Iterator iterator = new DepthFirstIterator<>(reversedGraph, vertex); Set ancestors = new HashSet<>(); // Do not add start vertex to result. if (iterator.hasNext()) { iterator.next(); } iterator.forEachRemaining(ancestors::add); return ancestors; } /** * Get the descendants of a vertex. * * @param vertex the vertex to get the descendants of * @return {@link Set} of descendants of a vertex */ public Set getDescendants(V vertex) { Iterator iterator = new DepthFirstIterator<>(this, vertex); Set descendants = new HashSet<>(); // Do not add start vertex to result. if (iterator.hasNext()) { iterator.next(); } iterator.forEachRemaining(descendants::add); return descendants; } /** * Returns a topological order iterator. * * @return a topological order iterator */ public Iterator iterator() { return new TopoIterator(); } /** * Update as if a new edge is added. * * @param sourceVertex the source vertex * @param targetVertex the target vertex */ private void updateDag(V sourceVertex, V targetVertex) throws CycleFoundException { Integer lb = topoOrderMap.getTopologicalIndex(targetVertex); Integer ub = topoOrderMap.getTopologicalIndex(sourceVertex); if (lb < ub) { Set df = new HashSet<>(); Set db = new HashSet<>(); // discovery Region affectedRegion = new Region(lb, ub); VisitedStrategy visited = visitedStrategyFactory.getVisitedStrategy(affectedRegion); // throws CycleFoundException if there is a cycle dfsF(targetVertex, df, visited, affectedRegion); dfsB(sourceVertex, db, visited, affectedRegion); reorder(df, db, visited); /* * if we do a reorder, then the topology has been updated */ ++topoModCount; } } /** * Depth first search forward, building up the set (df) of forward-connected vertices in the * Affected Region * * @param initialVertex the vertex being visited * @param df the set we are populating with forward connected vertices in the Affected Region * @param visited a simple data structure that lets us know if we already visited a node with a * given topo index * * @throws CycleFoundException if a cycle is discovered */ private void dfsF(V initialVertex, Set df, VisitedStrategy visited, Region affectedRegion) throws CycleFoundException { Deque vertices = new ArrayDeque<>(); vertices.push(initialVertex); while (!vertices.isEmpty()) { V vertex = vertices.pop(); int topoIndex = topoOrderMap.getTopologicalIndex(vertex); if (visited.getVisited(topoIndex)) { continue; } // Assumption: vertex is in the AR and so it will be in visited visited.setVisited(topoIndex); df.add(vertex); for (E outEdge : outgoingEdgesOf(vertex)) { V nextVertex = getEdgeTarget(outEdge); Integer nextVertexTopoIndex = topoOrderMap.getTopologicalIndex(nextVertex); if (nextVertexTopoIndex == affectedRegion.finish) { // reset visited try { for (V visitedVertex : df) { visited.clearVisited(topoOrderMap.getTopologicalIndex(visitedVertex)); } } catch (UnsupportedOperationException e) { // okay, fine, some implementations (ones that automatically // reset themselves out) don't work this way } throw new CycleFoundException(); } /* * Note, order of checks is important as we need to make sure the vertex is in the * affected region before we check its visited status (otherwise we will be causing * an ArrayIndexOutOfBoundsException). */ if (affectedRegion.isIn(nextVertexTopoIndex) && !visited.getVisited(nextVertexTopoIndex)) { vertices.push(nextVertex); // recurse } } } } /** * Depth first search backward, building up the set (db) of back-connected vertices in the * Affected Region * * @param initialVertex the vertex being visited * @param db the set we are populating with back-connected vertices in the AR * @param visited */ private void dfsB(V initialVertex, Set db, VisitedStrategy visited, Region affectedRegion) { Deque vertices = new ArrayDeque<>(); vertices.push(initialVertex); while (!vertices.isEmpty()) { V vertex = vertices.pop(); // Assumption: vertex is in the AR and so we will get a topoIndex from // the map int topoIndex = topoOrderMap.getTopologicalIndex(vertex); if (visited.getVisited(topoIndex)) { continue; } visited.setVisited(topoIndex); db.add(vertex); for (E inEdge : incomingEdgesOf(vertex)) { V previousVertex = getEdgeSource(inEdge); Integer previousVertexTopoIndex = topoOrderMap.getTopologicalIndex(previousVertex); /* * Note, order of checks is important as we need to make sure the vertex is in the * affected region before we check its visited status (otherwise we will be causing * an ArrayIndexOutOfBoundsException). */ if (affectedRegion.isIn(previousVertexTopoIndex) && !visited.getVisited(previousVertexTopoIndex)) { // if previousVertexTopoIndex != null, the vertex is in the // Affected Region according to our topoIndexMap vertices.push(previousVertex); } } } } @SuppressWarnings("unchecked") private void reorder(Set df, Set db, VisitedStrategy visited) { List topoDf = new ArrayList<>(df); List topoDb = new ArrayList<>(db); topoDf.sort(topoComparator); topoDb.sort(topoComparator); // merge these suckers together in topological order SortedSet availableTopoIndices = new TreeSet<>(); // we have to cast to the generic type, can't do "new V[size]" in java // 5; V[] bigL = (V[]) new Object[df.size() + db.size()]; int lIndex = 0; // this index is used for the sole purpose of pushing // into // the correct index of bigL // assume (for now) that we are resetting visited boolean clearVisited = true; for (V vertex : topoDb) { Integer topoIndex = topoOrderMap.getTopologicalIndex(vertex); // add the available indices to the set availableTopoIndices.add(topoIndex); bigL[lIndex++] = vertex; if (clearVisited) { // reset visited status if supported try { visited.clearVisited(topoIndex); } catch (UnsupportedOperationException e) { clearVisited = false; } } } for (V vertex : topoDf) { Integer topoIndex = topoOrderMap.getTopologicalIndex(vertex); // add the available indices to the set availableTopoIndices.add(topoIndex); bigL[lIndex++] = vertex; if (clearVisited) { // reset visited status if supported try { visited.clearVisited(topoIndex); } catch (UnsupportedOperationException e) { clearVisited = false; } } } lIndex = 0; // reusing lIndex for (Integer topoIndex : availableTopoIndices) { // assign the indexes to the elements of bigL in order V vertex = bigL[lIndex++]; // note the post-increment topoOrderMap.putVertex(topoIndex, vertex); } } /** * An interface for storing the topological ordering. * * @param the graph vertex type * * @author Peter Giles */ protected interface TopoOrderMap extends Serializable { /** * Add a vertex at the given topological index. * * @param index the topological index * @param vertex the vertex */ void putVertex(Integer index, V vertex); /** * Get the vertex at the given topological index. * * @param index the topological index * @return vertex the vertex */ V getVertex(Integer index); /** * Get the topological index of the given vertex. * * @param vertex the vertex * @return the index that the vertex is at, or null if the vertex isn't in the topological * ordering */ Integer getTopologicalIndex(V vertex); /** * Remove the given vertex from the topological ordering. * * @param vertex the vertex * @return the index that the vertex was at, or null if the vertex wasn't in the topological * ordering */ Integer removeVertex(V vertex); /** * Remove all vertices from the topological ordering. */ void removeAllVertices(); } /** * A strategy for marking vertices as visited. * *

    * Vertices are indexed by their topological index, to avoid using the vertex type in the * interface. * * @author Peter Giles */ protected interface VisitedStrategy { /** * Mark the given topological index as visited. * * @param index the topological index */ void setVisited(int index); /** * Get if the given topological index has been visited. * * @param index the topological index * @return true if the given topological index has been visited, false otherwise */ boolean getVisited(int index); /** * Clear the visited state of the given topological index. * * @param index the index * @throws UnsupportedOperationException if the implementation doesn't support (or doesn't * need) clearance. For example, if the factory creates a new instance every time, * it is a waste of cycles to reset the state after the search of the Affected * Region is done, so an UnsupportedOperationException *should* be thrown. */ void clearVisited(int index) throws UnsupportedOperationException; } /** * A visited strategy factory. * * @author Peter Giles */ protected interface VisitedStrategyFactory extends Serializable { /** * Create a new instance of {@link VisitedStrategy}. * * @param affectedRegion the affected region * @return a new instance of {@link VisitedStrategy} for the affected region */ VisitedStrategy getVisitedStrategy(Region affectedRegion); } /** * A dual map implementation of the topological order map. * * @author Peter Giles */ protected static class TopoVertexBiMap implements TopoOrderMap { private static final long serialVersionUID = 1L; private final Map topoToVertex = new HashMap<>(); private final Map vertexToTopo = new HashMap<>(); /** * Constructor */ public TopoVertexBiMap() { } @Override public void putVertex(Integer index, V vertex) { topoToVertex.put(index, vertex); vertexToTopo.put(vertex, index); } @Override public V getVertex(Integer index) { return topoToVertex.get(index); } @Override public Integer getTopologicalIndex(V vertex) { return vertexToTopo.get(vertex); } @Override public Integer removeVertex(V vertex) { Integer topoIndex = vertexToTopo.remove(vertex); if (topoIndex != null) { topoToVertex.remove(topoIndex); } return topoIndex; } @Override public void removeAllVertices() { vertexToTopo.clear(); topoToVertex.clear(); } } /** * An implementation of the topological order map which for performance and flexibility uses an * ArrayList for topological index to vertex mapping, and a HashMap for vertex to topological * index mapping. * * @author Peter Giles */ protected class TopoVertexMap implements TopoOrderMap { private static final long serialVersionUID = 1L; private final List topoToVertex = new ArrayList<>(); private final Map vertexToTopo = new HashMap<>(); /** * Constructor */ public TopoVertexMap() { } @Override public void putVertex(Integer index, V vertex) { int translatedIndex = translateIndex(index); // grow topoToVertex as needed to accommodate elements while ((translatedIndex + 1) > topoToVertex.size()) { topoToVertex.add(null); } topoToVertex.set(translatedIndex, vertex); vertexToTopo.put(vertex, index); } @Override public V getVertex(Integer index) { return topoToVertex.get(translateIndex(index)); } @Override public Integer getTopologicalIndex(V vertex) { return vertexToTopo.get(vertex); } @Override public Integer removeVertex(V vertex) { Integer topoIndex = vertexToTopo.remove(vertex); if (topoIndex != null) { topoToVertex.set(translateIndex(topoIndex), null); } return topoIndex; } @Override public void removeAllVertices() { vertexToTopo.clear(); topoToVertex.clear(); } /** * We translate the topological index to an ArrayList index. We have to do this because * topological indices can be negative, and we want to do it because we can make better use * of space by only needing an ArrayList of size |AR|. * * @return the ArrayList index */ private int translateIndex(int index) { if (index >= 0) { return 2 * index; } return -1 * ((index * 2) - 1); } } /** * An inclusive range of indices: [start, finish]. * * @author Peter Giles */ protected static class Region implements Serializable { private static final long serialVersionUID = 1L; private final int start; private final int finish; /** * Construct a new region. * * @param start the start of the region * @param finish the end of the region (inclusive) */ public Region(int start, int finish) { if (start > finish) { throw new IllegalArgumentException("(start > finish): invariant broken"); } this.start = start; this.finish = finish; } /** * Get the size of the region. * * @return the size of the region */ public int getSize() { return (finish - start) + 1; } /** * Check if index is in the region. * * @param index the index to check * @return true if the index is in the region, false otherwise */ public boolean isIn(int index) { return (index >= start) && (index <= finish); } /** * Get the start of the region. * * @return the start of the region */ public int getStart() { return start; } /** * Get the end of the region (inclusive). * * @return the end of the region (inclusive) */ public int getFinish() { return finish; } } /** * A visited strategy which uses a {@link BitSet}. * *

    * This implementation is close to the performance of {@link VisitedArrayListImpl}, with 1/8 the * memory usage. * * @author John V. Sichi */ protected static class VisitedBitSetImpl implements VisitedStrategy, VisitedStrategyFactory { private static final long serialVersionUID = 1L; private final BitSet visited = new BitSet(); private Region affectedRegion; /** * Constructor */ public VisitedBitSetImpl() { } @Override public VisitedStrategy getVisitedStrategy(Region affectedRegion) { this.affectedRegion = affectedRegion; return this; } @Override public void setVisited(int index) { visited.set(translateIndex(index), true); } @Override public boolean getVisited(int index) { return visited.get(translateIndex(index)); } @Override public void clearVisited(int index) throws UnsupportedOperationException { visited.clear(translateIndex(index)); } /** * We translate the topological index to an ArrayList index. We have to do this because * topological indices can be negative, and we want to do it because we can make better use * of space by only needing an ArrayList of size |AR|. * * @return the ArrayList index */ private int translateIndex(int index) { return index - affectedRegion.start; } } /** * A visited strategy using an {@link ArrayList}. * *

    * This implementation seems to offer the best performance in most cases. It grows the internal * ArrayList as needed to be as large as |AR|, so it will be more memory intensive than the * HashSet implementation, and unlike the Array implementation, it will hold on to that memory * (it expands, but never contracts). * * @author Peter Giles */ protected static class VisitedArrayListImpl implements VisitedStrategy, VisitedStrategyFactory { private static final long serialVersionUID = 1L; private final List visited = new ArrayList<>(); private Region affectedRegion; /** * Constructor */ public VisitedArrayListImpl() { } @Override public VisitedStrategy getVisitedStrategy(Region affectedRegion) { // Make sure visited is big enough int minSize = (affectedRegion.finish - affectedRegion.start) + 1; /* plus one because the region range is inclusive of both indices */ while (visited.size() < minSize) { visited.add(Boolean.FALSE); } this.affectedRegion = affectedRegion; return this; } @Override public void setVisited(int index) { visited.set(translateIndex(index), Boolean.TRUE); } @Override public boolean getVisited(int index) { return visited.get(translateIndex(index)); } @Override public void clearVisited(int index) throws UnsupportedOperationException { visited.set(translateIndex(index), Boolean.FALSE); } /** * We translate the topological index to an ArrayList index. We have to do this because * topological indices can be negative, and we want to do it because we can make better use * of space by only needing an ArrayList of size |AR|. * * @return the ArrayList index */ private int translateIndex(int index) { return index - affectedRegion.start; } } /** * A visited strategy using a {@link HashSet}. * *

    * This implementation doesn't seem to perform as well, though I can imagine circumstances where * it should shine (lots and lots of vertices). It also should have the lowest memory footprint * as it only uses storage for indices that have been visited. * * @author Peter Giles */ protected static class VisitedHashSetImpl implements VisitedStrategy, VisitedStrategyFactory { private static final long serialVersionUID = 1L; private final Set visited = new HashSet<>(); /** * Constructor */ public VisitedHashSetImpl() { } @Override public VisitedStrategy getVisitedStrategy(Region affectedRegion) { visited.clear(); return this; } @Override public void setVisited(int index) { visited.add(index); } @Override public boolean getVisited(int index) { return visited.contains(index); } @Override public void clearVisited(int index) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } } /** * A visited strategy using an array. * *

    * This implementation, somewhat to my surprise, is slower than the ArrayList version, probably * due to its reallocation of the underlying array for every topology reorder that is required. * * @author Peter Giles */ protected static class VisitedArrayImpl implements VisitedStrategy, VisitedStrategyFactory { private static final long serialVersionUID = 1L; private final boolean[] visited; private final Region region; /** * Constructs empty instance */ public VisitedArrayImpl() { this(null); } /** * Construct an empty instance for a region. * * @param region the region */ public VisitedArrayImpl(Region region) { if (region == null) { // make empty instance this.visited = null; this.region = null; } else { // fill in the needed pieces this.region = region; // initialized to all false by default visited = new boolean[region.getSize()]; } } @Override public VisitedStrategy getVisitedStrategy(Region affectedRegion) { return new VisitedArrayImpl(affectedRegion); } @Override public void setVisited(int index) { visited[index - region.start] = true; } @Override public boolean getVisited(int index) { return visited[index - region.start]; } @Override public void clearVisited(int index) throws UnsupportedOperationException { throw new UnsupportedOperationException(); } } /** * Exception used in dfsF when a cycle is found * * @author Peter Giles */ private static class CycleFoundException extends Exception { private static final long serialVersionUID = 5583471522212552754L; } /** * Comparator for vertices based on their topological ordering * * @author Peter Giles */ private class TopoComparator implements Comparator, Serializable { private static final long serialVersionUID = 8144905376266340066L; @Override public int compare(V o1, V o2) { return topoOrderMap .getTopologicalIndex(o1).compareTo(topoOrderMap.getTopologicalIndex(o2)); } } /** * An iterator which follows topological order * * @author Peter Giles */ private class TopoIterator implements Iterator { private int currentTopoIndex; private final long expectedTopoModCount = topoModCount; private Integer nextIndex = null; public TopoIterator() { currentTopoIndex = minTopoIndex - 1; } @Override public boolean hasNext() { if (expectedTopoModCount != topoModCount) { throw new ConcurrentModificationException(); } nextIndex = getNextIndex(); return nextIndex != null; } @Override public V next() { if (expectedTopoModCount != topoModCount) { throw new ConcurrentModificationException(); } if (nextIndex == null) { // find nextIndex nextIndex = getNextIndex(); } if (nextIndex == null) { throw new NoSuchElementException(); } currentTopoIndex = nextIndex; nextIndex = null; return topoOrderMap.getVertex(currentTopoIndex); } @Override public void remove() { if (expectedTopoModCount != topoModCount) { throw new ConcurrentModificationException(); } V vertexToRemove; if ((vertexToRemove = topoOrderMap.getVertex(currentTopoIndex)) != null) { topoOrderMap.removeVertex(vertexToRemove); } else { // should only happen if next() hasn't been called throw new IllegalStateException(); } } private Integer getNextIndex() { for (int i = currentTopoIndex + 1; i <= maxTopoIndex; i++) { if (topoOrderMap.getVertex(i) != null) { return i; } } return null; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedMultigraph.java000066400000000000000000000056371402514743400316240ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A directed multigraph. A directed multigraph is a non-simple directed graph in which no loops are * permitted, but multiple (parallel) edges between any two vertices are. * * @param the graph vertex type * @param the graph edge type */ public class DirectedMultigraph extends AbstractBaseGraph { private static final long serialVersionUID = 2919338637676573948L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public DirectedMultigraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass), false); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param weighted whether the graph is weighted or not */ public DirectedMultigraph( Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted) { super( vertexSupplier, edgeSupplier, new DefaultGraphType.Builder() .directed().allowMultipleEdges(true).allowSelfLoops(false).weighted(weighted) .build()); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new DirectedMultigraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier of the new graph * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new DirectedMultigraph<>(null, edgeSupplier, false)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedPseudograph.java000066400000000000000000000060651402514743400317650ustar00rootroot00000000000000/* * (C) Copyright 2004-2021, by Christian Hammer and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A directed pseudograph. A directed pseudograph is a non-simple directed graph in which both graph * loops and multiple (parallel) edges are permitted. If you're unsure about pseudographs, see: * * http://mathworld.wolfram.com/Pseudograph.html. * * @param the graph vertex type * @param the graph edge type * */ public class DirectedPseudograph extends AbstractBaseGraph { private static final long serialVersionUID = -7461248851245878913L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public DirectedPseudograph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass), false); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param weighted whether the graph is weighted or not */ public DirectedPseudograph( Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted) { super( vertexSupplier, edgeSupplier, new DefaultGraphType.Builder() .directed().allowMultipleEdges(true).allowSelfLoops(true).weighted(weighted) .build()); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new DirectedPseudograph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier of the new graph * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new DirectedPseudograph<>(null, edgeSupplier, false)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedMultigraph.java000066400000000000000000000054301402514743400332740ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A directed weighted multigraph. A directed weighted multigraph is a non-simple directed graph in * which no loops are permitted, but multiple (parallel) edges between any two vertices are * permitted, and edges have weights. * * @param the graph vertex type * @param the graph edge type */ public class DirectedWeightedMultigraph extends DirectedMultigraph { private static final long serialVersionUID = 1984381120642160572L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public DirectedWeightedMultigraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass)); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null */ public DirectedWeightedMultigraph(Supplier vertexSupplier, Supplier edgeSupplier) { super(vertexSupplier, edgeSupplier, true); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new DirectedWeightedMultigraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier of the new graph * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new DirectedWeightedMultigraph<>(null, edgeSupplier)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/DirectedWeightedPseudograph.java000066400000000000000000000054051402514743400334430ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A directed weighted pseudograph. A directed weighted pseudograph is a non-simple directed graph * in which both graph loops and multiple (parallel) edges are permitted, and edges have weights. * * @param the graph vertex type * @param the graph edge type * */ public class DirectedWeightedPseudograph extends DirectedPseudograph { private static final long serialVersionUID = -4775269773843490859L; /** * Creates a new weighted graph. * * @param edgeClass class on which to base the edge supplier */ public DirectedWeightedPseudograph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass)); } /** * Creates a new weighted graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null */ public DirectedWeightedPseudograph(Supplier vertexSupplier, Supplier edgeSupplier) { super(vertexSupplier, edgeSupplier, true); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new DirectedWeightedPseudograph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new DirectedWeightedPseudograph<>(null, edgeSupplier)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeReversedGraph.java000066400000000000000000000077661402514743400313770ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.util.*; /** * Provides an edge-reversed view $g'$ of a directed graph $g$. The vertex sets for the two graphs * are the same, but g' contains an edge $(v2, v1)$ iff g$$ contains an edge $(v1, v2)$. $g'$ is * backed by $g$, so changes to $g$ are reflected in $g'$, and vice versa. * *

    * This class allows you to use a directed graph algorithm in reverse. For example, suppose you have * a directed graph representing a tree, with edges from parent to child, and you want to find all * of the parents of a node. To do this, simply create an edge-reversed graph and pass that as input * to {@link org.jgrapht.traverse.DepthFirstIterator}. * * @param the graph vertex type * @param the graph edge type * * @author John V. Sichi * @see AsUndirectedGraph */ public class EdgeReversedGraph extends GraphDelegator implements Graph { private static final long serialVersionUID = -3806030402468293063L; /** * Creates a new EdgeReversedGraph. * * @param g the base (backing) graph on which the edge-reversed view will be based. */ public EdgeReversedGraph(Graph g) { super(g); } /** * @see Graph#getEdge(Object, Object) */ @Override public E getEdge(V sourceVertex, V targetVertex) { return super.getEdge(targetVertex, sourceVertex); } /** * @see Graph#getAllEdges(Object, Object) */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { return super.getAllEdges(targetVertex, sourceVertex); } /** * @see Graph#addEdge(Object, Object) */ @Override public E addEdge(V sourceVertex, V targetVertex) { return super.addEdge(targetVertex, sourceVertex); } /** * @see Graph#addEdge(Object, Object, Object) */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { return super.addEdge(targetVertex, sourceVertex, e); } /** * @see Graph#inDegreeOf(Object) */ @Override public int inDegreeOf(V vertex) { return super.outDegreeOf(vertex); } /** * @see Graph#outDegreeOf(Object) */ @Override public int outDegreeOf(V vertex) { return super.inDegreeOf(vertex); } /** * @see Graph#incomingEdgesOf(Object) */ @Override public Set incomingEdgesOf(V vertex) { return super.outgoingEdgesOf(vertex); } /** * @see Graph#outgoingEdgesOf(Object) */ @Override public Set outgoingEdgesOf(V vertex) { return super.incomingEdgesOf(vertex); } /** * @see Graph#removeEdge(Object, Object) */ @Override public E removeEdge(V sourceVertex, V targetVertex) { return super.removeEdge(targetVertex, sourceVertex); } /** * @see Graph#getEdgeSource(Object) */ @Override public V getEdgeSource(E e) { return super.getEdgeTarget(e); } /** * @see Graph#getEdgeTarget(Object) */ @Override public V getEdgeTarget(E e) { return super.getEdgeSource(e); } /** * @see java.lang.Object#toString() */ @Override public String toString() { return toStringFromSets(vertexSet(), edgeSet(), getType().isDirected()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/EdgeSetFactory.java000066400000000000000000000030661402514743400307060ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import java.util.*; /** * A factory for edge sets. This interface allows the creator of a graph to choose the * {@link java.util.Set} implementation used internally by the graph to maintain sets of edges. This * provides control over performance tradeoffs between memory and CPU usage. * * @param the graph vertex type * @param the graph edge type * * @author John V. Sichi */ public interface EdgeSetFactory { /** * Create a new edge set for a particular vertex. * * @param vertex the vertex for which the edge set is being created; sophisticated factories may * be able to use this information to choose an optimal set representation (e.g. * ArrayUnenforcedSet for a vertex expected to have low degree, and LinkedHashSet for a * vertex expected to have high degree) * * @return new set */ Set createEdgeSet(V vertex); } FastLookupGraphSpecificsStrategy.java000066400000000000000000000051111402514743400343750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.graph.specifics.*; import java.io.*; import java.util.*; import java.util.function.*; /** * The fast lookup specifics strategy implementation. * *

    * Graphs constructed using this strategy use additional data structures to improve the performance * of methods which depend on edge retrievals, e.g. getEdge(V u, V v), containsEdge(V u, V * v),addEdge(V u, V v). A disadvantage is an increase in memory consumption. If memory utilization * is an issue, use the {@link DefaultGraphSpecificsStrategy} instead. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class FastLookupGraphSpecificsStrategy implements GraphSpecificsStrategy { private static final long serialVersionUID = -5490869870275054280L; @Override public Function> getIntrusiveEdgesSpecificsFactory() { return (Function> & Serializable) (type) -> { if (type.isWeighted()) { return new WeightedIntrusiveEdgesSpecifics(new LinkedHashMap<>()); } else { return new UniformIntrusiveEdgesSpecifics<>(new LinkedHashMap<>()); } }; } @Override public BiFunction, GraphType, Specifics> getSpecificsFactory() { return (BiFunction, GraphType, Specifics> & Serializable) (graph, type) -> { if (type.isDirected()) { return new FastLookupDirectedSpecifics<>( graph, new LinkedHashMap<>(), new HashMap<>(), getEdgeSetFactory()); } else { return new FastLookupUndirectedSpecifics<>( graph, new LinkedHashMap<>(), new HashMap<>(), getEdgeSetFactory()); } }; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/GraphDelegator.java000066400000000000000000000175451402514743400307350ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.io.*; import java.util.*; import java.util.function.*; /** * A graph backed by the the graph specified at the constructor, which delegates all its methods to * the backing graph. Operations on this graph "pass through" to the to the backing graph. Any * modification made to this graph or the backing graph is reflected by the other. * *

    * This graph does not pass the hashCode and equals operations through to the backing graph, * but relies on Object's equals and hashCode methods. *

    * *

    * This class is mostly used as a base for extending subclasses. It can also be used in order to * override the vertex and edge supplier of a graph. *

    * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public class GraphDelegator extends AbstractGraph implements Graph, Serializable { private static final long serialVersionUID = -215068279981825448L; /* * The graph to which operations are delegated. */ private final Graph delegate; private final Supplier vertexSupplier; private final Supplier edgeSupplier; /** * Constructor * * @param graph the backing graph (the delegate). */ public GraphDelegator(Graph graph) { this(graph, null, null); } /** * * @param graph the backing graph (the delegate). * @param vertexSupplier vertex supplier for the delegator. Can be null in which case the * backing graph vertex supplier will be used. * @param edgeSupplier edge supplier for the delegator. Can be null in which case the backing * graph edge supplier will be used. */ public GraphDelegator(Graph graph, Supplier vertexSupplier, Supplier edgeSupplier) { super(); this.delegate = Objects.requireNonNull(graph, "graph must not be null"); this.vertexSupplier = vertexSupplier; this.edgeSupplier = edgeSupplier; } /** * {@inheritDoc} * *

    * Returns the delegator's vertex supplier or the backing graph's vertex supplier in case of * null. */ @Override public Supplier getVertexSupplier() { if (vertexSupplier != null) { return vertexSupplier; } else { return delegate.getVertexSupplier(); } } /** * {@inheritDoc} * *

    * Returns the delegator's edge supplier or the backing graph's edge supplier in case of null. */ @Override public Supplier getEdgeSupplier() { if (edgeSupplier != null) { return edgeSupplier; } else { return delegate.getEdgeSupplier(); } } /** * {@inheritDoc} */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { return delegate.getAllEdges(sourceVertex, targetVertex); } /** * {@inheritDoc} */ @Override public E getEdge(V sourceVertex, V targetVertex) { return delegate.getEdge(sourceVertex, targetVertex); } /** * {@inheritDoc} */ @Override public E addEdge(V sourceVertex, V targetVertex) { /* * Use our own edge supplier, if provided. */ if (edgeSupplier != null) { E e = edgeSupplier.get(); return this.addEdge(sourceVertex, targetVertex, e) ? e : null; } return delegate.addEdge(sourceVertex, targetVertex); } /** * {@inheritDoc} */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { return delegate.addEdge(sourceVertex, targetVertex, e); } /** * {@inheritDoc} */ @Override public V addVertex() { /* * Use our own vertex supplier, if provided. */ if (vertexSupplier != null) { V v = vertexSupplier.get(); return this.addVertex(v) ? v : null; } return delegate.addVertex(); } /** * {@inheritDoc} */ @Override public boolean addVertex(V v) { return delegate.addVertex(v); } /** * {@inheritDoc} */ @Override public boolean containsEdge(E e) { return delegate.containsEdge(e); } /** * {@inheritDoc} */ @Override public boolean containsVertex(V v) { return delegate.containsVertex(v); } /** * Returns the degree of the specified vertex. * * @param vertex vertex whose degree is to be calculated * @return the degree of the specified vertex */ public int degreeOf(V vertex) { return delegate.degreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set edgeSet() { return delegate.edgeSet(); } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { return delegate.edgesOf(vertex); } /** * {@inheritDoc} */ @Override public int inDegreeOf(V vertex) { return delegate.inDegreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { return delegate.incomingEdgesOf(vertex); } /** * {@inheritDoc} */ @Override public int outDegreeOf(V vertex) { return delegate.outDegreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { return delegate.outgoingEdgesOf(vertex); } /** * {@inheritDoc} */ @Override public boolean removeEdge(E e) { return delegate.removeEdge(e); } /** * {@inheritDoc} */ @Override public E removeEdge(V sourceVertex, V targetVertex) { return delegate.removeEdge(sourceVertex, targetVertex); } /** * {@inheritDoc} */ @Override public boolean removeVertex(V v) { return delegate.removeVertex(v); } /** * {@inheritDoc} */ @Override public String toString() { return delegate.toString(); } /** * {@inheritDoc} */ @Override public Set vertexSet() { return delegate.vertexSet(); } /** * {@inheritDoc} */ @Override public V getEdgeSource(E e) { return delegate.getEdgeSource(e); } /** * {@inheritDoc} */ @Override public V getEdgeTarget(E e) { return delegate.getEdgeTarget(e); } /** * {@inheritDoc} */ @Override public double getEdgeWeight(E e) { return delegate.getEdgeWeight(e); } /** * {@inheritDoc} */ @Override public void setEdgeWeight(E e, double weight) { delegate.setEdgeWeight(e, weight); } /** * {@inheritDoc} */ @Override public GraphType getType() { return delegate.getType(); } /** * Return the backing graph (the delegate). * * @return the backing graph (the delegate) */ protected Graph getDelegate() { return delegate; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/GraphSpecificsStrategy.java000066400000000000000000000041241402514743400324470ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.graph.specifics.*; import java.io.*; import java.util.function.*; /** * A graph specifics construction factory. * *

    * Such a strategy can be used to adjust the internals of the default graph implementations. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type * * @see FastLookupGraphSpecificsStrategy * @see DefaultGraphSpecificsStrategy */ public interface GraphSpecificsStrategy extends Serializable { /** * Get a function which creates the intrusive edges specifics. The factory will accept the graph * type as a parameter. * *

    * Note that it is very important to use a map implementation which respects iteration order. * * @return a function which creates intrusive edges specifics. */ Function> getIntrusiveEdgesSpecificsFactory(); /** * Get a function which creates the specifics. The factory will accept the graph type as a * parameter. * * @return a function which creates intrusive edges specifics. */ BiFunction, GraphType, Specifics> getSpecificsFactory(); /** * Get an edge set factory. * * @return an edge set factory */ default EdgeSetFactory getEdgeSetFactory() { return new ArrayUnenforcedSetEdgeSetFactory<>(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/GraphWalk.java000066400000000000000000000471501402514743400277200ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.io.*; import java.util.*; import java.util.function.*; /** * A walk in a graph is an alternating sequence of vertices and edges, starting and ending at a * vertex, in which each edge is adjacent in the sequence to its two endpoints. More precisely, a * walk is a connected sequence of vertices and edges in a graph $v_0, e_0, v_1, e_1, v_2, \dotso, * v_{k-1}, e_{k-1}, v_{k}$, such that for $1 \leq i \leq k$, the edge $e_i$ has endpoints $v_{i-1}$ * and $v_i$. The class makes no assumptions with respect to the shape of the walk: edges may be * repeated, and the start and end point of the walk may be different. * *

    * See http://mathworld.wolfram.com/Walk.html * *

    * GraphWalk is the default implementation of {@link GraphPath}. * *

    * Two special cases exist: *

      *
    1. A singleton GraphWalk has an empty edge list (the length of the path equals 0), the vertex * list contains a single vertex v, and the start and end vertex equal v.
    2. *
    3. An empty Graphwalk has empty edge and vertex lists, and the start and end vertex are both * null.
    4. *
    * *

    * This class is implemented as a light-weight data structure; this class does not verify whether * the sequence of edges or the sequence of vertices provided during construction forms an actual * walk. It is the responsibility of the invoking class to provide correct input data. * *

    * Note: Serialization of a GraphWalk implies the serialization of the entire underlying graph. *

    * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable * */ public class GraphWalk implements GraphPath, Serializable { private static final long serialVersionUID = 7663410644865380676L; protected Graph graph; protected List vertexList; protected List edgeList; protected V startVertex; protected V endVertex; protected double weight; /** * Creates a walk defined by a sequence of edges. A walk defined by its edges can be specified * for non-simple graphs. Edge repetition is permitted, the start and end point points ($v_0$ * and $v_k$) can be different. * * @param graph the graph * @param startVertex the starting vertex * @param endVertex the last vertex of the path * @param edgeList the list of edges of the path * @param weight the total weight of the path */ public GraphWalk(Graph graph, V startVertex, V endVertex, List edgeList, double weight) { this(graph, startVertex, endVertex, null, edgeList, weight); } /** * Creates a walk defined by a sequence of vertices. Note that the input graph must be simple, * otherwise the vertex sequence does not necessarily define a unique path. Furthermore, all * vertices must be pairwise adjacent. * * @param graph the graph * @param vertexList the list of vertices of the path * @param weight the total weight of the path */ public GraphWalk(Graph graph, List vertexList, double weight) { this( graph, (vertexList.isEmpty() ? null : vertexList.get(0)), (vertexList.isEmpty() ? null : vertexList.get(vertexList.size() - 1)), vertexList, null, weight); } /** * Creates a walk defined by both a sequence of edges and a sequence of vertices. Note that both * the sequence of edges and the sequence of vertices must describe the same path! This is not * verified during the construction of the walk. This constructor makes it possible to store * both a vertex and an edge view of the same walk, thereby saving computational overhead when * switching from one to the other. * * @param graph the graph * @param startVertex the starting vertex * @param endVertex the last vertex of the path * @param vertexList the list of vertices of the path * @param edgeList the list of edges of the path * @param weight the total weight of the path */ public GraphWalk( Graph graph, V startVertex, V endVertex, List vertexList, List edgeList, double weight) { // Some necessary but not sufficient conditions for valid paths if (vertexList == null && edgeList == null) throw new IllegalArgumentException("Vertex list and edge list cannot both be null!"); if (startVertex != null && vertexList != null && edgeList != null && edgeList.size() + 1 != vertexList.size()) throw new IllegalArgumentException( "VertexList and edgeList do not correspond to the same path (cardinality of vertexList +1 must equal the cardinality of the edgeList)"); if (startVertex == null ^ endVertex == null) throw new IllegalArgumentException( "Either the start and end vertices must both be null, or they must both be not null (one of them is null)"); this.graph = Objects.requireNonNull(graph); this.startVertex = startVertex; this.endVertex = endVertex; this.vertexList = vertexList; this.edgeList = edgeList; this.weight = weight; } @Override public Graph getGraph() { return graph; } @Override public V getStartVertex() { return startVertex; } @Override public V getEndVertex() { return endVertex; } @Override public List getEdgeList() { return (edgeList != null ? edgeList : GraphPath.super.getEdgeList()); } @Override public List getVertexList() { return (vertexList != null ? vertexList : GraphPath.super.getVertexList()); } @Override public double getWeight() { return weight; } /** * Updates the weight of this walk * * @param weight weight of the walk */ public void setWeight(double weight) { this.weight = weight; } @Override public int getLength() { if (edgeList != null) return edgeList.size(); else if (vertexList != null && !vertexList.isEmpty()) return vertexList.size() - 1; else return 0; } @Override public String toString() { if (vertexList != null) return vertexList.toString(); else return edgeList.toString(); } @Override public boolean equals(Object o) { if (o == null || !(o instanceof GraphWalk)) return false; else if (this == o) return true; @SuppressWarnings("unchecked") GraphWalk other = (GraphWalk) o; if (this.isEmpty() && other.isEmpty()) return true; if (this.isEmpty()) return false; if (!this.startVertex.equals(other.getStartVertex()) || !this.endVertex.equals(other.getEndVertex())) return false; // If this path is expressed as a vertex list, we may get away by comparing the other path's // vertex list // This only works if its vertexList identifies a unique path in the graph if (this.edgeList == null && !other.getGraph().getType().isAllowingMultipleEdges()) return this.vertexList.equals(other.getVertexList()); else // Unlucky, we need to compare the edge lists, return this.getEdgeList().equals(other.getEdgeList()); } @Override public int hashCode() { int hashCode = 1; if (isEmpty()) return hashCode; hashCode = 31 * hashCode + startVertex.hashCode(); hashCode = 31 * hashCode + endVertex.hashCode(); if (edgeList != null) return 31 * hashCode + edgeList.hashCode(); else return 31 * hashCode + vertexList.hashCode(); } /** * Reverses the direction of the walk. In case of directed/mixed graphs, the arc directions will * be reversed. An exception is thrown if reversing an arc $(u,v)$ is impossible because arc * $(v,u)$ is not present in the graph. The weight of the resulting walk equals the sum of edge * weights in the walk. * * @throws InvalidGraphWalkException if the path is invalid * @return a reversed GraphWalk */ public GraphWalk reverse() { return this.reverse(null); } /** * Reverses the direction of the walk. In case of directed/mixed graphs, the arc directions will * be reversed. An exception is thrown if reversing an arc $(u,v)$ is impossible because arc * $(v,u)$ is not present in the graph. * * @param walkWeightCalculator Function used to calculate the weight of the reversed GraphWalk * @throws InvalidGraphWalkException if the path is invalid * @return a reversed GraphWalk */ public GraphWalk reverse(Function, Double> walkWeightCalculator) { List revVertexList = null; List revEdgeList = null; double revWeight = 0; if (vertexList != null) { revVertexList = new ArrayList<>(this.vertexList); Collections.reverse(revVertexList); if (graph.getType().isUndirected()) revWeight = this.weight; // Check validity of the path. If the path is invalid, then calculating its weight may // result in an undefined exception. // If an edgeList is provided, then this check can be postponed to the construction of // the reversed edge list if (!graph.getType().isUndirected() && edgeList == null) { for (int i = 0; i < revVertexList.size() - 1; i++) { V u = revVertexList.get(i); V v = revVertexList.get(i + 1); E edge = graph.getEdge(u, v); if (edge == null) throw new InvalidGraphWalkException( "this walk cannot be reversed. The graph does not contain a reverse arc for arc " + graph.getEdge(v, u)); else revWeight += graph.getEdgeWeight(edge); } } } if (edgeList != null) { revEdgeList = new ArrayList<>(this.edgeList.size()); if (graph.getType().isUndirected()) { revEdgeList.addAll(this.edgeList); Collections.reverse(revEdgeList); revWeight = this.weight; } else { ListIterator listIterator = this.edgeList.listIterator(edgeList.size()); while (listIterator.hasPrevious()) { E e = listIterator.previous(); V u = graph.getEdgeSource(e); V v = graph.getEdgeTarget(e); E revEdge = graph.getEdge(v, u); if (revEdge == null) throw new InvalidGraphWalkException( "this walk cannot be reversed. The graph does not contain a reverse arc for arc " + e); revEdgeList.add(revEdge); revWeight += graph.getEdgeWeight(revEdge); } } } // Update weight of reversed walk GraphWalk gw = new GraphWalk<>( this.graph, this.endVertex, this.startVertex, revVertexList, revEdgeList, 0); if (walkWeightCalculator == null) gw.weight = revWeight; else gw.weight = walkWeightCalculator.apply(gw); return gw; } /** * Concatenates the specified GraphWalk to the end of this GraphWalk. This action can only be * performed if the end vertex of this GraphWalk is the same as the start vertex of the * extending GraphWalk * * @param extension GraphPath used for the concatenation. * @param walkWeightCalculator Function used to calculate the weight of the GraphWalk obtained * after the concatenation. * @return a GraphWalk that represents the concatenation of this object's walk followed by the * walk specified in the extension argument. */ public GraphWalk concat( GraphWalk extension, Function, Double> walkWeightCalculator) { if (this.isEmpty()) throw new IllegalArgumentException("An empty path cannot be extended"); if (!this.endVertex.equals(extension.getStartVertex())) throw new IllegalArgumentException( "This path can only be extended by another path if the end vertex of the orginal path and the start vertex of the extension are equal."); List concatVertexList = null; List concatEdgeList = null; if (vertexList != null) { concatVertexList = new ArrayList<>(this.vertexList); List vertexListExtension = extension.getVertexList(); concatVertexList.addAll(vertexListExtension.subList(1, vertexListExtension.size())); } if (edgeList != null) { concatEdgeList = new ArrayList<>(this.edgeList); concatEdgeList.addAll(extension.getEdgeList()); } GraphWalk gw = new GraphWalk<>( this.graph, startVertex, extension.getEndVertex(), concatVertexList, concatEdgeList, 0); gw.setWeight(walkWeightCalculator.apply(gw)); return gw; } /** * Returns true if the path is an empty path, that is, a path with startVertex=endVertex=null * and with an empty vertex and edge list. * * @return Returns true if the path is an empty path. */ public boolean isEmpty() { return startVertex == null; } /** * Convenience method which verifies whether the given path is feasible wrt the input graph and * forms an actual path. * * @throws InvalidGraphWalkException if the path is invalid */ public void verify() { if (isEmpty()) // Empty path return; if (vertexList != null && !vertexList.isEmpty()) { if (!startVertex.equals(vertexList.get(0))) throw new InvalidGraphWalkException( "The start vertex must be the first vertex in the vertex list"); if (!endVertex.equals(vertexList.get(vertexList.size() - 1))) throw new InvalidGraphWalkException( "The end vertex must be the last vertex in the vertex list"); // All vertices and edges in the path must be contained in the graph if (!graph.vertexSet().containsAll(vertexList)) throw new InvalidGraphWalkException( "Not all vertices in the path are contained in the graph"); if (edgeList == null) { // Verify sequence Iterator it = vertexList.iterator(); V u = it.next(); while (it.hasNext()) { V v = it.next(); if (graph.getEdge(u, v) == null) throw new InvalidGraphWalkException( "The vertexList does not constitute to a feasible path. Edge (" + u + "," + v + " does not exist in the graph."); u = v; } } } if (edgeList != null && !edgeList.isEmpty()) { if (!Graphs.testIncidence(graph, edgeList.get(0), startVertex)) throw new InvalidGraphWalkException( "The first edge in the edge list must leave the start vertex"); if (!graph.edgeSet().containsAll(edgeList)) throw new InvalidGraphWalkException( "Not all edges in the path are contained in the graph"); if (vertexList == null) { V u = startVertex; for (E edge : edgeList) { if (!Graphs.testIncidence(graph, edge, u)) throw new InvalidGraphWalkException( "The edgeList does not constitute to a feasible path. Conflicting edge: " + edge); u = Graphs.getOppositeVertex(graph, edge, u); } if (!u.equals(endVertex)) throw new InvalidGraphWalkException( "The path defined by the edgeList does not end in the endVertex."); } } if (vertexList != null && edgeList != null) { // Verify that the path is an actual path in the graph if (edgeList.size() + 1 != vertexList.size()) throw new InvalidGraphWalkException( "VertexList and edgeList do not correspond to the same path (cardinality of vertexList +1 must equal the cardinality of the edgeList)"); for (int i = 0; i < vertexList.size() - 1; i++) { V u = vertexList.get(i); V v = vertexList.get(i + 1); E edge = getEdgeList().get(i); if (graph.getType().isDirected()) { // Directed graph if (!graph.getEdgeSource(edge).equals(u) || !graph.getEdgeTarget(edge).equals(v)) throw new InvalidGraphWalkException( "VertexList and edgeList do not form a feasible path"); } else { // Undirected or mixed if (!Graphs.testIncidence(graph, edge, u) || !Graphs.getOppositeVertex(graph, edge, u).equals(v)) throw new InvalidGraphWalkException( "VertexList and edgeList do not form a feasible path"); } } } } /** * Convenience method which creates an empty walk. * * @param graph input graph * @param vertex type * @param edge type * @return an empty walk */ public static GraphWalk emptyWalk(Graph graph) { return new GraphWalk<>( graph, null, null, Collections.emptyList(), Collections.emptyList(), 0.0); } /** * Convenience method which creates a walk consisting of a single vertex with weight 0.0. * * @param graph input graph * @param v single vertex * @param vertex type * @param edge type * @return an empty walk */ public static GraphWalk singletonWalk(Graph graph, V v) { return singletonWalk(graph, v, 0d); } /** * Convenience method which creates a walk consisting of a single vertex. * * @param graph input graph * @param v single vertex * @param weight weight of the path * @param vertex type * @param edge type * @return an empty walk */ public static GraphWalk singletonWalk(Graph graph, V v, double weight) { return new GraphWalk<>( graph, v, v, Collections.singletonList(v), Collections.emptyList(), weight); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdge.java000066400000000000000000000025511402514743400306110ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import java.io.*; /** * IntrusiveEdge encapsulates the internals for the default edge implementation. It is not intended * to be referenced directly (which is why it's not public); use DefaultEdge for that. * * @author John V. Sichi */ class IntrusiveEdge implements Cloneable, Serializable { private static final long serialVersionUID = 3258408452177932855L; Object source; Object target; /** * @see Object#clone() */ @Override public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { // shouldn't happen as we are Cloneable throw new InternalError(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdgeException.java000066400000000000000000000021351402514743400324660ustar00rootroot00000000000000/* * (C) Copyright 2021-2021, by Hannes Wellmann and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; /** * Special {@link RuntimeException} to signal that {@link IntrusiveEdge} is used incorrectly. * * @author Hannes Wellmann */ public class IntrusiveEdgeException extends RuntimeException { private static final long serialVersionUID = 7261763645809925025L; public IntrusiveEdgeException(V source, V target) { super("Edge already associated with source <" + source + "> and target <" + target + ">"); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveEdgesSpecifics.java000066400000000000000000000045651402514743400326340ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import java.io.*; import java.util.*; /** * An interface for the set of intrusive edges of a graph. * *

    * Since the library supports edges which can be any user defined object, we need to provide * explicit support for storing vertex source, target and weight. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public interface IntrusiveEdgesSpecifics extends Serializable { /** * Get the source vertex of an edge. * * @param e the edge * @return the source vertex */ V getEdgeSource(E e); /** * Get the target vertex of an edge. * * @param e the edge * @return the target vertex */ V getEdgeTarget(E e); /** * Add a new edge. * * @param e the edge to add * @param sourceVertex the source vertex * @param targetVertex the target vertex * @return true if the edge was added, false if the edge was already present */ boolean add(E e, V sourceVertex, V targetVertex); /** * Check if an edge exists * * @param e the input edge * @return true if an edge exists, false otherwise */ boolean containsEdge(E e); /** * Get the edge set * * @return the edge set */ Set getEdgeSet(); /** * Remove an edge. * * @param e the edge to remove. */ void remove(E e); /** * Get the weight of an edge. * * @param e the edge * @return the edge weight */ double getEdgeWeight(E e); /** * Set the edge weight * * @param e the edge * @param weight the new weight */ void setEdgeWeight(E e, double weight); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/IntrusiveWeightedEdge.java000066400000000000000000000022301402514743400322640ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; /** * IntrusiveEdge extension for weighted edges. IntrusiveWeightedEdge encapsulates the internals for * the default weighted edge implementation. It is not intended to be referenced directly (which is * why it's not public); use DefaultWeightedEdge for that. * * @author Dimitrios Michail */ class IntrusiveWeightedEdge extends IntrusiveEdge { private static final long serialVersionUID = 2890534758523920741L; double weight = Graph.DEFAULT_EDGE_WEIGHT; } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/InvalidGraphWalkException.java000066400000000000000000000017151402514743400331030ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; /** * Exception thrown in the event that the path is invalid. */ public class InvalidGraphWalkException extends RuntimeException { private static final long serialVersionUID = 3811666107707436479L; public InvalidGraphWalkException(String message) { super(message); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/MaskEdgeSet.java000066400000000000000000000047761402514743400302030ustar00rootroot00000000000000/* * (C) Copyright 2007-2021, by France Telecom and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Helper for {@link MaskSubgraph}. * */ class MaskEdgeSet extends AbstractSet implements Serializable { private static final long serialVersionUID = 4208908842850100708L; private final Graph graph; private final Set edgeSet; private final Predicate vertexMask; private final Predicate edgeMask; public MaskEdgeSet( Graph graph, Set edgeSet, Predicate vertexMask, Predicate edgeMask) { this.graph = graph; this.edgeSet = edgeSet; this.vertexMask = vertexMask; this.edgeMask = edgeMask; } /** * {@inheritDoc} */ @Override public boolean contains(Object o) { if (!edgeSet.contains(o)) { return false; } E e = TypeUtil.uncheckedCast(o); return !edgeMask.test(e) && !vertexMask.test(graph.getEdgeSource(e)) && !vertexMask.test(graph.getEdgeTarget(e)); } /** * {@inheritDoc} */ @Override public Iterator iterator() { return edgeSet .stream() .filter( e -> !edgeMask.test(e) && !vertexMask.test(graph.getEdgeSource(e)) && !vertexMask.test(graph.getEdgeTarget(e))) .iterator(); } /** * {@inheritDoc} */ @Override public int size() { return (int) edgeSet .stream() .filter( e -> !edgeMask.test(e) && !vertexMask.test(graph.getEdgeSource(e)) && !vertexMask.test(graph.getEdgeTarget(e))) .count(); } /** * {@inheritDoc} */ @Override public boolean isEmpty() { return !iterator().hasNext(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/MaskSubgraph.java000066400000000000000000000213121402514743400304170ustar00rootroot00000000000000/* * (C) Copyright 2007-2021, by France Telecom and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.io.*; import java.util.*; import java.util.function.*; /** * An unmodifiable subgraph induced by a vertex/edge masking function. The subgraph will keep track * of edges being added to its vertex subset as well as deletion of edges and vertices. When * iterating over the vertices/edges, it will iterate over the vertices/edges of the base graph and * discard vertices/edges that are masked (an edge with a masked extremity vertex is discarded as * well). * * @param the graph vertex type * @param the graph edge type * */ public class MaskSubgraph extends AbstractGraph implements Serializable { private static final long serialVersionUID = -7397441126669119179L; private static final String UNMODIFIABLE = "this graph is unmodifiable"; protected final Graph base; protected final GraphType baseType; protected final Set edges; protected final Set vertices; protected final Predicate vertexMask; protected final Predicate edgeMask; /** * Creates a new induced subgraph. Running-time = O(1). * * @param base the base (backing) graph on which the subgraph will be based. * @param vertexMask vertices to exclude in the subgraph. If a vertex is masked, it is as if it * is not in the subgraph. Edges incident to the masked vertex are also masked. * @param edgeMask edges to exclude in the subgraph. If an edge is masked, it is as if it is not * in the subgraph. */ public MaskSubgraph(Graph base, Predicate vertexMask, Predicate edgeMask) { super(); this.base = Objects.requireNonNull(base, "Invalid graph provided"); this.baseType = base.getType(); this.vertexMask = Objects.requireNonNull(vertexMask, "Invalid vertex mask provided"); this.edgeMask = Objects.requireNonNull(edgeMask, "Invalid edge mask provided"); this.vertices = new MaskVertexSet<>(base.vertexSet(), vertexMask); this.edges = new MaskEdgeSet<>(base, base.edgeSet(), vertexMask, edgeMask); } /** * {@inheritDoc} */ @Override public E addEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E edge) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public V addVertex() { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean addVertex(V v) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean containsEdge(E e) { return edgeSet().contains(e); } /** * {@inheritDoc} */ @Override public boolean containsVertex(V v) { return vertexSet().contains(v); } /** * {@inheritDoc} */ @Override public Set edgeSet() { return edges; } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { assertVertexExist(vertex); return new MaskEdgeSet<>(base, base.edgesOf(vertex), vertexMask, edgeMask); } /** * {@inheritDoc} * *

    * By default this method returns the sum of in-degree and out-degree. The exact value returned * depends on the type of the underlying graph. */ @Override public int degreeOf(V vertex) { if (baseType.isDirected()) { return inDegreeOf(vertex) + outDegreeOf(vertex); } else { int degree = 0; Iterator it = edgesOf(vertex).iterator(); while (it.hasNext()) { E e = it.next(); degree++; if (getEdgeSource(e).equals(getEdgeTarget(e))) { degree++; } } return degree; } } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { assertVertexExist(vertex); return new MaskEdgeSet<>(base, base.incomingEdgesOf(vertex), vertexMask, edgeMask); } /** * {@inheritDoc} */ @Override public int inDegreeOf(V vertex) { if (baseType.isUndirected()) { return degreeOf(vertex); } else { return incomingEdgesOf(vertex).size(); } } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { assertVertexExist(vertex); return new MaskEdgeSet<>(base, base.outgoingEdgesOf(vertex), vertexMask, edgeMask); } /** * {@inheritDoc} */ @Override public int outDegreeOf(V vertex) { if (baseType.isUndirected()) { return degreeOf(vertex); } else { return outgoingEdgesOf(vertex).size(); } } /** * {@inheritDoc} */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { if (containsVertex(sourceVertex) && containsVertex(targetVertex)) { return new MaskEdgeSet<>( base, base.getAllEdges(sourceVertex, targetVertex), vertexMask, edgeMask); } else return null; } /** * {@inheritDoc} */ @Override public E getEdge(V sourceVertex, V targetVertex) { Set edges = getAllEdges(sourceVertex, targetVertex); if (edges == null) { return null; } else { return edges.stream().findAny().orElse(null); } } /** * {@inheritDoc} */ @Override public Supplier getVertexSupplier() { return base.getVertexSupplier(); } /** * {@inheritDoc} */ @Override public Supplier getEdgeSupplier() { return base.getEdgeSupplier(); } /** * {@inheritDoc} */ @Override public V getEdgeSource(E edge) { assert (edgeSet().contains(edge)); return base.getEdgeSource(edge); } /** * {@inheritDoc} */ @Override public V getEdgeTarget(E edge) { assert (edgeSet().contains(edge)); return base.getEdgeTarget(edge); } /** * {@inheritDoc} */ @Override public GraphType getType() { return baseType.asUnmodifiable(); } /** * {@inheritDoc} */ @Override public double getEdgeWeight(E edge) { assert (edgeSet().contains(edge)); return base.getEdgeWeight(edge); } /** * {@inheritDoc} */ @Override public void setEdgeWeight(E edge, double weight) { assert (edgeSet().contains(edge)); base.setEdgeWeight(edge, weight); } /** * {@inheritDoc} */ @Override public boolean removeAllEdges(Collection edges) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public Set removeAllEdges(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean removeAllVertices(Collection vertices) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean removeEdge(E e) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public E removeEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean removeVertex(V v) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public Set vertexSet() { return vertices; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/MaskVertexSet.java000066400000000000000000000035351402514743400306040ustar00rootroot00000000000000/* * (C) Copyright 2007-2021, by France Telecom and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Helper for {@link MaskSubgraph}. * */ class MaskVertexSet extends AbstractSet implements Serializable { private static final long serialVersionUID = 3751931017141472763L; private final Set vertexSet; private final Predicate mask; public MaskVertexSet(Set vertexSet, Predicate mask) { this.vertexSet = vertexSet; this.mask = mask; } /** * {@inheritDoc} */ @Override public boolean contains(Object o) { if (!vertexSet.contains(o)) { return false; } V v = TypeUtil.uncheckedCast(o); return !mask.test(v); } /** * {@inheritDoc} */ @Override public Iterator iterator() { return vertexSet.stream().filter(mask.negate()).iterator(); } /** * {@inheritDoc} */ @Override public int size() { return (int) vertexSet.stream().filter(mask.negate()).count(); } /** * {@inheritDoc} */ @Override public boolean isEmpty() { return !iterator().hasNext(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/Multigraph.java000066400000000000000000000057541402514743400301600ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A multigraph. A multigraph is a non-simple undirected graph in which no loops are permitted, but * multiple (parallel) edges between any two vertices are. If you're unsure about multigraphs, see: * * http://mathworld.wolfram.com/Multigraph.html. * * @param the graph vertex type * @param the graph edge type * */ public class Multigraph extends AbstractBaseGraph { private static final long serialVersionUID = -8313058939737164595L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public Multigraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass), false); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param weighted whether the graph is weighted or not */ public Multigraph(Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted) { super( vertexSupplier, edgeSupplier, new DefaultGraphType.Builder() .undirected().allowMultipleEdges(true).allowSelfLoops(false).weighted(weighted) .build()); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new Multigraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier of the new graph * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder(Supplier edgeSupplier) { return new GraphBuilder<>(new Multigraph<>(null, edgeSupplier, false)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/ParanoidGraph.java000066400000000000000000000044611402514743400305550ustar00rootroot00000000000000/* * (C) Copyright 2007-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import java.util.*; /** * ParanoidGraph provides a way to verify that objects added to a graph obey the standard * equals/hashCode contract. It can be used to wrap an underlying graph to be verified. Note that * the verification is very expensive, so ParanoidGraph should only be used during debugging. * * @param the graph vertex type * @param the graph edge type * * @author John Sichi */ public class ParanoidGraph extends GraphDelegator { private static final long serialVersionUID = 5075284167422166539L; /** * Create a new paranoid graph. * * @param g the underlying wrapped graph */ public ParanoidGraph(Graph g) { super(g); } /** * {@inheritDoc} */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { verifyAdd(edgeSet(), e); return super.addEdge(sourceVertex, targetVertex, e); } /** * {@inheritDoc} */ @Override public boolean addVertex(V v) { verifyAdd(vertexSet(), v); return super.addVertex(v); } private static void verifyAdd(Set set, T t) { for (T o : set) { if (o == t) { continue; } if (o.equals(t) && (o.hashCode() != t.hashCode())) { throw new IllegalArgumentException( "ParanoidGraph detected objects " + "o1 (hashCode=" + o.hashCode() + ") and o2 (hashCode=" + t.hashCode() + ") where o1.equals(o2) " + "but o1.hashCode() != o2.hashCode()"); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/Pseudograph.java000066400000000000000000000057361402514743400303250ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A pseudograph. A pseudograph is a non-simple undirected graph in which both graph loops and * multiple (parallel) edges are permitted. If you're unsure about pseudographs, see: * * http://mathworld.wolfram.com/Pseudograph.html. * * @param the graph vertex type * @param the graph edge type */ public class Pseudograph extends AbstractBaseGraph { private static final long serialVersionUID = -7574564204896552581L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public Pseudograph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass), false); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param weighted whether the graph is weighted or not */ public Pseudograph(Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted) { super( vertexSupplier, edgeSupplier, new DefaultGraphType.Builder() .undirected().allowMultipleEdges(true).allowSelfLoops(true).weighted(weighted) .build()); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new Pseudograph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier of the new graph * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder(Supplier edgeSupplier) { return new GraphBuilder<>(new Pseudograph<>(null, edgeSupplier, false)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedGraph.java000066400000000000000000000056401402514743400317150ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A simple directed graph. A simple directed graph is a directed graph in which neither multiple * (parallel) edges between any two vertices nor loops are permitted. * * @param the graph vertex type * @param the graph edge type */ public class SimpleDirectedGraph extends AbstractBaseGraph { private static final long serialVersionUID = 1665314455034181409L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public SimpleDirectedGraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass), false); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param weighted whether the graph is weighted or not */ public SimpleDirectedGraph( Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted) { super( vertexSupplier, edgeSupplier, new DefaultGraphType.Builder() .directed().allowMultipleEdges(false).allowSelfLoops(false).weighted(weighted) .build()); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new SimpleDirectedGraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier of the new graph * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new SimpleDirectedGraph<>(null, edgeSupplier, false)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleDirectedWeightedGraph.java000066400000000000000000000052641402514743400334000ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A simple directed weighted graph. A simple directed weighted graph is a simple directed graph for * which edges are assigned weights. * * @param the graph vertex type * @param the graph edge type */ public class SimpleDirectedWeightedGraph extends SimpleDirectedGraph { private static final long serialVersionUID = -3301373580757772501L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public SimpleDirectedWeightedGraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass)); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null */ public SimpleDirectedWeightedGraph(Supplier vertexSupplier, Supplier edgeSupplier) { super(vertexSupplier, edgeSupplier, true); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new SimpleDirectedWeightedGraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new SimpleDirectedWeightedGraph<>(null, edgeSupplier)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleGraph.java000066400000000000000000000061131402514743400302450ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * Implementation of a Simple Graph. A * Simple Graph is an undirected graph containing no * graph loops or * multiple edges. This particular * implementation supports both weighted and unweighted edges. * * @param the graph vertex type * @param the graph edge type * */ public class SimpleGraph extends AbstractBaseGraph { private static final long serialVersionUID = 4607246833824317836L; /** * Creates a new simple graph. * * @param edgeClass class on which to base the edge supplier */ public SimpleGraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass), false); } /** * Creates a new simple graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param weighted whether the graph is weighted or not */ public SimpleGraph(Supplier vertexSupplier, Supplier edgeSupplier, boolean weighted) { super( vertexSupplier, edgeSupplier, new DefaultGraphType.Builder() .undirected().allowMultipleEdges(false).allowSelfLoops(false).weighted(weighted) .build()); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new SimpleGraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier of the new graph * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder(Supplier edgeSupplier) { return new GraphBuilder<>(new SimpleGraph<>(null, edgeSupplier, false)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/SimpleWeightedGraph.java000066400000000000000000000050161402514743400317270ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A simple weighted graph. * * @param the graph vertex type * @param the graph edge type */ public class SimpleWeightedGraph extends SimpleGraph { private static final long serialVersionUID = -1568410577378365671L; /** * Creates a new simple weighted graph. * * @param edgeClass class on which to base the edge supplier */ public SimpleWeightedGraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass)); } /** * Creates a new simple weighted graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null */ public SimpleWeightedGraph(Supplier vertexSupplier, Supplier edgeSupplier) { super(vertexSupplier, edgeSupplier, true); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new SimpleWeightedGraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new SimpleWeightedGraph<>(null, edgeSupplier)); } } UniformIntrusiveEdgesSpecifics.java000066400000000000000000000044211402514743400341040ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import java.util.*; /** * An uniform weights variant of the intrusive edges specifics. * *

    * The implementation optimizes the use of {@link DefaultEdge} and subclasses. For other custom user * edge types, a map is used to store vertex source and target. * * @author Barak Naveh * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class UniformIntrusiveEdgesSpecifics extends BaseIntrusiveEdgesSpecifics implements IntrusiveEdgesSpecifics { private static final long serialVersionUID = -5736320893697031114L; /** * Constructor * * @param map the map to use for storage */ public UniformIntrusiveEdgesSpecifics(Map map) { super(map); } @Override public boolean add(E e, V sourceVertex, V targetVertex) { if (e instanceof IntrusiveEdge) { return addIntrusiveEdge(e, sourceVertex, targetVertex, (IntrusiveEdge) e); } else { int previousSize = edgeMap.size(); IntrusiveEdge intrusiveEdge = edgeMap.computeIfAbsent(e, i -> new IntrusiveEdge()); if (previousSize < edgeMap.size()) { // edge was added intrusiveEdge.source = sourceVertex; intrusiveEdge.target = targetVertex; return true; } return false; } } @Override protected IntrusiveEdge getIntrusiveEdge(E e) { if (e instanceof IntrusiveEdge) { return (IntrusiveEdge) e; } return edgeMap.get(e); } } WeightedIntrusiveEdgesSpecifics.java000066400000000000000000000056411402514743400342320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import java.util.*; /** * A weighted variant of the intrusive edges specifics. * *

    * The implementation optimizes the use of {@link DefaultWeightedEdge} and subclasses. For other * custom user edge types, a map is used to store vertex source, target and weight. * * @author Barak Naveh * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class WeightedIntrusiveEdgesSpecifics extends BaseIntrusiveEdgesSpecifics implements IntrusiveEdgesSpecifics { private static final long serialVersionUID = 5327226615635500554L; /** * Constructor * * @param map the map to use for storage */ public WeightedIntrusiveEdgesSpecifics(Map map) { super(map); } @Override public boolean add(E e, V sourceVertex, V targetVertex) { if (e instanceof IntrusiveWeightedEdge) { return addIntrusiveEdge(e, sourceVertex, targetVertex, (IntrusiveWeightedEdge) e); } else { int previousSize = edgeMap.size(); IntrusiveWeightedEdge intrusiveEdge = edgeMap.computeIfAbsent(e, i -> new IntrusiveWeightedEdge()); if (previousSize < edgeMap.size()) { // edge was added intrusiveEdge.source = sourceVertex; intrusiveEdge.target = targetVertex; return true; } return false; } } @Override public double getEdgeWeight(E e) { IntrusiveWeightedEdge ie = getIntrusiveEdge(e); if (ie == null) { throw new IllegalArgumentException("no such edge in graph: " + e.toString()); } return ie.weight; } @Override public void setEdgeWeight(E e, double weight) { IntrusiveWeightedEdge ie = getIntrusiveEdge(e); if (ie == null) { throw new IllegalArgumentException("no such edge in graph: " + e.toString()); } ie.weight = weight; } @Override protected IntrusiveWeightedEdge getIntrusiveEdge(E e) { if (e instanceof IntrusiveWeightedEdge) { return (IntrusiveWeightedEdge) e; } return edgeMap.get(e); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedMultigraph.java000066400000000000000000000055101402514743400316270ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A weighted multigraph. A weighted multigraph is a non-simple undirected graph in which no loops * are permitted, but multiple (parallel) edges between any two vertices are. The edges of a * weighted multigraph have weights. If you're unsure about multigraphs, see: * * http://mathworld.wolfram.com/Multigraph.html. * * @param the graph vertex type * @param the graph edge type */ public class WeightedMultigraph extends Multigraph { private static final long serialVersionUID = -6009321659287373874L; /** * Creates a new graph. * * @param edgeClass class on which to base the edge supplier */ public WeightedMultigraph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass)); } /** * Creates a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null */ public WeightedMultigraph(Supplier vertexSupplier, Supplier edgeSupplier) { super(vertexSupplier, edgeSupplier, true); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new WeightedMultigraph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new WeightedMultigraph<>(null, edgeSupplier)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/WeightedPseudograph.java000066400000000000000000000055211402514743400317760ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import java.util.function.*; /** * A weighted pseudograph. A weighted pseudograph is a non-simple undirected graph in which both * graph loops and multiple (parallel) edges are permitted. The edges of a weighted pseudograph have * weights. If you're unsure about pseudographs, see: * * http://mathworld.wolfram.com/Pseudograph.html. * * @param the graph vertex type * @param the graph edge type */ public class WeightedPseudograph extends Pseudograph { private static final long serialVersionUID = 3037964528481084240L; /** * Creates a new weighted graph. * * @param edgeClass class on which to base the edge supplier */ public WeightedPseudograph(Class edgeClass) { this(null, SupplierUtil.createSupplier(edgeClass)); } /** * Creates a new weighted graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null */ public WeightedPseudograph(Supplier vertexSupplier, Supplier edgeSupplier) { super(vertexSupplier, edgeSupplier, true); } /** * Create a builder for this kind of graph. * * @param edgeClass class on which to base factory for edges * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Class edgeClass) { return new GraphBuilder<>(new WeightedPseudograph<>(edgeClass)); } /** * Create a builder for this kind of graph. * * @param edgeSupplier the edge supplier * @param the graph vertex type * @param the graph edge type * @return a builder for this kind of graph */ public static GraphBuilder> createBuilder( Supplier edgeSupplier) { return new GraphBuilder<>(new WeightedPseudograph<>(null, edgeSupplier)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/builder/000077500000000000000000000000001402514743400266145ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/builder/AbstractGraphBuilder.java000066400000000000000000000174761402514743400335320ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Andrew Chen and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.builder; import org.jgrapht.*; import org.jgrapht.graph.*; /** * Base class for builders of {@link Graph} * * @param the graph vertex type * @param the graph edge type * @param type of the resulting graph * @param type of this builder * * @author Andrew Chen */ public abstract class AbstractGraphBuilder, B extends AbstractGraphBuilder> { protected final G graph; /** * Creates a builder based on {@code baseGraph}. {@code baseGraph} must be mutable. * * @param baseGraph the graph object to base building on */ public AbstractGraphBuilder(G baseGraph) { this.graph = baseGraph; } /** * @return the {@code this} object. */ protected abstract B self(); /** * Adds {@code vertex} to the graph being built. * * @param vertex the vertex to add * * @return this builder object * * @see Graph#addVertex(Object) */ public B addVertex(V vertex) { this.graph.addVertex(vertex); return this.self(); } /** * Adds each vertex of {@code vertices} to the graph being built. * * @param vertices the vertices to add * * @return this builder object * * @see #addVertex(Object) */ @SafeVarargs public final B addVertices(V... vertices) { for (V vertex : vertices) { this.addVertex(vertex); } return this.self(); } /** * Adds an edge to the graph being built. The source and target vertices are added to the graph, * if not already included. * * @param source source vertex of the edge. * @param target target vertex of the edge. * * @return this builder object * * @see Graphs#addEdgeWithVertices(Graph, Object, Object) */ public B addEdge(V source, V target) { Graphs.addEdgeWithVertices(this.graph, source, target); return this.self(); } /** * Adds the specified edge to the graph being built. The source and target vertices are added to * the graph, if not already included. * * @param source source vertex of the edge. * @param target target vertex of the edge. * @param edge edge to be added to this graph. * @return this builder object * * @see Graph#addEdge(Object, Object, Object) */ public B addEdge(V source, V target, E edge) { this.addVertex(source); this.addVertex(target); this.graph.addEdge(source, target, edge); return this.self(); } /** * Adds a chain of edges to the graph being built. The vertices are added to the graph, if not * already included. * * @param first the first vertex * @param second the second vertex * @param rest the remaining vertices * @return this builder object * * @see #addEdge(Object, Object) */ @SafeVarargs public final B addEdgeChain(V first, V second, V... rest) { this.addEdge(first, second); V last = second; for (V vertex : rest) { this.addEdge(last, vertex); last = vertex; } return this.self(); } /** * Adds all the vertices and all the edges of the {@code sourceGraph} to the graph being built. * * @param sourceGraph the source graph * @return this builder object * * @see Graphs#addGraph(Graph, Graph) */ public B addGraph(Graph sourceGraph) { Graphs.addGraph(this.graph, sourceGraph); return this.self(); } /** * Removes {@code vertex} from the graph being built, if such vertex exist in graph. * * @param vertex the vertex to remove * * @return this builder object * * @see Graph#removeVertex(Object) */ public B removeVertex(V vertex) { this.graph.removeVertex(vertex); return this.self(); } /** * Removes each vertex of {@code vertices} from the graph being built, if such vertices exist in * graph. * * @param vertices the vertices to remove * * @return this builder object * * @see #removeVertex(Object) */ @SafeVarargs public final B removeVertices(V... vertices) { for (V vertex : vertices) { this.removeVertex(vertex); } return this.self(); } /** * Removes an edge going from source vertex to target vertex from the graph being built, if such * vertices and such edge exist in the graph. * * @param source source vertex of the edge. * @param target target vertex of the edge. * * @return this builder object * * @see Graph#removeVertex(Object) */ public B removeEdge(V source, V target) { this.graph.removeEdge(source, target); return this.self(); } /** * Removes the specified edge from the graph. Removes the specified edge from this graph if it * is present. * * @param edge edge to be removed from this graph, if present. * @return this builder object * * @see Graph#removeEdge(Object) */ public B removeEdge(E edge) { this.graph.removeEdge(edge); return this.self(); } /** * Adds an weighted edge to the graph being built. The source and target vertices are added to * the graph, if not already included. * * @param source source vertex of the edge. * @param target target vertex of the edge. * @param weight weight of the edge. * * @return this builder object * * @see Graphs#addEdgeWithVertices(Graph, Object, Object, double) */ public B addEdge(V source, V target, double weight) { Graphs.addEdgeWithVertices(this.graph, source, target, weight); return this.self(); } /** * Adds the specified weighted edge to the graph being built. The source and target vertices are * added to the graph, if not already included. * * @param source source vertex of the edge. * @param target target vertex of the edge. * @param edge edge to be added to this graph. * @param weight weight of the edge. * * @return this builder object * * @see #addEdge(Object, Object, Object) * @see Graph#setEdgeWeight(Object, double) */ public B addEdge(V source, V target, E edge, double weight) { this.addEdge(source, target, edge); // adds vertices if needed this.graph.setEdgeWeight(edge, weight); return this.self(); } /** * Build the graph. Calling any method (including this method) on this builder object after * calling this method is undefined behaviour. * * @return the built graph. */ public G build() { return this.graph; } /** * Build an unmodifiable version graph. Calling any method (including this method) on this * builder object after calling this method is undefined behaviour. * * @return the built unmodifiable graph. * * @see #build() */ public Graph buildAsUnmodifiable() { return new AsUnmodifiableGraph<>(this.graph); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/builder/GraphBuilder.java000066400000000000000000000042431402514743400320320ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Andrew Chen and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.builder; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.function.*; /** * A builder class for {@link Graph}. This is a helper class which helps adding vertices and edges * into an already constructed graph instance. * *

    * Each graph implementation contains a static helper method for the construction of such a builder. * For example class {@link DefaultDirectedGraph} contains method * {@link DefaultDirectedGraph#createBuilder(Supplier)}. * *

    * See {@link GraphTypeBuilder} for a builder of the actual graph instance. * * @param the graph vertex type * @param the graph edge type * @param type of the resulting graph * * @author Andrew Chen * @see GraphTypeBuilder */ public class GraphBuilder> extends AbstractGraphBuilder> { /** * Creates a builder based on {@code baseGraph}. {@code baseGraph} must be mutable. * *

    * The recommended way to use this constructor is: {@code new * GraphBuilderBase<...>(new YourGraph<...>(...))}. * *

    * NOTE: {@code baseGraph} should not be an existing graph. If you want to add an existing graph * to the graph being built, you should use the {@link #addVertex(Object)} method. * * @param baseGraph the graph object to base building on */ public GraphBuilder(G baseGraph) { super(baseGraph); } @Override protected GraphBuilder self() { return this; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/builder/GraphTypeBuilder.java000066400000000000000000000270551402514743400327020ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.builder; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.function.*; /** * A builder class for the hierarchy of {@link Graph}s that the library provides. * *

    * The following example creates a directed graph which allows multiple (parallel) edges and * self-loops:

    * *
     * Graph<Integer,
     *     DefaultEdge> g = GraphTypeBuilder
     *         .<Integer, DefaultEdge> directed().allowingMultipleEdges(true).allowingSelfLoops(true)
     *         .edgeClass(DefaultEdge.class).buildGraph();
     * 
    * *
    * * Similarly one could get a weighted multigraph by using:
    * *
     * Graph<Integer, DefaultWeightedEdge> g = GraphTypeBuilder
     *     .<Integer, DefaultWeightedEdge> undirected().allowingMultipleEdges(true)
     *     .allowingSelfLoops(false).edgeClass(DefaultWeightedEdge.class).weighted(true).buildGraph();
     * 
    * *
    * *

    * The builder also provides the ability to construct a graph from another graph such as: *

    * *
     * Graph<Integer, DefaultWeightedEdge> g1 = GraphTypeBuilder
     *     .<Integer, DefaultWeightedEdge> undirected().allowingMultipleEdges(true)
     *     .allowingSelfLoops(false).edgeClass(DefaultWeightedEdge.class).weighted(true).buildGraph();
     * 
     * Graph<Integer, DefaultWeightedEdge> g2 = GraphTypeBuilder.asGraph(g1).buildGraph();
     * 
    * *
    * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail * * @see GraphType * @see GraphBuilder */ public final class GraphTypeBuilder { private boolean undirected; private boolean directed; private boolean weighted; private boolean allowingMultipleEdges; private boolean allowingSelfLoops; private Supplier vertexSupplier; private Supplier edgeSupplier; private GraphTypeBuilder(boolean directed, boolean undirected) { this.directed = directed; this.undirected = undirected; this.weighted = false; this.allowingMultipleEdges = false; this.allowingSelfLoops = false; } /** * Create a graph type builder for a directed graph. * * @return the graph type builder * @param the graph vertex type * @param the graph edge type */ public static GraphTypeBuilder directed() { return new GraphTypeBuilder<>(true, false); } /** * Create a graph type builder for an undirected graph. * * @return the graph type builder * @param the graph vertex type * @param the graph edge type */ public static GraphTypeBuilder undirected() { return new GraphTypeBuilder<>(false, true); } /** * Create a graph type builder for a mixed graph. * * @return the graph type builder * @param the graph vertex type * @param the graph edge type */ public static GraphTypeBuilder mixed() { return new GraphTypeBuilder<>(true, true); } /** * Create a graph type builder which will create a graph with the same type as the one provided. * * @param type the graph type * @return the graph type builder * @param the graph vertex type * @param the graph edge type */ public static GraphTypeBuilder forGraphType(GraphType type) { GraphTypeBuilder builder = new GraphTypeBuilder<>( type.isDirected() || type.isMixed(), type.isUndirected() || type.isMixed()); builder.weighted = type.isWeighted(); builder.allowingSelfLoops = type.isAllowingSelfLoops(); builder.allowingMultipleEdges = type.isAllowingMultipleEdges(); return builder; } /** * Create a graph type builder which will create the same graph type as the parameter graph. The * new graph will use the same vertex and edge suppliers as the input graph. * * @param graph a graph * @return a type builder * @param the graph vertex type * @param the graph edge type */ public static GraphTypeBuilder forGraph(Graph graph) { GraphTypeBuilder builder = forGraphType(graph.getType()); builder.vertexSupplier = graph.getVertexSupplier(); builder.edgeSupplier = graph.getEdgeSupplier(); return builder; } /** * Set whether the graph will be weighted or not. * * @param weighted if true the graph will be weighted * @return the graph type builder */ public GraphTypeBuilder weighted(boolean weighted) { this.weighted = weighted; return this; } /** * Set whether the graph will allow self loops (edges with same source and target vertices). * * @param allowingSelfLoops if true the graph will allow self-loops * @return the graph type builder */ public GraphTypeBuilder allowingSelfLoops(boolean allowingSelfLoops) { this.allowingSelfLoops = allowingSelfLoops; return this; } /** * Set whether the graph will allow multiple (parallel) edges between the same two vertices. * * @param allowingMultipleEdges if true the graph will allow multiple (parallel) edges * @return the graph type builder */ public GraphTypeBuilder allowingMultipleEdges(boolean allowingMultipleEdges) { this.allowingMultipleEdges = allowingMultipleEdges; return this; } /** * Set the vertex supplier. * * @param vertexSupplier the vertex supplier to use * @return the graph type builder * @param the graph vertex type */ public GraphTypeBuilder vertexSupplier(Supplier vertexSupplier) { GraphTypeBuilder newBuilder = TypeUtil.uncheckedCast(this); newBuilder.vertexSupplier = vertexSupplier; return newBuilder; } /** * Set the edge supplier. * * @param edgeSupplier the edge supplier to use * @return the graph type builder * @param the graph edge type */ public GraphTypeBuilder edgeSupplier(Supplier edgeSupplier) { GraphTypeBuilder newBuilder = TypeUtil.uncheckedCast(this); newBuilder.edgeSupplier = edgeSupplier; return newBuilder; } /** * Set the vertex class. * * @param vertexClass the vertex class * @return the graph type builder * @param the graph vertex type */ public GraphTypeBuilder vertexClass(Class vertexClass) { GraphTypeBuilder newBuilder = TypeUtil.uncheckedCast(this); newBuilder.vertexSupplier = SupplierUtil.createSupplier(vertexClass); return newBuilder; } /** * Set the edge class. * * @param edgeClass the edge class * @return the graph type builder * @param the graph edge type */ public GraphTypeBuilder edgeClass(Class edgeClass) { GraphTypeBuilder newBuilder = TypeUtil.uncheckedCast(this); newBuilder.edgeSupplier = SupplierUtil.createSupplier(edgeClass); return newBuilder; } /** * Build the graph type. * * @return a graph type */ public GraphType buildType() { DefaultGraphType.Builder typeBuilder = new DefaultGraphType.Builder(); if (directed && undirected) { typeBuilder = typeBuilder.mixed(); } else if (directed) { typeBuilder = typeBuilder.directed(); } else if (undirected) { typeBuilder = typeBuilder.undirected(); } return typeBuilder .allowMultipleEdges(allowingMultipleEdges).allowSelfLoops(allowingSelfLoops) .weighted(weighted).build(); } /** * Build the graph and acquire a {@link GraphBuilder} in order to add vertices and edges. * * @return a graph builder */ public GraphBuilder> buildGraphBuilder() { return new GraphBuilder>(buildGraph()); } /** * Build the actual graph. * * @return the graph * @throws UnsupportedOperationException in case a graph type is not supported */ public Graph buildGraph() { if (directed && undirected) { throw new UnsupportedOperationException("Mixed graphs are not supported"); } else if (directed) { if (allowingSelfLoops && allowingMultipleEdges) { if (weighted) { return new DirectedWeightedPseudograph<>(vertexSupplier, edgeSupplier); } else { return new DirectedPseudograph<>(vertexSupplier, edgeSupplier, false); } } else if (allowingMultipleEdges) { if (weighted) { return new DirectedWeightedMultigraph<>(vertexSupplier, edgeSupplier); } else { return new DirectedMultigraph<>(vertexSupplier, edgeSupplier, false); } } else if (allowingSelfLoops) { if (weighted) { return new DefaultDirectedWeightedGraph<>(vertexSupplier, edgeSupplier); } else { return new DefaultDirectedGraph<>(vertexSupplier, edgeSupplier, false); } } else { if (weighted) { return new SimpleDirectedWeightedGraph<>(vertexSupplier, edgeSupplier); } else { return new SimpleDirectedGraph<>(vertexSupplier, edgeSupplier, false); } } } else { if (allowingSelfLoops && allowingMultipleEdges) { if (weighted) { return new WeightedPseudograph<>(vertexSupplier, edgeSupplier); } else { return new Pseudograph<>(vertexSupplier, edgeSupplier, false); } } else if (allowingMultipleEdges) { if (weighted) { return new WeightedMultigraph<>(vertexSupplier, edgeSupplier); } else { return new Multigraph<>(vertexSupplier, edgeSupplier, false); } } else if (allowingSelfLoops) { if (weighted) { return new DefaultUndirectedWeightedGraph<>(vertexSupplier, edgeSupplier); } else { return new DefaultUndirectedGraph<>(vertexSupplier, edgeSupplier, false); } } else { if (weighted) { return new SimpleWeightedGraph<>(vertexSupplier, edgeSupplier); } else { return new SimpleGraph<>(vertexSupplier, edgeSupplier, false); } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/builder/package-info.java000066400000000000000000000001121402514743400317750ustar00rootroot00000000000000/** * Various builder for graphs. */ package org.jgrapht.graph.builder; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/concurrent/000077500000000000000000000000001402514743400273505ustar00rootroot00000000000000AsSynchronizedGraph.java000066400000000000000000001226171402514743400340720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/concurrent/* * (C) Copyright 2018-2021, by CHEN Kui and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.concurrent; import org.jgrapht.*; import org.jgrapht.graph.*; import java.io.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.locks.*; import java.util.function.*; import java.util.stream.*; /** * Create a synchronized (thread-safe) Graph backed by the specified Graph. This Graph is designed * to support concurrent reads which are mutually exclusive with writes. In order to guarantee * serial access, it is critical that all access to the backing Graph is * accomplished through the created Graph. * *

    * Users need to manually synchronize on edge supplier (see {@link Graph#getEdgeSupplier()}) if * creating an edge needs to access shared resources. Failure to follow this advice may result in * non-deterministic behavior. *

    * *

    * For all methods returning a Set, the Graph guarantees that all operations on the returned Set do * not affect the backing Graph. For edgeSet and vertexSet methods, the * returned Set is backed by the underlying graph, but when a traversal over the set is started via * a method such as iterator(), a snapshot of the underlying Set is copied for iteration purposes. * For edgesOf, incomingEdgesOf and outgoingEdgesOf methods, * the returned Set is a unmodifiable copy of the result produced by the underlying Graph. Users can * control whether those copies should be cached; caching may significantly increase memory * requirements. If users decide to cache those copies and the backing graph's changes don't affect * them, those copies will be returned the next time the method is called. If the backing graph's * changes affect them, they will be removed from cache and re-created the next time the method is * called. If users decide to not cache those copies, the graph will create ephemeral copies every * time the method is called. For other methods returning a Set, the Set is just the backing Graph's * return. *

    * *

    * As an alternative, a copyless mode is supported. When enabled, no collection copies are * made at all (and hence the cache setting is ignored). This requires the caller to explicitly * synchronize iteration via the {@link #getLock} method. This approach requires quite a bit of care * on the part of the calling application, so it is disabled by default. *

    * *

    * Even though this graph implementation is thread-safe, callers should still be aware of potential * hazards from removal methods. If calling code obtains a reference to a vertex or edge from the * graph, and then calls another graph method to access information about that object, an * {@link IllegalArgumentException} may be thrown if another thread has concurrently removed that * object. Therefore, calling the remove methods concurrently with a typical algorithm is likely to * cause the algorithm to fail with an {@link IllegalArgumentException}. So really the main * concurrent read/write use case is add-only.
    * eg: If threadA tries to get all edges touching a certain vertex after threadB removes the vertex, * the algorithm will be interrupted by {@link IllegalArgumentException}. *

    * *
     * Thread threadA = new Thread(() -> {
     *     Set vertices = graph.vertexSet();
     *     for (Object v : vertices) {
     *         // {@link IllegalArgumentException} may be thrown since other threads may have removed
     *         // the vertex.
     *         Set edges = graph.edgesOf(v);
     *         doOtherThings();
     *     }
     * });
     * Thread threadB = new Thread(() -> {
     *     Set vertices = graph.vertexSet();
     *     for (Object v : vertices) {
     *         if (someConditions) {
     *             graph.removeVertex(v);
     *         }
     *     }
     * });
     * 
    * *

    * * One way to avoid the hazard noted above is for the calling application to explicitly synchronize * all iterations using the {@link #getLock} method. * *

    * *

    * The created Graph's hashCode is equal to the backing set's hashCode. And the created Graph is * equal to another Graph if they are the same Graph or the backing Graph is equal to the other * Graph. *

    * * @param the graph vertex type * @param the graph edge type * * @author CHEN Kui */ public class AsSynchronizedGraph extends GraphDelegator implements Graph, Serializable { private static final long serialVersionUID = 5144561442831050752L; private final ReentrantReadWriteLock readWriteLock; // A set encapsulating backing vertexSet. private transient CopyOnDemandSet allVerticesSet; // A set encapsulating backing edgeSet. private transient CopyOnDemandSet allEdgesSet; private CacheStrategy cacheStrategy; /** * Constructor for AsSynchronizedGraph with default settings (cache disabled, non-fair mode, and * copyless mode disabled). * * @param g the backing graph (the delegate) */ public AsSynchronizedGraph(Graph g) { this(g, false, false, false); } /** * Constructor for AsSynchronizedGraph with specified properties. * * @param g the backing graph (the delegate) * @param cacheEnable a flag describing whether a cache will be used * @param fair a flag describing whether fair mode will be used * @param copyless a flag describing whether copyless mode will be used */ private AsSynchronizedGraph(Graph g, boolean cacheEnable, boolean fair, boolean copyless) { super(g); readWriteLock = new ReentrantReadWriteLock(fair); if (copyless) { cacheStrategy = new NoCopy(); } else if (cacheEnable) { cacheStrategy = new CacheAccess(); } else { cacheStrategy = new NoCache(); } allEdgesSet = new CopyOnDemandSet<>(super.edgeSet(), readWriteLock, copyless); allVerticesSet = new CopyOnDemandSet<>(super.vertexSet(), readWriteLock, copyless); } /** * {@inheritDoc} */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { readWriteLock.readLock().lock(); try { return super.getAllEdges(sourceVertex, targetVertex); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public E getEdge(V sourceVertex, V targetVertex) { readWriteLock.readLock().lock(); try { return super.getEdge(sourceVertex, targetVertex); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public E addEdge(V sourceVertex, V targetVertex) { readWriteLock.writeLock().lock(); try { E e = cacheStrategy.addEdge(sourceVertex, targetVertex); if (e != null) edgeSetModified(); return e; } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { readWriteLock.writeLock().lock(); try { if (cacheStrategy.addEdge(sourceVertex, targetVertex, e)) { edgeSetModified(); return true; } return false; } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean addVertex(V v) { readWriteLock.writeLock().lock(); try { if (super.addVertex(v)) { vertexSetModified(); return true; } return false; } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean containsEdge(V sourceVertex, V targetVertex) { readWriteLock.readLock().lock(); try { return super.containsEdge(sourceVertex, targetVertex); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean containsEdge(E e) { readWriteLock.readLock().lock(); try { return super.containsEdge(e); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean containsVertex(V v) { readWriteLock.readLock().lock(); try { return super.containsVertex(v); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public int degreeOf(V vertex) { readWriteLock.readLock().lock(); try { return super.degreeOf(vertex); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public Set edgeSet() { return allEdgesSet; } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { readWriteLock.readLock().lock(); try { return cacheStrategy.edgesOf(vertex); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public int inDegreeOf(V vertex) { readWriteLock.readLock().lock(); try { return super.inDegreeOf(vertex); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { readWriteLock.readLock().lock(); try { return cacheStrategy.incomingEdgesOf(vertex); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public int outDegreeOf(V vertex) { readWriteLock.readLock().lock(); try { return super.outDegreeOf(vertex); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { readWriteLock.readLock().lock(); try { return cacheStrategy.outgoingEdgesOf(vertex); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean removeAllEdges(Collection edges) { readWriteLock.writeLock().lock(); try { return super.removeAllEdges(edges); } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public Set removeAllEdges(V sourceVertex, V targetVertex) { readWriteLock.writeLock().lock(); try { return super.removeAllEdges(sourceVertex, targetVertex); } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean removeAllVertices(Collection vertices) { readWriteLock.writeLock().lock(); try { return super.removeAllVertices(vertices); } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean removeEdge(E e) { readWriteLock.writeLock().lock(); try { if (cacheStrategy.removeEdge(e)) { edgeSetModified(); return true; } return false; } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public E removeEdge(V sourceVertex, V targetVertex) { readWriteLock.writeLock().lock(); try { E e = cacheStrategy.removeEdge(sourceVertex, targetVertex); if (e != null) edgeSetModified(); return e; } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean removeVertex(V v) { readWriteLock.writeLock().lock(); try { if (cacheStrategy.removeVertex(v)) { edgeSetModified(); vertexSetModified(); return true; } return false; } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public String toString() { readWriteLock.readLock().lock(); try { return super.toString(); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public Set vertexSet() { return allVerticesSet; } /** * {@inheritDoc} */ @Override public V getEdgeSource(E e) { readWriteLock.readLock().lock(); try { return super.getEdgeSource(e); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public V getEdgeTarget(E e) { readWriteLock.readLock().lock(); try { return super.getEdgeTarget(e); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public double getEdgeWeight(E e) { readWriteLock.readLock().lock(); try { return super.getEdgeWeight(e); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public void setEdgeWeight(E e, double weight) { readWriteLock.writeLock().lock(); try { super.setEdgeWeight(e, weight); } finally { readWriteLock.writeLock().unlock(); } } /** * Return whether the graph uses cache for edgesOf, incomingEdgesOf * and outgoingEdgesOf methods. * * @return true if cache is in use, false if cache is not in use. */ public boolean isCacheEnabled() { readWriteLock.readLock().lock(); try { return cacheStrategy.isCacheEnabled(); } finally { readWriteLock.readLock().unlock(); } } /** * Return whether copyless mode is used for collection-returning methods. * * @return true if the graph uses copyless mode, false otherwise */ public boolean isCopyless() { return allVerticesSet.isCopyless(); } /** * Set the cache strategy for edgesOf, incomingEdgesOf and * outgoingEdgesOf methods. * * @param cacheEnabled a flag whether to use cache for those methods, if true, * cache will be used for those methods, otherwise cache will not be used. * @return the AsSynchronizedGraph */ public AsSynchronizedGraph setCache(boolean cacheEnabled) { readWriteLock.writeLock().lock(); try { if (cacheEnabled == isCacheEnabled()) return this; if (cacheEnabled) cacheStrategy = new CacheAccess(); else cacheStrategy = new NoCache(); return this; } finally { readWriteLock.writeLock().unlock(); } } /** * {@inheritDoc} */ @Override public int hashCode() { readWriteLock.readLock().lock(); try { return getDelegate().hashCode(); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) return true; readWriteLock.readLock().lock(); try { return getDelegate().equals(o); } finally { readWriteLock.readLock().unlock(); } } /** * Create a unmodifiable copy of the set. * * @param set the set to be copied. * * @return a unmodifiable copy of the set */ private Set copySet(Set set) { return Collections.unmodifiableSet(new LinkedHashSet<>(set)); } /** * Inform allVerticesSet that the backing data has been modified. */ private void vertexSetModified() { allVerticesSet.modified(); } /** * Inform allEdgesSet that the backing data has been modified. */ private void edgeSetModified() { allEdgesSet.modified(); } /** * Return whether fair mode is used for synchronizing access to this graph. * * @return true if the graph uses fair mode, false if non-fair mode */ public boolean isFair() { return readWriteLock.isFair(); } /** * Get the read/write lock used to synchronize all access to this graph. This can be used by * calling applications to explicitly synchronize compound sequences of graph accessses. The * lock is reentrant, so the locks acquired internally by AsSynchronizedGraph will not interfere * with the caller's acquired lock. However, write methods MUST NOT be called * while holding a read lock, otherwise a deadlock will occur. * * @return the reentrant read/write lock used to synchronize all access to this graph */ public ReentrantReadWriteLock getLock() { return readWriteLock; } /** * Create a synchronized (thread-safe) and unmodifiable Set backed by the specified Set. In * order to guarantee serial access, it is critical that all access to the * backing Set is accomplished through the created Set. * *

    * When a traversal over the set is started via a method such as iterator(), a snapshot of the * underlying set is copied for iteration purposes (unless copyless mode is enabled). *

    * *

    * The created Set's hashCode is equal to the backing Set's hashCode. And the created Set is * equal to another set if they are the same Set or the backing Set is equal to the other Set. *

    * *

    * The created set will be serializable if the backing set is serializable. *

    * * @param the class of the objects in the set * * @author CHEN Kui */ private static class CopyOnDemandSet implements Set, Serializable { private static final long serialVersionUID = 5553953818148294283L; // Backing set. private Set set; // When this flag is set, the backing set is used directly rather than // a copy. private final boolean copyless; // Backing set's unmodifiable copy. If null, needs to be recomputed on next access. volatile private transient Set copy; final ReadWriteLock readWriteLock; private static final String UNMODIFIABLE = "this set is unmodifiable"; /** * Constructor for CopyOnDemandSet. * * @param s the backing set. * @param readWriteLock the ReadWriteLock on which to locked * @param copyless whether copyless mode should be used */ private CopyOnDemandSet(Set s, ReadWriteLock readWriteLock, boolean copyless) { set = Objects.requireNonNull(s, "s must not be null"); copy = null; this.readWriteLock = readWriteLock; this.copyless = copyless; } /** * Return whether copyless mode is used for iteration. * * @return true if the set uses copyless mode, false otherwise */ public boolean isCopyless() { return copyless; } /** * {@inheritDoc} */ @Override public int size() { readWriteLock.readLock().lock(); try { return set.size(); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean isEmpty() { readWriteLock.readLock().lock(); try { return set.isEmpty(); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean contains(Object o) { readWriteLock.readLock().lock(); try { return set.contains(o); } finally { readWriteLock.readLock().unlock(); } } /** * Returns an iterator over the elements in the backing set's unmodifiable copy. The * elements are returned in the same order of the backing set. * * @return an iterator over the elements in the backing set's unmodifiable copy. */ @Override public Iterator iterator() { return getCopy().iterator(); } /** * {@inheritDoc} */ @Override public Object[] toArray() { readWriteLock.readLock().lock(); try { return set.toArray(); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public T[] toArray(T[] a) { readWriteLock.readLock().lock(); try { return set.toArray(a); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean add(E e) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean remove(Object o) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean containsAll(Collection c) { readWriteLock.readLock().lock(); try { return set.containsAll(c); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ @Override public void clear() { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * {@inheritDoc} */ // Override default methods in Collection @Override public void forEach(Consumer action) { readWriteLock.readLock().lock(); try { set.forEach(action); } finally { readWriteLock.readLock().unlock(); } } /** * {@inheritDoc} */ @Override public boolean removeIf(Predicate filter) { throw new UnsupportedOperationException(UNMODIFIABLE); } /** * Creates a Spliterator over the elements in the set's unmodifiable copy. * * @return a Spliterator over the elements in the backing set's unmodifiable * copy. */ @Override public Spliterator spliterator() { return getCopy().spliterator(); } /** * Return a sequential Stream with the backing set's unmodifiable copy as its * source. * * @return a sequential Stream with the backing set's unmodifiable copy as its * source. */ @Override public Stream stream() { return getCopy().stream(); } /** * Return a possibly parallel Stream with the backing set's unmodifiable copy * as its source. * * @return a possibly parallel Stream with the backing set's unmodifiable copy * as its source. */ @Override public Stream parallelStream() { return getCopy().parallelStream(); } /** * Compares the specified object with this set for equality. * * @param o object to be compared for equality with this set. * @return true if o and this set are the same object or o is equal to the * backing object, false otherwise. */ @Override public boolean equals(Object o) { if (this == o) return true; readWriteLock.readLock().lock(); try { return set.equals(o); } finally { readWriteLock.readLock().unlock(); } } /** * Return the backing set's hashcode. * * @return the backing set's hashcode. */ @Override public int hashCode() { readWriteLock.readLock().lock(); try { return set.hashCode(); } finally { readWriteLock.readLock().unlock(); } } /** * Return the backing set's toString result. * * @return the backing set's toString result. */ @Override public String toString() { readWriteLock.readLock().lock(); try { return set.toString(); } finally { readWriteLock.readLock().unlock(); } } /** * Get the backing set's unmodifiable copy, or a direct reference to the backing set if in * copyless mode. * * @return the backing set or its unmodifiable copy */ private Set getCopy() { if (copyless) { return set; } readWriteLock.readLock().lock(); try { Set tempCopy = copy; if (tempCopy == null) { synchronized (this) { tempCopy = copy; if (tempCopy == null) { copy = tempCopy = new LinkedHashSet<>(set); } } } return tempCopy; } finally { readWriteLock.readLock().unlock(); } } /** * If the backing set is modified, call this method to let this set knows the backing set's * copy need to update. */ private void modified() { copy = null; } } /** * An interface for cache strategy of AsSynchronizedGraph's edgesOf, * incomingEdgesOf and outgoingEdgesOf methods. */ private interface CacheStrategy { /** * Add an edge into AsSynchronizedGraph's backing graph. */ E addEdge(V sourceVertex, V targetVertex); /** * Add an edge into AsSynchronizedGraph's backing graph. */ boolean addEdge(V sourceVertex, V targetVertex, E e); /** * Get all edges touching the specified vertex in AsSynchronizedGraph's backing graph. */ Set edgesOf(V vertex); /** * Get a set of all edges in AsSynchronizedGraph's backing graph incoming into the specified * vertex. */ Set incomingEdgesOf(V vertex); /** * Get a set of all edges in AsSynchronizedGraph's backing graph outgoing from the specified * vertex. */ Set outgoingEdgesOf(V vertex); /** * Remove the specified edge from AsSynchronizedGraph's backing graph. */ boolean removeEdge(E e); /** * Remove an edge from AsSynchronizedGraph's backing graph. */ E removeEdge(V sourceVertex, V targetVertex); /** * Remove the specified vertex from AsSynchronizedGraph's backing graph. */ boolean removeVertex(V v); /** * Return whether the graph uses cache for edgesOf, * incomingEdgesOf and outgoingEdgesOf methods. * * @return true if cache is in use, false if cache is not in use. */ boolean isCacheEnabled(); } /** * Don't use cache for AsSynchronizedGraph's edgesOf, incomingEdgesOf * and outgoingEdgesOf methods. */ private class NoCache implements CacheStrategy, Serializable { private static final long serialVersionUID = 19246150051213471L; /** * {@inheritDoc} */ @Override public E addEdge(V sourceVertex, V targetVertex) { return AsSynchronizedGraph.super.addEdge(sourceVertex, targetVertex); } /** * {@inheritDoc} */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { return AsSynchronizedGraph.super.addEdge(sourceVertex, targetVertex, e); } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { return copySet(AsSynchronizedGraph.super.edgesOf(vertex)); } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { return copySet(AsSynchronizedGraph.super.incomingEdgesOf(vertex)); } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { return copySet(AsSynchronizedGraph.super.outgoingEdgesOf(vertex)); } /** * {@inheritDoc} */ @Override public boolean removeEdge(E e) { return AsSynchronizedGraph.super.removeEdge(e); } /** * {@inheritDoc} */ @Override public E removeEdge(V sourceVertex, V targetVertex) { return AsSynchronizedGraph.super.removeEdge(sourceVertex, targetVertex); } /** * {@inheritDoc} */ @Override public boolean removeVertex(V v) { return AsSynchronizedGraph.super.removeVertex(v); } /** * {@inheritDoc} */ @Override public boolean isCacheEnabled() { return false; } } /** * Disable cache as per NoCache, and also don't produce copies; instead, just * directly return the results from the underlying graph. This requires the caller to explicitly * synchronize iterations over these collections. */ private class NoCopy extends NoCache { private static final long serialVersionUID = -5046944235164395939L; /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { return AsSynchronizedGraph.super.edgesOf(vertex); } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { return AsSynchronizedGraph.super.incomingEdgesOf(vertex); } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { return AsSynchronizedGraph.super.outgoingEdgesOf(vertex); } } /** * Use cache for AsSynchronizedGraph's edgesOf, incomingEdgesOf and * outgoingEdgesOf methods. */ private class CacheAccess implements CacheStrategy, Serializable { private static final long serialVersionUID = -18262921841829294L; // A map caching for incomingEdges operation. private final transient Map> incomingEdgesMap = new ConcurrentHashMap<>(); // A map caching for outgoingEdges operation. private final transient Map> outgoingEdgesMap = new ConcurrentHashMap<>(); // A map caching for edgesOf operation. private final transient Map> edgesOfMap = new ConcurrentHashMap<>(); /** * {@inheritDoc} */ @Override public E addEdge(V sourceVertex, V targetVertex) { E e = AsSynchronizedGraph.super.addEdge(sourceVertex, targetVertex); if (e != null) edgeModified(sourceVertex, targetVertex); return e; } /** * {@inheritDoc} */ @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { if (AsSynchronizedGraph.super.addEdge(sourceVertex, targetVertex, e)) { edgeModified(sourceVertex, targetVertex); return true; } return false; } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { Set s = edgesOfMap.get(vertex); if (s != null) return s; s = copySet(AsSynchronizedGraph.super.edgesOf(vertex)); edgesOfMap.put(vertex, s); return s; } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { Set s = incomingEdgesMap.get(vertex); if (s != null) return s; s = copySet(AsSynchronizedGraph.super.incomingEdgesOf(vertex)); incomingEdgesMap.put(vertex, s); return s; } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { Set s = outgoingEdgesMap.get(vertex); if (s != null) return s; s = copySet(AsSynchronizedGraph.super.outgoingEdgesOf(vertex)); outgoingEdgesMap.put(vertex, s); return s; } /** * {@inheritDoc} */ @Override public boolean removeEdge(E e) { V sourceVertex = getEdgeSource(e); V targetVertex = getEdgeTarget(e); if (AsSynchronizedGraph.super.removeEdge(e)) { edgeModified(sourceVertex, targetVertex); return true; } return false; } /** * {@inheritDoc} */ @Override public E removeEdge(V sourceVertex, V targetVertex) { E e = AsSynchronizedGraph.super.removeEdge(sourceVertex, targetVertex); if (e != null) edgeModified(sourceVertex, targetVertex); return e; } /** * {@inheritDoc} */ @Override public boolean removeVertex(V v) { if (AsSynchronizedGraph.super.removeVertex(v)) { edgesOfMap.clear(); incomingEdgesMap.clear(); outgoingEdgesMap.clear(); return true; } return false; } /** * Clear the copies which the edge to be added or removed can affect. * * @param sourceVertex source vertex of the modified edge. * @param targetVertex target vertex of the modified edge. */ private void edgeModified(V sourceVertex, V targetVertex) { outgoingEdgesMap.remove(sourceVertex); incomingEdgesMap.remove(targetVertex); edgesOfMap.remove(sourceVertex); edgesOfMap.remove(targetVertex); if (!AsSynchronizedGraph.super.getType().isDirected()) { outgoingEdgesMap.remove(targetVertex); incomingEdgesMap.remove(sourceVertex); } } /** * {@inheritDoc} */ @Override public boolean isCacheEnabled() { return true; } } /** * A builder for {@link AsSynchronizedGraph}. * * @param the graph vertex type * @param the graph edge type * * @author CHEN Kui */ public static class Builder { private boolean cacheEnable; private boolean fair; private boolean copyless; /** * Construct a new Builder with non-fair mode, cache disabled, and copyless mode disabled. */ public Builder() { cacheEnable = false; fair = false; copyless = false; } /** * Construct a new Builder matching the settings of an existing graph. * * @param graph the graph on which to base the builder */ public Builder(AsSynchronizedGraph graph) { this.cacheEnable = graph.isCacheEnabled(); this.fair = graph.isFair(); this.copyless = graph.isCopyless(); } /** * Request a synchronized graph without caching. * * @return the Builder */ public Builder cacheDisable() { cacheEnable = false; return this; } /** * Request a synchronized graph with caching. * * @return the Builder */ public Builder cacheEnable() { cacheEnable = true; return this; } /** * Return whether a cache will be used for the synchronized graph being built. * * @return true if cache will be used, false if cache will not be * used */ public boolean isCacheEnable() { return cacheEnable; } /** * Request a synchronized graph which does not return collection copies. * * @return the Builder */ public Builder setCopyless() { copyless = true; return this; } /** * Request a synchronized graph which returns collection copies. * * @return the Builder */ public Builder clearCopyless() { copyless = false; return this; } /** * Return whether copyless mode will be used for the synchronized graph being built. * * @return true if constructed as copyless, false otherwise */ public boolean isCopyless() { return copyless; } /** * Request a synchronized graph with fair mode. * * @return the SynchronizedGraphParams */ public Builder setFair() { fair = true; return this; } /** * Request a synchronized graph with non-fair mode. * * @return the SynchronizedGraphParams */ public Builder setNonfair() { fair = false; return this; } /** * Return whether fair mode will be used for the synchronized graph being built. * * @return true if constructed as fair mode, false if non-fair */ public boolean isFair() { return fair; } /** * Build the AsSynchronizedGraph. * * @param graph the backing graph (the delegate) * @return the AsSynchronizedGraph */ public AsSynchronizedGraph build(Graph graph) { return new AsSynchronizedGraph<>(graph, cacheEnable, fair, copyless); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/concurrent/package-info.java000066400000000000000000000001511402514743400325340ustar00rootroot00000000000000/** * Implementations of various concurrent graph structures. */ package org.jgrapht.graph.concurrent; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/package-info.java000066400000000000000000000001111402514743400303460ustar00rootroot00000000000000/** * Implementations of various graphs. */ package org.jgrapht.graph; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/000077500000000000000000000000001402514743400271365ustar00rootroot00000000000000ArrayUnenforcedSetEdgeSetFactory.java000066400000000000000000000026531402514743400362640ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.specifics; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; /** * An edge set factory which creates {@link ArrayUnenforcedSet} of size 1, suitable for small degree * vertices. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public class ArrayUnenforcedSetEdgeSetFactory implements EdgeSetFactory, Serializable { private static final long serialVersionUID = 5936902837403445985L; /** * {@inheritDoc} */ @Override public Set createEdgeSet(V vertex) { // NOTE: use size 1 to keep memory usage under control // for the common case of vertices with low degree return new ArrayUnenforcedSet<>(1); } } DirectedEdgeContainer.java000066400000000000000000000056641402514743400341100ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/* * (C) Copyright 2015-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.specifics; import org.jgrapht.graph.*; import java.io.*; import java.util.*; /** * A container for vertex edges. * *

    * In this edge container we use array lists to minimize memory toll. However, for high-degree * vertices we replace the entire edge container with a direct access subclass (to be implemented). * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public class DirectedEdgeContainer implements Serializable { private static final long serialVersionUID = 7494242245729767106L; Set incoming; Set outgoing; private transient Set unmodifiableIncoming = null; private transient Set unmodifiableOutgoing = null; DirectedEdgeContainer(EdgeSetFactory edgeSetFactory, V vertex) { incoming = edgeSetFactory.createEdgeSet(vertex); outgoing = edgeSetFactory.createEdgeSet(vertex); } /** * A lazy build of unmodifiable incoming edge set. * * @return an unmodifiable version of the incoming edge set */ public Set getUnmodifiableIncomingEdges() { if (unmodifiableIncoming == null) { unmodifiableIncoming = Collections.unmodifiableSet(incoming); } return unmodifiableIncoming; } /** * A lazy build of unmodifiable outgoing edge set. * * @return an unmodifiable version of the outgoing edge set */ public Set getUnmodifiableOutgoingEdges() { if (unmodifiableOutgoing == null) { unmodifiableOutgoing = Collections.unmodifiableSet(outgoing); } return unmodifiableOutgoing; } /** * Add an incoming edge. * * @param e the edge to add */ public void addIncomingEdge(E e) { incoming.add(e); } /** * Add an outgoing edge. * * @param e the edge to add */ public void addOutgoingEdge(E e) { outgoing.add(e); } /** * Remove an incoming edge. * * @param e the edge to remove */ public void removeIncomingEdge(E e) { incoming.remove(e); } /** * Remove an outgoing edge. * * @param e the edge to remove */ public void removeOutgoingEdge(E e) { outgoing.remove(e); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/DirectedSpecifics.java000066400000000000000000000165041402514743400333630ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.specifics; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Plain implementation of DirectedSpecifics. This implementation requires the least amount of * memory, at the expense of slow edge retrievals. Methods which depend on edge retrievals, e.g. * getEdge(V u, V v), containsEdge(V u, V v), addEdge(V u, V v), etc may be relatively slow when the * average degree of a vertex is high (dense graphs). For a fast implementation, use * {@link FastLookupDirectedSpecifics}. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh * @author Joris Kinable */ public class DirectedSpecifics implements Specifics, Serializable { private static final long serialVersionUID = 5964807709682219859L; protected Graph graph; protected Map> vertexMap; protected EdgeSetFactory edgeSetFactory; /** * Construct a new directed specifics. * * @param graph the graph for which these specifics are for * @param vertexMap map for the storage of vertex edge sets. Needs to have a predictable * iteration order. * @param edgeSetFactory factory for the creation of vertex edge sets */ public DirectedSpecifics( Graph graph, Map> vertexMap, EdgeSetFactory edgeSetFactory) { this.graph = Objects.requireNonNull(graph); this.vertexMap = Objects.requireNonNull(vertexMap); this.edgeSetFactory = Objects.requireNonNull(edgeSetFactory); } /** * {@inheritDoc} */ @Override public boolean addVertex(V v) { DirectedEdgeContainer ec = vertexMap.get(v); if (ec == null) { vertexMap.put(v, new DirectedEdgeContainer<>(edgeSetFactory, v)); return true; } return false; } /** * {@inheritDoc} */ @Override public Set getVertexSet() { return vertexMap.keySet(); } /** * {@inheritDoc} */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { Set edges = null; if (graph.containsVertex(sourceVertex) && graph.containsVertex(targetVertex)) { edges = new ArrayUnenforcedSet<>(); DirectedEdgeContainer ec = getEdgeContainer(sourceVertex); for (E e : ec.outgoing) { if (graph.getEdgeTarget(e).equals(targetVertex)) { edges.add(e); } } } return edges; } /** * {@inheritDoc} */ @Override public E getEdge(V sourceVertex, V targetVertex) { if (graph.containsVertex(sourceVertex) && graph.containsVertex(targetVertex)) { DirectedEdgeContainer ec = getEdgeContainer(sourceVertex); for (E e : ec.outgoing) { if (graph.getEdgeTarget(e).equals(targetVertex)) { return e; } } } return null; } /** * {@inheritDoc} */ @Override public boolean addEdgeToTouchingVertices(V sourceVertex, V targetVertex, E e) { getEdgeContainer(sourceVertex).addOutgoingEdge(e); getEdgeContainer(targetVertex).addIncomingEdge(e); return true; } @Override public boolean addEdgeToTouchingVerticesIfAbsent(V sourceVertex, V targetVertex, E e) { // lookup for edge with same source and target DirectedEdgeContainer ec = getEdgeContainer(sourceVertex); for (E outEdge : ec.outgoing) { if (graph.getEdgeTarget(outEdge).equals(targetVertex)) { return false; } } // add ec.addOutgoingEdge(e); getEdgeContainer(targetVertex).addIncomingEdge(e); return true; } @Override public E createEdgeToTouchingVerticesIfAbsent( V sourceVertex, V targetVertex, Supplier edgeSupplier) { // lookup for edge with same source and target DirectedEdgeContainer ec = getEdgeContainer(sourceVertex); for (E e : ec.outgoing) { if (graph.getEdgeTarget(e).equals(targetVertex)) { return null; } } // create and add E e = edgeSupplier.get(); ec.addOutgoingEdge(e); getEdgeContainer(targetVertex).addIncomingEdge(e); return e; } /** * {@inheritDoc} */ @Override public int degreeOf(V vertex) { return inDegreeOf(vertex) + outDegreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { ArrayUnenforcedSet inAndOut = new ArrayUnenforcedSet<>(getEdgeContainer(vertex).incoming); if (graph.getType().isAllowingSelfLoops()) { for (E e : getEdgeContainer(vertex).outgoing) { V target = graph.getEdgeTarget(e); if (!vertex.equals(target)) { inAndOut.add(e); } } } else { inAndOut.addAll(getEdgeContainer(vertex).outgoing); } return Collections.unmodifiableSet(inAndOut); } /** * {@inheritDoc} */ @Override public int inDegreeOf(V vertex) { return getEdgeContainer(vertex).incoming.size(); } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { return getEdgeContainer(vertex).getUnmodifiableIncomingEdges(); } /** * {@inheritDoc} */ @Override public int outDegreeOf(V vertex) { return getEdgeContainer(vertex).outgoing.size(); } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { return getEdgeContainer(vertex).getUnmodifiableOutgoingEdges(); } /** * {@inheritDoc} */ @Override public void removeEdgeFromTouchingVertices(V sourceVertex, V targetVertex, E e) { getEdgeContainer(sourceVertex).removeOutgoingEdge(e); getEdgeContainer(targetVertex).removeIncomingEdge(e); } /** * Get the edge container for specified vertex. * * @param vertex a vertex in this graph. * * @return an edge container */ protected DirectedEdgeContainer getEdgeContainer(V vertex) { DirectedEdgeContainer ec = vertexMap.get(vertex); if (ec == null) { ec = new DirectedEdgeContainer<>(edgeSetFactory, vertex); vertexMap.put(vertex, ec); } return ec; } } FastLookupDirectedSpecifics.java000066400000000000000000000135101402514743400353060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/* * (C) Copyright 2015-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.specifics; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.util.*; import java.util.function.*; /** * Fast implementation of DirectedSpecifics. This class uses additional data structures to improve * the performance of methods which depend on edge retrievals, e.g. getEdge(V u, V v), * containsEdge(V u, V v),addEdge(V u, V v). A disadvantage is an increase in memory consumption. If * memory utilization is an issue, use a {@link DirectedSpecifics} instead. * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class FastLookupDirectedSpecifics extends DirectedSpecifics { private static final long serialVersionUID = 4089085208843722263L; /** * Maps a pair of vertices <u,v> to a set of edges {(u,v)}. In case of a multigraph, all * edges which touch both u and v are included in the set. */ protected Map, Set> touchingVerticesToEdgeMap; /** * Construct a new fast lookup directed specifics. * * @param graph the graph for which these specifics are for * @param vertexMap map for the storage of vertex edge sets. Needs to have a predictable * iteration order. * @param touchingVerticesToEdgeMap Additional map for caching. No need for a predictable * iteration order. * @param edgeSetFactory factory for the creation of vertex edge sets */ public FastLookupDirectedSpecifics( Graph graph, Map> vertexMap, Map, Set> touchingVerticesToEdgeMap, EdgeSetFactory edgeSetFactory) { super(graph, vertexMap, edgeSetFactory); this.touchingVerticesToEdgeMap = Objects.requireNonNull(touchingVerticesToEdgeMap); } @Override public Set getAllEdges(V sourceVertex, V targetVertex) { if (graph.containsVertex(sourceVertex) && graph.containsVertex(targetVertex)) { Set edges = touchingVerticesToEdgeMap.get(new Pair<>(sourceVertex, targetVertex)); if (edges == null) { return Collections.emptySet(); } else { Set edgeSet = edgeSetFactory.createEdgeSet(sourceVertex); edgeSet.addAll(edges); return edgeSet; } } else { return null; } } @Override public E getEdge(V sourceVertex, V targetVertex) { Set edges = touchingVerticesToEdgeMap.get(new Pair<>(sourceVertex, targetVertex)); if (edges == null || edges.isEmpty()) return null; else { return edges.iterator().next(); } } @Override public boolean addEdgeToTouchingVertices(V sourceVertex, V targetVertex, E e) { if (!super.addEdgeToTouchingVertices(sourceVertex, targetVertex, e)) { return false; } addToIndex(sourceVertex, targetVertex, e); return true; } @Override public boolean addEdgeToTouchingVerticesIfAbsent(V sourceVertex, V targetVertex, E e) { // first lookup using our own index E edge = getEdge(sourceVertex, targetVertex); if (edge != null) { return false; } return addEdgeToTouchingVertices(sourceVertex, targetVertex, e); } @Override public E createEdgeToTouchingVerticesIfAbsent( V sourceVertex, V targetVertex, Supplier edgeSupplier) { // first lookup using our own index E edge = getEdge(sourceVertex, targetVertex); if (edge != null) { return null; } E e = edgeSupplier.get(); addEdgeToTouchingVertices(sourceVertex, targetVertex, e); return e; } @Override public void removeEdgeFromTouchingVertices(V sourceVertex, V targetVertex, E e) { super.removeEdgeFromTouchingVertices(sourceVertex, targetVertex, e); removeFromIndex(sourceVertex, targetVertex, e); } /** * Add an edge to the index. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @param e the edge */ protected void addToIndex(V sourceVertex, V targetVertex, E e) { Pair vertexPair = new Pair<>(sourceVertex, targetVertex); Set edgeSet = touchingVerticesToEdgeMap.get(vertexPair); if (edgeSet != null) edgeSet.add(e); else { edgeSet = edgeSetFactory.createEdgeSet(sourceVertex); edgeSet.add(e); touchingVerticesToEdgeMap.put(vertexPair, edgeSet); } } /** * Remove an edge from the index. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @param e the edge */ protected void removeFromIndex(V sourceVertex, V targetVertex, E e) { Pair vertexPair = new Pair<>(sourceVertex, targetVertex); Set edgeSet = touchingVerticesToEdgeMap.get(vertexPair); if (edgeSet != null) { edgeSet.remove(e); if (edgeSet.isEmpty()) { touchingVerticesToEdgeMap.remove(vertexPair); } } } } FastLookupUndirectedSpecifics.java000066400000000000000000000135721402514743400356610ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/* * (C) Copyright 2015-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.specifics; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.util.*; import java.util.function.*; /** * Fast implementation of UndirectedSpecifics. This class uses additional data structures to improve * the performance of methods which depend on edge retrievals, e.g. getEdge(V u, V v), * containsEdge(V u, V v), addEdge(V u, V v). A disadvantage is an increase in memory consumption. * If memory utilization is an issue, use a {@link UndirectedSpecifics} instead. * * @param the graph vertex type * @param the graph edge type * * @author Joris Kinable */ public class FastLookupUndirectedSpecifics extends UndirectedSpecifics { private static final long serialVersionUID = 225772727571597846L; /** * Maps a pair of vertices <u,v> to a set of edges {(u,v)}. In case of a multigraph, all * edges which touch both u and v are included in the set. */ protected Map, Set> touchingVerticesToEdgeMap; /** * Construct a new fast lookup undirected specifics. * * @param graph the graph for which these specifics are for * @param vertexMap map for the storage of vertex edge sets. Needs to have a predictable * iteration order. * @param touchingVerticesToEdgeMap Additional map for caching. No need for a predictable * iteration order. * @param edgeSetFactory factory for the creation of vertex edge sets */ public FastLookupUndirectedSpecifics( Graph graph, Map> vertexMap, Map, Set> touchingVerticesToEdgeMap, EdgeSetFactory edgeSetFactory) { super(graph, vertexMap, edgeSetFactory); this.touchingVerticesToEdgeMap = Objects.requireNonNull(touchingVerticesToEdgeMap); } @Override public Set getAllEdges(V sourceVertex, V targetVertex) { if (graph.containsVertex(sourceVertex) && graph.containsVertex(targetVertex)) { Set edges = touchingVerticesToEdgeMap.get(new UnorderedPair<>(sourceVertex, targetVertex)); if (edges == null) { return Collections.emptySet(); } else { Set edgeSet = edgeSetFactory.createEdgeSet(sourceVertex); edgeSet.addAll(edges); return edgeSet; } } else { return null; } } @Override public E getEdge(V sourceVertex, V targetVertex) { Set edges = touchingVerticesToEdgeMap.get(new UnorderedPair<>(sourceVertex, targetVertex)); if (edges == null || edges.isEmpty()) return null; else return edges.iterator().next(); } @Override public boolean addEdgeToTouchingVertices(V sourceVertex, V targetVertex, E e) { if (!super.addEdgeToTouchingVertices(sourceVertex, targetVertex, e)) { return false; } addToIndex(sourceVertex, targetVertex, e); return true; } @Override public boolean addEdgeToTouchingVerticesIfAbsent(V sourceVertex, V targetVertex, E e) { // first lookup using our own index E edge = getEdge(sourceVertex, targetVertex); if (edge != null) { return false; } return addEdgeToTouchingVertices(sourceVertex, targetVertex, e); } @Override public E createEdgeToTouchingVerticesIfAbsent( V sourceVertex, V targetVertex, Supplier edgeSupplier) { // first lookup using our own index E edge = getEdge(sourceVertex, targetVertex); if (edge != null) { return null; } E e = edgeSupplier.get(); addEdgeToTouchingVertices(sourceVertex, targetVertex, e); return e; } @Override public void removeEdgeFromTouchingVertices(V sourceVertex, V targetVertex, E e) { super.removeEdgeFromTouchingVertices(sourceVertex, targetVertex, e); removeFromIndex(sourceVertex, targetVertex, e); } /** * Add an edge to the index. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @param e the edge */ protected void addToIndex(V sourceVertex, V targetVertex, E e) { Pair vertexPair = new UnorderedPair<>(sourceVertex, targetVertex); Set edgeSet = touchingVerticesToEdgeMap.get(vertexPair); if (edgeSet != null) edgeSet.add(e); else { edgeSet = edgeSetFactory.createEdgeSet(sourceVertex); edgeSet.add(e); touchingVerticesToEdgeMap.put(vertexPair, edgeSet); } } /** * Remove an edge from the index. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @param e the edge */ protected void removeFromIndex(V sourceVertex, V targetVertex, E e) { Pair vertexPair = new UnorderedPair<>(sourceVertex, targetVertex); Set edgeSet = touchingVerticesToEdgeMap.get(vertexPair); if (edgeSet != null) { edgeSet.remove(e); if (edgeSet.isEmpty()) touchingVerticesToEdgeMap.remove(vertexPair); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/Specifics.java000066400000000000000000000135101402514743400317110ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.specifics; import java.util.*; import java.util.function.*; /** * An interface encapsulating the basic graph operations. Different implementations have different * space-time tradeoffs. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public interface Specifics { /** * Adds a vertex. * * @param vertex vertex to be added. * @return true if the vertex was added, false if the vertex was already present */ boolean addVertex(V vertex); /** * Get the vertex set. * * @return the vertex set */ Set getVertexSet(); /** * Returns a set of all edges connecting source vertex to target vertex if such vertices exist * in this graph. If any of the vertices does not exist or is null, returns * null. If both vertices exist but no edges found, returns an empty set. * * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return a set of all edges connecting source vertex to target vertex. */ Set getAllEdges(V sourceVertex, V targetVertex); /** * Returns an edge connecting source vertex to target vertex if such vertices and such edge * exist in this graph. Otherwise returns * null. If any of the specified vertices is null returns null * *

    * In undirected graphs, the returned edge may have its source and target vertices in the * opposite order. *

    * * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return an edge connecting source vertex to target vertex. */ E getEdge(V sourceVertex, V targetVertex); /** * Adds the specified edge to the edge containers of its source and target vertices. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @param e the edge * @return true if the edge was added, false otherwise */ boolean addEdgeToTouchingVertices(V sourceVertex, V targetVertex, E e); /** * Adds the specified edge to the edge containers of its source and target vertices only if the * edge is not already in the graph. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @param e the edge * @return true if the edge was added, false otherwise */ boolean addEdgeToTouchingVerticesIfAbsent(V sourceVertex, V targetVertex, E e); /** * Creates an edge given an edge supplier and adds it to the edge containers of its source and * target vertices only if the graph does not contain other edges with the same source and * target vertices. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @param edgeSupplier the function which will create the edge * @return the newly created edge or null if an edge with the same source and target vertices * was already present */ E createEdgeToTouchingVerticesIfAbsent( V sourceVertex, V targetVertex, Supplier edgeSupplier); /** * Returns the degree of the specified vertex. A degree of a vertex in an undirected graph is * the number of edges touching that vertex. * * @param vertex vertex whose degree is to be calculated. * * @return the degree of the specified vertex. */ int degreeOf(V vertex); /** * Returns a set of all edges touching the specified vertex. If no edges are touching the * specified vertex returns an empty set. * * @param vertex the vertex for which a set of touching edges is to be returned. * @return a set of all edges touching the specified vertex. */ Set edgesOf(V vertex); /** * Returns the "in degree" of the specified vertex. * * @param vertex vertex whose in degree is to be calculated. * @return the in degree of the specified vertex. */ int inDegreeOf(V vertex); /** * Returns a set of all edges incoming into the specified vertex. * * @param vertex the vertex for which the list of incoming edges to be returned. * @return a set of all edges incoming into the specified vertex. */ Set incomingEdgesOf(V vertex); /** * Returns the "out degree" of the specified vertex. * * @param vertex vertex whose out degree is to be calculated. * @return the out degree of the specified vertex. */ int outDegreeOf(V vertex); /** * Returns a set of all edges outgoing from the specified vertex. * * @param vertex the vertex for which the list of outgoing edges to be returned. * * @return a set of all edges outgoing from the specified vertex. */ Set outgoingEdgesOf(V vertex); /** * Removes the specified edge from the edge containers of its source and target vertices. * * @param sourceVertex the source vertex * @param targetVertex the target vertex * @param e the edge */ void removeEdgeFromTouchingVertices(V sourceVertex, V targetVertex, E e); } UndirectedEdgeContainer.java000066400000000000000000000044401402514743400344420ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/* * (C) Copyright 2015-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.specifics; import org.jgrapht.graph.*; import java.io.*; import java.util.*; /** * A container for vertex edges. * *

    * In this edge container we use array lists to minimize memory toll. However, for high-degree * vertices we replace the entire edge container with a direct access subclass (to be implemented). *

    * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public class UndirectedEdgeContainer implements Serializable { private static final long serialVersionUID = -6623207588411170010L; Set vertexEdges; private transient Set unmodifiableVertexEdges = null; UndirectedEdgeContainer(EdgeSetFactory edgeSetFactory, V vertex) { vertexEdges = edgeSetFactory.createEdgeSet(vertex); } /** * A lazy build of unmodifiable list of vertex edges * * @return an unmodifiable set of vertex edges */ public Set getUnmodifiableVertexEdges() { if (unmodifiableVertexEdges == null) { unmodifiableVertexEdges = Collections.unmodifiableSet(vertexEdges); } return unmodifiableVertexEdges; } /** * Add a vertex edge * * @param e the edge to add */ public void addEdge(E e) { vertexEdges.add(e); } /** * Get number of vertex edges * * @return the number of vertex edges */ public int edgeCount() { return vertexEdges.size(); } /** * Remove a vertex edge * * @param e the edge to remove */ public void removeEdge(E e) { vertexEdges.remove(e); } } UndirectedSpecifics.java000066400000000000000000000175261402514743400336540ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/* * (C) Copyright 2015-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.specifics; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Plain implementation of UndirectedSpecifics. This implementation requires the least amount of * memory, at the expense of slow edge retrievals. Methods which depend on edge retrievals, e.g. * getEdge(V u, V v), containsEdge(V u, V v), addEdge(V u, V v), etc may be relatively slow when the * average degree of a vertex is high (dense graphs). For a fast implementation, use * {@link FastLookupUndirectedSpecifics}. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh * @author Joris Kinable */ public class UndirectedSpecifics implements Specifics, Serializable { private static final long serialVersionUID = 4206026440450450992L; protected Graph graph; protected Map> vertexMap; protected EdgeSetFactory edgeSetFactory; /** * Construct a new undirected specifics. * * @param graph the graph for which these specifics are for * @param vertexMap map for the storage of vertex edge sets. Needs to have a predictable * iteration order. * @param edgeSetFactory factory for the creation of vertex edge sets */ public UndirectedSpecifics( Graph graph, Map> vertexMap, EdgeSetFactory edgeSetFactory) { this.graph = Objects.requireNonNull(graph); this.vertexMap = Objects.requireNonNull(vertexMap); this.edgeSetFactory = Objects.requireNonNull(edgeSetFactory); } /** * {@inheritDoc} */ @Override public boolean addVertex(V v) { UndirectedEdgeContainer ec = vertexMap.get(v); if (ec == null) { vertexMap.put(v, new UndirectedEdgeContainer<>(edgeSetFactory, v)); return true; } return false; } /** * {@inheritDoc} */ @Override public Set getVertexSet() { return vertexMap.keySet(); } /** * {@inheritDoc} */ @Override public Set getAllEdges(V sourceVertex, V targetVertex) { Set edges = null; if (graph.containsVertex(sourceVertex) && graph.containsVertex(targetVertex)) { edges = new ArrayUnenforcedSet<>(); for (E e : getEdgeContainer(sourceVertex).vertexEdges) { boolean equal = isEqualsStraightOrInverted(sourceVertex, targetVertex, e); if (equal) { edges.add(e); } } } return edges; } /** * {@inheritDoc} */ @Override public E getEdge(V sourceVertex, V targetVertex) { if (graph.containsVertex(sourceVertex) && graph.containsVertex(targetVertex)) { for (E e : getEdgeContainer(sourceVertex).vertexEdges) { boolean equal = isEqualsStraightOrInverted(sourceVertex, targetVertex, e); if (equal) { return e; } } } return null; } private boolean isEqualsStraightOrInverted(Object sourceVertex, Object targetVertex, E e) { boolean equalStraight = sourceVertex.equals(graph.getEdgeSource(e)) && targetVertex.equals(graph.getEdgeTarget(e)); boolean equalInverted = sourceVertex.equals(graph.getEdgeTarget(e)) && targetVertex.equals(graph.getEdgeSource(e)); return equalStraight || equalInverted; } @Override public boolean addEdgeToTouchingVertices(V sourceVertex, V targetVertex, E e) { getEdgeContainer(sourceVertex).addEdge(e); if (!sourceVertex.equals(targetVertex)) { getEdgeContainer(targetVertex).addEdge(e); } return true; } @Override public boolean addEdgeToTouchingVerticesIfAbsent(V sourceVertex, V targetVertex, E e) { // lookup for edge with same source and target UndirectedEdgeContainer ec = getEdgeContainer(sourceVertex); for (E edge : ec.vertexEdges) { if (isEqualsStraightOrInverted(sourceVertex, targetVertex, edge)) { return false; } } // add ec.addEdge(e); getEdgeContainer(targetVertex).addEdge(e); return true; } @Override public E createEdgeToTouchingVerticesIfAbsent( V sourceVertex, V targetVertex, Supplier edgeSupplier) { // lookup for edge with same source and target UndirectedEdgeContainer ec = getEdgeContainer(sourceVertex); for (E edge : ec.vertexEdges) { if (isEqualsStraightOrInverted(sourceVertex, targetVertex, edge)) { return null; } } // create and add E e = edgeSupplier.get(); ec.addEdge(e); getEdgeContainer(targetVertex).addEdge(e); return e; } /** * {@inheritDoc} */ @Override public int degreeOf(V vertex) { if (graph.getType().isAllowingSelfLoops()) { /* * Then we must count, and add loops twice */ int degree = 0; Set edges = getEdgeContainer(vertex).vertexEdges; for (E e : edges) { if (graph.getEdgeSource(e).equals(graph.getEdgeTarget(e))) { degree += 2; } else { degree += 1; } } return degree; } else { return getEdgeContainer(vertex).edgeCount(); } } /** * {@inheritDoc} */ @Override public Set edgesOf(V vertex) { return getEdgeContainer(vertex).getUnmodifiableVertexEdges(); } /** * {@inheritDoc} */ @Override public int inDegreeOf(V vertex) { return degreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set incomingEdgesOf(V vertex) { return getEdgeContainer(vertex).getUnmodifiableVertexEdges(); } /** * {@inheritDoc} */ @Override public int outDegreeOf(V vertex) { return degreeOf(vertex); } /** * {@inheritDoc} */ @Override public Set outgoingEdgesOf(V vertex) { return getEdgeContainer(vertex).getUnmodifiableVertexEdges(); } /** * {@inheritDoc} */ @Override public void removeEdgeFromTouchingVertices(V sourceVertex, V targetVertex, E e) { getEdgeContainer(sourceVertex).removeEdge(e); if (!sourceVertex.equals(targetVertex)) { getEdgeContainer(targetVertex).removeEdge(e); } } /** * Get the edge container for a specified vertex. * * @param vertex a vertex in this graph * * @return an edge container */ protected UndirectedEdgeContainer getEdgeContainer(V vertex) { UndirectedEdgeContainer ec = vertexMap.get(vertex); if (ec == null) { ec = new UndirectedEdgeContainer<>(edgeSetFactory, vertex); vertexMap.put(vertex, ec); } return ec; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/graph/specifics/package-info.java000066400000000000000000000001461402514743400323260ustar00rootroot00000000000000/** * Implementations of specifics for various graph types. */ package org.jgrapht.graph.specifics; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/package-info.java000066400000000000000000000001611402514743400272520ustar00rootroot00000000000000/** * The front-end API's interfaces and classes, including {@link org.jgrapht.Graph}. */ package org.jgrapht; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/000077500000000000000000000000001402514743400257205ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/AbstractGraphIterator.java000066400000000000000000000171321402514743400330260ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.event.*; import java.util.*; /** * An empty implementation of a graph iterator to minimize the effort required to implement graph * iterators. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public abstract class AbstractGraphIterator implements GraphIterator { private final Set> traversalListeners = new LinkedHashSet<>(); // We keep this cached redundantly with traversalListeners.size() // so that subclasses can use it as a fast check to see if // event firing calls can be skipped. protected int nListeners = 0; protected final FlyweightEdgeEvent reusableEdgeEvent; protected final FlyweightVertexEvent reusableVertexEvent; protected final Graph graph; protected boolean crossComponentTraversal; protected boolean reuseEvents; /** * Create a new iterator * * @param graph the graph */ public AbstractGraphIterator(Graph graph) { this.graph = Objects.requireNonNull(graph, "graph must not be null"); this.reusableEdgeEvent = new FlyweightEdgeEvent<>(this, null); this.reusableVertexEvent = new FlyweightVertexEvent<>(this, null); this.crossComponentTraversal = true; this.reuseEvents = false; } /** * Get the graph being traversed. * * @return the graph being traversed */ public Graph getGraph() { return graph; } /** * Sets the cross component traversal flag - indicates whether to traverse the graph across * connected components. * * @param crossComponentTraversal if true traverses across connected components. */ public void setCrossComponentTraversal(boolean crossComponentTraversal) { this.crossComponentTraversal = crossComponentTraversal; } /** * Test whether this iterator is set to traverse the graph across connected components. * * @return true if traverses across connected components, otherwise * false. */ @Override public boolean isCrossComponentTraversal() { return crossComponentTraversal; } @Override public void setReuseEvents(boolean reuseEvents) { this.reuseEvents = reuseEvents; } @Override public boolean isReuseEvents() { return reuseEvents; } @Override public void addTraversalListener(TraversalListener l) { traversalListeners.add(l); nListeners = traversalListeners.size(); } @Override public void remove() { throw new UnsupportedOperationException("remove"); } @Override public void removeTraversalListener(TraversalListener l) { traversalListeners.remove(l); nListeners = traversalListeners.size(); } /** * Informs all listeners that the traversal of the current connected component finished. * * @param e the connected component finished event. */ protected void fireConnectedComponentFinished(ConnectedComponentTraversalEvent e) { for (TraversalListener l : traversalListeners) { l.connectedComponentFinished(e); } } /** * Informs all listeners that a traversal of a new connected component has started. * * @param e the connected component started event. */ protected void fireConnectedComponentStarted(ConnectedComponentTraversalEvent e) { for (TraversalListener l : traversalListeners) { l.connectedComponentStarted(e); } } /** * Informs all listeners that a the specified edge was visited. * * @param e the edge traversal event. */ protected void fireEdgeTraversed(EdgeTraversalEvent e) { for (TraversalListener l : traversalListeners) { l.edgeTraversed(e); } } /** * Informs all listeners that a the specified vertex was visited. * * @param e the vertex traversal event. */ protected void fireVertexTraversed(VertexTraversalEvent e) { for (TraversalListener l : traversalListeners) { l.vertexTraversed(e); } } /** * Informs all listeners that a the specified vertex was finished. * * @param e the vertex traversal event. */ protected void fireVertexFinished(VertexTraversalEvent e) { for (TraversalListener l : traversalListeners) { l.vertexFinished(e); } } /** * Create a vertex traversal event. * * @param vertex the vertex * @return the event */ protected VertexTraversalEvent createVertexTraversalEvent(V vertex) { if (reuseEvents) { reusableVertexEvent.setVertex(vertex); return reusableVertexEvent; } else { return new VertexTraversalEvent<>(this, vertex); } } /** * Create an edge traversal event. * * @param edge the edge * @return the event */ protected EdgeTraversalEvent createEdgeTraversalEvent(E edge) { if (isReuseEvents()) { reusableEdgeEvent.setEdge(edge); return reusableEdgeEvent; } else { return new EdgeTraversalEvent<>(this, edge); } } /** * A reusable edge event. * * @author Barak Naveh */ static class FlyweightEdgeEvent extends EdgeTraversalEvent { private static final long serialVersionUID = 4051327833765000755L; /** * Creates a new FlyweightEdgeEvent. * * @param eventSource the source of the event. * @param edge the traversed edge. */ public FlyweightEdgeEvent(Object eventSource, localE edge) { super(eventSource, edge); } /** * Sets the edge of this event. * * @param edge the edge to be set. */ protected void setEdge(localE edge) { this.edge = edge; } } /** * A reusable vertex event. * * @author Barak Naveh */ static class FlyweightVertexEvent extends VertexTraversalEvent { private static final long serialVersionUID = 3834024753848399924L; /** * Creates a new FlyweightVertexEvent. * * @param eventSource the source of the event. * @param vertex the traversed vertex. */ public FlyweightVertexEvent(Object eventSource, VV vertex) { super(eventSource, vertex); } /** * Sets the vertex of this event. * * @param vertex the vertex to be set. */ protected void setVertex(VV vertex) { this.vertex = vertex; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/BreadthFirstIterator.java000066400000000000000000000137421402514743400326650ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import java.util.*; /** * A breadth-first iterator for a directed or undirected graph. * *

    * For this iterator to work correctly the graph must not be modified during iteration. Currently * there are no means to ensure that, nor to fail-fast. The results of such modifications are * undefined. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public class BreadthFirstIterator extends CrossComponentIterator> { private Deque queue = new ArrayDeque<>(); /** * Creates a new breadth-first iterator for the specified graph. * * @param g the graph to be iterated. */ public BreadthFirstIterator(Graph g) { this(g, (V) null); } /** * Creates a new breadth-first iterator for the specified graph. Iteration will start at the * specified start vertex and will be limited to the connected component that includes that * vertex. If the specified start vertex is null, iteration will start at an * arbitrary vertex and will not be limited, that is, will be able to traverse all the graph. * * @param g the graph to be iterated. * @param startVertex the vertex iteration to be started. */ public BreadthFirstIterator(Graph g, V startVertex) { super(g, startVertex); } /** * Creates a new breadth-first iterator for the specified graph. Iteration will start at the * specified start vertices and will be limited to the connected component that includes those * vertices. If the specified start vertices is null, iteration will start at an * arbitrary vertex and will not be limited, that is, will be able to traverse all the graph. * * @param g the graph to be iterated. * @param startVertices the vertices iteration to be started. */ public BreadthFirstIterator(Graph g, Iterable startVertices) { super(g, startVertices); } /** * {@inheritDoc} */ @Override protected boolean isConnectedComponentExhausted() { return queue.isEmpty(); } /** * {@inheritDoc} */ @Override protected void encounterVertex(V vertex, E edge) { int depth = (edge == null ? 0 : getSeenData(Graphs.getOppositeVertex(graph, edge, vertex)).depth + 1); putSeenData(vertex, new SearchNodeData<>(edge, depth)); queue.add(vertex); } /** * {@inheritDoc} */ @Override protected void encounterVertexAgain(V vertex, E edge) { } /** * Returns the parent node of vertex $v$ in the BFS search tree, or null if $v$ is the root * node. This method can only be invoked on a vertex $v$ once the iterator has visited vertex * $v$! * * @param v vertex * @return parent node of vertex $v$ in the BFS search tree, or null if $v$ is a root node */ public V getParent(V v) { assert getSeenData(v) != null; E edge = getSeenData(v).edge; if (edge == null) return null; else return Graphs.getOppositeVertex(graph, edge, v); } /** * Returns the edge connecting vertex $v$ to its parent in the spanning tree formed by the BFS * search, or null if $v$ is a root node. This method can only be invoked on a vertex $v$ once * the iterator has visited vertex $v$! * * @param v vertex * @return edge connecting vertex $v$ in the BFS search tree to its parent, or null if $v$ is a * root node */ public E getSpanningTreeEdge(V v) { assert getSeenData(v) != null; return getSeenData(v).edge; } /** * Returns the depth of vertex $v$ in the search tree. The depth of a vertex $v$ is defined as * the number of edges traversed on the path from the root of the BFS tree to vertex $v$. The * root of the search tree has depth 0. This method can only be invoked on a vertex $v$ once the * iterator has visited vertex $v$! * * @param v vertex * @return depth of vertex $v$ in the search tree */ public int getDepth(V v) { assert getSeenData(v) != null; return getSeenData(v).depth; } /** * @see CrossComponentIterator#provideNextVertex() */ @Override protected V provideNextVertex() { return queue.removeFirst(); } /** * Data kept for discovered vertices. * * @param the graph edge type */ protected static class SearchNodeData { private final E edge; private final int depth; /** * Constructor * * @param edge edge to parent * @param depth depth of node in search tree */ public SearchNodeData(E edge, int depth) { this.edge = edge; this.depth = depth; } /** * Edge to parent * * @return the edge to the parent */ public E getEdge() { return edge; } /** * Depth of node in search tree * * @return the depth of the node in the search tree */ public int getDepth() { return depth; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/ClosestFirstIterator.java000066400000000000000000000336701402514743400327320ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jheaps.*; import org.jheaps.tree.*; import java.util.*; import java.util.function.*; /** * A closest-first iterator for a directed or undirected graph. For this iterator to work correctly * the graph must not be modified during iteration. Currently there are no means to ensure that, nor * to fail-fast. The results of such modifications are undefined. * *

    * The metric for closest here is the weighted path length from a start vertex, i.e. * Graph.getEdgeWeight(Edge) is summed to calculate path length. Negative edge weights will result * in an IllegalArgumentException. Optionally, path length may be bounded by a finite radius. A * custom heap implementation can be specified during the construction time. Pairing heap is used by * default. *

    * * @param the graph vertex type * @param the graph edge type * @author John V. Sichi */ public class ClosestFirstIterator extends CrossComponentIterator>> { /** * Priority queue of fringe vertices. */ private AddressableHeap> heap; /** * Maximum distance to search. */ private double radius = Double.POSITIVE_INFINITY; private boolean initialized = false; /** * Creates a new closest-first iterator for the specified graph. Iteration will start at the * specified start vertex and will be limited to the connected component that includes that * vertex. If the specified start vertex is null, iteration will start at an * arbitrary vertex and will not be limited, that is, will be able to traverse all the graph. * * @param g the graph to be iterated. * @param startVertex the vertex iteration to be started. */ public ClosestFirstIterator(Graph g, V startVertex) { this(g, startVertex, Double.POSITIVE_INFINITY); } /** * Creates a new closest-first iterator for the specified graph. Iteration will start at the * specified start vertices and will be limited to the subset of the graph reachable from those * vertices. Iteration order is based on minimum distance from any of the start vertices, * regardless of the order in which the start vertices are supplied. Because of this, the entire * traversal is treated as if it were over a single connected component with respect to events * fired. * * @param g the graph to be iterated. * @param startVertices the vertices iteration to be started. */ public ClosestFirstIterator(Graph g, Iterable startVertices) { this(g, startVertices, Double.POSITIVE_INFINITY); } /** * Creates a new radius-bounded closest-first iterator for the specified graph. Iteration will * start at the specified start vertex and will be limited to the subset of the connected * component which includes that vertex and is reachable via paths of weighted length less than * or equal to the specified radius. The specified start vertex may not be * null. * * @param g the graph to be iterated. * @param startVertex the vertex iteration to be started. * @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded * search. */ public ClosestFirstIterator(Graph g, V startVertex, double radius) { this( g, startVertex == null ? null : Collections.singletonList(startVertex), radius, PairingHeap::new); } /** * Creates a new radius-bounded closest-first iterator for the specified graph. Iteration will * start at the specified start vertex and will be limited to the subset of the connected * component which includes that vertex and is reachable via paths of weighted length less than * or equal to the specified radius. The specified start vertex may not be * null. This algorithm will use the heap supplied by the {@code heapSupplier}. * * @param g the graph to be iterated. * @param startVertex the vertex iteration to be started. * @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded * search. * @param heapSupplier supplier of the preferable heap implementation */ public ClosestFirstIterator( Graph g, V startVertex, double radius, Supplier>> heapSupplier) { this( g, startVertex == null ? null : Collections.singletonList(startVertex), radius, heapSupplier); } /** * Creates a new radius-bounded closest-first iterator for the specified graph. Iteration will * start at the specified start vertices and will be limited to the subset of the graph * reachable from those vertices via paths of weighted length less than or equal to the * specified radius. The specified collection of start vertices may not be null. * Iteration order is based on minimum distance from any of the start vertices, regardless of * the order in which the start vertices are supplied. Because of this, the entire traversal is * treated as if it were over a single connected component with respect to events fired. * * @param g the graph to be iterated. * @param startVertices the vertices iteration to be started. * @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded * search. */ public ClosestFirstIterator(Graph g, Iterable startVertices, double radius) { this(g, startVertices, radius, PairingHeap::new); } /** * Creates a new radius-bounded closest-first iterator for the specified graph. Iteration will * start at the specified start vertices and will be limited to the subset of the graph * reachable from those vertices via paths of weighted length less than or equal to the * specified radius. The specified collection of start vertices may not be null. * Iteration order is based on minimum distance from any of the start vertices, regardless of * the order in which the start vertices are supplied. Because of this, the entire traversal is * treated as if it were over a single connected component with respect to events fired. This * algorithm will use the heap supplied by the {@code heapSupplier}. * * @param g the graph to be iterated. * @param startVertices the vertices iteration to be started. * @param radius limit on weighted path length, or Double.POSITIVE_INFINITY for unbounded * search. * @param heapSupplier supplier of the preferable heap implementation */ public ClosestFirstIterator( Graph g, Iterable startVertices, double radius, Supplier>> heapSupplier) { super(g, startVertices); this.radius = radius; Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null"); this.heap = heapSupplier.get(); checkRadiusTraversal(isCrossComponentTraversal()); initialized = true; if (!crossComponentTraversal) { // prime the heap by processing the first start vertex hasNext(); Iterator iter = startVertices.iterator(); if (iter.hasNext()) { // discard the first since we already primed it above iter.next(); // prime the heap with the rest of the start vertices so that // we can process them all simultaneously while (iter.hasNext()) { V v = iter.next(); encounterVertex(v, null); } } } } // override AbstractGraphIterator @Override public void setCrossComponentTraversal(boolean crossComponentTraversal) { if (initialized) { checkRadiusTraversal(crossComponentTraversal); } super.setCrossComponentTraversal(crossComponentTraversal); } /** * Get the weighted length of the shortest path known to the given vertex. If the vertex has * already been visited, then it is truly the shortest path length; otherwise, it is the best * known upper bound. * * @param vertex vertex being sought from start vertex * @return weighted length of shortest path known, or Double.POSITIVE_INFINITY if no path found * yet */ public double getShortestPathLength(V vertex) { AddressableHeap.Handle> node = getSeenData(vertex); if (node == null) { return Double.POSITIVE_INFINITY; } return node.getKey(); } /** * Get the spanning tree edge reaching a vertex which has been seen already in this traversal. * This edge is the last link in the shortest known path between the start vertex and the * requested vertex. If the vertex has already been visited, then it is truly the minimum * spanning tree edge; otherwise, it is the best candidate seen so far. * * @param vertex the spanned vertex. * @return the spanning tree edge, or null if the vertex either has not been seen yet or is a * start vertex. */ public E getSpanningTreeEdge(V vertex) { AddressableHeap.Handle> node = getSeenData(vertex); if (node == null) { return null; } return node.getValue().spanningTreeEdge; } /** * @see CrossComponentIterator#isConnectedComponentExhausted() */ @Override protected boolean isConnectedComponentExhausted() { if (heap.size() == 0) { return true; } else { if (heap.findMin().getKey() > radius) { heap.clear(); return true; } else { return false; } } } /** * @see CrossComponentIterator#encounterVertex(Object, Object) */ @Override protected void encounterVertex(V vertex, E edge) { double shortestPathLength; if (edge == null) { shortestPathLength = 0; } else { shortestPathLength = calculatePathLength(vertex, edge); } AddressableHeap.Handle> handle = heap.insert(shortestPathLength, new QueueEntry<>(vertex, edge)); putSeenData(vertex, handle); } /** * Override superclass. When we see a vertex again, we need to see if the new edge provides a * shorter path than the old edge. * * @param vertex the vertex re-encountered * @param edge the edge via which the vertex was re-encountered */ @Override protected void encounterVertexAgain(V vertex, E edge) { AddressableHeap.Handle> node = getSeenData(vertex); if (node.getValue().frozen) { // no improvement for this vertex possible return; } double candidatePathLength = calculatePathLength(vertex, edge); if (candidatePathLength < node.getKey()) { node.getValue().spanningTreeEdge = edge; node.decreaseKey(candidatePathLength); } } /** * @see CrossComponentIterator#provideNextVertex() */ @Override protected V provideNextVertex() { AddressableHeap.Handle> node = heap.deleteMin(); node.getValue().frozen = true; return node.getValue().vertex; } private void assertNonNegativeEdge(E edge) { if (getGraph().getEdgeWeight(edge) < 0) { throw new IllegalArgumentException("negative edge weights not allowed"); } } /** * Determine weighted path length to a vertex via an edge, using the path length for the * opposite vertex. * * @param vertex the vertex for which to calculate the path length. * @param edge the edge via which the path is being extended. * @return calculated path length. */ private double calculatePathLength(V vertex, E edge) { assertNonNegativeEdge(edge); V otherVertex = Graphs.getOppositeVertex(getGraph(), edge, vertex); AddressableHeap.Handle> otherEntry = getSeenData(otherVertex); return otherEntry.getKey() + getGraph().getEdgeWeight(edge); } private void checkRadiusTraversal(boolean crossComponentTraversal) { if (crossComponentTraversal && (radius != Double.POSITIVE_INFINITY)) { throw new IllegalArgumentException( "radius may not be specified for cross-component traversal"); } } /** * Private data to associate with each entry in the priority queue. */ static class QueueEntry { /** * The vertex reached. */ V vertex; /** * Best spanning tree edge to vertex seen so far. */ E spanningTreeEdge; /** * True once spanningTreeEdge is guaranteed to be the true minimum. */ boolean frozen; QueueEntry(V vertex, E spanningTreeEdge) { this.vertex = vertex; this.spanningTreeEdge = spanningTreeEdge; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/CrossComponentIterator.java000066400000000000000000000251601402514743400332550ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.event.*; import java.util.*; /** * Provides a cross-connected-component traversal functionality for iterator subclasses. * * @param vertex type * @param edge type * @param type of data associated to seen vertices * * @author Barak Naveh */ public abstract class CrossComponentIterator extends AbstractGraphIterator { private static final int CCS_BEFORE_COMPONENT = 1; private static final int CCS_WITHIN_COMPONENT = 2; private static final int CCS_AFTER_COMPONENT = 3; private final ConnectedComponentTraversalEvent ccFinishedEvent = new ConnectedComponentTraversalEvent( this, ConnectedComponentTraversalEvent.CONNECTED_COMPONENT_FINISHED); private final ConnectedComponentTraversalEvent ccStartedEvent = new ConnectedComponentTraversalEvent( this, ConnectedComponentTraversalEvent.CONNECTED_COMPONENT_STARTED); /** * Stores the vertices that have been seen during iteration and (optionally) some additional * traversal info regarding each vertex. */ private Map seen = new HashMap<>(); /** * Iterator which provides start vertices for cross-component iteration. */ private Iterator entireGraphVertexIterator = null; /** * Iterator which provides start vertices for specified start vertices. */ private Iterator startVertexIterator = null; /** * The current vertex. */ private V startVertex; /** * The connected component state */ private int state = CCS_BEFORE_COMPONENT; /** * Creates a new iterator for the specified graph. * * @param g the graph to be iterated */ public CrossComponentIterator(Graph g) { this(g, (V) null); } /** * Creates a new iterator for the specified graph. Iteration will start at the specified start * vertex. If the specified start vertex is * null, Iteration will start at an arbitrary graph vertex. * * @param g the graph to be iterated. * @param startVertex the vertex iteration to be started. * * @throws IllegalArgumentException if g==null or does not contain * startVertex */ public CrossComponentIterator(Graph g, V startVertex) { this(g, startVertex == null ? null : Collections.singletonList(startVertex)); } /** * Creates a new iterator for the specified graph. Iteration will start at the specified start * vertices. If the specified start vertices is * null, Iteration will start at an arbitrary graph vertex. * * @param g the graph to be iterated. * @param startVertices the vertices iteration to be started. * * @throws IllegalArgumentException if g==null or does not contain * startVertex */ public CrossComponentIterator(Graph g, Iterable startVertices) { super(g); /* * Initialize crossComponentTraversal and test for containment */ if (startVertices == null) { this.crossComponentTraversal = true; } else { this.crossComponentTraversal = false; this.startVertexIterator = startVertices.iterator(); } /* * Initialize start vertex */ Iterator it = crossComponentTraversal ? getEntireGraphVertexIterator() : startVertexIterator; // pick a start vertex if possible if (it.hasNext()) { this.startVertex = it.next(); if (!graph.containsVertex(startVertex)) { throw new IllegalArgumentException("graph must contain the start vertex"); } } else { this.startVertex = null; } } @Override public boolean hasNext() { if (startVertex != null) { encounterStartVertex(); } if (isConnectedComponentExhausted()) { if (state == CCS_WITHIN_COMPONENT) { state = CCS_AFTER_COMPONENT; if (nListeners != 0) { fireConnectedComponentFinished(ccFinishedEvent); } } Iterator it = isCrossComponentTraversal() ? getEntireGraphVertexIterator() : startVertexIterator; while (it != null && it.hasNext()) { V v = it.next(); if (!graph.containsVertex(v)) { throw new IllegalArgumentException("graph must contain the start vertex"); } if (!isSeenVertex(v)) { encounterVertex(v, null); state = CCS_BEFORE_COMPONENT; return true; } } return false; } else { return true; } } @Override public V next() { if (startVertex != null) { encounterStartVertex(); } if (hasNext()) { if (state == CCS_BEFORE_COMPONENT) { state = CCS_WITHIN_COMPONENT; if (nListeners != 0) { fireConnectedComponentStarted(ccStartedEvent); } } V nextVertex = provideNextVertex(); if (nListeners != 0) { fireVertexTraversed(createVertexTraversalEvent(nextVertex)); } addUnseenChildrenOf(nextVertex); return nextVertex; } else { throw new NoSuchElementException(); } } /** * Lazily instantiates {@code entireGraphVertexIterator}. * * @return iterator which provides start vertices for cross-component iteration */ protected Iterator getEntireGraphVertexIterator() { if (entireGraphVertexIterator == null) { assert (isCrossComponentTraversal()); entireGraphVertexIterator = graph.vertexSet().iterator(); } return entireGraphVertexIterator; } /** * Returns true if there are no more uniterated vertices in the currently iterated * connected component; false otherwise. * * @return true if there are no more uniterated vertices in the currently iterated * connected component; false otherwise. */ protected abstract boolean isConnectedComponentExhausted(); /** * Update data structures the first time we see a vertex. * * @param vertex the vertex encountered * @param edge the edge via which the vertex was encountered, or null if the vertex is a * starting point */ protected abstract void encounterVertex(V vertex, E edge); /** * Returns the vertex to be returned in the following call to the iterator next * method. * * @return the next vertex to be returned by this iterator. */ protected abstract V provideNextVertex(); /** * Access the data stored for a seen vertex. * * @param vertex a vertex which has already been seen. * * @return data associated with the seen vertex or null if no data was associated * with the vertex. A null return can also indicate that the vertex was * explicitly associated with * null. */ protected D getSeenData(V vertex) { return seen.get(vertex); } /** * Determines whether a vertex has been seen yet by this traversal. * * @param vertex vertex in question * * @return true if vertex has already been seen */ protected boolean isSeenVertex(V vertex) { return seen.containsKey(vertex); } /** * Called whenever we re-encounter a vertex. The default implementation does nothing. * * @param vertex the vertex re-encountered * @param edge the edge via which the vertex was re-encountered */ protected abstract void encounterVertexAgain(V vertex, E edge); /** * Stores iterator-dependent data for a vertex that has been seen. * * @param vertex a vertex which has been seen. * @param data data to be associated with the seen vertex. * * @return previous value associated with specified vertex or * null if no data was associated with the vertex. A * null return can also indicate that the vertex was explicitly associated with * null. */ protected D putSeenData(V vertex, D data) { return seen.put(vertex, data); } /** * Called when a vertex has been finished (meaning is dependent on traversal represented by * subclass). * * @param vertex vertex which has been finished */ protected void finishVertex(V vertex) { if (nListeners != 0) { fireVertexFinished(createVertexTraversalEvent(vertex)); } } /** * Selects the outgoing edges for a given vertex based on the source vertex and other traversal * state. The default implementation returns all outgoing edges. * * @param vertex vertex in question * @return set of outgoing edges connected to the vertex */ protected Set selectOutgoingEdges(V vertex) { return graph.outgoingEdgesOf(vertex); } private void addUnseenChildrenOf(V vertex) { for (E edge : selectOutgoingEdges(vertex)) { if (nListeners != 0) { fireEdgeTraversed(createEdgeTraversalEvent(edge)); } V oppositeV = Graphs.getOppositeVertex(graph, edge, vertex); if (isSeenVertex(oppositeV)) { encounterVertexAgain(oppositeV, edge); } else { encounterVertex(oppositeV, edge); } } } private void encounterStartVertex() { encounterVertex(startVertex, null); startVertex = null; } } DegeneracyOrderingIterator.java000066400000000000000000000121141402514743400337550ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import java.lang.reflect.*; import java.util.*; /** * A degeneracy ordering iterator. * *

    * The degeneracy of a graph $G $is the smallest value d such that every nonempty subgraph of $G$ * contains a vertex of degree at most $d.$ If a graph has degeneracy $d$, then it has a degeneracy * ordering, an ordering such that each vertex has $d$ or fewer neighbors that come later in the * ordering. * *

    * The iterator crosses components but does not track them, it only tracks visited vertices. * *

    * The iterator treats the input graph as undirected even if the graph is directed. Moreover, it * completely ignores self-loops, meaning that it operates as if self-loops do not contribute to the * degree of a vertex. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class DegeneracyOrderingIterator extends AbstractGraphIterator { private Set[] buckets; private Map degrees; private int minDegree; private V cur; /** * Constructor * * @param graph the graph to be iterated */ @SuppressWarnings("unchecked") public DegeneracyOrderingIterator(Graph graph) { super(graph); /* * Count degrees, but skip self-loops */ this.minDegree = Integer.MAX_VALUE; int maxDegree = 0; this.degrees = new HashMap<>(); for (V v : graph.vertexSet()) { int d = 0; for (E e : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (!v.equals(u)) { d++; } } degrees.put(v, d); minDegree = Math.min(minDegree, d); maxDegree = Math.max(maxDegree, d); } minDegree = Math.min(minDegree, maxDegree); /* * Create buckets */ this.buckets = (Set[]) Array.newInstance(Set.class, maxDegree + 1); for (int i = 0; i < buckets.length; i++) { buckets[i] = new HashSet<>(); } for (V v : graph.vertexSet()) { buckets[degrees.get(v)].add(v); } } /** * {@inheritDoc} * * Always returns true since the iterator does not care about components. */ @Override public boolean isCrossComponentTraversal() { return true; } /** * {@inheritDoc} * * Trying to disable the cross components nature of this iterator will result into throwing a * {@link IllegalArgumentException}. */ @Override public void setCrossComponentTraversal(boolean crossComponentTraversal) { if (!crossComponentTraversal) { throw new IllegalArgumentException("Iterator is always cross-component"); } } @Override public boolean hasNext() { if (cur != null) { return true; } cur = advance(); if (cur != null && nListeners != 0) { fireVertexTraversed(createVertexTraversalEvent(cur)); } return cur != null; } @Override public V next() { if (!hasNext()) { throw new NoSuchElementException(); } V result = cur; cur = null; if (nListeners != 0) { fireVertexFinished(createVertexTraversalEvent(result)); } return result; } private V advance() { while (minDegree < buckets.length && buckets[minDegree].isEmpty()) { minDegree++; } V result = null; if (minDegree < buckets.length) { Set b = buckets[minDegree]; V v = b.iterator().next(); b.remove(v); degrees.remove(v); for (E e : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (v.equals(u)) { // ignore self-loop continue; } if (degrees.containsKey(u)) { int uDegree = degrees.get(u); if (uDegree > minDegree) { buckets[uDegree].remove(u); uDegree--; degrees.put(u, uDegree); buckets[uDegree].add(u); } } } result = v; } return result; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/DepthFirstIterator.java000066400000000000000000000146061402514743400323600ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Liviu Rau and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * A depth-first iterator for a directed or undirected graph. * *

    * For this iterator to work correctly the graph must not be modified during iteration. Currently * there are no means to ensure that, nor to fail-fast. The results of such modifications are * undefined. * * @param the graph vertex type * @param the graph edge type * * @author Liviu Rau * @author Barak Naveh */ public class DepthFirstIterator extends CrossComponentIterator { /** * Sentinel object. Unfortunately, we can't use null, because ArrayDeque won't accept those. And * we don't want to rely on the caller to provide a sentinel object for us. So we have to play * typecasting games. */ public static final Object SENTINEL = new Object(); /** * Standard vertex visit state enumeration. */ protected static enum VisitColor { /** * Vertex has not been returned via iterator yet. */ WHITE, /** * Vertex has been returned via iterator, but we're not done with all of its out-edges yet. */ GRAY, /** * Vertex has been returned via iterator, and we're done with all of its out-edges. */ BLACK } private Deque stack = new ArrayDeque<>(); /** * Creates a new depth-first iterator for the specified graph. * * @param g the graph to be iterated. */ public DepthFirstIterator(Graph g) { this(g, (V) null); } /** * Creates a new depth-first iterator for the specified graph. Iteration will start at the * specified start vertex and will be limited to the connected component that includes that * vertex. If the specified start vertex is null, iteration will start at an * arbitrary vertex and will not be limited, that is, will be able to traverse all the graph. * * @param g the graph to be iterated. * @param startVertex the vertex iteration to be started. */ public DepthFirstIterator(Graph g, V startVertex) { super(g, startVertex); } /** * Creates a new depth-first iterator for the specified graph. Iteration will start at the * specified start vertices and will be limited to the connected component that includes those * vertices. If the specified start vertices is null, iteration will start at an * arbitrary vertex and will not be limited, that is, will be able to traverse all the graph. * * @param g the graph to be iterated. * @param startVertices the vertices iteration to be started. */ public DepthFirstIterator(Graph g, Iterable startVertices) { super(g, startVertices); } @Override protected boolean isConnectedComponentExhausted() { for (;;) { if (stack.isEmpty()) { return true; } if (stack.getLast() != SENTINEL) { // Found a non-sentinel. return false; } // Found a sentinel: pop it, record the finish time, // and then loop to check the rest of the stack. // Pop null we peeked at above. stack.removeLast(); // This will pop corresponding vertex to be recorded as finished. recordFinish(); } } @Override protected void encounterVertex(V vertex, E edge) { putSeenData(vertex, VisitColor.WHITE); stack.addLast(vertex); } @Override protected void encounterVertexAgain(V vertex, E edge) { VisitColor color = getSeenData(vertex); if (color != VisitColor.WHITE) { // We've already visited this vertex; no need to mess with the // stack (either it's BLACK and not there at all, or it's GRAY // and therefore just a sentinel). return; } // Since we've encountered it before, and it's still WHITE, it // *must* be on the stack. Use removeLastOccurrence on the // assumption that for typical topologies and traversals, // it's likely to be nearer the top of the stack than // the bottom of the stack. boolean found = stack.removeLastOccurrence(vertex); assert (found); stack.addLast(vertex); } @Override protected V provideNextVertex() { V v; for (;;) { Object o = stack.removeLast(); if (o == SENTINEL) { // This is a finish-time sentinel we previously pushed. recordFinish(); // Now carry on with another pop until we find a non-sentinel } else { // Got a real vertex to start working on v = TypeUtil.uncheckedCast(o); break; } } // Push a sentinel for v onto the stack so that we'll know // when we're done with it. stack.addLast(v); stack.addLast(SENTINEL); putSeenData(v, VisitColor.GRAY); return v; } private void recordFinish() { V v = TypeUtil.uncheckedCast(stack.removeLast()); putSeenData(v, VisitColor.BLACK); finishVertex(v); } /** * Retrieves the LIFO stack of vertices which have been encountered but not yet visited (WHITE). * This stack also contains sentinel entries representing vertices which have been * visited but are still GRAY. A sentinel entry is a sequence (v, SENTINEL), whereas a * non-sentinel entry is just (v). * * @return stack */ public Deque getStack() { return stack; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/GraphIterator.java000066400000000000000000000053021402514743400313360ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.event.*; import java.util.*; /** * A graph iterator. * * @param the graph vertex type * @param the graph edge type * * @author Barak Naveh */ public interface GraphIterator extends Iterator { /** * Test whether this iterator is set to traverse the graph across connected components. * * @return true if traverses across connected components, otherwise * false. */ boolean isCrossComponentTraversal(); /** * Tests whether the reuseEvents flag is set. If the flag is set to * true this class will reuse previously fired events and will not create a new * object for each event. This option increases performance but should be used with care, * especially in multithreaded environment. * * @return the value of the reuseEvents flag. */ boolean isReuseEvents(); /** * Sets a value the reuseEvents flag. If the * reuseEvents flag is set to true this class will reuse previously fired * events and will not create a new object for each event. This option increases performance but * should be used with care, especially in multithreaded environment. * * @param reuseEvents whether to reuse previously fired event objects instead of creating a new * event object for each event. */ void setReuseEvents(boolean reuseEvents); /** * Adds the specified traversal listener to this iterator. * * @param l the traversal listener to be added. */ void addTraversalListener(TraversalListener l); /** * Removes the specified traversal listener from this iterator. * * @param l the traversal listener to be removed. */ void removeTraversalListener(TraversalListener l); /** * Unsupported. * * @throws UnsupportedOperationException always since operation is not supported */ @Override void remove(); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/LexBreadthFirstIterator.java000066400000000000000000000324751402514743400333420ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * A lexicographical breadth-first iterator for an undirected graph. *

    * Every vertex has an implicit label (they aren't used explicitly in order to reduce time and * memory complexity). When some vertex is returned by this iterator, its index is the number of * vertices in this graph minus number of already returned vertices. For a given vertex v its label * is a concatenation of indices of already returned vertices, that were also its neighbours, with * some separator between them. For example, 7#4#3 is a valid vertex label. *

    * Iterator chooses vertex with lexicographically largest label and returns it. It breaks ties * arbitrarily. For more information on lexicographical BFS see the following article: Corneil D.G. * (2004) * Lexicographic Breadth First Search – A Survey. In: Hromkovič J., Nagl M., Westfechtel * B. (eds) Graph-Theoretic Concepts in Computer Science. WG 2004. Lecture Notes in Computer * Science, vol 3353. Springer, Berlin, Heidelberg; and the following * paper:CS 762: * Graph-theoretic algorithms. Lecture notes of a graduate course. University of Waterloo. *

    * For this iterator to work correctly the graph must not be modified during iteration. Currently * there are no means to ensure that, nor to fail-fast. The results of such modifications are * undefined. *

    * Note: only vertex events are fired by this iterator. * * @param the graph vertex type. * @param the graph edge type. * @author Timofey Chudakov */ public class LexBreadthFirstIterator extends AbstractGraphIterator { /** * Reference to the {@code BucketList} that contains unvisited vertices. */ private BucketList bucketList; /** * Contains current vertex of the {@code graph}. */ private V current; /** * Creates new lexicographical breadth-first iterator for {@code graph}. * * @param graph the graph to be iterated. */ public LexBreadthFirstIterator(Graph graph) { super(graph); GraphTests.requireUndirected(graph); bucketList = new BucketList(graph.vertexSet()); } /** * Checks whether there exist unvisited vertices. * * @return true if there exist unvisited vertices. */ @Override public boolean hasNext() { if (current != null) { return true; } current = advance(); if (current != null && nListeners != 0) { fireVertexTraversed(createVertexTraversalEvent(current)); } return current != null; } /** * Returns the next vertex in the ordering. * * @return the next vertex in the ordering. */ @Override public V next() { if (!hasNext()) { throw new NoSuchElementException(); } V result = current; current = null; if (nListeners != 0) { fireVertexFinished(createVertexTraversalEvent(result)); } return result; } /** * {@inheritDoc} *

    * Always returns true since this iterator doesn't care about connected components. */ @Override public boolean isCrossComponentTraversal() { return true; } /** * {@inheritDoc} *

    * Trying to disable the cross components nature of this iterator will result into throwing a * {@link IllegalArgumentException}. */ @Override public void setCrossComponentTraversal(boolean crossComponentTraversal) { if (!crossComponentTraversal) { throw new IllegalArgumentException("Iterator is always cross-component"); } } /** * Retrieves vertex from the {@code bucketList} and returns it. * * @return the vertex retrieved from the {@code bucketList}. */ private V advance() { V vertex = bucketList.poll(); if (vertex != null) { bucketList.updateBuckets(getUnvisitedNeighbours(vertex)); } return vertex; } /** * Computes and returns neighbours of {@code vertex} which haven't been visited by this * iterator. * * @param vertex the vertex, whose neighbours are being explored. * @return neighbours of {@code vertex} which have yet to be visited by this iterator. */ private Set getUnvisitedNeighbours(V vertex) { Set unmapped = new HashSet<>(); Set edges = graph.edgesOf(vertex); for (E edge : edges) { V oppositeVertex = Graphs.getOppositeVertex(graph, edge, vertex); if (bucketList.containsBucketWith(oppositeVertex)) { unmapped.add(oppositeVertex); } } return unmapped; } /** * Data structure for performing lexicographical breadth-first search. Allows to add and * retrieve vertices from buckets, update their buckets after a new vertex has been added to the * LexBFS order. Labels aren't used explicitly, which results in time and space optimization. * * @author Timofey Chudakov */ class BucketList { /** * Bucket with the vertices that have lexicographically largest label. */ private Bucket head; /** * Map for mapping vertices to buckets they are currently in. Is used for finding the bucket * of the vertex in constant time. */ private Map bucketMap; /** * Creates a {@code BucketList} with a single bucket and all specified {@code vertices} in * it. * * @param vertices the vertices of the graph, that should be stored in the {@code head} * bucket. */ BucketList(Collection vertices) { head = new Bucket(vertices); bucketMap = CollectionUtil.newHashMapWithExpectedSize(vertices.size()); for (V vertex : vertices) { bucketMap.put(vertex, head); } } /** * Checks whether there exists a bucket with the specified {@code vertex}. * * @param vertex the vertex whose presence in some {@code Bucket} in this {@code BucketList} * is checked. * @return true if there exists a bucket with {@code vertex} in it, otherwise * false. */ boolean containsBucketWith(V vertex) { return bucketMap.containsKey(vertex); } /** * Retrieves element from the head bucket by invoking {@link Bucket#poll()} or null if this * {@code BucketList} is empty. *

    * Removes the head bucket if it becomes empty after the operation. * * @return vertex returned by {@link Bucket#poll()} invoked on head bucket or null if this * {@code BucketList} is empty. */ V poll() { if (bucketMap.size() > 0) { V res = head.poll(); bucketMap.remove(res); if (head.isEmpty()) { head = head.next; if (head != null) { head.prev = null; } } return res; } else { return null; } } /** * For every bucket B in this {@code BucketList}, which contains vertices from the set * {@code * vertices}, creates a new {@code Bucket} B' and moves vertices from B to B' according to * the following rule: $B' = B\cap vertices$ and $B = B\backslash B'$. For every such * {@code Bucket} B only one {@code Bucket} B' is created. If some bucket B becomes empty * after this operation, it is removed from the data structure. * * @param vertices the vertices, that should be moved to new buckets. */ void updateBuckets(Set vertices) { Set visitedBuckets = new HashSet<>(); for (V vertex : vertices) { Bucket bucket = bucketMap.get(vertex); if (visitedBuckets.contains(bucket)) { bucket.prev.addVertex(vertex); bucketMap.put(vertex, bucket.prev); } else { visitedBuckets.add(bucket); Bucket newBucket = new Bucket(vertex); newBucket.insertBefore(bucket); bucketMap.put(vertex, newBucket); if (head == bucket) { head = newBucket; } } bucket.removeVertex(vertex); if (bucket.isEmpty()) { visitedBuckets.remove(bucket); bucket.removeSelf(); } } } /** * Plays the role of the container of vertices. All vertices stored in a bucket have * identical label. Labels aren't used explicitly. *

    * Encapsulates operations of addition and removal of vertices from the bucket and removal * of a bucket from the data structure. */ private class Bucket { /** * Reference of the bucket with lexicographically smaller label. */ private Bucket next; /** * Reference of the bucket with lexicographically larger label. */ private Bucket prev; /** * Set of vertices currently stored in this bucket. */ private Set vertices; /** * Creates a new bucket with all {@code vertices} stored in it. * * @param vertices vertices to store in this bucket. */ Bucket(Collection vertices) { this.vertices = new HashSet<>(vertices); } /** * Creates a new Bucket with a single {@code vertex} in it. * * @param vertex the vertex to store in this bucket. */ Bucket(V vertex) { this.vertices = new HashSet<>(); vertices.add(vertex); } /** * Removes the {@code vertex} from this bucket. * * @param vertex the vertex to remove. */ void removeVertex(V vertex) { vertices.remove(vertex); } /** * Removes this bucket from the data structure. */ void removeSelf() { if (next != null) { next.prev = prev; } if (prev != null) { prev.next = next; } } /** * Inserts this bucket in the data structure before the {@code bucket}. * * @param bucket the bucket, that will be the next to this bucket. */ void insertBefore(Bucket bucket) { this.next = bucket; if (bucket != null) { this.prev = bucket.prev; if (bucket.prev != null) { bucket.prev.next = this; } bucket.prev = this; } else { this.prev = null; } } /** * Adds the {@code vertex} to this bucket. * * @param vertex the vertex to add. */ void addVertex(V vertex) { vertices.add(vertex); } /** * Retrieves one vertex from this bucket. * * @return vertex, that was removed from this bucket, null if the bucket was empty. */ V poll() { if (vertices.isEmpty()) { return null; } else { V vertex = vertices.iterator().next(); vertices.remove(vertex); return vertex; } } /** * Checks whether this bucket is empty. * * @return true if this bucket doesn't contain any elements, otherwise false. */ boolean isEmpty() { return vertices.size() == 0; } } } } MaximumCardinalityIterator.java000066400000000000000000000167071402514743400340320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * A maximum cardinality search iterator for an undirected graph. *

    * For every vertex in graph its cardinality is defined as the number of its neighbours, which have * been already visited by this iterator. Iterator chooses vertex with the maximum cardinality, * breaking ties arbitrarily. For more information of maximum cardinality search see: Berry, A., * Blair, J., Heggernes, P. et al. Algorithmica (2004) 39: 287. * https://doi.org/10.1007/s00453-004-1084-3 * Maximum Cardinality Search for Computing * Minimal Triangulations. *

    * For this iterator to work correctly the graph must not be modified during iteration. Currently * there are no means to ensure that, nor to fail-fast. The results of such modifications are * undefined. *

    * Note: only vertex events are fired by this iterator. * * @param the graph vertex type. * @param the graph edge type. * @author Timofey Chudakov */ public class MaximumCardinalityIterator extends AbstractGraphIterator { /** * The maximum index of non-empty set in {@code buckets}. */ private int maxCardinality; /** * Number of unvisited vertices. */ private int remainingVertices; /** * Contains current vertex. */ private V current; /** * Disjoint sets of vertices of the graph, indexed by the cardinalities of already visited * neighbours. */ private ArrayList> buckets; /** * Maps every vertex to its cardinality. */ private Map cardinalityMap; /** * Creates a maximum cardinality iterator for the {@code graph}. * * @param graph the graph to be iterated. */ public MaximumCardinalityIterator(Graph graph) { super(graph); remainingVertices = graph.vertexSet().size(); if (remainingVertices > 0) { GraphTests.requireUndirected(graph); buckets = new ArrayList<>(Collections.nCopies(graph.vertexSet().size(), null)); buckets.set(0, new LinkedHashSet<>(graph.vertexSet())); cardinalityMap = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); for (V v : graph.vertexSet()) { cardinalityMap.put(v, 0); } maxCardinality = 0; } } /** * Checks whether there exists unvisited vertex. * * @return true if there exists unvisited vertex. */ @Override public boolean hasNext() { if (current != null) { return true; } current = advance(); if (current != null && nListeners != 0) { fireVertexTraversed(createVertexTraversalEvent(current)); } return current != null; } /** * Returns the next vertex in the ordering. * * @return the next vertex in the ordering. */ @Override public V next() { if (!hasNext()) { throw new NoSuchElementException(); } V result = current; current = null; if (nListeners != 0) { fireVertexFinished(createVertexTraversalEvent(result)); } return result; } /** * {@inheritDoc} *

    * Always returns true since this iterator doesn't care about connected components. */ @Override public boolean isCrossComponentTraversal() { return true; } /** * {@inheritDoc} *

    * Trying to disable the cross components nature of this iterator will result into throwing a * {@link IllegalArgumentException}. */ @Override public void setCrossComponentTraversal(boolean crossComponentTraversal) { if (!crossComponentTraversal) { throw new IllegalArgumentException("Iterator is always cross-component"); } } /** * Retrieves a vertex from the {@code buckets} with the maximum cardinality and returns it. * * @return vertex retrieved from {@code buckets}. */ private V advance() { if (remainingVertices > 0) { Set bucket = buckets.get(maxCardinality); V vertex = bucket.iterator().next(); removeFromBucket(vertex); if (bucket.isEmpty()) { buckets.set(maxCardinality, null); do { --maxCardinality; } while (maxCardinality >= 0 && buckets.get(maxCardinality) == null); } updateNeighbours(vertex); --remainingVertices; return vertex; } else { return null; } } /** * Removes {@code vertex} from the bucket it was contained in. * * @param vertex the vertex, which has to be removed from the bucket it was contained in. * @return the cardinality of the removed vertex or -1, if the vertex wasn't contained in any * bucket. */ private int removeFromBucket(V vertex) { if (cardinalityMap.containsKey(vertex)) { int cardinality = cardinalityMap.get(vertex); buckets.get(cardinality).remove(vertex); cardinalityMap.remove(vertex); if (buckets.get(cardinality).isEmpty()) { buckets.set(cardinality, null); } return cardinality; } return -1; } /** * Adds the {@code vertex} to the bucket with the given {@code cardinality}. * * @param vertex the vertex, which has to be added to the the bucket. * @param cardinality the cardinality of the destination bucket. */ private void addToBucket(V vertex, int cardinality) { cardinalityMap.put(vertex, cardinality); if (buckets.get(cardinality) == null) { buckets.set(cardinality, new LinkedHashSet<>()); } buckets.get(cardinality).add(vertex); } /** * Increments the cardinalities of the neighbours of the {@code vertex} by 1. If the maximum * cardinality increases, increments {@code maxCardinality} by 1. * * @param vertex the vertex whose neighbours are to be updated. */ private void updateNeighbours(V vertex) { Set processed = new HashSet<>(); for (E edge : graph.edgesOf(vertex)) { V opposite = Graphs.getOppositeVertex(graph, edge, vertex); if (cardinalityMap.containsKey(opposite) && !processed.contains(opposite)) { processed.add(opposite); addToBucket(opposite, removeFromBucket(opposite) + 1); } } if (maxCardinality < graph.vertexSet().size() && maxCardinality >= 0 && buckets.get(maxCardinality + 1) != null) { ++maxCardinality; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/RandomWalkIterator.java000066400000000000000000000225231402514743400323400ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import java.util.*; /** * A random walk iterator for a directed or undirected graph. * *

    * At each step the iterator selects a random (uniformly distributed) edge out of the current vertex * and follows it to the next vertex. In case of directed graphs the outgoing edge set is used. See * wikipedia for more * details. * *

    * In case a weighted walk is desired, edges are selected with probability respective to its weight * (out of the total weight of the edges). The walk can be bounded by number of steps (default * {@code Long#MAX_VALUE} . When the bound is reached the iterator is considered exhausted. Calling * {@code next()} on exhausted iterator will throw {@code NoSuchElementException}. * * In case a sink (i.e. no edges) vertex is reached, any consecutive calls to {@code next()} will * throw {@code NoSuchElementException}. * *

    * For this iterator to work correctly the graph must not be modified during iteration. Currently * there are no means to ensure that, nor to fail-fast. The results of such modifications are * undefined. * * @author Assaf Mizrachi * * @param vertex type * @param edge type * @deprecated Use {@link RandomWalkVertexIterator} instead. */ @Deprecated public class RandomWalkIterator extends AbstractGraphIterator { private V currentVertex; private final boolean isWeighted; private boolean sinkReached; private long maxSteps; private Random random; /** * Creates a new iterator for the specified graph. Iteration will start at arbitrary vertex. * Walk is un-weighted and bounded by {@code Long#MAX_VALUE} steps. * * @param graph the graph to be iterated. * * @throws IllegalArgumentException if graph==null or does not contain * startVertex */ public RandomWalkIterator(Graph graph) { this(graph, null); } /** * Creates a new iterator for the specified graph. Iteration will start at the specified start * vertex. If the specified start vertex is * null, Iteration will start at an arbitrary graph vertex. Walk is un-weighted and * bounded by {@code Long#MAX_VALUE} steps. * * @param graph the graph to be iterated. * @param startVertex the vertex iteration to be started. * * @throws IllegalArgumentException if graph==null or does not contain * startVertex */ public RandomWalkIterator(Graph graph, V startVertex) { this(graph, startVertex, true); } /** * Creates a new iterator for the specified graph. Iteration will start at the specified start * vertex. If the specified start vertex is * null, Iteration will start at an arbitrary graph vertex. Walk is bounded by * {@code Long#MAX_VALUE} steps. * * @param graph the graph to be iterated. * @param startVertex the vertex iteration to be started. * @param isWeighted set to true if a weighted walk is desired. * * @throws IllegalArgumentException if graph==null or does not contain * startVertex */ public RandomWalkIterator(Graph graph, V startVertex, boolean isWeighted) { this(graph, startVertex, isWeighted, Long.MAX_VALUE); } /** * Creates a new iterator for the specified graph. Iteration will start at the specified start * vertex. If the specified start vertex is * null, Iteration will start at an arbitrary graph vertex. Walk is bounded by the * provided number steps. * * @param graph the graph to be iterated. * @param startVertex the vertex iteration to be started. * @param isWeighted set to true if a weighted walk is desired. * @param maxSteps number of steps before walk is exhausted. * * @throws IllegalArgumentException if graph==null or does not contain * startVertex */ public RandomWalkIterator(Graph graph, V startVertex, boolean isWeighted, long maxSteps) { this(graph, startVertex, isWeighted, maxSteps, new Random()); } /** * Creates a new iterator for the specified graph. Iteration will start at the specified start * vertex. If the specified start vertex is * null, Iteration will start at an arbitrary graph vertex. Walk is bounded by the * provided number steps. * * @param graph the graph to be iterated. * @param startVertex the vertex iteration to be started. * @param isWeighted set to true if a weighted walk is desired. * @param maxSteps number of steps before walk is exhausted. * @param rng the random number generator to use * * @throws IllegalArgumentException if graph==null or does not contain * startVertex */ public RandomWalkIterator( Graph graph, V startVertex, boolean isWeighted, long maxSteps, Random rng) { super(graph); // do not cross components. this.crossComponentTraversal = false; this.isWeighted = isWeighted; this.maxSteps = maxSteps; // select a random start vertex in case not provided. if (startVertex == null) { if (graph.vertexSet().size() > 0) { currentVertex = graph.vertexSet().iterator().next(); } } else if (graph.containsVertex(startVertex)) { currentVertex = startVertex; } else { throw new IllegalArgumentException("graph must contain the start vertex"); } this.sinkReached = false; this.random = Objects.requireNonNull(rng, "Random number generator cannot be null"); } /** * Check if this walk is exhausted. Calling {@link #next()} on exhausted iterator will throw * {@link NoSuchElementException}. * * @return trueif this iterator is exhausted, false otherwise. */ protected boolean isExhausted() { return maxSteps == 0; } /** * Update data structures every time we see a vertex. * * @param vertex the vertex encountered * @param edge the edge via which the vertex was encountered, or null if the vertex is a * starting point */ protected void encounterVertex(V vertex, E edge) { maxSteps--; } @Override public boolean hasNext() { return currentVertex != null && !isExhausted() && !sinkReached; } @Override public V next() { if (!hasNext()) { throw new NoSuchElementException(); } Set potentialEdges = graph.outgoingEdgesOf(currentVertex); // randomly select an edge from the set of potential edges. E nextEdge = drawEdge(potentialEdges); if (nextEdge != null) { V nextVertex; nextVertex = Graphs.getOppositeVertex(graph, nextEdge, currentVertex); encounterVertex(nextVertex, nextEdge); fireEdgeTraversed(createEdgeTraversalEvent(nextEdge)); fireVertexTraversed(createVertexTraversalEvent(nextVertex)); currentVertex = nextVertex; return nextVertex; } else { sinkReached = true; return currentVertex; } } /** * Randomly draws an edges out of the provided set. In case of un-weighted walk, edge will be * selected with uniform distribution across all outgoing edges. In case of a weighted walk, * edge will be selected with probability respective to its weight across all outgoing edges. * * @param edges the set to select the edge from * @return the drawn edges or null if set is empty. */ private E drawEdge(Set edges) { if (edges.isEmpty()) { return null; } int drawn; List list = new ArrayList(edges); if (isWeighted) { Iterator safeIter = list.iterator(); double border = random.nextDouble() * getTotalWeight(list); double d = 0; drawn = -1; do { d += graph.getEdgeWeight(safeIter.next()); drawn++; } while (d < border); } else { drawn = random.nextInt(list.size()); } return list.get(drawn); } private double getTotalWeight(Collection edges) { double total = 0; for (E e : edges) { total += graph.getEdgeWeight(e); } return total; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/RandomWalkVertexIterator.java000066400000000000000000000123251402514743400335350ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Random; import java.util.stream.Collectors; import org.jgrapht.Graph; import org.jgrapht.Graphs; /** * A random walk iterator. * * "Given a graph and a starting point, we select a neighbor of it at random, and move to this * neighbor; then we select a neighbor of this point at random, and move to it etc. The (random) * sequence of points selected this way is a random walk on the graph." This very simple definition, * together with a comprehensive survey can be found at: "Lovász, L. (1993). Random walks on graphs: * A survey. Combinatorics, Paul erdos is eighty, 2(1), 1-46." * * In its default variant the probability of selecting an outgoing edge is one over the (out) degree * of the vertex. In case the user requests a weighted walk, then the probability of each edge is * equal to its weight divided by the total weight of all outgoing edges. The walk can also be * bounded by a maximum number of hops (edges traversed). The iterator returns * {@link NoSuchElementException} when this bound is reached. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class RandomWalkVertexIterator implements Iterator { private final Random rng; private final Graph graph; private final boolean weighted; private final Map outEdgesTotalWeight; private final long maxHops; private long hops; private V nextVertex; /** * Create a new iterator * * @param graph the graph * @param vertex the starting vertex */ public RandomWalkVertexIterator(Graph graph, V vertex) { this(graph, vertex, Long.MAX_VALUE, false, new Random()); } /** * Create a new iterator * * @param graph the graph * @param vertex the starting vertex * @param maxHops maximum hops to perform during the walk */ public RandomWalkVertexIterator(Graph graph, V vertex, long maxHops) { this(graph, vertex, maxHops, false, new Random()); } /** * Create a new iterator * * @param graph the graph * @param vertex the starting vertex * @param maxHops maximum hops to perform during the walk * @param weighted whether to perform a weighted random walk (compute probabilities based on the * edge weights) * @param rng the random number generator */ public RandomWalkVertexIterator( Graph graph, V vertex, long maxHops, boolean weighted, Random rng) { this.graph = Objects.requireNonNull(graph); this.weighted = weighted; this.outEdgesTotalWeight = new HashMap<>(); this.hops = 0; this.nextVertex = Objects.requireNonNull(vertex); if (!graph.containsVertex(vertex)) { throw new IllegalArgumentException("Random walk must start at a graph vertex"); } this.maxHops = maxHops; this.rng = rng; } @Override public boolean hasNext() { return nextVertex != null; } @Override public V next() { if (nextVertex == null) { throw new NoSuchElementException(); } V value = nextVertex; computeNext(); return value; } private void computeNext() { if (hops >= maxHops) { nextVertex = null; return; } hops++; if (graph.outDegreeOf(nextVertex) == 0) { nextVertex = null; return; } E e = null; if (weighted) { double outEdgesWeight = outEdgesTotalWeight.computeIfAbsent(nextVertex, v -> { return graph .outgoingEdgesOf(v).stream() .collect(Collectors.summingDouble(graph::getEdgeWeight)); }); double p = outEdgesWeight * rng.nextDouble(); double cumulativeP = 0d; for (E curEdge : graph.outgoingEdgesOf(nextVertex)) { cumulativeP += graph.getEdgeWeight(curEdge); if (p <= cumulativeP) { e = curEdge; break; } } } else { List outEdges = new ArrayList<>(graph.outgoingEdgesOf(nextVertex)); e = outEdges.get(rng.nextInt(outEdges.size())); } nextVertex = Graphs.getOppositeVertex(graph, e, nextVertex); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/TopologicalOrderIterator.java000066400000000000000000000141011402514743400335420ustar00rootroot00000000000000/* * (C) Copyright 2004-2021, by Marden Neubert and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.util.*; import java.util.*; /** * A topological ordering iterator for a directed acyclic graph. * *

    * A topological order is a permutation p of the vertices of a graph such that an edge * (i,j) implies that i appears before j in p. * For more information see * wikipedia or * wolfram. * *

    * The iterator crosses components but does not track them, it only tracks visited vertices. The * iterator will detect (at some point) if the graph is not a directed acyclic graph and throw a * {@link IllegalArgumentException}. * *

    * For this iterator to work correctly the graph must not be modified during iteration. Currently * there are no means to ensure that, nor to fail-fast. The results of such modifications are * undefined. * * @param the graph vertex type * @param the graph edge type * * @author Marden Neubert * @author Dimitrios Michail */ public class TopologicalOrderIterator extends AbstractGraphIterator { private static final String GRAPH_IS_NOT_A_DAG = "Graph is not a DAG"; private Queue queue; private Map inDegreeMap; private int remainingVertices; private V cur; /** * Construct a topological order iterator. * *

    * Traversal will start at one of the graph's sources. See the definition of source at * * http://mathworld.wolfram.com/Source.html. In case of partial order, tie-breaking is * arbitrary. * * @param graph the directed graph to be iterated */ public TopologicalOrderIterator(Graph graph) { this(graph, (Comparator) null); } /** * Construct a topological order iterator. * *

    * Traversal will start at one of the graph's sources. See the definition of source at * * http://mathworld.wolfram.com/Source.html. In case of partial order, a comparator is used * to break ties. * * @param graph the directed graph to be iterated * @param comparator comparator in order to break ties in case of partial order */ public TopologicalOrderIterator(Graph graph, Comparator comparator) { super(graph); GraphTests.requireDirected(graph); // create queue if (comparator == null) { this.queue = new ArrayDeque<>(); } else { this.queue = new PriorityQueue<>(comparator); } // count in-degrees this.inDegreeMap = new HashMap<>(); for (V v : graph.vertexSet()) { int d = 0; for (E e : graph.incomingEdgesOf(v)) { V u = Graphs.getOppositeVertex(graph, e, v); if (v.equals(u)) { throw new IllegalArgumentException(GRAPH_IS_NOT_A_DAG); } d++; } inDegreeMap.put(v, new ModifiableInteger(d)); if (d == 0) { queue.offer(v); } } // record vertices count this.remainingVertices = graph.vertexSet().size(); } /** * {@inheritDoc} * * Always returns true since the iterator does not care about components. */ @Override public boolean isCrossComponentTraversal() { return true; } /** * {@inheritDoc} * * Trying to disable the cross components nature of this iterator will result into throwing a * {@link IllegalArgumentException}. */ @Override public void setCrossComponentTraversal(boolean crossComponentTraversal) { if (!crossComponentTraversal) { throw new IllegalArgumentException("Iterator is always cross-component"); } } @Override public boolean hasNext() { if (cur != null) { return true; } cur = advance(); if (cur != null && nListeners != 0) { fireVertexTraversed(createVertexTraversalEvent(cur)); } return cur != null; } @Override public V next() { if (!hasNext()) { throw new NoSuchElementException(); } V result = cur; cur = null; if (nListeners != 0) { fireVertexFinished(createVertexTraversalEvent(result)); } return result; } private V advance() { V result = queue.poll(); if (result != null) { for (E e : graph.outgoingEdgesOf(result)) { V other = Graphs.getOppositeVertex(graph, e, result); ModifiableInteger inDegree = inDegreeMap.get(other); if (inDegree.value > 0) { inDegree.value--; if (inDegree.value == 0) { queue.offer(other); } } } --remainingVertices; } else { /* * Still expecting some vertices, but no vertex has zero degree. */ if (remainingVertices > 0) { throw new IllegalArgumentException(GRAPH_IS_NOT_A_DAG); } } return result; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/traverse/package-info.java000066400000000000000000000001001402514743400310760ustar00rootroot00000000000000/** * Graph traversal means. */ package org.jgrapht.traverse; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/000077500000000000000000000000001402514743400250425ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/AVLTree.java000066400000000000000000001000361402514743400271470ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.*; /** * Implementation of the AVL tree data * structure. Note: this tree doesn't use key comparisons, so this tree can't be used as a * binary search tree. This implies that the same key can be added to this tree multiple times. *

    * AVL tree is a self-balancing binary tree data structure. In an AVL tree, the heights of two child * subtrees differ by at most one. This ensures that the height of the tree is $\mathcal{O}(\log n)$ * where $n$ is the number of elements in the tree. Also this tree doesn't support key comparisons, * it does define an element order. As a result, this tree can be used to query node * successor/predecessor. *

    * Subtree query means that the result is being computed only on the subtree nodes. This tree * supports the following operations: *

      *
    • Min/max insertion and deletion in $\mathcal{O}(\log n)$ time
    • *
    • Subtree min/max queries in $\mathcal{O}(1)$ time
    • *
    • Node successor/predecessor queries in $\mathcal{O}(1)$ time
    • *
    • Tree split in $\mathcal{O}(\log n)$ time
    • *
    • Tree merge in $\mathcal{O}(\log n)$ time
    • *
    *

    * This implementation gives users access to the tree nodes which hold the inserted elements. The * user is able to store the tree nodes references but isn't able to modify them. * * @param the key data type * @author Timofey Chudakov */ public class AVLTree implements Iterable { /** * An auxiliary node which's always present in a tree and doesn't contain any data. */ private TreeNode virtualRoot = new TreeNode<>(null); /** * Modification tracker */ private int modCount = 0; /** * Constructs an empty tree */ public AVLTree() { } /** * Constructor for internal usage * * @param root the root of the newly create tree */ private AVLTree(TreeNode root) { makeRoot(root); } /** * Adds {@code value} as a maximum element to this tree. The running time of this method is * $\mathcal{O}(\log n)$ * * @param value a value to add as a tree max * @return a tree node holding the {@code value} */ public TreeNode addMax(T value) { TreeNode newMax = new TreeNode<>(value); addMaxNode(newMax); return newMax; } /** * Adds the {@code newMax} as a maximum node to this tree. * * @param newMax a node to add as a tree max */ public void addMaxNode(TreeNode newMax) { registerModification(); if (isEmpty()) { virtualRoot.left = newMax; newMax.parent = virtualRoot; } else { TreeNode max = getMax(); max.setRightChild(newMax); balance(max); } } /** * Adds the {@code value} as a minimum element to this tree * * @param value a value to add as a tree min * @return a tree node holding the {@code value} */ public TreeNode addMin(T value) { TreeNode newMin = new TreeNode<>(value); addMinNode(newMin); return newMin; } /** * Adds the {@code newMin} as a minimum node to this tree * * @param newMin a node to add as a tree min */ public void addMinNode(TreeNode newMin) { registerModification(); if (isEmpty()) { virtualRoot.left = newMin; newMin.parent = virtualRoot; } else { TreeNode min = getMin(); min.setLeftChild(newMin); balance(min); } } /** * Splits the tree into two parts. *

    * The first part contains the nodes which are smaller than or equal to the {@code node}. The * first part stays in this tree. The second part contains the nodes which are strictly greater * than the {@code node}. The second part is returned as a tree. * * @param node a separating node * @return a tree containing the nodes which are strictly greater than the {@code node} */ public AVLTree splitAfter(TreeNode node) { registerModification(); TreeNode parent = node.parent; boolean nextMove = node.isLeftChild(); TreeNode left = node.left; TreeNode right = node.right; node.parent.substituteChild(node, null); node.reset(); if (left != null) { left.parent = null; } if (right != null) { right.parent = null; } if (left == null) { left = node; } else { // insert node as a left subtree max TreeNode t = left; while (t.right != null) { t = t.right; } t.setRightChild(node); while (t != left) { TreeNode p = t.parent; p.substituteChild(t, balanceNode(t)); t = p; } left = balanceNode(left); } return split(left, right, parent, nextMove); } /** * Splits the tree into two parts. *

    * The first part contains the nodes which are smaller than the {@code node}. The first part * stays in this tree. The second part contains the nodes which are greater than or equal to the * {@code node}. The second part is returned as a tree. * * @param node a separating node * @return a tree containing the nodes which are greater than or equal to the {@code node} */ public AVLTree splitBefore(TreeNode node) { registerModification(); TreeNode predecessor = predecessor(node); if (predecessor == null) { // node is a minimum node AVLTree tree = new AVLTree<>(); swap(tree); return tree; } return splitAfter(predecessor); } /** * Append the nodes in the {@code tree} after the nodes in this tree. *

    * The result of this operation is stored in this tree. * * @param tree a tree to append */ public void mergeAfter(AVLTree tree) { registerModification(); if (tree.isEmpty()) { return; } else if (tree.getSize() == 1) { addMaxNode(tree.removeMin()); return; } TreeNode junctionNode = tree.removeMin(); TreeNode treeRoot = tree.getRoot(); tree.clear(); makeRoot(merge(junctionNode, getRoot(), treeRoot)); } /** * Prepends the nodes in the {@code tree} before the nodes in this tree. *

    * The result of this operation is stored in this tree. * * @param tree a tree to prepend */ public void mergeBefore(AVLTree tree) { registerModification(); tree.mergeAfter(this); swap(tree); } /** * Removes the minimum node in this tree. Returns {@code null} if this tree is empty * * @return the removed node or {@code null} if this tree is empty */ public TreeNode removeMin() { registerModification(); if (isEmpty()) { return null; } TreeNode min = getMin(); // min.parent != null if (min.parent == virtualRoot) { makeRoot(min.right); } else { min.parent.setLeftChild(min.right); } balance(min.parent); return min; } /** * Removes the maximum node in this tree. Returns {@code null} if this tree is empty * * @return the removed node or {@code null} if this tree is empty */ public TreeNode removeMax() { registerModification(); if (isEmpty()) { return null; } TreeNode max = getMax(); if (max.parent == virtualRoot) { makeRoot(max.left); } else { max.parent.setRightChild(max.left); } balance(max.parent); return max; } /** * Returns the root of this tree or null if this tree is empty. * * @return the root of this tree or null if this tree is empty. */ public TreeNode getRoot() { return virtualRoot.left; } /** * Returns the node following the {@code node} in the order defined by this tree. Returns null * if the {@code node} is the maximum node in the tree. * * @param node a node to compute successor of * @return the successor of the {@code node} */ public TreeNode successor(TreeNode node) { return node.successor; } /** * Returns the node, which is before the {@code node} in the order defined by this tree. Returns * null if the {@code node} is the minimum node in the tree. * * @param node a node to compute predecessor of * @return the predecessor of the {@code node} */ public TreeNode predecessor(TreeNode node) { return node.predecessor; } /** * Returns the minimum node in this tree or null if the tree is empty. * * @return the minimum node in this tree or null if the tree is empty. */ public TreeNode getMin() { return getRoot() == null ? null : getRoot().getSubtreeMin(); } /** * Returns the maximum node in this tree or null if the tree is empty. * * @return the maximum node in this tree or null if the tree is empty. */ public TreeNode getMax() { return getRoot() == null ? null : getRoot().getSubtreeMax(); } /** * Check if this tree is empty * * @return {@code true} if this tree is empty, {@code false otherwise} */ public boolean isEmpty() { return getRoot() == null; } /** * Removes all nodes from this tree. *

    * Note: the memory allocated for the tree structure won't be deallocated until there are * active external referenced to the nodes of this tree. */ public void clear() { registerModification(); virtualRoot.left = null; } /** * Returns the size of this tree * * @return the size of this tree */ public int getSize() { return virtualRoot.left == null ? 0 : virtualRoot.left.subtreeSize; } /** * Makes the {@code node} the root of this tree * * @param node a new root of this tree */ private void makeRoot(TreeNode node) { virtualRoot.left = node; if (node != null) { node.subtreeMax.successor = null; node.subtreeMin.predecessor = null; node.parent = virtualRoot; } } /** * Traverses the tree up until the virtual root and splits it into two parts. *

    * The algorithm is described in Donald E. Knuth. The art of computer programming. Second * Edition. Volume 3 / Sorting and Searching, p. 474. * * @param left a left subtree * @param right a right subtree * @param p next parent node * @param leftMove {@code true} if we're moving from the left child, {@code false} otherwise. * @return the resulting right tree */ private AVLTree split(TreeNode left, TreeNode right, TreeNode p, boolean leftMove) { while (p != virtualRoot) { boolean nextMove = p.isLeftChild(); TreeNode nextP = p.parent; p.parent.substituteChild(p, null); p.parent = null; if (leftMove) { right = merge(p, right, p.right); } else { left = merge(p, p.left, left); } p = nextP; leftMove = nextMove; } makeRoot(left); return new AVLTree<>(right); } /** * Merges the {@code left} and {@code right} subtrees using the {@code junctionNode}. *

    * The algorithm is described in Donald E. Knuth. The art of computer programming. Second * Edition. Volume 3 / Sorting and Searching, p. 474. * * @param junctionNode a node between left and right subtrees * @param left a left subtree * @param right a right subtree * @return the root of the resulting tree */ private TreeNode merge(TreeNode junctionNode, TreeNode left, TreeNode right) { if (left == null && right == null) { junctionNode.reset(); return junctionNode; } else if (left == null) { right.setLeftChild(merge(junctionNode, left, right.left)); return balanceNode(right); } else if (right == null) { left.setRightChild(merge(junctionNode, left.right, right)); return balanceNode(left); } else if (left.getHeight() > right.getHeight() + 1) { left.setRightChild(merge(junctionNode, left.right, right)); return balanceNode(left); } else if (right.getHeight() > left.getHeight() + 1) { right.setLeftChild(merge(junctionNode, left, right.left)); return balanceNode(right); } else { junctionNode.setLeftChild(left); junctionNode.setRightChild(right); return balanceNode(junctionNode); } } /** * Swaps the contents of this tree and the {@code tree} * * @param tree a tree to swap content of */ private void swap(AVLTree tree) { TreeNode t = virtualRoot.left; makeRoot(tree.virtualRoot.left); tree.makeRoot(t); } /** * Performs a right node rotation. * * @param node a node to rotate * @return a new parent of the {@code node} */ private TreeNode rotateRight(TreeNode node) { TreeNode left = node.left; left.parent = null; node.setLeftChild(left.right); left.setRightChild(node); node.updateHeightAndSubtreeSize(); left.updateHeightAndSubtreeSize(); return left; } /** * Performs a left node rotation. * * @param node a node to rotate * @return a new parent of the {@code node} */ private TreeNode rotateLeft(TreeNode node) { TreeNode right = node.right; right.parent = null; node.setRightChild(right.left); right.setLeftChild(node); node.updateHeightAndSubtreeSize(); right.updateHeightAndSubtreeSize(); return right; } /** * Performs a node balancing on the path from {@code node} up until the root * * @param node a node to start tree balancing from */ private void balance(TreeNode node) { balance(node, virtualRoot); } /** * Performs a node balancing on the path from {@code node} up until the {@code stop} node * * @param node a node to start tree balancing from * @param stop a node to stop balancing at (this node is not being balanced) */ private void balance(TreeNode node, TreeNode stop) { if (node == stop) { return; } TreeNode p = node.parent; if (p == virtualRoot) { makeRoot(balanceNode(node)); } else { p.substituteChild(node, balanceNode(node)); } balance(p, stop); } /** * Checks whether the {@code node} is unbalanced. If so, balances the {@code node} * * @param node a node to balance * @return a new parent of {@code node} if the balancing occurs, {@code node} otherwise */ private TreeNode balanceNode(TreeNode node) { node.updateHeightAndSubtreeSize(); if (node.isLeftDoubleHeavy()) { if (node.left.isRightHeavy()) { node.setLeftChild(rotateLeft(node.left)); } rotateRight(node); return node.parent; } else if (node.isRightDoubleHeavy()) { if (node.right.isLeftHeavy()) { node.setRightChild(rotateRight(node.right)); } rotateLeft(node); return node.parent; } return node; } /** * Registers a modifying operation */ private void registerModification() { ++modCount; } /** * {@inheritDoc} */ @Override public String toString() { StringBuilder builder = new StringBuilder(); for (Iterator> i = nodeIterator(); i.hasNext();) { TreeNode node = i.next(); builder.append(node.toString()).append("\n"); } return builder.toString(); } /** * {@inheritDoc} */ @Override public Iterator iterator() { return new TreeValuesIterator(); } /** * Returns an iterator over the tree nodes rather than the node values. The tree are returned in * the same order as the tree values. * * @return an iterator over the tree nodes */ public Iterator> nodeIterator() { return new TreeNodeIterator(); } /** * Iterator over the values stored in this tree. This implementation uses the * {@code TreeNodeIterator} to iterator over the values. */ private class TreeValuesIterator implements Iterator { /** * Internally used {@code TreeNodeIterator} */ private TreeNodeIterator iterator; /** * Constructs a new {@code TreeValuesIterator} */ public TreeValuesIterator() { iterator = new TreeNodeIterator(); } /** * {@inheritDoc} */ @Override public boolean hasNext() { return iterator.hasNext(); } /** * {@inheritDoc} */ @Override public T next() { return iterator.next().getValue(); } } /** * Iterator over the tree nodes. The nodes are returned according to the in order tree * traversal. */ private class TreeNodeIterator implements Iterator> { /** * A node that is returned next or {@code null} if all nodes are traversed */ private TreeNode nextNode; /** * Number of modifications of the tree at the time this iterator is created. */ private final int expectedModCount; /** * Constructs a new {@code TreeNodeIterator} */ public TreeNodeIterator() { nextNode = getMin(); expectedModCount = modCount; } /** * {@inheritDoc} */ @Override public boolean hasNext() { checkForComodification(); return nextNode != null; } /** * {@inheritDoc} */ @Override public TreeNode next() { if (!hasNext()) { throw new NoSuchElementException(); } TreeNode result = nextNode; nextNode = successor(nextNode); return result; } /** * Checks if the tree has been modified during the iteration process */ private void checkForComodification() { if (expectedModCount != modCount) { throw new ConcurrentModificationException(); } } } /** * Container holding the values stored in the tree. * * @param a tree node value type */ public static class TreeNode { /** * A value stored in this tree node */ T value; /** * Parent of this node */ TreeNode parent; /** * Left child of this node */ TreeNode left; /** * Right child of this node */ TreeNode right; /** * Next node in the tree according to the in order traversal */ TreeNode successor; /** * Previous node in the tree according to the in order traversal */ TreeNode predecessor; /** * A minimum node in the subtree rooted at this node */ TreeNode subtreeMin; /** * A maximum node in the subtree rooted at this node */ TreeNode subtreeMax; /** * Height of the node */ int height; /** * Size of the subtree rooted at this node */ int subtreeSize; /** * Constructs a new node with the {@code value} stored in it * * @param value a value to store in this node */ TreeNode(T value) { this.value = value; reset(); } /** * Returns a value stored in this node * * @return a value stored in this node */ public T getValue() { return value; } /** * Returns a root of the tree this node is stored in * * @return a root of the tree this node is stored in */ public TreeNode getRoot() { TreeNode current = this; while (current.parent != null) { current = current.parent; } return current.left; } /** * Returns a minimum node stored in the subtree rooted at this node * * @return a minimum node stored in the subtree rooted at this node */ public TreeNode getSubtreeMin() { return subtreeMin; } /** * Returns a maximum node stored in the subtree rooted at this node * * @return a maximum node stored in the subtree rooted at this node */ public TreeNode getSubtreeMax() { return subtreeMax; } /** * Returns a minimum node stored in the tree * * @return a minimum node stored in the tree */ public TreeNode getTreeMin() { return getRoot().getSubtreeMin(); } /** * Returns a maximum node stored in the tree * * @return a maximum node stored in the tree */ public TreeNode getTreeMax() { return getRoot().getSubtreeMax(); } /** * Returns a parent of this node * * @return a parent of this node */ public TreeNode getParent() { return parent; } /** * Returns a left child of this node * * @return a left child of this node */ public TreeNode getLeft() { return left; } /** * Returns a right child of this node * * @return a right child of this node */ public TreeNode getRight() { return right; } /** * Returns a height of this node * * @return a height of this node */ int getHeight() { return height; } /** * Returns a subtree size of the tree rooted at this node * * @return a subtree size of the tree rooted at this node */ int getSubtreeSize() { return subtreeSize; } /** * Resets this node to the default state */ void reset() { this.height = 1; this.subtreeSize = 1; this.subtreeMin = this; this.subtreeMax = this; this.left = this.right = this.parent = this.predecessor = this.successor = null; } /** * Returns a height of the right subtree * * @return a height of the right subtree */ int getRightHeight() { return right == null ? 0 : right.height; } /** * Returns a height of the left subtree * * @return a height of the right subtree */ int getLeftHeight() { return left == null ? 0 : left.height; } /** * Returns a size of the left subtree * * @return a size of the left subtree */ int getLeftSubtreeSize() { return left == null ? 0 : left.subtreeSize; } /** * Returns a size of the right subtree * * @return a size of the right subtree */ int getRightSubtreeSize() { return right == null ? 0 : right.subtreeSize; } /** * Updates the height and subtree size of this node according to the values of the left and * right children */ void updateHeightAndSubtreeSize() { height = Math.max(getLeftHeight(), getRightHeight()) + 1; subtreeSize = getLeftSubtreeSize() + getRightSubtreeSize() + 1; } /** * Returns {@code true} if this node is unbalanced and the left child's height is greater, * {@code false otherwise} * * @return {@code true} if this node is unbalanced and the left child's height is greater, * {@code false otherwise} */ boolean isLeftDoubleHeavy() { return getLeftHeight() > getRightHeight() + 1; } /** * Returns {@code true} if this node is unbalanced and the right child's height is greater, * {@code false otherwise} * * @return {@code true} if this node is unbalanced and the right child's height is greater, * {@code false otherwise} */ boolean isRightDoubleHeavy() { return getRightHeight() > getLeftHeight() + 1; } /** * Returns {@code true} if the height of the left child is greater than the height of the * right child * * @return {@code true} if the height of the left child is greater than the height of the * right child */ boolean isLeftHeavy() { return getLeftHeight() > getRightHeight(); } /** * Returns {@code true} if the height of the right child is greater than the height of the * left child * * @return {@code true} if the height of the right child is greater than the height of the * left child */ boolean isRightHeavy() { return getRightHeight() > getLeftHeight(); } /** * Returns {@code true} if this node is a left child of its parent, {@code false} otherwise * * @return {@code true} if this node is a left child of its parent, {@code false} otherwise */ boolean isLeftChild() { return this == parent.left; } /** * Returns {@code true} if this node is a right child of its parent, {@code false} otherwise * * @return {@code true} if this node is a right child of its parent, {@code false} otherwise */ boolean isRightChild() { return this == parent.right; } /** * Returns a successor of this node according to the tree in order traversal, or * {@code null} if this node is a maximum node in the tree * * @return successor of this node, or {@code} null if this node in a maximum node in the * tree */ public TreeNode getSuccessor() { return successor; } /** * Returns a predecessor of this node according to the tree in order traversal, or * {@code null} if this node is a minimum node in the tree * * @return predecessor of this node, or {@code} null if this node in a minimum node in the * tree */ public TreeNode getPredecessor() { return predecessor; } /** * Updates the successor reference of this node. If the {@code node} is not {@code null}, * updates its predecessor reference as well * * @param node new successor */ void setSuccessor(TreeNode node) { successor = node; if (node != null) { node.predecessor = this; } } /** * Updates the predecessor reference of this node. If the {@code node} is not {@code null}, * updates its successor reference as well * * @param node new predecessor */ void setPredecessor(TreeNode node) { predecessor = node; if (node != null) { node.successor = this; } } /** * Sets the left child reference of this node to {@code node}. If the {@code node} is not * {@code null}, updates its parent reference as well. * * @param node a new left child */ void setLeftChild(TreeNode node) { left = node; if (node != null) { node.parent = this; setPredecessor(node.subtreeMax); subtreeMin = node.subtreeMin; } else { subtreeMin = this; predecessor = null; } } /** * Sets the right child reference of this node to {@code node}. If the {@code node} is not * {@code null}, updates its parent reference as well. * * @param node a new right child */ void setRightChild(TreeNode node) { right = node; if (node != null) { node.parent = this; setSuccessor(node.subtreeMin); subtreeMax = node.subtreeMax; } else { successor = null; subtreeMax = this; } } /** * Substitutes the {@code prevChild} with the {@code newChild}. If the {@code newChild} is * not {@code null}, updates its parent reference as well * * @param prevChild either left or right child of this node * @param newChild a new child of this node */ void substituteChild(TreeNode prevChild, TreeNode newChild) { assert left == prevChild || right == prevChild; assert !(left == prevChild && right == prevChild); if (left == prevChild) { setLeftChild(newChild); } else { setRightChild(newChild); } } /** * {@inheritDoc} */ @Override public String toString() { return String .format( "{%s}: [parent = %s, left = %s, right = %s], [subtreeMin = %s, subtreeMax = %s], [predecessor = %s, successor = %s], [height = %d, subtreeSize = %d]", value, parent == null ? "null" : parent.value, left == null ? "null" : left.value, right == null ? "null" : right.value, subtreeMin == null ? "null" : subtreeMin.value, subtreeMax == null ? "null" : subtreeMax.value, predecessor == null ? "null" : predecessor.value, successor == null ? "null" : successor.value, height, subtreeSize); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/ArrayUnenforcedSet.java000066400000000000000000000052111402514743400314470ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.*; /** * Helper for efficiently representing small sets whose elements are known to be unique by * construction, implying we don't need to enforce the uniqueness property in the data structure * itself. Use with caution. * *

    * Note that for equals/hashCode, the class implements the Set behavior (unordered), not the list * behavior (ordered); the fact that it subclasses ArrayList should be considered an implementation * detail. * * @param the element type * * @author John V. Sichi */ public class ArrayUnenforcedSet extends ArrayList implements Set { private static final long serialVersionUID = -7413250161201811238L; /** * Constructs a new empty set */ public ArrayUnenforcedSet() { super(); } /** * Constructs a set containing the elements of the specified collection. * * @param c the collection whose elements are to be placed into this set * @throws NullPointerException if the specified collection is null */ public ArrayUnenforcedSet(Collection c) { super(c); } /** * Constructs an empty set with the specified initial capacity. * * @param n the initial capacity of the set * @throws IllegalArgumentException if the specified initial capacity is negative */ public ArrayUnenforcedSet(int n) { super(n); } @Override public boolean equals(Object o) { return new SetForEquality().equals(o); } @Override public int hashCode() { return new SetForEquality().hashCode(); } /** * Multiple inheritance helper. */ private class SetForEquality extends AbstractSet { @Override public Iterator iterator() { return ArrayUnenforcedSet.this.iterator(); } @Override public int size() { return ArrayUnenforcedSet.this.size(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/ArrayUtil.java000066400000000000000000000044531402514743400276270ustar00rootroot00000000000000/* * (C) Copyright 2021-2021, by Hannes Wellmann and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; /** * Utility class to simplify handling of arrays. * * @author Hannes Wellmann * */ public class ArrayUtil { private ArrayUtil() { // static use only } /** * Reverses the order of the elements in the specified range within the given array. * * @param the type of elements in the array * @param arr the array * @param from the index of the first element (inclusive) inside the range to reverse * @param to the index of the last element (inclusive) inside the range to reverse */ public static final void reverse(V[] arr, int from, int to) { for (int i = from, j = to; i < j; ++i, --j) { swap(arr, i, j); } } /** * Reverses the order of the elements in the specified range within the given array. * * @param arr the array * @param from the index of the first element (inclusive) inside the range to reverse * @param to the index of the last element (inclusive) inside the range to reverse */ public static final void reverse(int[] arr, int from, int to) { for (int i = from, j = to; i < j; ++i, --j) { int tmp = arr[j]; arr[j] = arr[i]; arr[i] = tmp; } } /** * Swaps the two elements at the specified indices in the given array. * * @param the type of elements in the array * @param arr the array * @param i the index of the first element * @param j the index of the second element */ public static final void swap(V[] arr, int i, int j) { V tmp = arr[j]; arr[j] = arr[i]; arr[i] = tmp; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/CollectionUtil.java000066400000000000000000000136171402514743400306460ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Hannes Wellmann and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.*; /** * Utility class to create {@link Collection} instances. * * @author Hannes Wellmann * */ public class CollectionUtil { private CollectionUtil() { // static use only } /** * Returns a {@link HashMap} with an initial capacity that is sufficient to hold * {@code expectedSize} mappings without rehashing its internal backing storage. *

    * The returned {@code HashMap} has a capacity that is the specified expected size divided by * the load factor of the Map, which is sufficient to hold {@code expectedSize} mappings without * rehashing. As the Javadoc of {@link HashMap} states: "If the initial capacity is greater than * the maximum number of entries divided by the load factor, no rehash operations will ever * occur". *

    * * @param the type of keys in the returned {@code HashMap} * @param the type of values in the returned {@code HashMap} * @param expectedSize of mappings that will be put into the returned {@code HashMap} * @return an empty {@code HashMap} with sufficient capacity to hold expectedSize mappings * @see HashMap */ public static HashMap newHashMapWithExpectedSize(int expectedSize) { return new HashMap<>(capacityForSize(expectedSize)); } /** * Returns a {@link LinkedHashMap} with an initial capacity that is sufficient to hold * {@code expectedSize} mappings without rehashing its internal backing storage. *

    * Because {@code LinkedHashMap} extends {@link HashMap} it inherits the issue that the capacity * is not equivalent to the number of mappings it can hold without rehashing. See * {@link #newHashMapWithExpectedSize(int)} for details. *

    * * @param the type of keys in the returned {@code LinkedHashMap} * @param the type of values in the returned {@code LinkedHashMap} * @param expectedSize of mappings that will be put into the returned {@code LinkedHashMap} * @return an empty {@code LinkedHashMap} with sufficient capacity to hold expectedSize mappings * @see HashMap */ public static LinkedHashMap newLinkedHashMapWithExpectedSize(int expectedSize) { return new LinkedHashMap<>(capacityForSize(expectedSize)); } /** * Returns a {@link HashSet} with an initial capacity that is sufficient to hold * {@code expectedSize} elements without rehashing its internal backing storage. *

    * Because a {@code HashSet} is backed by a {@link HashMap} it inherits the issue that the * capacity is not equivalent to the number of elements it can hold without rehashing. See * {@link #newHashMapWithExpectedSize(int)} for details. *

    * * @param the type of elements in the returned {@code HashSet} * @param expectedSize of elements that will be add to the returned {@code HashSet} * @return an empty {@code HashSet} with sufficient capacity to hold expectedSize elements * @see HashMap */ public static HashSet newHashSetWithExpectedSize(int expectedSize) { return new HashSet<>(capacityForSize(expectedSize)); } /** * Returns a {@link LinkedHashSet} with an initial capacity that is sufficient to hold * {@code expectedSize} elements without rehashing its internal backing storage. *

    * Because a {@code LinkedHashSet} is backed by a {@link HashMap} it inherits the issue that the * capacity is not equivalent to the number of elements it can hold without rehashing. See * {@link #newHashMapWithExpectedSize(int)} for details. *

    * * @param the type of elements in the returned {@code LinkedHashSet} * @param expectedSize of elements that will be add to the returned {@code LinkedHashSet} * @return an empty {@code LinkedHashSet} with sufficient capacity to hold expectedSize elements * @see HashMap */ public static LinkedHashSet newLinkedHashSetWithExpectedSize(int expectedSize) { return new LinkedHashSet<>(capacityForSize(expectedSize)); } private static int capacityForSize(int size) { // consider default load factor 0.75f of (Linked)HashMap return (int) (size / 0.75f + 1.0f); // let (Linked)HashMap limit it if it's too large } /** * Returns from the given {@code Iterable} the element with the given {@code index}. *

    * The order to which the index applies is that defined by the {@link Iterable#iterator()}. *

    * * @param the type of elements in the {@code Iterable} * @param iterable the Iterable from which the element at {@code index} is returned * @param index the index of the returned element * @return the element with {@code index} in the {@code iterable} */ public static E getElement(Iterable iterable, int index) { if (iterable instanceof List) { return ((List) iterable).get(index); } Iterator it = iterable.iterator(); for (int i = 0; i < index && it.hasNext(); i++) { it.next(); } if (it.hasNext()) { return it.next(); } else { throw new IndexOutOfBoundsException(index); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/ConcurrencyUtil.java000066400000000000000000000051301402514743400310340ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * Utility class to manage creation and shutting down instance of the {@link ThreadPoolExecutor}. */ public class ConcurrencyUtil { /** * Creates a {@link ThreadPoolExecutor} with fixed number of threads which is equal to * {@code parallelism}. * * @param parallelism number of thread for the executor * @return created executor */ public static ThreadPoolExecutor createThreadPoolExecutor(int parallelism) { return (ThreadPoolExecutor) Executors.newFixedThreadPool(parallelism); } /** * Shuts down the {@code executor}. This operation puts the {@code service} into a state where * every subsequent task submitted to the {@code service} will be rejected. This method calls * {@link #shutdownExecutionService(ExecutorService, long, TimeUnit)} with $time = * Long.MAX_VALUE$ and $timeUnit = TimeUnit.MILLISECONDS$. * * @param service service to be shut down */ public static void shutdownExecutionService(ExecutorService service) throws InterruptedException { shutdownExecutionService(service, Long.MAX_VALUE, TimeUnit.MILLISECONDS); } /** * Shuts down the {@code executor}. This operation puts the {@code service} into a state where * every subsequent task submitted to the {@code service} will be rejected. * * @param service service to be shut down * @param time period of time to wait for the completion of the termination * @param timeUnit time duration granularity for the provided {@code time} */ public static void shutdownExecutionService( ExecutorService service, long time, TimeUnit timeUnit) throws InterruptedException { service.shutdown(); service.awaitTermination(time, timeUnit); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/DoublyLinkedList.java000066400000000000000000001241551402514743400311360ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import org.jgrapht.alg.util.*; import java.util.*; import java.util.function.*; /** * {@code DoublyLinkedList} implements a doubly linked {@link List} data structure, that exposes its * {@link ListNode ListNodes} where the data is stored in. *

    * An element holding {@code ListNode} can be removed or added to a {@code DoublyLinkedList} in * constant time O(1). Other methods that operate on {@code ListNodes} directly also have constant * runtime. This is also the case for methods that operate on the first(head) and last(tail) node or * element. Random access methods have a runtime O(n) that is linearly dependent on the size of the * {@code DoublyLinkedList}. *

    *

    * A {@code DoublyLinkedList} supports {@code null} elements but does not support * {@code null ListNodes}. This class is not thread safe and needs to be synchronized externally if * modified by concurrent threads. *

    *

    * The iterators over this list have a fail-fast behavior meaning that they throw a * {@link ConcurrentModificationException} after they detect a structural modification of the list, * that they're not responsible for. *

    *

    * This class is similar to {@link LinkedList}. The general difference is that the {@code ListNodes} * of this {@code List} are accessible and can be removed or added directly. To ensure the integrity * of the {@code List} nodes of this List have a reference to the List they belong to. This * increases the memory occupied by this list implementation compared to {@code LinkedList} for the * same elements. Instances of {@code LinkedList.Node} have three references each (the element, next * and previous), instances of {@code DoublyLinkedList.ListNode} have four (the element, next, * previous and the list). *

    * * @param the list element type * @author Timofey Chudakov * @author Hannes Wellmann */ public class DoublyLinkedList extends AbstractSequentialList implements Deque { /** The first element of the list, {@code null} if this list is empty. */ private ListNodeImpl head = null; private int size; private ListNodeImpl tail() { return head.prev; } /** * {@inheritDoc} */ @Override public boolean isEmpty() { return head == null; } /** * {@inheritDoc} */ @Override public int size() { return size; } /** * {@inheritDoc} */ @Override public void clear() { if (!isEmpty()) { ListNodeImpl node = head; do { ListNodeImpl next = node.next; boolean removed = removeListNode(node); // clears all links of removed node assert removed; node = next; } while (node != head); head = null; assert size == 0; } } // internal modification methods /** * Adds the given {@link ListNode} to this {@code List}. *

    * Sets the {@code list} reference of {@code node} to this list, increases this lists * {@code size} and {@code modcount} by one. *

    * * @param node the node to add to this list * @throws IllegalArgumentException if {@code node} is already contained in this or another * {@code DoublyLinkedList} */ private void addListNode(ListNodeImpl node) { // call this before any modification of this list is done if (node.list != null) { String list = (node.list == this) ? "this" : "other"; throw new IllegalArgumentException( "Node <" + node + "> already contained in " + list + " list"); } node.list = this; size++; modCount++; } /** * Atomically moves all {@link ListNode ListNodes} from {@code list} to this list as if each * node was removed with {@link #removeListNode(ListNodeImpl)} from {@code list} and * subsequently added to this list by {@link #addListNode(ListNodeImpl)}. */ private void moveAllListNodes(DoublyLinkedList list) { // call this before any modification of this list is done for (ListNodeIteratorImpl it = list.new ListNodeIteratorImpl(0); it.hasNext();) { ListNodeImpl node = it.nextNode(); assert node.list == list; node.list = this; } size += list.size; list.size = 0; modCount++; list.modCount++; } /** * Removes the given {@link ListNode} from this {@code List}, if it is contained in this * {@code List}. *

    * If {@code node} is contained in this list, sets the {@code list}, {@code next} and * {@code prev} reference of {@code node} to {@code null} decreases this list's {@code size} and * increases the {@code modcount} by one. *

    * * @param node the node to remove from this list * @return true if {@code node} was removed from this list, else false */ private boolean removeListNode(ListNodeImpl node) { // call this before any modification of this list is done if (node.list == this) { node.list = null; node.next = null; node.prev = null; size--; modCount++; return true; } return false; } /** * Establishes the links between the given {@link ListNodeImpl nodes} in such a way that the * {@code predecessor} is linked before the {@code successor}. * * @param predecessor the first node linked before the other * @param successor the second node linked after the other */ private void link(ListNodeImpl predecessor, ListNodeImpl successor) { predecessor.next = successor; successor.prev = predecessor; } /** Insert non null {@code node} before non null {@code successor} into the list. */ private void linkBefore(ListNodeImpl node, ListNodeImpl successor) { addListNode(node); link(successor.prev, node); link(node, successor); } /** Insert non null {@code node} as last node into the list. */ private void linkLast(ListNodeImpl node) { if (isEmpty()) { // node will be the first and only one addListNode(node); link(node, node); // self link head = node; } else { linkBefore(node, head); } } /** Insert non null {@code list} before node at {@code index} into the list. */ private void linkListIntoThisBefore(int index, DoublyLinkedList list) { int previousSize = size; moveAllListNodes(list); // link list's node into this list if (previousSize == 0) { head = list.head; // head and tail already linked together } else { ListNodeImpl refNode = (index == previousSize) ? head : getNodeAt(index); ListNodeImpl listTail = list.tail(); link(refNode.prev, list.head); // changes list.tail() link(listTail, refNode); if (index == 0) { head = list.head; } } // clear list but do not call list.clear(), since their nodes are still used list.head = null; } /** Remove the non null {@code node} from the list. */ private boolean unlink(ListNodeImpl node) { ListNodeImpl prev = node.prev; ListNodeImpl next = node.next; if (removeListNode(node)) { // clears prev and next of node if (size == 0) { head = null; } else { // list is circular, don't have to worry about null values link(prev, next); if (head == node) { head = next; } } return true; } return false; } // ---------------------------------------------------------------------------- // public modification and access methods // ListNode methods: // Base methods to access, add and remove nodes to/from this list. // Used by all public methods if possible /** * Inserts the specified {@link ListNode node} at the specified position in this list. *

    * This method has a linear runtime complexity O(n) that depends linearly on the distance of the * index to the nearest end. Adding {@code node} as first or last takes only constant time O(1). *

    * * @param index index at which the specified {@code node} is to be inserted * @param node the node to add * @throws IndexOutOfBoundsException if the index is out of range * ({@code index < 0 || index > size()}) * @throws IllegalArgumentException if {@code node} is already part of this or another * {@code DoublyLinkedList} * @throws NullPointerException if {@code node} is {@code null} */ public void addNode(int index, ListNode node) { ListNodeImpl nodeImpl = (ListNodeImpl) node; if (index == size) { // also true if this is empty linkLast(nodeImpl); } else { ListNodeImpl successor = index == 0 ? head : getNodeAt(index); linkBefore(nodeImpl, successor); if (head == successor) { head = nodeImpl; } } } /** * Inserts the specified {@link ListNode node} at the front of this list. *

    * This method has constant runtime complexity O(1). *

    * * @param node the node to add * @throws IllegalArgumentException if {@code node} is already part of this or another * {@code DoublyLinkedList} * @throws NullPointerException if {@code node} is {@code null} */ public void addNodeFirst(ListNode node) { addNode(0, node); } /** * Inserts the specified {@link ListNode node} at the end of this list. *

    * This method has constant runtime complexity O(1). *

    * * @param node the node to add * @throws IllegalArgumentException if {@code node} is already part of this or another * {@code DoublyLinkedList} * @throws NullPointerException if {@code node} is {@code null} */ public void addNodeLast(ListNode node) { addNode(size, node); } /** * Inserts the specified {@link ListNode node} before the specified {@code successor} in this * list. *

    * This method has constant runtime complexity O(1). *

    * * @param node the node to add * @param successor {@code ListNode} before which the {@code node} is inserted * @throws IllegalArgumentException if {@code node} is already contained in this or another * {@code DoublyLinkedList} or {@code successor} is not contained in this list * @throws NullPointerException if {@code successor} or {@code node} is {@code null} */ public void addNodeBefore(ListNode node, ListNode successor) { ListNodeImpl successorImpl = (ListNodeImpl) successor; ListNodeImpl nodeImpl = (ListNodeImpl) node; if (successorImpl.list != this) { throw new IllegalArgumentException("Node <" + successorImpl + "> not in this list"); } linkBefore(nodeImpl, successorImpl); if (head == successorImpl) { head = nodeImpl; } } /** * Returns the first {@link ListNode node} of this list. *

    * This method has constant runtime complexity O(1). *

    * * @return the first {@code ListNode} of this list * @throws NoSuchElementException if this list is empty */ public ListNode getFirstNode() { if (isEmpty()) { throw new NoSuchElementException(); } return head; } /** * Returns the last {@link ListNode node} of this list. *

    * This method has constant runtime complexity O(1). *

    * * @return the last {@code ListNode} of this list * @throws NoSuchElementException if this list is empty */ public ListNode getLastNode() { if (isEmpty()) { throw new NoSuchElementException(); } return tail(); } /** * Returns the {@link ListNode node} at the specified position in this list. *

    * This method has linear runtime complexity O(n). *

    * * @param index index of the {@code ListNode} to return * @return the {@code ListNode} at the specified position in this list * @throws IndexOutOfBoundsException if the index is out of range * ({@code index < 0 || index >= size()}) */ public ListNode getNode(int index) { return getNodeAt(index); } /** * Returns the {@link ListNodeImpl node} at the specified position in this list. * * @param index index of the {@code ListNodeImpl} to return * @return the {@code ListNode} at the specified position in this list * @throws IndexOutOfBoundsException if the index is out of range * ({@code index < 0 || index >= size()}) */ private ListNodeImpl getNodeAt(int index) { if (index < 0 || size <= index) { throw new IndexOutOfBoundsException("Index: " + index); } ListNodeImpl node; if (index < size / 2) { node = head; for (int i = 0; i < index; i++) { node = node.next; } } else { node = tail(); for (int i = size - 1; index < i; i--) { node = node.prev; } } return node; } /** * Returns the index of the specified {@link ListNode node} in this list, or -1 if this list * does not contain the {@code node}. *

    * More formally, returns the index {@code i} such that {@code node == getNode(i)}, or -1 if * there is no such index. Because a {@code ListNode} is contained in at most one list exactly * once, the returned index (if not -1) is the only occurrence of that {@code node}. *

    *

    * This method has linear runtime complexity O(n) to find {@code node} but returns in constant * time O(1) if {@code node} is not {@link #containsNode(ListNode) contained} in this list. *

    * * @param node the node to search for * @return the index of the specified {@code node} in this list, or -1 if this list does not * contain {@code node} * @throws NullPointerException if {@code node} is {@code null} */ public int indexOfNode(ListNode node) { if (!containsNode(node)) { return -1; } ListNodeImpl current = head; for (int i = 0; i < size; i++) { if (current == node) { return i; } current = current.next; } // should never happen: throw new IllegalStateException("Node contained in list not found: " + node); } /** * Returns true if this {@code DoublyLinkedList} contains the specified {@link ListNode}. *

    * This method has constant runtime complexity O(1). *

    * * @param node the node whose presence in this {@code DoublyLinkedList} is to be tested * @return true if this {@code DoublyLinkedList} contains the {@link ListNode} * @throws NullPointerException if {@code node} is {@code null} */ public boolean containsNode(ListNode node) { return ((ListNodeImpl) node).list == this; } /** * Removes the {@link ListNode node} from this list. Returns true if {@code node} was in this * list and is now removed. If {@code node} is not contained in this list, the list is left * unchanged. *

    * This method has constant runtime complexity O(1). *

    * * @param node the node to remove from this list * @return true if node was removed from this list * @throws NullPointerException if {@code node} is {@code null} */ public boolean removeNode(ListNode node) { return unlink((ListNodeImpl) node); } /** * Returns the first {@link ListNode node} holding the specified {@code element} in this list. * More formally, returns the first {@code ListNode} such that * {@code Objects.equals(element, node.getValue())}, or {@code null} if there is no such node. *

    * This method has linear runtime complexity O(n). *

    * * @param element the element whose {@code ListNode} is to return * @return the first {@code ListNode} holding the {@code element} or null if no node was found */ public ListNode nodeOf(Object element) { return searchNode(() -> head, n -> n.next, element).getFirst(); } /** * Returns the last {@link ListNode node} holding the specified {@code element} in this list. * More formally, returns the last {@code ListNode} such that * {@code Objects.equals(element, node.getValue())}, or {@code null} if there is no such node. *

    * This method has linear runtime complexity O(n). *

    * * @param element the element whose {@code ListNode} is to return * @return the last {@code ListNode} holding the {@code element} or null if no node was found */ public ListNode lastNodeOf(Object element) { return searchNode(this::tail, n -> n.prev, element).getFirst(); } /** * Returns a {@link Pair} of the first encountered {@link ListNode} in this list, whose * {@code value} is equal to the given {@code element}, and its index. Or if this list does not * contain such node a Pair of {@code null} and {@code -1}; *

    * The search starts at the node supplied by {@code first} and advances in the direction induced * by the specified {@code next} operator. *

    * * @param first supplier of the first node to check if this list is not empty * @param next {@code Function} to get from the current node the next node to check * @param element the element for that the first node with equal value is searched. * @return a {@link Pair} of the first encountered {@code ListNode} holding a {@code value} * equal to {@code element} and its index, or if no such node was found a * {@code Pair.of(null, -1)} */ private Pair, Integer> searchNode( Supplier> first, UnaryOperator> next, Object element) { if (!isEmpty()) { int index = 0; ListNodeImpl firstNode = first.get(); ListNodeImpl node = firstNode; do { if (Objects.equals(node.value, element)) { return Pair.of(node, index); } index++; node = next.apply(node); } while (node != firstNode); } return Pair.of(null, -1); } /** * Inserts the specified element at the front of this list. Returns the {@link ListNode} * allocated to store the {@code value}. The returned {@code ListNode} is the new head of the * list. *

    * This method is equivalent to {@link #addFirst(Object)} but returns the allocated * {@code ListNode}. *

    * * @param element the element to add * @return the {@code ListNode} allocated to store the {@code value} */ public ListNode addElementFirst(E element) { ListNode node = new ListNodeImpl<>(element); addNode(0, node); return node; } /** * Inserts the specified element at the end of this list. Returns the {@link ListNode} allocated * to store the {@code value}. The returned {@code ListNode} is the new tail of the list. *

    * This method is equivalent to {@link #addLast(Object)} but returns the allocated * {@code ListNode}. *

    * * @param element the element to add * @return the {@code ListNode} allocated to store the {@code value} */ public ListNode addElementLast(E element) { ListNode node = new ListNodeImpl<>(element); addNode(size, node); return node; } /** * Inserts the specified element before the specified {@link ListNode successor} in this list. * Returns the {@code ListNode} allocated to store the {@code value}. * * @param successor {@code ListNode} before which the node holding {@code value} is inserted * @param element the element to add * @return the {@code ListNode} allocated to store the {@code value} * @throws IllegalArgumentException if {@code successor} is not contained in this list * @throws NullPointerException if {@code successor} is {@code null} */ public ListNode addElementBeforeNode(ListNode successor, E element) { ListNode node = new ListNodeImpl<>(element); addNodeBefore(node, successor); return node; } // List methods (shortcut for most commonly used methods to avoid iterator creation) /** * {@inheritDoc} */ @Override public void add(int index, E element) { if (index == size) { // also true if this is empty addElementLast(element); } else { addElementBeforeNode(getNode(index), element); } } /** * {@inheritDoc} */ @Override public E get(int index) { return getNodeAt(index).value; } /** * {@inheritDoc} */ @Override public E remove(int index) { ListNode node = getNode(index); removeNode(node); return node.getValue(); } // Deque methods /** * {@inheritDoc} */ @Override public void addFirst(E e) { addElementFirst(e); } /** * {@inheritDoc} */ @Override public void addLast(E e) { addElementLast(e); } /** * {@inheritDoc} */ @Override public boolean offerFirst(E e) { addElementFirst(e); return true; } /** * {@inheritDoc} */ @Override public boolean offerLast(E e) { addElementLast(e); return true; } /** * {@inheritDoc} */ @Override public E removeFirst() { if (isEmpty()) { throw new NoSuchElementException(); } ListNode node = head; removeNode(node); // changes head return node.getValue(); } /** * {@inheritDoc} */ @Override public E removeLast() { if (isEmpty()) { throw new NoSuchElementException(); } ListNode node = tail(); removeNode(node); // changes tail return node.getValue(); } /** * {@inheritDoc} */ @Override public E pollFirst() { if (isEmpty()) { return null; } ListNode node = head; removeNode(node); // changes head return node.getValue(); } /** * {@inheritDoc} */ @Override public E pollLast() { if (isEmpty()) { return null; } ListNode node = tail(); removeNode(node); // changes tail() return node.getValue(); } /** * {@inheritDoc} */ @Override public E getFirst() { return getFirstNode().getValue(); } /** * {@inheritDoc} */ @Override public E getLast() { return getLastNode().getValue(); } /** * {@inheritDoc} */ @Override public E peekFirst() { return isEmpty() ? null : getFirst(); } /** * {@inheritDoc} */ @Override public E peekLast() { return isEmpty() ? null : getLast(); } /** * {@inheritDoc} */ @Override public boolean removeFirstOccurrence(Object o) { ListNode node = nodeOf(o); if (node != null) { removeNode(node); return true; } return false; } /** * {@inheritDoc} */ @Override public boolean removeLastOccurrence(Object o) { ListNode node = lastNodeOf(o); if (node != null) { removeNode(node); return true; } return false; } // Queue methods /** * {@inheritDoc} */ @Override public boolean offer(E e) { return offerLast(e); } /** * {@inheritDoc} */ @Override public E remove() { return removeFirst(); } /** * {@inheritDoc} */ @Override public E poll() { return pollFirst(); } /** * {@inheritDoc} */ @Override public E element() { return getFirst(); } /** * {@inheritDoc} */ @Override public E peek() { return peekFirst(); } // Stack methods /** * {@inheritDoc} */ @Override public void push(E e) { addFirst(e); } /** * {@inheritDoc} */ @Override public E pop() { return removeFirst(); } // special bulk methods /** * Inverts the list. For instance, calling this method on the list $(a,b,c,\dots,x,y,z)$ will * result in the list $(z,y,x,\dots,c,b,a)$. This method does only pointer manipulation, meaning * that all the list nodes allocated for the previously added elements are valid after this * method finishes. */ public void invert() { if (size < 2) { return; } ListNodeImpl newHead = tail(); ListNodeImpl current = head; do { ListNodeImpl next = current.next; current.next = current.prev; current.prev = next; current = next; } while (current != head); head = newHead; ++modCount; } /** * Moves all {@link ListNode ListNodes} of the given {@code sourceList} to this list and inserts * them all before the node previously at the given position. All the {@code nodes} of * {@code movedList} are moved to this list. When this method terminates this list contains all * nodes of {@code movedList} and {@code movedList} is empty. * * @param index index of the first element of {@code list} in this {@code list} after it was * added * @param movedList the {@code DoublyLinkedList} to move to this one * @throws NullPointerException if {@code movedList} is {@code null} */ public void moveFrom(int index, DoublyLinkedList movedList) { linkListIntoThisBefore(index, movedList); } /** * Appends the {@code movedList} to the end of this list. All the elements from * {@code movedList} are transferred to this list, i.e. the {@code list} is empty after calling * this method. * * @param movedList the {@code DoublyLinkedList} to append to this one * @throws NullPointerException if {@code movedList} is {@code null} */ public void append(DoublyLinkedList movedList) { moveFrom(size, movedList); } /** * Prepends the {@code movedList} to the beginning of this list. All the elements from * {@code movedList} are transferred to this list, i.e. the {@code movedList} is empty after * calling this method. * * @param movedList the {@code DoublyLinkedList} to prepend to this one * @throws NullPointerException if {@code movedList} is {@code null} */ public void prepend(DoublyLinkedList movedList) { moveFrom(0, movedList); } // ---------------------------------------------------------------------------- // (List)Iterators /** * Returns a {@link NodeIterator} that starts at the first {@link ListNode} of this list that is * equal to the specified {@code firstElement}, iterates in forward direction over the end of * this list until the first node. *

    * The first call to {@link NodeIterator#nextNode()} returns the first {@code node} that holds a * value such that {@code Objects.equals(node.getValue, firstElement)} returns {@code true}. The * returned {@code NodeIterator} iterates in forward direction returning the respective next * element in subsequent calls to {@code next(Node)}. The returned iterator ignores the actual * bounds of this {@code DoublyLinkedList} and iterates until the node before the first one is * reached. Its {@link NodeIterator#hasNext() hasNext()} returns {@code false} if the next node * would be the first one. *

    * * @param firstElement the element equal to the first {@code next()} * @return a circular {@code NodeIterator} iterating forward from {@code firstElement} */ public NodeIterator circularIterator(E firstElement) { ListNodeImpl startNode = (ListNodeImpl) nodeOf(firstElement); if (startNode == null) { throw new NoSuchElementException(); } return new ListNodeIteratorImpl(0, startNode); } /** * Returns a {@link NodeIterator} that starts at the first {@link ListNode} of this list that is * equal to the specified {@code firstElement}, iterates in reverse direction over the end of * this list until the first node. *

    * The first call to {@link NodeIterator#nextNode()} returns the first {@code node} that holds a * value such that {@code Objects.equals(node.getValue, firstElement)} returns {@code true}. The * returned {@code NodeIterator} iterates in reverse direction returning the respective previous * element in subsequent calls to {@code next(Node)}. The returned iterator ignores the actual * bounds of this {@code DoublyLinkedList} and iterates until the node before the first one is * reached. Its {@link NodeIterator#hasNext() hasNext()} returns {@code false} if the next node * would be the first one. *

    * * @param firstElement the element equal to the first {@code next()} * @return a circular {@code NodeIterator} iterating backwards from {@code firstElement} */ public NodeIterator reverseCircularIterator(E firstElement) { ListNodeImpl startNode = (ListNodeImpl) nodeOf(firstElement); if (startNode == null) { throw new NoSuchElementException(); } return reverseIterator(new ListNodeIteratorImpl(size, startNode.next)); } /** * {@inheritDoc} */ @Override public NodeIterator descendingIterator() { return reverseIterator(listIterator(size)); } /** * {@inheritDoc} */ @Override public NodeIterator iterator() { return listIterator(); } /** * {@inheritDoc} */ @Override public ListNodeIterator listIterator() { return listIterator(0); } /** * {@inheritDoc} */ @Override public ListNodeIterator listIterator(int index) { return new ListNodeIteratorImpl(index); } /** * Returns a {@link ListNodeIterator} over the elements in this list (in proper sequence) * starting with the first {@link ListNode} whose value is equal to the specified * {@code element}. * * @param element the first element to be returned from the list iterator (by a call to the * {@code next} method) * @return a list iterator over the elements in this list (in proper sequence) * @throws NoSuchElementException if {@code element} is not in the list */ public ListNodeIterator listIterator(E element) { Pair, Integer> startPair = searchNode(() -> head, n -> n.next, element); ListNodeImpl startNode = startPair.getFirst(); int startIndex = startPair.getSecond(); if (startNode == null) { throw new NoSuchElementException(); } return new ListNodeIteratorImpl(startIndex, startNode); } /** * An extension of the {@link Iterator} interface for {@link DoublyLinkedList DoublyLinkedLists} * exposing their {@link ListNode ListNodes}. * * @param the list element type */ public interface NodeIterator extends Iterator { /** * {@inheritDoc} */ @Override default E next() { return nextNode().getValue(); } /** * Returns the next {@link ListNode} in the list and advances the cursor position. * * @return the next {@code ListNode} * @see ListIterator#next() */ ListNode nextNode(); } /** * An extension of the {@link ListIterator} interface for {@link DoublyLinkedList * DoublyLinkedLists} exposing their {@link ListNode ListNodes}. * * @param the list element type */ public interface ListNodeIterator extends ListIterator, NodeIterator { /** * {@inheritDoc} */ @Override default E next() { return nextNode().getValue(); } /** * {@inheritDoc} */ @Override default E previous() { return previousNode().getValue(); } /** * Returns the previous {@link ListNode} in the list and moves the cursor position * backwards. * * @return the previous {@code ListNode} * @see ListIterator#previous() */ ListNode previousNode(); } /** * An implementation of the {@link DoublyLinkedList.ListNodeIterator} interface. */ private class ListNodeIteratorImpl implements ListNodeIterator { /** Index in this list of the ListNode returned next. */ private int nextIndex; /** ListNode this iterator will return next. Null if this list is empty. */ private ListNodeImpl next; /** ListNode this iterator returned last. */ private ListNodeImpl last = null; /** * The number of modifications the list have had at the moment when this iterator was * created */ private int expectedModCount = modCount; private ListNodeIteratorImpl(int startIndex) { this.nextIndex = startIndex; if (startIndex == size) { this.next = isEmpty() ? null : head; } else { this.next = getNodeAt(startIndex); } } private ListNodeIteratorImpl(int startIndex, ListNodeImpl startNode) { this.nextIndex = startIndex; this.next = startNode; } /** * {@inheritDoc} */ @Override public boolean hasNext() { return nextIndex < size; } /** * {@inheritDoc} */ @Override public boolean hasPrevious() { return nextIndex > 0; } /** * {@inheritDoc} */ @Override public int nextIndex() { return nextIndex; } /** * {@inheritDoc} */ @Override public int previousIndex() { return nextIndex - 1; } /** * {@inheritDoc} */ @Override public ListNodeImpl nextNode() { checkForComodification(); if (!hasNext()) { throw new NoSuchElementException(); } last = next; next = next.next; nextIndex++; return last; } /** * {@inheritDoc} */ @Override public ListNode previousNode() { checkForComodification(); if (!hasPrevious()) { throw new NoSuchElementException(); } last = next = next.prev; nextIndex--; return last; } /** * {@inheritDoc} */ @Override public void add(E e) { checkForComodification(); if (nextIndex == size) { addElementLast(e); // sets head to new node of e if was empty if (size == 1) { // was empty next = head; // jump over head threshold, so cursor is at the end } } else { addElementBeforeNode(next, e); } last = null; nextIndex++; expectedModCount++; } /** * {@inheritDoc} */ @Override public void set(E e) { if (last == null) { throw new IllegalStateException(); } checkForComodification(); // replace node returned last with a new node holding e ListNode nextNode = last.next; boolean wasLast = last == tail(); removeNode(last); if (wasLast) { // or the sole node last = (ListNodeImpl) addElementLast(e); } else { last = (ListNodeImpl) addElementBeforeNode(nextNode, e); } expectedModCount += 2; // because of unlink and add } /** * {@inheritDoc} */ @Override public void remove() { if (last == null) { throw new IllegalStateException(); } checkForComodification(); ListNodeImpl lastsNext = last.next; removeNode(last); if (next == last) { // previousNode() called before // removed element after cursor (which would have been next) next = lastsNext; } else { // nextNode() called before // removed element before cursor (next is unaffected but the index decreases) nextIndex--; } last = null; expectedModCount++; } /** * Verifies that the list structure hasn't been changed since the iteration started */ private void checkForComodification() { if (expectedModCount != modCount) { throw new ConcurrentModificationException(); } } } /** * Returns a {@link NodeIterator} that iterates in reverse order, assuming the cursor of the * specified {@link ListNodeIterator} is behind the tail of the list. */ private static NodeIterator reverseIterator(ListNodeIterator listIterator) { return new NodeIterator() { /** * {@inheritDoc} */ @Override public boolean hasNext() { return listIterator.hasPrevious(); } /** * {@inheritDoc} */ @Override public ListNode nextNode() { return listIterator.previousNode(); } /** * {@inheritDoc} */ @Override public void remove() { listIterator.remove(); } }; } /** * Container for the elements stored in a {@link DoublyLinkedList}. *

    * A {@link ListNode} is either contained exactly once in exactly one {@code DoublyLinkedList} * or contained in no {@code DoublyLinkedList}. *

    * * @param the type of the element stored in this node */ public interface ListNode { /** * Returns the immutable value this {@code ListNode} contains. * * @return the value this list node contains */ V getValue(); /** * Returns the next node in the list structure with respect to this node * * @return the next node in the list structure with respect to this node */ ListNode getNext(); /** * Returns the previous node in the list structure with respect to this node * * @return the previous node in the list structure with respect to this node */ ListNode getPrev(); } /** * The default {@link ListNode} implementation that enables checks and enforcement of a single * container list policy. */ private static class ListNodeImpl implements ListNode { private final V value; private DoublyLinkedList list = null; private ListNodeImpl next = null; private ListNodeImpl prev = null; /** * Creates new list node * * @param value the value this list node stores */ ListNodeImpl(V value) { this.value = value; } /** * {@inheritDoc} */ @Override public String toString() { if (list == null) { return " - " + value + " - "; // not in a list } else { return prev.value + " -> " + value + " -> " + next.value; } } /** * {@inheritDoc} */ @Override public V getValue() { return value; } /** * {@inheritDoc} */ @Override public ListNodeImpl getNext() { return next; } /** * {@inheritDoc} */ @Override public ListNodeImpl getPrev() { return prev; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/ElementsSequenceGenerator.java000066400000000000000000000071301402514743400330220ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.*; /** * Generates elements from the input collection in random order. *

    * An element can be generated only once. After all elements have been generated, this generator * halts. At every step, an element is generated uniformly at random, which means that every element * has an equal probability to be generated. This implementation is based on the * Fisher-Yates algorithm. * The generator is unbiased meaning the every permutation is equally likely. * * @param element type * @author Timofey Chudakov */ public class ElementsSequenceGenerator implements Iterator, Iterable { /** * Input elements ordered as a list. This list is being decreased in size as the elements are * generated. */ private List elements; /** * Random instance used by this generator. */ private Random rng; /** * Constructs a new {@link ElementsSequenceGenerator}. * * @param elements a collection of elements to generate elements from. */ public ElementsSequenceGenerator(Collection elements) { this(elements, System.nanoTime()); } /** * Constructs a new {@link ElementsSequenceGenerator} using the specified {@code seed}. Two * different generators with the same seed will produce identical sequences given that the same * collection of elements is provided. * * @param elements a collection of elements to generate elements from. * @param seed a seed for the random number generator */ public ElementsSequenceGenerator(Collection elements, long seed) { this(elements, new Random(seed)); } /** * Constructs a new {@link ElementsSequenceGenerator} using the specified random number * generator {@code rng}. Two different generators will produce identical sequences from a * collection of elements given that the random number generator produces the same sequence of * numbers. * * @param elements a collection of elements to generate elements from. * @param rng a random number generator */ public ElementsSequenceGenerator(Collection elements, Random rng) { this.elements = new ArrayList<>(elements); this.rng = rng; } /** * {@inheritDoc} */ @Override public boolean hasNext() { return !elements.isEmpty(); } /** * {@inheritDoc} */ @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } int index = rng.nextInt(elements.size()); T result = elements.get(index); elements.set(index, elements.get(elements.size() - 1)); elements.remove(elements.size() - 1); return result; } /** * {@inheritDoc} */ @Override public Iterator iterator() { return this; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/LiveIterableWrapper.java000066400000000000000000000034261402514743400316220ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.Iterator; import java.util.Objects; import java.util.function.Supplier; /** * A wrapper around a supplier of an iterable. * * @author Dimitrios Michail * * @param the element type */ public class LiveIterableWrapper implements Iterable { private Supplier> supplier; /** * Create a new wrapper */ public LiveIterableWrapper() { this(null); } /** * Create a new wrapper * * @param supplier the supplier which provides the iterable */ public LiveIterableWrapper(Supplier> supplier) { this.supplier = Objects.requireNonNull(supplier); } @Override public Iterator iterator() { return supplier.get().iterator(); } /** * Get the supplier * * @return the supplier */ public Supplier> getSupplier() { return supplier; } /** * Set the supplier * * @param supplier the supplier */ public void setSupplier(Supplier> supplier) { this.supplier = supplier; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/MathUtil.java000066400000000000000000000031621402514743400274360ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by Assaf Lehr and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; /** * Math Utilities. * * @author Assaf Lehr */ public class MathUtil { /** * Calculate the factorial of $n$. * * @param n the input number * @return the factorial */ public static long factorial(int n) { long multi = 1; for (int i = 1; i <= n; i++) { multi = multi * i; } return multi; } /** * Calculate the floor of the binary logarithm of $n$. * * @param n the input number * @return the binary logarithm */ public static int log2(int n) { // returns 0 for n=0 int log = 0; if ((n & 0xffff0000) != 0) { n >>>= 16; log = 16; } if (n >= 256) { n >>>= 8; log += 8; } if (n >= 16) { n >>>= 4; log += 4; } if (n >= 4) { n >>>= 2; log += 2; } return log + (n >>> 1); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/ModifiableInteger.java000066400000000000000000000153011402514743400312560ustar00rootroot00000000000000/* * (C) Copyright 2002-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; /** * The ModifiableInteger class wraps a value of the primitive type int in * an object, similarly to {@link java.lang.Integer}. An object of type * ModifiableInteger contains a single field whose type is int. * *

    * Unlike java.lang.Integer, the int value which the ModifiableInteger represents can * be modified. It becomes useful when used together with the collection framework. For example, if * you want to have a {@link java.util.List} of counters. You could use Integer but * that would have became wasteful and inefficient if you frequently had to update the counters. *

    * *

    * WARNING: Because instances of this class are mutable, great care must be exercised if used as * keys of a {@link java.util.Map} or as values in a {@link java.util.Set} in a manner that affects * equals comparisons while the instances are keys in the map (or values in the set). For more see * documentation of Map and Set. *

    * * @author Barak Naveh */ public class ModifiableInteger extends Number implements Comparable { private static final long serialVersionUID = 3618698612851422261L; /** * The int value represented by this ModifiableInteger. */ public int value; /** * !!! DON'T USE - Use the {@link #ModifiableInteger(int)} constructor instead !!! * *

    * This constructor is for the use of java.beans.XMLDecoder deserialization. The constructor is * marked as 'deprecated' to indicate to the programmer against using it by mistake. *

    * * @deprecated not really deprecated, just marked so to avoid mistaken use. */ @Deprecated public ModifiableInteger() { } /** * Constructs a newly allocated ModifiableInteger object that represents the * specified int value. * * @param value the value to be represented by the * ModifiableInteger object. */ public ModifiableInteger(int value) { this.value = value; } /** * Sets a new value for this modifiable integer. * * @param value the new value to set. */ public void setValue(int value) { this.value = value; } /** * Returns the value of this object, similarly to {@link #intValue()}. This getter is NOT * redundant. It is used for serialization by java.beans.XMLEncoder. * * @return the value. */ public int getValue() { return this.value; } /** * Adds one to the value of this modifiable integer. */ public void increment() { this.value++; } /** * Subtracts one from the value of this modifiable integer. */ public void decrement() { this.value--; } /** * Compares two ModifiableInteger objects numerically. * * @param anotherInteger the ModifiableInteger to be compared. * * @return the value 0 if this ModifiableInteger is equal to the * argument ModifiableInteger; a value less than 0 if this * ModifiableInteger is numerically less than the argument * ModifiableInteger; and a value greater than 0 if this * ModifiableInteger is numerically greater than the argument * ModifiableInteger (signed comparison). */ @Override public int compareTo(ModifiableInteger anotherInteger) { int thisVal = this.value; int anotherVal = anotherInteger.value; return Integer.compare(thisVal, anotherVal); } /** * @see Number#doubleValue() */ @Override public double doubleValue() { return this.value; } /** * Compares this object to the specified object. The result is * true if and only if the argument is not null and is an * ModifiableInteger object that contains the same * int value as this object. * * @param o the object to compare with. * * @return true if the objects are the same; false otherwise. */ @Override public boolean equals(Object o) { if (o instanceof ModifiableInteger) { return this.value == ((ModifiableInteger) o).value; } return false; } /** * @see Number#floatValue() */ @Override public float floatValue() { return this.value; } /** * Returns a hash code for this ModifiableInteger. * * @return a hash code value for this object, equal to the primitive * int value represented by this ModifiableInteger object. */ @Override public int hashCode() { return this.value; } /** * @see Number#intValue() */ @Override public int intValue() { return this.value; } /** * @see Number#longValue() */ @Override public long longValue() { return this.value; } /** * Returns an Integer object representing this * ModifiableInteger's value. * * @return an Integer representation of the value of this object. */ public Integer toInteger() { return this.value; } /** * Returns a String object representing this * ModifiableInteger's value. The value is converted to signed decimal representation and * returned as a string, exactly as if the integer value were given as an argument to the * {@link java.lang.Integer#toString(int)} method. * * @return a string representation of the value of this object in base 10. */ @Override public String toString() { return String.valueOf(this.value); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/PrefetchIterator.java000066400000000000000000000150421402514743400311610ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by Assaf Lehr and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.*; /** * Utility class to help implement an iterator/enumerator in which the hasNext() method needs to * calculate the next elements ahead of time. * *

    * Many classes which implement an iterator face a common problem: if there is no easy way to * calculate hasNext() other than to call getNext(), then they save the result for fetching in the * next call to getNext(). This utility helps in doing just that. * *

    * Usage: The new iterator class will hold this class as a member variable and forward the * hasNext() and next() to it. When creating an instance of this class, you supply it with a functor * that is doing the real job of calculating the next element. * *

     * 
     *  //This class supplies enumeration of integer till 100.
     *  public class IteratorExample implements Enumeration{
     *  private int counter=0;
     *  private PrefetchIterator nextSupplier;
     *
     *      IteratorExample()
     *      {
     *          nextSupplier = new PrefetchIterator(new PrefetchIterator.NextElementFunctor(){
     *
     *              public Object nextElement() throws NoSuchElementException {
     *                  counter++;
     *                  if (counter <= 100)
     *                      throw new NoSuchElementException();
     *                  else
     *                      return new Integer(counter);
     *              }
     *
     *          });
     *      }
     *      
     *      // forwarding to nextSupplier and return its returned value
     *      public boolean hasMoreElements() {
     *          return this.nextSupplier.hasMoreElements();
     *      }
     *      
     *      // forwarding to nextSupplier and return its returned value
     *      public Object nextElement() {
     *          return this.nextSupplier.nextElement();
     *      }
     *  }
     * 
    * * @param the element type * * @author Assaf Lehr */ public class PrefetchIterator implements Iterator, Enumeration { private NextElementFunctor innerEnum; private E getNextLastResult; private boolean isGetNextLastResultUpToDate = false; private boolean endOfEnumerationReached = false; private boolean flagIsEnumerationStartedEmpty = true; private int innerFunctorUsageCounter = 0; /** * Construct a new prefetch iterator. * * @param aEnum the next element functor */ public PrefetchIterator(NextElementFunctor aEnum) { innerEnum = aEnum; } /** * Serves as one contact place to the functor; all must use it and not directly the * NextElementFunctor. */ private E getNextElementFromInnerFunctor() { innerFunctorUsageCounter++; E result = this.innerEnum.nextElement(); // if we got here , an exception was not thrown, so at least // one time a good value returned flagIsEnumerationStartedEmpty = false; return result; } /** * {@inheritDoc} */ @Override public E nextElement() { /* * 1. Retrieves the saved value or calculates it if it does not exist 2. Changes * isGetNextLastResultUpToDate to false. (Because it does not save the NEXT element now; it * saves the current one!) */ E result; if (this.isGetNextLastResultUpToDate) { result = this.getNextLastResult; } else { result = getNextElementFromInnerFunctor(); } this.isGetNextLastResultUpToDate = false; return result; } /** * {@inheritDoc} */ @Override public boolean hasMoreElements() { /* * If (isGetNextLastResultUpToDate==true) returns true else 1. calculates getNext() and * saves it 2. sets isGetNextLastResultUpToDate to true. */ if (endOfEnumerationReached) { return false; } if (isGetNextLastResultUpToDate) { return true; } else { try { this.getNextLastResult = getNextElementFromInnerFunctor(); this.isGetNextLastResultUpToDate = true; return true; } catch (NoSuchElementException noSuchE) { endOfEnumerationReached = true; return false; } } // else } // method /** * Tests whether the enumeration started as an empty one. It does not matter if it * hasMoreElements() now, only at initialization time. Efficiency: if nextElements(), * hasMoreElements() were never used, it activates the hasMoreElements() once. Else it is * immediately(O(1)) * * @return true if the enumeration started as an empty one, false otherwise. */ public boolean isEnumerationStartedEmpty() { if (this.innerFunctorUsageCounter == 0) { return !hasMoreElements(); } else // it is not the first time , so use the saved value // which was initilaizeed during a call to // getNextElementFromInnerFunctor { return flagIsEnumerationStartedEmpty; } } /** * {@inheritDoc} */ @Override public boolean hasNext() { return this.hasMoreElements(); } /** * {@inheritDoc} */ @Override public E next() { return this.nextElement(); } /** * {@inheritDoc} */ @Override public void remove() throws UnsupportedOperationException { throw new UnsupportedOperationException(); } /** * A functor for the calculation of the next element. * * @param the element type */ public interface NextElementFunctor { /** * Return the next element or throw a {@link NoSuchElementException} if there are no more * elements. * * @return the next element * @throws NoSuchElementException in case there is no next element */ EE nextElement() throws NoSuchElementException; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/RadixSort.java000066400000000000000000000055031402514743400276270ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.*; /** * Sorts the specified list of integers into ascending order using the Radix Sort method. * * This algorithms runs in $O(N + V)$ time and uses $O(N + V)$ extra memory, where $V = 256$. * * If $N \leq RadixSort.CUT\_OFF$ then the standard Java sorting algorithm is used. * * The specified list must be modifiable, but need not be resizable. */ public class RadixSort { public static int CUT_OFF = 40; private static final int MAX_DIGITS = 32; private static final int MAX_D = 4; private static final int SIZE_RADIX = 1 << (MAX_DIGITS / MAX_D); private static final int MASK = SIZE_RADIX - 1; private static int[] count = new int[SIZE_RADIX]; // Suppresses default constructor, ensuring non-instantiability. private RadixSort() { } private static void radixSort(int array[], int n, int tempArray[], int cnt[]) { for (int d = 0, shift = 0; d < MAX_D; d++, shift += (MAX_DIGITS / MAX_D)) { Arrays.fill(cnt, 0); for (int i = 0; i < n; ++i) ++cnt[(array[i] >> shift) & MASK]; for (int i = 1; i < SIZE_RADIX; ++i) cnt[i] += cnt[i - 1]; for (int i = n - 1; i >= 0; i--) tempArray[--cnt[(array[i] >> shift) & MASK]] = array[i]; System.arraycopy(tempArray, 0, array, 0, n); } } /** * Sort the given list in ascending order. * * @param list the input list of integers */ public static void sort(List list) { if (list == null) { return; } final int n = list.size(); if (n <= CUT_OFF) { list.sort(null); return; } int[] array = new int[n]; ListIterator listIterator = list.listIterator(); while (listIterator.hasNext()) { array[listIterator.nextIndex()] = listIterator.next(); } radixSort(array, n, new int[n], count); listIterator = list.listIterator(); while (listIterator.hasNext()) { listIterator.next(); listIterator.set(array[listIterator.previousIndex()]); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/SupplierException.java000066400000000000000000000020601402514743400313650ustar00rootroot00000000000000/* * (C) Copyright 2021-2021, by Hannes Wellmann and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.function.*; /** * Exception thrown to indicate that a {@link Supplier} is in an invalid state. * * @author Hannes Wellmann */ public class SupplierException extends IllegalArgumentException { private static final long serialVersionUID = -8192314371524515620L; public SupplierException(String message, Throwable cause) { super(message, cause); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/SupplierUtil.java000066400000000000000000000172221402514743400303520ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import org.jgrapht.graph.*; import java.io.*; import java.lang.reflect.*; import java.util.*; import java.util.function.*; /** * Helper class for suppliers. * * @author Dimitrios Michail */ public class SupplierUtil { /** * Supplier for {@link DefaultEdge}. */ @SuppressWarnings("unchecked") public static final Supplier DEFAULT_EDGE_SUPPLIER = (Supplier & Serializable) DefaultEdge::new; /** * Supplier for {@link DefaultWeightedEdge}. */ @SuppressWarnings("unchecked") public static final Supplier DEFAULT_WEIGHTED_EDGE_SUPPLIER = (Supplier & Serializable) DefaultWeightedEdge::new; /** * Supplier for {@link Object}. */ @SuppressWarnings("unchecked") public static final Supplier OBJECT_SUPPLIER = (Supplier & Serializable) Object::new; /** * Create a supplier from a class which calls the default constructor. * * @param clazz the class * @return the supplier * @param the type of results supplied by this supplier */ @SuppressWarnings("unchecked") public static Supplier createSupplier(Class clazz) { // shortcut to use pre-defined constructor method reference based suppliers if (clazz == DefaultEdge.class) { return (Supplier) DEFAULT_EDGE_SUPPLIER; } else if (clazz == DefaultWeightedEdge.class) { return (Supplier) DEFAULT_WEIGHTED_EDGE_SUPPLIER; } else if (clazz == Object.class) { return (Supplier) OBJECT_SUPPLIER; } try { final Constructor constructor = clazz.getDeclaredConstructor(); if ((!Modifier.isPublic(constructor.getModifiers()) || !Modifier.isPublic(constructor.getDeclaringClass().getModifiers())) && !constructor.canAccess(null)) { constructor.setAccessible(true); } return new ConstructorSupplier<>(constructor); } catch (ReflectiveOperationException e) { // Defer throwing an exception to the first time the supplier is called return getThrowingSupplier(e); } } @SuppressWarnings("unchecked") private static Supplier getThrowingSupplier(Throwable e) { return (Supplier & Serializable) () -> { throw new SupplierException(e.getMessage(), e); }; } /** * Create a default edge supplier. * * @return a default edge supplier */ public static Supplier createDefaultEdgeSupplier() { return DEFAULT_EDGE_SUPPLIER; } /** * Create a default weighted edge supplier. * * @return a default weighted edge supplier */ public static Supplier createDefaultWeightedEdgeSupplier() { return DEFAULT_WEIGHTED_EDGE_SUPPLIER; } /** * Create an integer supplier which returns a sequence starting from zero. * * @return an integer supplier */ public static Supplier createIntegerSupplier() { return createIntegerSupplier(0); } /** * Create an integer supplier which returns a sequence starting from a specific numbers. * * @param start where to start the sequence * @return an integer supplier */ @SuppressWarnings("unchecked") public static Supplier createIntegerSupplier(int start) { int[] modifiableInt = new int[] { start }; // like a modifiable int return (Supplier & Serializable) () -> modifiableInt[0]++; } /** * Create a long supplier which returns a sequence starting from zero. * * @return a long supplier */ public static Supplier createLongSupplier() { return createLongSupplier(0); } /** * Create a long supplier which returns a sequence starting from a specific numbers. * * @param start where to start the sequence * @return a long supplier */ @SuppressWarnings("unchecked") public static Supplier createLongSupplier(long start) { long[] modifiableLong = new long[] { start }; // like a modifiable long return (Supplier & Serializable) () -> modifiableLong[0]++; } /** * Create a string supplier which returns unique strings. The returns strings are simply * integers starting from zero. * * @return a string supplier */ public static Supplier createStringSupplier() { return createStringSupplier(0); } /** * Create a string supplier which returns random UUIDs. * * @return a string supplier */ @SuppressWarnings("unchecked") public static Supplier createRandomUUIDStringSupplier() { return (Supplier & Serializable) () -> UUID.randomUUID().toString(); } /** * Create a string supplier which returns unique strings. The returns strings are simply * integers starting from start. * * @param start where to start the sequence * @return a string supplier */ @SuppressWarnings("unchecked") public static Supplier createStringSupplier(int start) { int[] container = new int[] { start }; return (Supplier & Serializable) () -> String.valueOf(container[0]++); } private static class ConstructorSupplier implements Supplier, Serializable { private final Constructor constructor; private static class SerializedForm implements Serializable { private static final long serialVersionUID = -2385289829144892760L; private final Class type; public SerializedForm(Class type) { this.type = type; } Object readResolve() throws ObjectStreamException { try { return new ConstructorSupplier<>(type.getDeclaredConstructor()); } catch (ReflectiveOperationException e) { InvalidObjectException ex = new InvalidObjectException( "Failed to get no-args constructor from " + type); ex.initCause(e); throw ex; } } } public ConstructorSupplier(Constructor constructor) { this.constructor = constructor; } @Override public T get() { try { return constructor.newInstance(); } catch (ReflectiveOperationException ex) { throw new SupplierException("Supplier failed", ex); } } Object writeReplace() throws ObjectStreamException { return new SerializedForm<>(constructor.getDeclaringClass()); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/TypeUtil.java000066400000000000000000000021561402514743400274700ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; /** * TypeUtil isolates type-unsafety so that code which uses it for legitimate reasons can stay * warning-free. * * @author John V. Sichi */ public class TypeUtil { /** * Casts an object to a type. * * @param o object to be cast * @param the type of the result * * @return the result of the cast */ @SuppressWarnings("unchecked") public static T uncheckedCast(Object o) { return (T) o; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/UnmodifiableUnionSet.java000066400000000000000000000111361402514743400317720ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.io.*; import java.util.*; /** * An unmodifiable live view of the union of two sets. * * @param the element type * * @author Dimitrios Michail */ public class UnmodifiableUnionSet extends AbstractSet implements Serializable { private static final long serialVersionUID = -1937327799873331354L; private final Set first; private final Set second; /** * Constructs a new set. * * @param first the first set * @param second the second set */ public UnmodifiableUnionSet(Set first, Set second) { Objects.requireNonNull(first); Objects.requireNonNull(second); this.first = first; this.second = second; } @Override public Iterator iterator() { return new UnionIterator(orderSetsBySize()); } /** * {@inheritDoc} * * Since the view is live, this operation is no longer a constant time operation. */ @Override public int size() { SetSizeOrdering ordering = orderSetsBySize(); Set bigger = ordering.bigger; int count = ordering.biggerSize; for (E e : ordering.smaller) { if (!bigger.contains(e)) { count++; } } return count; } @Override public boolean contains(Object o) { return first.contains(o) || second.contains(o); } private SetSizeOrdering orderSetsBySize() { int firstSize = first.size(); int secondSize = second.size(); if (secondSize > firstSize) { return new SetSizeOrdering(second, first, secondSize, firstSize); } else { return new SetSizeOrdering(first, second, firstSize, secondSize); } } // note that these inner classes could be static, but we // declare them as non-static to avoid the clutter from // duplicating the generic type parameter private class SetSizeOrdering { final Set bigger; final Set smaller; final int biggerSize; final int smallerSize; SetSizeOrdering(Set bigger, Set smaller, int biggerSize, int smallerSize) { this.bigger = bigger; this.smaller = smaller; this.biggerSize = biggerSize; this.smallerSize = smallerSize; } } private class UnionIterator implements Iterator { private SetSizeOrdering ordering; private boolean inBiggerSet; private Iterator iterator; private E cur; UnionIterator(SetSizeOrdering ordering) { this.ordering = ordering; this.inBiggerSet = true; this.iterator = ordering.bigger.iterator(); this.cur = prefetch(); } @Override public boolean hasNext() { if (cur != null) { return true; } return (cur = prefetch()) != null; } @Override public E next() { if (!hasNext()) { throw new NoSuchElementException(); } E result = cur; cur = null; return result; } private E prefetch() { while (true) { if (inBiggerSet) { if (iterator.hasNext()) { return iterator.next(); } else { inBiggerSet = false; iterator = ordering.smaller.iterator(); } } else { if (iterator.hasNext()) { E elem = iterator.next(); if (!ordering.bigger.contains(elem)) { return elem; } } else { return null; } } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/VertexToIntegerMapping.java000066400000000000000000000057711402514743400323310ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.*; /** * Helper class for building a one-to-one mapping for a collection of vertices to the integer range * $[0, n)$ where $n$ is the number of vertices in the collection. * *

    * This class computes the mapping only once, on instantiation. It does not support live updates. *

    * * @author Alexandru Valeanu * * @param the graph vertex type */ public class VertexToIntegerMapping { private final Map vertexMap; private final List indexList; /** * Create a new mapping from a list of vertices. The input list will be used as the * {@code indexList} so it must not be modified. * * @param vertices the input list of vertices * @throws NullPointerException if {@code vertices} is {@code null} * @throws IllegalArgumentException if the vertices are not distinct */ public VertexToIntegerMapping(List vertices) { Objects.requireNonNull(vertices, "the input collection of vertices cannot be null"); vertexMap = CollectionUtil.newHashMapWithExpectedSize(vertices.size()); indexList = vertices; for (V v : vertices) { if (vertexMap.put(v, vertexMap.size()) != null) { throw new IllegalArgumentException("vertices are not distinct"); } } } /** * Create a new mapping from a collection of vertices. * * @param vertices the input collection of vertices * @throws NullPointerException if {@code vertices} is {@code null} * @throws IllegalArgumentException if the vertices are not distinct */ public VertexToIntegerMapping(Collection vertices) { this( new ArrayList<>( Objects .requireNonNull(vertices, "the input collection of vertices cannot be null"))); } /** * Get the {@code vertexMap}, a mapping from vertices to integers (i.e. the inverse of * {@code indexList}). * * @return a mapping from vertices to integers */ public Map getVertexMap() { return vertexMap; } /** * Get the {@code indexList}, a mapping from integers to vertices (i.e. the inverse of * {@code vertexMap}). * * @return a mapping from integers to vertices */ public List getIndexList() { return indexList; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/WeightCombiner.java000066400000000000000000000032211402514743400306110ustar00rootroot00000000000000/* * (C) Copyright 2009-2021, by Ilya Razenshteyn and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.io.*; /** * Binary operator for edge weights. There are some prewritten operators. */ public interface WeightCombiner { /** * Sum of weights. */ WeightCombiner SUM = (WeightCombiner & Serializable) (a, b) -> a + b; /** * Multiplication of weights. */ WeightCombiner MULT = (WeightCombiner & Serializable) (a, b) -> a * b; /** * Minimum weight. */ WeightCombiner MIN = (WeightCombiner & Serializable) Math::min; /** * Maximum weight. */ WeightCombiner MAX = (WeightCombiner & Serializable) Math::max; /** * First weight. */ WeightCombiner FIRST = (WeightCombiner & Serializable) (a, b) -> a; /** * Second weight. */ WeightCombiner SECOND = (WeightCombiner & Serializable) (a, b) -> b; /** * Combines two weights. * * @param a first weight * @param b second weight * * @return result of the operator */ double combine(double a, double b); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/WeightedUnmodifiableSet.java000066400000000000000000000077661402514743400324600ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.io.*; import java.util.*; /** * Implementation of a weighted, unmodifiable set. This class can for instance be used to store a * weighted vertex cover. The {@code hashCode()} and {@code equals()} methods are identical to those * of a normal set, i.e. they are independent of the {@code weight} of this class. All methods are * delegated to the underlying set. * * @param element type * * @author Joris Kinable */ public class WeightedUnmodifiableSet extends AbstractSet implements Serializable { private static final long serialVersionUID = -5913435131882975869L; public final Set backingSet; public final double weight; /** * Constructs a WeightedUnmodifiableSet instance * * @param backingSet underlying set */ public WeightedUnmodifiableSet(Set backingSet) { this.backingSet = backingSet; this.weight = backingSet.size(); } /** * Constructs a WeightedUnmodifiableSet instance * * @param backingSet underlying set * @param weight weight of the set */ public WeightedUnmodifiableSet(Set backingSet, double weight) { this.backingSet = backingSet; this.weight = weight; } /** * Returns the weight of the set. * * @return weight of the set */ public double getWeight() { return weight; } @Override public int size() { return backingSet.size(); } @Override public boolean isEmpty() { return backingSet.isEmpty(); } @Override public boolean contains(Object o) { return backingSet.contains(o); } @Override public Iterator iterator() { return backingSet.iterator(); } @Override public Object[] toArray() { return backingSet.toArray(); } @Override public T[] toArray(T[] a) { return backingSet.toArray(a); } @Override public boolean add(E v) { throw new UnsupportedOperationException("This set is unmodifiable"); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException("This set is unmodifiable"); } @Override public boolean containsAll(Collection c) { return backingSet.containsAll(c); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException("This set is unmodifiable"); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException("This set is unmodifiable"); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException("This set is unmodifiable"); } @Override public void clear() { throw new UnsupportedOperationException("This set is unmodifiable"); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof WeightedUnmodifiableSet)) return false; @SuppressWarnings("unchecked") WeightedUnmodifiableSet other = (WeightedUnmodifiableSet) o; return this.backingSet.equals(other.backingSet); } @Override public int hashCode() { return backingSet.hashCode(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/org/jgrapht/util/package-info.java000066400000000000000000000001731402514743400302320ustar00rootroot00000000000000/** * Non-graph-specific data structures, algorithms, and utilities used by JGraphT. */ package org.jgrapht.util; jgrapht-jgrapht-1.5.1/jgrapht-core/src/main/java/overview.html000066400000000000000000000007501402514743400243750ustar00rootroot00000000000000 JGraphT is a free Java class library that provides mathematical graph-theory objects and algorithms. This is an open-source java graph library that supports a rich gallery of graphs and is designed to be powerful, extensible and easy to use.

    Visit http://www.jgrapht.org to download and to get the latest info on JGraphT. jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/000077500000000000000000000000001402514743400207515ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/000077500000000000000000000000001402514743400216725ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/000077500000000000000000000000001402514743400224615ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/000077500000000000000000000000001402514743400241205ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/FastTestSuite.java000066400000000000000000000020201402514743400275240ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by John Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import com.googlecode.junittoolbox.*; import org.junit.experimental.categories.*; import org.junit.runner.*; /** * Suite of fast unit tests only (as run by mvn test). * * @author John Sichi */ @RunWith(ParallelSuite.class) @Categories.ExcludeCategory({ SlowTests.class, OptionalTests.class }) @SuiteClasses({ "**/*Test.class", "!**/perf/**" }) public class FastTestSuite { } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/GraphMetricsTest.java000066400000000000000000000353521402514743400302230ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import static org.junit.Assert.assertEquals; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Random; import org.jgrapht.alg.cycle.TarjanSimpleCycles; import org.jgrapht.generate.BarabasiAlbertGraphGenerator; import org.jgrapht.generate.CompleteGraphGenerator; import org.jgrapht.generate.GnpRandomGraphGenerator; import org.jgrapht.generate.GraphGenerator; import org.jgrapht.generate.GridGraphGenerator; import org.jgrapht.generate.NamedGraphGenerator; import org.jgrapht.generate.RingGraphGenerator; import org.jgrapht.generate.WheelGraphGenerator; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.DirectedMultigraph; import org.jgrapht.graph.DirectedPseudograph; import org.jgrapht.graph.Multigraph; import org.jgrapht.graph.Pseudograph; import org.jgrapht.graph.SimpleDirectedGraph; import org.jgrapht.graph.SimpleDirectedWeightedGraph; import org.jgrapht.graph.SimpleGraph; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Assert; import org.junit.Test; /** * Tests for GraphMetrics * * @author Joris Kinable * @author Alexandru Valeanu */ public class GraphMetricsTest { private final static double EPSILON = 0.000000001; @Test public void testGraphDiameter() { Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(g, 0, 1, 10); Graphs.addEdgeWithVertices(g, 1, 0, 12); double diameter = GraphMetrics.getDiameter(g); assertEquals(12.0, diameter, EPSILON); } @Test public void testGraphRadius() { Graph g = new SimpleGraph<>(DefaultEdge.class); double radius = GraphMetrics.getRadius(g); assertEquals(0.0, radius, EPSILON); } @Test public void testGraphGirthAcyclic() { Graph tree = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(tree, Arrays.asList(0, 1, 2, 3, 4, 5)); tree.addEdge(0, 1); tree.addEdge(0, 4); tree.addEdge(0, 5); tree.addEdge(1, 2); tree.addEdge(1, 3); assertEquals(Integer.MAX_VALUE, GraphMetrics.getGirth(tree)); } @Test public void testGraphDirectedAcyclic() { Graph tree = new SimpleDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(tree, Arrays.asList(0, 1, 2, 3)); tree.addEdge(0, 1); tree.addEdge(0, 2); tree.addEdge(1, 3); tree.addEdge(2, 3); assertEquals(Integer.MAX_VALUE, GraphMetrics.getGirth(tree)); } @Test public void testGraphDirectedCyclic() { Graph tree = new SimpleDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(tree, Arrays.asList(0, 1, 2, 3)); tree.addEdge(0, 1); tree.addEdge(1, 2); tree.addEdge(2, 3); tree.addEdge(3, 0); assertEquals(4, GraphMetrics.getGirth(tree)); } @Test public void testGraphDirectedCyclic2() { Graph tree = new SimpleDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(tree, Arrays.asList(0, 1)); tree.addEdge(0, 1); tree.addEdge(1, 0); assertEquals(2, GraphMetrics.getGirth(tree)); } @Test public void testGraphGirthGridGraph() { Graph grid = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator gen = new GridGraphGenerator<>(3, 4); gen.generateGraph(grid); assertEquals(4, GraphMetrics.getGirth(grid)); } @Test public void testGraphGirthRingGraphEven() { Graph ring = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator gen = new RingGraphGenerator<>(10); gen.generateGraph(ring); assertEquals(10, GraphMetrics.getGirth(ring)); } @Test public void testGraphGirthRingGraphOdd() { Graph ring = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator gen = new RingGraphGenerator<>(9); gen.generateGraph(ring); assertEquals(9, GraphMetrics.getGirth(ring)); } @Test public void testGraphGirthWheelGraph() { Graph grid = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator gen = new WheelGraphGenerator<>(5); gen.generateGraph(grid); assertEquals(3, GraphMetrics.getGirth(grid)); } @Test public void testGraphDirected1() { Graph graph = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); graph.addEdge(1, 0); graph.addEdge(3, 0); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 2); assertEquals(2, GraphMetrics.getGirth(graph)); } @Test public void testPseudoGraphUndirected() { Graph graph = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); graph.addEdge(0, 1); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 2); graph.addEdge(2, 3); graph.addEdge(3, 0); assertEquals(1, GraphMetrics.getGirth(graph)); } @Test public void testPseudoGraphDirected() { Graph graph = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); graph.addEdge(0, 1); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 2); graph.addEdge(2, 3); graph.addEdge(3, 0); assertEquals(1, GraphMetrics.getGirth(graph)); } @Test public void testMultiGraphUndirected() { Graph graph = new Multigraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); graph.addEdge(0, 1); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 0); assertEquals(2, GraphMetrics.getGirth(graph)); } @Test public void testMultiGraphDirected() { Graph graph = new DirectedMultigraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); graph.addEdge(0, 1); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 0); assertEquals(4, GraphMetrics.getGirth(graph)); } @Test public void testDirectedGraphs() { GraphGenerator gen = new GnpRandomGraphGenerator<>(10, .55, 0); for (int i = 0; i < 10; i++) { Graph graph = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(graph); TarjanSimpleCycles tarjanSimpleCycles = new TarjanSimpleCycles<>(graph); int minCycle = tarjanSimpleCycles .findSimpleCycles().stream().mapToInt(List::size).min().orElse(Integer.MAX_VALUE); assertEquals(minCycle, GraphMetrics.getGirth(graph)); } } private static long naiveCountTriangles(Graph graph) { return GraphMetrics.naiveCountTriangles(graph, new ArrayList<>(graph.vertexSet())); } @Test public void testCountTriangles() { final int NUM_TESTS = 300; Random random = new Random(0x88_88); for (int test = 0; test < NUM_TESTS; test++) { final int N = 20 + random.nextInt(100); Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BarabasiAlbertGraphGenerator generator = new BarabasiAlbertGraphGenerator<>( 10 + random.nextInt(10), 1 + random.nextInt(7), N, random); generator.generateGraph(graph); Assert .assertEquals(naiveCountTriangles(graph), GraphMetrics.getNumberOfTriangles(graph)); } } @Test public void testCountTriangles2() { final int NUM_TESTS = 100; Random random = new Random(0x88_88); for (int test = 0; test < NUM_TESTS; test++) { final int N = 1 + random.nextInt(100); Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator generator = new GnpRandomGraphGenerator<>(N, .55, random.nextInt()); generator.generateGraph(graph); Assert .assertEquals(naiveCountTriangles(graph), GraphMetrics.getNumberOfTriangles(graph)); } } @Test public void testCountTriangles3() { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); // Complete graph: expected (|V| choose 3) GraphGenerator generator = new CompleteGraphGenerator<>(50); generator.generateGraph(graph); Assert.assertEquals(50 * 49 * 48 / 6, GraphMetrics.getNumberOfTriangles(graph)); Assert.assertEquals(50 * 49 * 48 / 6, naiveCountTriangles(graph)); // Wheel graph: expected |V|-1 triangles graph.removeAllVertices(new HashSet<>(graph.vertexSet())); generator = new WheelGraphGenerator<>(50); generator.generateGraph(graph); Assert.assertEquals(49, GraphMetrics.getNumberOfTriangles(graph)); Assert.assertEquals(49, naiveCountTriangles(graph)); // Named graphs NamedGraphGenerator gen = new NamedGraphGenerator<>(); graph.removeAllVertices(new HashSet<>(graph.vertexSet())); gen.generatePetersenGraph(graph); Assert.assertEquals(0, GraphMetrics.getNumberOfTriangles(graph)); Assert.assertEquals(0, naiveCountTriangles(graph)); graph.removeAllVertices(new HashSet<>(graph.vertexSet())); gen.generateDiamondGraph(graph); Assert.assertEquals(2, GraphMetrics.getNumberOfTriangles(graph)); Assert.assertEquals(2, naiveCountTriangles(graph)); graph.removeAllVertices(new HashSet<>(graph.vertexSet())); gen.generateGoldnerHararyGraph(graph); Assert.assertEquals(25, GraphMetrics.getNumberOfTriangles(graph)); Assert.assertEquals(25, naiveCountTriangles(graph)); graph.removeAllVertices(new HashSet<>(graph.vertexSet())); gen.generateKlein7RegularGraph(graph); Assert.assertEquals(56, GraphMetrics.getNumberOfTriangles(graph)); Assert.assertEquals(56, naiveCountTriangles(graph)); } @Test public void testCountTriangles4() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 0; i < 25; i++) { g.addVertex(i); } int[][] edges = { { 0, 1 }, { 1, 2 }, { 0, 3 }, { 1, 3 }, { 2, 5 }, { 3, 5 }, { 4, 5 }, { 1, 6 }, { 2, 6 }, { 1, 7 }, { 2, 7 }, { 3, 7 }, { 4, 7 }, { 1, 8 }, { 2, 8 }, { 2, 9 }, { 1, 10 }, { 7, 10 }, { 1, 11 }, { 2, 11 }, { 2, 12 }, { 3, 13 }, { 4, 13 }, { 1, 15 }, { 6, 15 }, { 9, 15 }, { 1, 16 }, { 4, 16 }, { 11, 16 }, { 1, 18 }, { 2, 18 }, { 1, 19 }, { 3, 19 }, { 6, 19 }, { 1, 20 }, { 2, 20 }, { 2, 21 }, { 3, 21 }, { 3, 22 }, { 5, 22 }, { 10, 22 }, { 3, 23 }, { 19, 23 }, { 1, 24 }, { 2, 24 } }; for (int[] e : edges) { g.addEdge(e[0], e[1]); } long t1 = GraphMetrics.getNumberOfTriangles(g); List allVertices = new ArrayList<>(g.vertexSet()); long t2 = GraphMetrics.naiveCountTriangles(g, allVertices); assertEquals(t1, t2); } @Test public void testMultipleEdges() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 0 }, { 1, 3 }, { 2, 3 }, { 2, 1 } }; for (int[] e : edges) { Graphs.addEdgeWithVertices(g, e[0], e[1]); } assertEquals(4, GraphMetrics.getNumberOfTriangles(g)); } @Test public void testMultipleEdges2() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 0 }, { 1, 3 }, { 2, 3 }, { 2, 1 }, { 0, 2 }, { 0, 2 } }; for (int[] e : edges) { Graphs.addEdgeWithVertices(g, e[0], e[1]); } assertEquals(8, GraphMetrics.getNumberOfTriangles(g)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/GraphTestsTest.java000066400000000000000000000464101402514743400277140ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static junit.framework.TestCase.fail; import static org.hamcrest.Matchers.is; import static org.junit.Assert.*; /** * Test class GraphTests. * * @author Dimitrios Michail */ public class GraphTestsTest { @Test public void testIsEmpty() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); assertTrue(GraphTests.isEmpty(g)); g.addVertex(1); assertTrue(GraphTests.isEmpty(g)); g.addVertex(2); assertTrue(GraphTests.isEmpty(g)); DefaultEdge e = g.addEdge(1, 2); assertFalse(GraphTests.isEmpty(g)); g.removeEdge(e); assertTrue(GraphTests.isEmpty(g)); } @Test public void testIsSimple() { // test empty Graph g1 = new DefaultDirectedGraph<>(DefaultEdge.class); assertTrue(GraphTests.isSimple(g1)); Graph g2 = new SimpleGraph<>(DefaultEdge.class); assertTrue(GraphTests.isSimple(g2)); Graph g3 = new DirectedPseudograph<>(DefaultEdge.class); Assert.assertTrue(GraphTests.isSimple(g3)); Graphs.addAllVertices(g3, Arrays.asList(1, 2)); g3.addEdge(1, 2); g3.addEdge(2, 1); assertTrue(GraphTests.isSimple(g3)); DefaultEdge g3e11 = g3.addEdge(1, 1); assertFalse(GraphTests.isSimple(g3)); g3.removeEdge(g3e11); assertTrue(GraphTests.isSimple(g3)); g3.addEdge(2, 1); assertFalse(GraphTests.isSimple(g3)); Graph g4 = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g4, Arrays.asList(1, 2)); assertTrue(GraphTests.isSimple(g4)); DefaultEdge g4e12 = g4.addEdge(1, 2); g4.addEdge(2, 1); assertFalse(GraphTests.isSimple(g4)); g4.removeEdge(g4e12); assertTrue(GraphTests.isSimple(g4)); g4.addEdge(1, 1); assertFalse(GraphTests.isSimple(g4)); } @Test public void testHasSelfLoops() { Graph g1 = new DefaultDirectedGraph<>(DefaultEdge.class); Assert.assertFalse(GraphTests.hasSelfLoops(g1)); Graph g2 = new SimpleGraph<>(DefaultEdge.class); Assert.assertFalse(GraphTests.hasSelfLoops(g2)); Graph g3 = new DirectedPseudograph<>(DefaultEdge.class); Assert.assertFalse(GraphTests.hasSelfLoops(g3)); Graphs.addAllVertices(g3, Arrays.asList(1, 2)); g3.addEdge(1, 2); g3.addEdge(2, 1); Assert.assertFalse(GraphTests.hasSelfLoops(g3)); g3.addEdge(2, 2); Assert.assertTrue(GraphTests.hasSelfLoops(g3)); Graph g4 = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g4, Arrays.asList(1, 2)); g4.addEdge(1, 2); g4.addEdge(2, 1); Assert.assertFalse(GraphTests.hasSelfLoops(g4)); g4.addEdge(2, 2); Assert.assertTrue(GraphTests.hasSelfLoops(g4)); } @Test public void testHasMultipleEdges() { Graph g1 = new DefaultDirectedGraph<>(DefaultEdge.class); Assert.assertFalse(GraphTests.hasMultipleEdges(g1)); Graph g2 = new SimpleGraph<>(DefaultEdge.class); Assert.assertFalse(GraphTests.hasMultipleEdges(g2)); Graph g3 = new DirectedPseudograph<>(DefaultEdge.class); Assert.assertFalse(GraphTests.hasMultipleEdges(g3)); Graphs.addAllVertices(g3, Arrays.asList(1, 2)); g3.addEdge(1, 2); g3.addEdge(2, 1); g3.addEdge(1, 1); Assert.assertFalse(GraphTests.hasMultipleEdges(g3)); g3.addEdge(2, 2); Assert.assertFalse(GraphTests.hasMultipleEdges(g3)); g3.addEdge(2, 1); Assert.assertTrue(GraphTests.hasMultipleEdges(g3)); Graph g4 = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g4, Arrays.asList(1, 2)); g4.addEdge(1, 2); g4.addEdge(1, 1); Assert.assertFalse(GraphTests.hasMultipleEdges(g4)); g4.addEdge(2, 1); Assert.assertTrue(GraphTests.hasMultipleEdges(g4)); Graph g5 = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g5, Arrays.asList(1, 2)); g5.addEdge(1, 2); g5.addEdge(1, 1); Assert.assertFalse(GraphTests.hasMultipleEdges(g5)); g5.addEdge(1, 1); Assert.assertTrue(GraphTests.hasMultipleEdges(g5)); } @Test public void testIsCompleteDirected() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); assertTrue(GraphTests.isComplete(g)); g.addVertex(1); assertTrue(GraphTests.isComplete(g)); g.addVertex(2); assertFalse(GraphTests.isComplete(g)); g.addEdge(1, 2); assertFalse(GraphTests.isComplete(g)); g.addEdge(2, 1); assertTrue(GraphTests.isComplete(g)); g.addVertex(3); assertFalse(GraphTests.isComplete(g)); g.addEdge(1, 3); assertFalse(GraphTests.isComplete(g)); g.addEdge(3, 1); assertFalse(GraphTests.isComplete(g)); g.addEdge(2, 3); assertFalse(GraphTests.isComplete(g)); g.addEdge(3, 2); assertTrue(GraphTests.isComplete(g)); // check loops Graph g1 = new DirectedPseudograph<>(DefaultEdge.class); assertTrue(GraphTests.isComplete(g1)); g1.addVertex(1); assertTrue(GraphTests.isComplete(g1)); g1.addVertex(2); assertFalse(GraphTests.isComplete(g1)); g1.addEdge(1, 1); g1.addEdge(2, 2); assertFalse(GraphTests.isComplete(g1)); // check multiple edges Graph g2 = new DirectedPseudograph<>(DefaultEdge.class); assertTrue(GraphTests.isComplete(g2)); Graphs.addAllVertices(g2, Arrays.asList(1, 2, 3)); assertFalse(GraphTests.isComplete(g2)); g2.addEdge(1, 2); g2.addEdge(1, 3); g2.addEdge(2, 3); g2.addEdge(1, 1); g2.addEdge(2, 2); g2.addEdge(3, 3); assertFalse(GraphTests.isComplete(g2)); } @Test public void testIsCompleteUndirected() { Graph g = new SimpleGraph<>(DefaultEdge.class); assertTrue(GraphTests.isComplete(g)); g.addVertex(1); assertTrue(GraphTests.isComplete(g)); g.addVertex(2); assertFalse(GraphTests.isComplete(g)); g.addEdge(1, 2); assertTrue(GraphTests.isComplete(g)); g.addVertex(3); assertFalse(GraphTests.isComplete(g)); g.addEdge(1, 3); assertFalse(GraphTests.isComplete(g)); g.addEdge(2, 3); assertTrue(GraphTests.isComplete(g)); // check loops Graph g1 = new Pseudograph<>(DefaultEdge.class); assertTrue(GraphTests.isComplete(g1)); g1.addVertex(1); assertTrue(GraphTests.isComplete(g1)); g1.addVertex(2); assertFalse(GraphTests.isComplete(g1)); g1.addEdge(1, 1); assertFalse(GraphTests.isComplete(g1)); // check multiple edges Graph g2 = new Pseudograph<>(DefaultEdge.class); assertTrue(GraphTests.isComplete(g2)); g2.addVertex(1); assertTrue(GraphTests.isComplete(g2)); g2.addVertex(2); assertFalse(GraphTests.isComplete(g2)); g2.addEdge(1, 2); assertTrue(GraphTests.isComplete(g2)); g2.addEdge(1, 2); assertFalse(GraphTests.isComplete(g2)); g2.addVertex(3); g2.addEdge(1, 3); assertFalse(GraphTests.isComplete(g2)); } @Test public void testIsConnectedUndirected() { Graph g = new SimpleGraph<>(DefaultEdge.class); assertFalse(GraphTests.isConnected(g)); g.addVertex(1); assertTrue(GraphTests.isConnected(g)); g.addVertex(2); assertFalse(GraphTests.isConnected(g)); g.addEdge(1, 2); assertTrue(GraphTests.isConnected(g)); g.addVertex(3); assertFalse(GraphTests.isConnected(g)); g.addEdge(1, 3); assertTrue(GraphTests.isConnected(g)); } @Test public void testIsConnectedDirected() { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); assertFalse(GraphTests.isWeaklyConnected(g)); assertFalse(GraphTests.isStronglyConnected(g)); g.addVertex(1); assertTrue(GraphTests.isWeaklyConnected(g)); assertTrue(GraphTests.isStronglyConnected(g)); g.addVertex(2); assertFalse(GraphTests.isWeaklyConnected(g)); assertFalse(GraphTests.isStronglyConnected(g)); g.addEdge(1, 2); assertTrue(GraphTests.isWeaklyConnected(g)); assertFalse(GraphTests.isStronglyConnected(g)); g.addVertex(3); assertFalse(GraphTests.isWeaklyConnected(g)); assertFalse(GraphTests.isStronglyConnected(g)); g.addEdge(2, 3); assertTrue(GraphTests.isWeaklyConnected(g)); assertFalse(GraphTests.isStronglyConnected(g)); g.addEdge(3, 1); assertTrue(GraphTests.isWeaklyConnected(g)); assertTrue(GraphTests.isStronglyConnected(g)); } @Test public void testIsTree() { Graph g = GraphTestsUtils.createPseudograph(); assertFalse(GraphTests.isTree(g)); g.addVertex(1); assertTrue(GraphTests.isTree(g)); g.addVertex(2); assertFalse(GraphTests.isTree(g)); g.addEdge(1, 2); assertTrue(GraphTests.isTree(g)); g.addVertex(3); assertFalse(GraphTests.isTree(g)); g.addEdge(1, 3); assertTrue(GraphTests.isTree(g)); g.addEdge(2, 3); assertFalse(GraphTests.isTree(g)); // disconnected but with correct number of edges Graph g1 = GraphTestsUtils.createPseudograph(); assertFalse(GraphTests.isTree(g1)); g1.addVertex(1); g1.addVertex(2); g.addEdge(1, 1); assertFalse(GraphTests.isTree(g1)); } @Test public void testIsForest1() { Graph g = GraphTestsUtils.createPseudograph(); assertFalse(GraphTests.isForest(g)); g.addVertex(1); assertTrue(GraphTests.isForest(g)); g.addVertex(2); assertTrue(GraphTests.isForest(g)); g.addEdge(1, 2); assertTrue(GraphTests.isForest(g)); g.addEdge(1, 2); assertFalse(GraphTests.isForest(g)); } @Test public void testIsForest2() { Graph g = GraphTestsUtils.createPseudograph(); StarGraphGenerator gen = new StarGraphGenerator<>(10); gen.generateGraph(g); gen.generateGraph(g); assertTrue(GraphTests.isForest(g)); } @Test public void testIsOverfull() { assertFalse(GraphTests.isOverfull(NamedGraphGenerator.clawGraph())); assertTrue(GraphTests.isOverfull(NamedGraphGenerator.doyleGraph())); Graph k6 = GraphTestsUtils.createPseudograph(); CompleteGraphGenerator gen = new CompleteGraphGenerator<>(6); gen.generateGraph(k6); assertFalse(GraphTests.isOverfull(k6)); Graph k7 = GraphTestsUtils.createPseudograph(); gen = new CompleteGraphGenerator<>(7); gen.generateGraph(k7); assertTrue(GraphTests.isOverfull(k7)); } @Test public void isSplit1() { assertFalse(GraphTests.isSplit(NamedGraphGenerator.petersenGraph())); Graph g = new Pseudograph<>(DefaultEdge.class); assertFalse(GraphTests.isSplit(g)); g.addVertex(0); assertTrue(GraphTests.isSplit(g)); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); // clique g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(2, 0); // independent set g.addEdge(3, 1); g.addEdge(3, 2); g.addEdge(4, 1); assertTrue(GraphTests.isSplit(g)); g.addEdge(3, 4); assertTrue(GraphTests.isSplit(g)); } @Test public void isSplit2() { // Create some random split graphs. Random rand = new Random(0); CompleteGraphGenerator gen = new CompleteGraphGenerator<>(6); for (int inst = 0; inst < 5; inst++) { // 1. create a clique Graph g = GraphTestsUtils.createSimpleGraph(); gen.generateGraph(g); // 2. add a number of vertices (the independent set) and connect some of these vertices // with vertices in the clique. for (int j = 6; j < 12; j++) { g.addVertex(j); for (int i = 0; i < 6; i++) if (rand.nextBoolean()) g.addEdge(i, j); } assertTrue(GraphTests.isSplit(g)); } } @Test public void testIsCubic() { assertTrue(GraphTests.isCubic(NamedGraphGenerator.petersenGraph())); Graph triangle = new SimpleGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(triangle, 1, 2); Graphs.addEdgeWithVertices(triangle, 2, 3); Graphs.addEdgeWithVertices(triangle, 3, 1); assertFalse(GraphTests.isCubic(triangle)); } @Test public void testIsChordal() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 2, 3); Graphs.addEdgeWithVertices(graph, 3, 4); Graphs.addEdgeWithVertices(graph, 4, 5); Graphs.addEdgeWithVertices(graph, 5, 1); Graphs.addEdgeWithVertices(graph, 1, 3); assertFalse(GraphTests.isChordal(graph)); Graphs.addEdgeWithVertices(graph, 1, 4); assertTrue(GraphTests.isChordal(graph)); } @Test public void testIsWeaklyChordal() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 2, 3); Graphs.addEdgeWithVertices(graph, 3, 4); Graphs.addEdgeWithVertices(graph, 4, 5); Graphs.addEdgeWithVertices(graph, 5, 1); assertFalse(GraphTests.isWeaklyChordal(graph)); Graphs.addEdgeWithVertices(graph, 1, 3); assertTrue(GraphTests.isWeaklyChordal(graph)); } /** * Full graph on 4 vertices (every graph on less that 5 is planar) */ @Test public void testIsPlanar1() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 } }; Graph graph = getGraph(edges); assertTrue(GraphTests.isPlanar(graph)); } /** * $K_{3,3}$ */ @Test public void testIsPlanar2() { int[][] edges = { { 1, 4 }, { 1, 5 }, { 1, 6 }, { 2, 4 }, { 2, 5 }, { 2, 6 }, { 3, 4 }, { 3, 5 }, { 3, 6 } }; Graph graph = getGraph(edges); assertFalse(GraphTests.isPlanar(graph)); } /** * $K_{5}$ */ @Test public void testIsPlanar3() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, { 2, 3 }, { 2, 4 }, { 2, 5 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }; Graph graph = getGraph(edges); assertFalse(GraphTests.isPlanar(graph)); } @Test public void testIsK33Subdivision1() { int[][] edges = { { 1, 4 }, { 1, 5 }, { 1, 6 }, { 2, 4 }, { 2, 5 }, { 2, 6 }, { 3, 4 }, { 3, 5 }, { 3, 6 } }; Graph graph = getGraph(edges); assertTrue(GraphTests.isKuratowskiSubdivision(graph)); assertTrue(GraphTests.isK33Subdivision(graph)); } @Test public void testIsK33Subdivision2() { int[][] edges = { { 1, 5 }, { 1, 6 }, { 2, 4 }, { 2, 6 }, { 3, 4 }, { 3, 5 }, { 1, 7 }, { 7, 4 }, { 2, 8 }, { 8, 5 }, { 3, 9 }, { 9, 6 } }; Graph graph = getGraph(edges); assertTrue(GraphTests.isKuratowskiSubdivision(graph)); assertTrue(GraphTests.isK33Subdivision(graph)); } @Test public void testIsK5Subdivision1() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, { 2, 3 }, { 2, 4 }, { 2, 5 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }; Graph graph = getGraph(edges); assertTrue(GraphTests.isKuratowskiSubdivision(graph)); assertTrue(GraphTests.isK5Subdivision(graph)); } @Test public void testIsK5Subdivision2() { int[][] edges = { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 }, { 8, 9 }, { 9, 10 }, { 10, 1 }, { 1, 5 }, { 1, 7 }, { 3, 7 }, { 3, 9 }, { 5, 9 }, }; Graph graph = getGraph(edges); assertTrue(GraphTests.isKuratowskiSubdivision(graph)); assertTrue(GraphTests.isK5Subdivision(graph)); } @Test public void failRequireIsWeightedOnUnweightedGraph() { try { Graph graph = new DefaultDirectedGraph<>(DefaultWeightedEdge.class); GraphTests.requireWeighted(graph); fail("Expected an IllegalArgumentException to be thrown"); } catch (IllegalArgumentException e) { assertThat(e.getMessage(), is("Graph must be weighted")); } } @Test public void failRequireIsWeightedOnNull() { try { GraphTests.requireWeighted(null); fail("Expected an NullPointerException to be thrown"); } catch (NullPointerException e) { assertThat(e.getMessage(), is("Graph cannot be null")); } } @Test public void testRequireIsWeighted() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultEdge.class); assertEquals(graph, GraphTests.requireWeighted(graph)); } /** * Creates a graph from the list of its edges * * @param edges the edge list of a graph * @return a graph specified by the {@code edges} */ private Graph getGraph(int[][] edges) { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); for (int[] edge : edges) { Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); } return graph; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/GraphTestsUtils.java000066400000000000000000000031031402514743400300650ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import org.jgrapht.graph.*; import org.jgrapht.util.*; /** * Helper methods for graph creation on all tests. * * @author Dimitrios Michail */ public class GraphTestsUtils { /** * Create a simple graph with integer vertices and default edges. * * @return a simple graph with integer vertices and default edges. */ public static Graph createSimpleGraph() { return new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); } /** * Create a pseudo graph with integer vertices and default edges. * * @return a pseudo graph with integer vertices and default edges */ public static Graph createPseudograph() { return new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/GraphsTest.java000066400000000000000000000243711402514743400270560ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Christoph Zauner and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; /** * @author Christoph Zauner */ public class GraphsTest { //@formatter:off /** * Graph before removing X: * * +--> C * | * A +--> B +--+ * | * +--> D * * Expected graph after removing X: * * +--> C * | * A +--> B +--+ * | * +--> D */ //@formatter:on @Test public void removeVertex_vertexNotFound() { Graph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; String x = "X"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Graph expectedGraph = new DefaultDirectedGraph<>(TestEdge.class); expectedGraph.addVertex(a); expectedGraph.addVertex(b); expectedGraph.addVertex(c); expectedGraph.addVertex(d); expectedGraph.addEdge(a, b); expectedGraph.addEdge(b, c); expectedGraph.addEdge(b, d); boolean vertexHasBeenRemoved = Graphs.removeVertexAndPreserveConnectivity(graph, x); Assert.assertEquals(expectedGraph, graph); Assert.assertFalse(vertexHasBeenRemoved); } //@formatter:off /** * Graph before removing B: * * +--> C * | * A +--> B +--+ * | * +--> D * * Graph after removing B: * * +--> C * | * A +--+ * | * +--> D */ //@formatter:on @Test public void removeVertex00() { Graph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Graph expectedGraph = new DefaultDirectedGraph<>(TestEdge.class); expectedGraph.addVertex(a); expectedGraph.addVertex(c); expectedGraph.addVertex(d); expectedGraph.addEdge(a, c); expectedGraph.addEdge(a, d); boolean vertexHasBeenRemoved = Graphs.removeVertexAndPreserveConnectivity(graph, b); Assert.assertEquals(expectedGraph, graph); Assert.assertTrue(vertexHasBeenRemoved); } //@formatter:off /** * Graph before removing A: * * A +--> B * * Expected graph after removing A: * * B */ //@formatter:on @Test public void removeVertex01() { Graph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; graph.addVertex(a); graph.addVertex(b); graph.addEdge(a, b); Graph expectedGraph = new DefaultDirectedGraph<>(TestEdge.class); expectedGraph.addVertex(b); boolean vertexHasBeenRemoved = Graphs.removeVertexAndPreserveConnectivity(graph, a); Assert.assertEquals(expectedGraph, graph); Assert.assertTrue(vertexHasBeenRemoved); } //@formatter:off /** * Graph before removing B: * * A +--> B * * Expected graph after removing B: * * A */ //@formatter:on @Test public void removeVertex02() { Graph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; graph.addVertex(a); graph.addVertex(b); graph.addEdge(a, b); Graph expectedGraph = new DefaultDirectedGraph<>(TestEdge.class); expectedGraph.addVertex(a); boolean vertexHasBeenRemoved = Graphs.removeVertexAndPreserveConnectivity(graph, b); Assert.assertEquals(expectedGraph, graph); Assert.assertTrue(vertexHasBeenRemoved); } //@formatter:off /** * Input: * * A (source, not part of graph) * B (target, already part of graph) * C (target, not part of graph) * * Expected output: * * +--> B * | * A +--+ * | * +--> C */ //@formatter:on @Test public void addOutgoingEdges() { DefaultDirectedGraph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; graph.addVertex(b); Graph expectedGraph = new DefaultDirectedGraph<>(TestEdge.class); expectedGraph.addVertex(a); expectedGraph.addVertex(b); expectedGraph.addVertex(c); expectedGraph.addEdge(a, b); expectedGraph.addEdge(a, c); List targets = new ArrayList(); targets.add(b); targets.add(c); Graphs.addOutgoingEdges(graph, a, targets); Assert.assertEquals(expectedGraph, graph); } //@formatter:off /** * Input: * * A (target, not part of graph) * B (source, already part of graph) * C (source, not part of graph) * * Expected output: * * +--+ B * | * A <--+ * | * +--+ C */ //@formatter:on @Test public void addIncomingEdges() { DefaultDirectedGraph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; graph.addVertex(b); Graph expectedGraph = new DefaultDirectedGraph<>(TestEdge.class); expectedGraph.addVertex(a); expectedGraph.addVertex(b); expectedGraph.addVertex(c); expectedGraph.addEdge(b, a); expectedGraph.addEdge(c, a); List targets = new ArrayList(); targets.add(b); targets.add(c); Graphs.addIncomingEdges(graph, a, targets); Assert.assertEquals(expectedGraph, graph); } //@formatter:off /** * Input: * * +--> C * | * A +--> B +--+ * | * +--> D */ //@formatter:on @Test public void vertexHasChildren_B() { DefaultDirectedGraph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Assert.assertTrue(Graphs.vertexHasSuccessors(graph, b)); } //@formatter:off /** * Input: * * +--> C * | * A +--> B +--+ * | * +--> D */ //@formatter:on @Test public void vertexHasChildren_C() { DefaultDirectedGraph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Assert.assertFalse(Graphs.vertexHasSuccessors(graph, c)); } //@formatter:off /** * Input: * * +--> C * | * A +--> B +--+ * | * +--> D */ //@formatter:on @Test public void vertexHasParents_B() { DefaultDirectedGraph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Assert.assertTrue(Graphs.vertexHasPredecessors(graph, b)); } //@formatter:off /** * Input: * * +--> C * | * A +--> B +--+ * | * +--> D */ //@formatter:on @Test public void vertexHasParents_A() { DefaultDirectedGraph graph = new DefaultDirectedGraph<>(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Assert.assertFalse(Graphs.vertexHasPredecessors(graph, a)); } @Test public void testNeighborSetOf() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 4); graph.addEdge(1, 4); Set neighborSet = Graphs.neighborSetOf(graph, 1); Assert.assertEquals(Set.of(2, 4), neighborSet); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/IntegrationTestSuite.java000066400000000000000000000021001402514743400311110ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by John Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import com.googlecode.junittoolbox.*; import org.junit.experimental.categories.*; import org.junit.runner.*; /** * Suite of all unit and integration tests (as run by mvn verify). Excludes performance tests and * optional tests. * * @author John Sichi */ @RunWith(ParallelSuite.class) @Categories.ExcludeCategory(OptionalTests.class) @SuiteClasses({ "**/*Test.class", "!**/perf/**" }) public class IntegrationTestSuite { } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/OptionalTests.java000066400000000000000000000014421402514743400275740ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Philipp Kaesgen and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; /** * Marker interface to designate an optional tests category. * * @author Philipp Kaesgen **/ public interface OptionalTests { } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/PerformanceTestSuite.java000066400000000000000000000020041402514743400310720ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by John Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import com.googlecode.junittoolbox.*; import org.junit.runner.*; /** * Suite of performance tests only. We use WildcardPatternSuite instead of ParallelSuite to avoid * running multiple benchmark tests simultaneously. * * @author John Sichi */ @RunWith(WildcardPatternSuite.class) @SuiteClasses({ "**/perf/**/*Test.class" }) public class PerformanceTestSuite { } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/SlowTests.java000066400000000000000000000014331402514743400267330ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; /** * Marker interface to designate a slow tests category. * * @author Dimitrios Michail */ public interface SlowTests { } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/TestUtil.java000066400000000000000000000054071402514743400265460ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht; import org.jgrapht.graph.*; /** * Test related utility methods. */ public class TestUtil { public static void constructGraph(Graph graph, int[][] edges) { boolean weighted = edges.length > 0 && edges[0].length > 2; for (int[] edge : edges) { DefaultEdge graphEdge = Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); if (weighted) { graph.setEdgeWeight(graphEdge, edge[2]); } } } public static void constructGraph(Graph graph, V[][] edges) { for (V[] edge : edges) { Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); } } public static Graph createUndirected(V[][] edges) { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultEdge.class); constructGraph(graph, edges); return graph; } public static Graph createUndirected(int[][] edges) { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultEdge.class); constructGraph(graph, edges); return graph; } public static Graph createDirected(int[][] edges) { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultEdge.class); constructGraph(graph, edges); return graph; } public static Graph createDirected(V[][] edges) { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultEdge.class); constructGraph(graph, edges); return graph; } public static Graph createPseudograph(int[][] edges) { Graph graph = new WeightedPseudograph<>(DefaultEdge.class); constructGraph(graph, edges); return graph; } public static Graph createPseudograph(V[][] edges) { Graph graph = new WeightedPseudograph<>(DefaultEdge.class); constructGraph(graph, edges); return graph; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/000077500000000000000000000000001402514743400246635ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/StoerWagnerMinimumCutTest.java000066400000000000000000000233561402514743400326470ustar00rootroot00000000000000/* * (C) Copyright 2011-2021, by Robby McKilliam and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Robby McKilliam */ public class StoerWagnerMinimumCutTest { // ~ Instance fields -------------------------------------------------------- private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; private String v5 = "v5"; private String v6 = "v6"; private String v7 = "v7"; private String v8 = "v8"; /** * Test of mergeVertices method, of class StoerWagnerMinimumCut. */ @Test public void testMinCut14() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); g.addVertex(v4); DefaultWeightedEdge e; e = g.addEdge(v1, v2); g.setEdgeWeight(e, 3.0); e = g.addEdge(v1, v3); g.setEdgeWeight(e, 2.0); e = g.addEdge(v1, v4); g.setEdgeWeight(e, 4.0); e = g.addEdge(v2, v3); g.setEdgeWeight(e, 1.0); e = g.addEdge(v3, v4); g.setEdgeWeight(e, 1.0); StoerWagnerMinimumCut mincut = new StoerWagnerMinimumCut<>(g); assertEquals(4.0, mincut.minCutWeight(), 0.000001); } /** * Test of mergeVertices method, of class StoerWagnerMinimumCut. */ @Test public void testMinCutDisconnected() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); g.addVertex(v4); DefaultWeightedEdge e; e = g.addEdge(v1, v2); g.setEdgeWeight(e, 3.0); e = g.addEdge(v1, v3); g.setEdgeWeight(e, 2.0); e = g.addEdge(v2, v3); g.setEdgeWeight(e, 1.0); StoerWagnerMinimumCut mincut = new StoerWagnerMinimumCut<>(g); assertEquals(0.0, mincut.minCutWeight(), 0.000001); } /** * Test of StoerWagnerMinimumCut when a 0-weight edge exists. */ @Test public void testMinCut0Weight() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); g.addVertex(v4); g.addVertex(v5); g.addVertex(v6); g.addVertex(v7); g.addVertex(v8); DefaultWeightedEdge e; e = g.addEdge(v1, v2); g.setEdgeWeight(e, 1.0); e = g.addEdge(v2, v3); g.setEdgeWeight(e, 2.0); e = g.addEdge(v3, v4); g.setEdgeWeight(e, 0.0); e = g.addEdge(v4, v5); g.setEdgeWeight(e, 1.0); e = g.addEdge(v5, v6); g.setEdgeWeight(e, 2.0); e = g.addEdge(v6, v1); g.setEdgeWeight(e, 0.0); e = g.addEdge(v6, v8); g.setEdgeWeight(e, 1.0); e = g.addEdge(v8, v7); g.setEdgeWeight(e, 0.0); e = g.addEdge(v7, v3); g.setEdgeWeight(e, 2.0); StoerWagnerMinimumCut mincut = new StoerWagnerMinimumCut<>(g); Set solution1 = new HashSet<>(); Collections.addAll(solution1, v4, v5, v6, v8); Set solution2 = new HashSet<>(); Collections.addAll(solution2, v1, v2, v3, v7); assertEquals(0.0, mincut.minCutWeight(), 0.000001); assertTrue(mincut.minCut().equals(solution1) || mincut.minCut().equals(solution2)); } /** * Test of StoerWagnerMinimumCut when a <1-weight edge exists. */ @Test public void testMinCutSmallWeight() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); g.addVertex(v4); DefaultWeightedEdge e; e = g.addEdge(v1, v2); g.setEdgeWeight(e, 0.5); e = g.addEdge(v2, v3); g.setEdgeWeight(e, 1.0); e = g.addEdge(v3, v4); g.setEdgeWeight(e, 0.5); e = g.addEdge(v4, v1); g.setEdgeWeight(e, 1.0); StoerWagnerMinimumCut mincut = new StoerWagnerMinimumCut<>(g); Set solution1 = new HashSet<>(); Collections.addAll(solution1, v1, v4); Set solution2 = new HashSet<>(); Collections.addAll(solution2, v2, v3); assertEquals(1.0, mincut.minCutWeight(), 0.000001); assertTrue(mincut.minCut().equals(solution1) || mincut.minCut().equals(solution2)); } /** * Test of StoerWagnerMinimumCut on a Multigraph. */ @Test public void testMinCutMultigraph() { WeightedMultigraph g = new WeightedMultigraph<>(DefaultWeightedEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); DefaultWeightedEdge e; e = g.addEdge(v1, v2); g.setEdgeWeight(e, 1.5); e = g.addEdge(v1, v2); g.setEdgeWeight(e, 1.5); e = g.addEdge(v2, v3); g.setEdgeWeight(e, 2.0); StoerWagnerMinimumCut mincut = new StoerWagnerMinimumCut<>(g); Set solution1 = new HashSet<>(); Collections.addAll(solution1, v1, v2); Set solution2 = new HashSet<>(); Collections.addAll(solution2, v3); assertEquals(2.0, mincut.minCutWeight(), 0.000001); assertTrue(mincut.minCut().equals(solution1) || mincut.minCut().equals(solution2)); } /** * Test of StoerWagnerMinimumCut on an unweighted graph */ @Test public void testMinCutUnweighted() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); g.addVertex(v4); g.addVertex(v5); g.addVertex(v6); g.addEdge(v1, v2); g.addEdge(v2, v3); g.addEdge(v3, v1); g.addEdge(v4, v5); g.addEdge(v5, v6); g.addEdge(v6, v4); g.addEdge(v3, v4); StoerWagnerMinimumCut mincut = new StoerWagnerMinimumCut<>(g); Set solution1 = new HashSet<>(); Collections.addAll(solution1, v1, v2, v3); Set solution2 = new HashSet<>(); Collections.addAll(solution2, v4, v5, v6); assertEquals(1.0, mincut.minCutWeight(), 0.000001); assertTrue(mincut.minCut().equals(solution1) || mincut.minCut().equals(solution2)); } /** * Test of StoerWagnerMinimumCut on empty and small graphs */ @Test public void testMinCutEmpty() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); boolean caught = false; // No vertices try { new StoerWagnerMinimumCut<>(g); } catch (IllegalArgumentException ex) { caught = true; } assertTrue(caught); } /** * Test of StoerWagnerMinimumCut on empty and small graphs */ @Test public void testMinCutSingleton() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); boolean caught = false; // 1 vertex g.addVertex(v1); try { new StoerWagnerMinimumCut<>(g); } catch (IllegalArgumentException ex) { caught = true; } assertTrue(caught); } /** * Test of StoerWagnerMinimumCut on empty and small graphs */ @Test public void testMinCutDoubleton() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); StoerWagnerMinimumCut mincut; // 2 vertices, no edges g.addVertex(v1); g.addVertex(v2); mincut = new StoerWagnerMinimumCut<>(g); Set solution1 = new HashSet<>(); Collections.addAll(solution1, v1); Set solution2 = new HashSet<>(); Collections.addAll(solution2, v2); assertEquals(0.0, mincut.minCutWeight(), 0.000001); assertTrue(mincut.minCut().equals(solution1) || mincut.minCut().equals(solution2)); } /** * Test of StoerWagnerMinimumCut on empty and small graphs */ @Test public void testMinCutSmall() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); StoerWagnerMinimumCut mincut; // 2 vertices, 1 edge g.addVertex(v1); g.addVertex(v2); g.addEdge(v1, v2); Set solution1 = new HashSet<>(); Collections.addAll(solution1, v1); Set solution2 = new HashSet<>(); Collections.addAll(solution2, v2); mincut = new StoerWagnerMinimumCut<>(g); assertEquals(1.0, mincut.minCutWeight(), 0.000001); assertTrue(mincut.minCut().equals(solution1) || mincut.minCut().equals(solution2)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/TransitiveClosureTest.java000066400000000000000000000104441402514743400320560ustar00rootroot00000000000000/* * (C) Copyright 2007-2021, by Vinayak R Borkar and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** */ public class TransitiveClosureTest { // ~ Methods ---------------------------------------------------------------- @Test public void testLinearGraph() { SimpleDirectedGraph graph = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); int N = 10; LinearGraphGenerator gen = new LinearGraphGenerator<>(N); gen.generateGraph(graph); TransitiveClosure.INSTANCE.closeSimpleDirectedGraph(graph); assertEquals(true, graph.edgeSet().size() == ((N * (N - 1)) / 2)); for (int i = 0; i < N; ++i) { for (int j = i + 1; j < N; ++j) { assertEquals(true, graph.getEdge(i, j) != null); } } } @Test public void testRingGraph() { SimpleDirectedGraph graph = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); int N = 10; RingGraphGenerator gen = new RingGraphGenerator<>(N); gen.generateGraph(graph); TransitiveClosure.INSTANCE.closeSimpleDirectedGraph(graph); assertEquals(true, graph.edgeSet().size() == (N * (N - 1))); for (int i = 0; i < N; ++i) { for (int j = 0; j < N; ++j) { assertEquals(true, (i == j) || (graph.getEdge(i, j) != null)); } } } @Test public void testNoVerticesDag() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph<>(DefaultEdge.class); TransitiveClosure.INSTANCE.closeDirectedAcyclicGraph(graph); assertEquals(0, graph.edgeSet().size()); } @Test public void testEmptyDag() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); int n = 10; EmptyGraphGenerator gen = new EmptyGraphGenerator<>(n); gen.generateGraph(graph); TransitiveClosure.INSTANCE.closeDirectedAcyclicGraph(graph); assertEquals(0, graph.edgeSet().size()); } @Test public void testCompleteBipartiteDag() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteBipartiteGraphGenerator gen = new CompleteBipartiteGraphGenerator<>(5, 5); gen.generateGraph(graph); TransitiveClosure.INSTANCE.closeDirectedAcyclicGraph(graph); assertEquals(25, graph.edgeSet().size()); } @Test public void testLinearGraphForDag() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); int n = 10; LinearGraphGenerator gen = new LinearGraphGenerator<>(n); gen.generateGraph(graph); TransitiveClosure.INSTANCE.closeDirectedAcyclicGraph(graph); assertEquals((n * (n - 1)) / 2, graph.edgeSet().size()); for (int i = 0; i < n; ++i) { for (int j = i + 1; j < n; ++j) { assertNotNull(graph.getEdge(i, j)); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/TransitiveReductionTest.java000066400000000000000000000234751402514743400324060ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Christophe Thiebaud and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; public class TransitiveReductionTest { // @formatter:off static final int[][] MATRIX = new int[][] { {0, 1, 1, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 1, 1}, {0, 0, 0, 0, 1}, {0, 1, 0, 0, 0} }; static final int[][] EXPECTED_TRANSITIVELY_REDUCED_MATRIX = new int[][] { {0, 0, 1, 0, 0}, {0, 0, 0, 0, 0}, {0, 0, 0, 1, 0}, {0, 0, 0, 0, 1}, {0, 1, 0, 0, 0} }; // @formatter:on @Test public void testInternals() { // @formatter:off final int[][] expected_path_matrix = new int[][] { {0, 1, 1, 1, 1}, {0, 0, 0, 0, 0}, {0, 1, 0, 1, 1}, {0, 1, 0, 0, 1}, {0, 1, 0, 0, 0} }; // @formatter:on // System.out.println(Arrays.deepToString(matrix) + " original matrix"); final int n = MATRIX.length; // calc path matrix int[][] path_matrix = new int[n][n]; { { System.arraycopy(MATRIX, 0, path_matrix, 0, MATRIX.length); final BitSet[] pathMatrixAsBitSetArray = asBitSetArray(path_matrix); TransitiveReduction.transformToPathMatrix(pathMatrixAsBitSetArray); path_matrix = asIntArray(pathMatrixAsBitSetArray); } // System.out.println(Arrays.deepToString(path_matrix) + " path // matrix"); Assert.assertArrayEquals(expected_path_matrix, path_matrix); } // calc transitive reduction { int[][] transitively_reduced_matrix = new int[n][n]; { System .arraycopy(path_matrix, 0, transitively_reduced_matrix, 0, path_matrix.length); final BitSet[] transitivelyReducedMatrixAsBitSetArray = asBitSetArray(transitively_reduced_matrix); TransitiveReduction.transitiveReduction(transitivelyReducedMatrixAsBitSetArray); transitively_reduced_matrix = asIntArray(transitivelyReducedMatrixAsBitSetArray); } // System.out.println(Arrays.deepToString(transitively_reduced_matrix) // + " transitive reduction"); Assert .assertArrayEquals( EXPECTED_TRANSITIVELY_REDUCED_MATRIX, transitively_reduced_matrix); } } static private BitSet[] asBitSetArray(final int[][] intArray) { final BitSet[] ret = new BitSet[intArray.length]; for (int i = 0; i < ret.length; i++) { ret[i] = new BitSet(intArray[i].length); for (int j = 0; j < intArray[i].length; j++) { ret[i].set(j, intArray[i][j] == 1); } } return ret; } static private int[][] asIntArray(final BitSet[] bitsetArray) { final int[][] ret = new int[bitsetArray.length][bitsetArray.length]; for (int i = 0; i < ret.length; i++) { for (int j = 0; j < ret.length; j++) { ret[i][j] = bitsetArray[i].get(j) ? 1 : 0; } } return ret; } @Test(expected = NullPointerException.class) public void testReduceNull() { TransitiveReduction.INSTANCE.reduce(null); } @Test public void testReduceNoVertexNoEdge() { SimpleDirectedGraph graph = new SimpleDirectedGraph<>(DefaultEdge.class); TransitiveReduction.INSTANCE.reduce(graph); assertEquals(graph.vertexSet().size(), 0); assertEquals(graph.edgeSet().size(), 0); } @Test public void testReduceSomeVerticesNoEdge() { SimpleDirectedGraph graph = new SimpleDirectedGraph<>(DefaultEdge.class); graph.addVertex("x"); graph.addVertex("y"); graph.addVertex("z"); TransitiveReduction.INSTANCE.reduce(graph); assertEquals(graph.vertexSet().size(), 3); assertEquals(graph.edgeSet().size(), 0); } @Test public void testReduceAlreadyReduced() { SimpleDirectedGraph graph = new SimpleDirectedGraph<>(DefaultEdge.class); graph.addVertex("x"); graph.addVertex("y"); graph.addVertex("z"); graph.addEdge("x", "y"); graph.addEdge("y", "z"); assertEquals(graph.vertexSet().size(), 3); assertEquals(graph.edgeSet().size(), 2); // reduce ! TransitiveReduction.INSTANCE.reduce(graph); assertEquals(graph.vertexSet().size(), 3); assertEquals(graph.edgeSet().size(), 2); assertTrue(graph.containsEdge("x", "y")); assertTrue(graph.containsEdge("y", "z")); assertFalse(graph.containsEdge("x", "z")); } @Test public void testReduceBasic() { SimpleDirectedGraph graph = new SimpleDirectedGraph<>(DefaultEdge.class); graph.addVertex("x"); graph.addVertex("y"); graph.addVertex("z"); graph.addEdge("x", "y"); graph.addEdge("y", "z"); graph.addEdge("x", "z"); // <-- reduce me, please assertEquals(graph.vertexSet().size(), 3); assertEquals(graph.edgeSet().size(), 3); // reduce ! TransitiveReduction.INSTANCE.reduce(graph); assertEquals(graph.vertexSet().size(), 3); assertEquals(graph.edgeSet().size(), 2); assertTrue(graph.containsEdge("x", "y")); assertTrue(graph.containsEdge("y", "z")); assertFalse(graph.containsEdge("x", "z")); } @Test public void testReduceFarAway() { SimpleDirectedGraph graph = new SimpleDirectedGraph<>(DefaultEdge.class); graph.addVertex("a"); graph.addVertex("b"); graph.addVertex("c"); graph.addVertex("x"); graph.addVertex("y"); graph.addVertex("z"); graph.addEdge("a", "b"); graph.addEdge("b", "c"); graph.addEdge("c", "x"); graph.addEdge("x", "y"); graph.addEdge("y", "z"); graph.addEdge("a", "z"); // <-- reduce me, please assertEquals(graph.vertexSet().size(), 6); assertEquals(graph.edgeSet().size(), 6); // reduce ! TransitiveReduction.INSTANCE.reduce(graph); assertEquals(graph.vertexSet().size(), 6); assertEquals(graph.edgeSet().size(), 5); assertTrue(graph.containsEdge("a", "b")); assertTrue(graph.containsEdge("b", "c")); assertTrue(graph.containsEdge("c", "x")); assertTrue(graph.containsEdge("x", "y")); assertTrue(graph.containsEdge("y", "z")); assertFalse(graph.containsEdge("a", "z")); } @Test public void testReduceCanonicalGraph() { Graph graph = fromMatrixToDirectedGraph(MATRIX); // a few spot tests to verify the graph looks like it should assertFalse(graph.containsEdge(0, 0)); assertTrue(graph.containsEdge(0, 1)); assertTrue(graph.containsEdge(2, 4)); assertTrue(graph.containsEdge(4, 1)); assertEquals(graph.vertexSet().size(), 5); assertEquals(graph.edgeSet().size(), 6); // reduce ! TransitiveReduction.INSTANCE.reduce(graph); assertEquals(graph.vertexSet().size(), 5); assertEquals(graph.edgeSet().size(), 4); // equivalent spot tests on the reduced graph assertFalse(graph.containsEdge(0, 0)); assertFalse(graph.containsEdge(0, 1)); assertFalse(graph.containsEdge(2, 4)); assertTrue(graph.containsEdge(4, 1)); // the full verification; less readable, but somewhat more complete :) int[][] actual_transitively_reduced_matrix = fromDirectedGraphToMatrix(graph); assertArrayEquals(EXPECTED_TRANSITIVELY_REDUCED_MATRIX, actual_transitively_reduced_matrix); } static private Graph fromMatrixToDirectedGraph(final int[][] matrix) { final SimpleDirectedGraph graph = new SimpleDirectedGraph<>(DefaultEdge.class); for (int i = 0; i < matrix.length; i++) { graph.addVertex(i); } for (int i = 0; i < matrix.length; i++) { for (int j = 0; j < matrix[i].length; j++) { if (matrix[i][j] == 1) { graph.addEdge(i, j); } } } return graph; } private int[][] fromDirectedGraphToMatrix(final Graph directedGraph) { final List vertices = new ArrayList<>(directedGraph.vertexSet()); final int n = vertices.size(); final int[][] matrix = new int[n][n]; final Set edges = directedGraph.edgeSet(); for (final DefaultEdge edge : edges) { final Integer v1 = directedGraph.getEdgeSource(edge); final Integer v2 = directedGraph.getEdgeTarget(edge); final int v_1 = vertices.indexOf(v1); final int v_2 = vertices.indexOf(v2); matrix[v_1][v_2] = 1; } return matrix; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clique/000077500000000000000000000000001402514743400261455ustar00rootroot00000000000000AllVariantsBronKerboschCliqueFinderTest.java000066400000000000000000000050401402514743400366050ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clique/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Test that all Bron-Kerbosch variants return the same results. * * @author Dimitrios Michail */ public class AllVariantsBronKerboschCliqueFinderTest { @Test public void testRandomInstances() { final Random rng = new Random(33); final double edgeProbability = 0.5; final int numberVertices = 30; final int repeat = 10; GraphGenerator gg = new GnpRandomGraphGenerator( numberVertices, edgeProbability, rng, false); for (int i = 0; i < repeat; i++) { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gg.generateGraph(g); Iterable> alg1 = new BronKerboschCliqueFinder<>(g); Iterable> alg2 = new PivotBronKerboschCliqueFinder<>(g); Iterable> alg3 = new DegeneracyBronKerboschCliqueFinder<>(g); Set> cliques1 = new HashSet<>(); for (Set c : alg1) { cliques1.add(c); } Set> cliques2 = new HashSet<>(); for (Set c : alg2) { cliques2.add(c); } Set> cliques3 = new HashSet<>(); for (Set c : alg3) { cliques3.add(c); } assertEquals(cliques1.size(), cliques2.size()); assertEquals(cliques2.size(), cliques3.size()); assertEquals(cliques1, cliques2); assertEquals(cliques2, cliques3); } } } BaseBronKerboschCliqueFinderTest.java000066400000000000000000000145141402514743400352450ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clique/* * (C) Copyright 2005-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.concurrent.*; import static org.junit.Assert.assertEquals; /** * . * * @author John V. Sichi */ public abstract class BaseBronKerboschCliqueFinderTest { protected static final String V1 = "v1"; protected static final String V2 = "v2"; protected static final String V3 = "v3"; protected static final String V4 = "v4"; protected static final String V5 = "v5"; protected static final String V6 = "v6"; protected static final String V7 = "v7"; protected static final String V8 = "v8"; protected static final String V9 = "v9"; protected static final String V10 = "v10"; protected abstract BaseBronKerboschCliqueFinder createFinder1( Graph graph); protected abstract BaseBronKerboschCliqueFinder createFinder2( Graph graph); protected abstract BaseBronKerboschCliqueFinder createFinder2( Graph graph, long timeout, TimeUnit unit); @Test public void testFindBiggest() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); createGraph(g); BaseBronKerboschCliqueFinder finder = createFinder1(g); Collection> cliques = new HashSet<>(); finder.maximumIterator().forEachRemaining(cliques::add); assertEquals(2, cliques.size()); Set> expected = new HashSet<>(); Set set = new HashSet<>(); set.add(V1); set.add(V2); set.add(V3); set.add(V4); expected.add(set); set = new HashSet<>(); set.add(V1); set.add(V2); set.add(V9); set.add(V10); expected.add(set); // convert result from Collection to Set because we don't want // order to be significant Set> actual = new HashSet<>(cliques); assertEquals(expected, actual); } @Test public void testFindAll() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); createGraph(g); MaximalCliqueEnumerationAlgorithm finder = createFinder1(g); Collection> cliques = new HashSet<>(); finder.iterator().forEachRemaining(cliques::add); assertEquals(5, cliques.size()); Set> expected = new HashSet<>(); Set set = new HashSet<>(); set.add(V1); set.add(V2); set.add(V3); set.add(V4); expected.add(set); set = new HashSet<>(); set.add(V5); set.add(V6); set.add(V7); expected.add(set); set = new HashSet<>(); set.add(V3); set.add(V4); set.add(V5); expected.add(set); set = new HashSet<>(); set.add(V7); set.add(V8); expected.add(set); set = new HashSet<>(); set.add(V1); set.add(V2); set.add(V9); set.add(V10); expected.add(set); // convert result from Collection to Set because we don't want // order to be significant Set> actual = new HashSet<>(cliques); assertEquals(expected, actual); } @Test(expected = IllegalArgumentException.class) public void testNonSimple() { Graph g = new Pseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addEdge("1", "2"); g.addEdge("1", "2"); MaximalCliqueEnumerationAlgorithm finder = createFinder1(g); Iterator> it = finder.iterator(); while (it.hasNext()) { it.next(); } } public static void createGraph(Graph g) { g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); g.addVertex(V6); g.addVertex(V7); g.addVertex(V8); g.addVertex(V9); g.addVertex(V10); // biggest clique: { V1, V2, V3, V4 } g.addEdge(V1, V2); g.addEdge(V1, V3); g.addEdge(V1, V4); g.addEdge(V2, V3); g.addEdge(V2, V4); g.addEdge(V3, V4); // smaller clique: { V5, V6, V7 } g.addEdge(V5, V6); g.addEdge(V5, V7); g.addEdge(V6, V7); // for fun, add an overlapping clique { V3, V4, V5 } g.addEdge(V3, V5); g.addEdge(V4, V5); // make V8 less lonely g.addEdge(V7, V8); // add one more maximal which is also the biggest { V1, V2, V9, V10 } g.addEdge(V1, V9); g.addEdge(V1, V10); g.addEdge(V2, V9); g.addEdge(V2, V10); g.addEdge(V9, V10); } @Test public void testComplete() { final int size = 6; Graph g = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator completeGraphGenerator = new CompleteGraphGenerator<>(size); completeGraphGenerator.generateGraph(g); MaximalCliqueEnumerationAlgorithm finder = createFinder2(g); Set> cliques = new HashSet<>(); finder.iterator().forEachRemaining(cliques::add); assertEquals(1, cliques.size()); cliques.stream().forEach(clique -> assertEquals(size, clique.size())); } } BronKerboschCliqueFinderTest.java000066400000000000000000000030061402514743400344440ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clique/* * (C) Copyright 2005-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.concurrent.*; /** * . * * @author John V. Sichi */ public class BronKerboschCliqueFinderTest extends BaseBronKerboschCliqueFinderTest { @Override protected BaseBronKerboschCliqueFinder createFinder1( Graph graph) { return new BronKerboschCliqueFinder<>(graph); } @Override protected BaseBronKerboschCliqueFinder createFinder2( Graph graph) { return new BronKerboschCliqueFinder<>(graph); } @Override protected BaseBronKerboschCliqueFinder createFinder2( Graph graph, long timeout, TimeUnit unit) { return new BronKerboschCliqueFinder<>(graph, timeout, unit); } } ChordalGraphMaxCliqueFinderTest.java000066400000000000000000000064021402514743400350720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clique/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for the {@link ChordalGraphMaxCliqueFinder} * * @author Timofey Chudakov */ public class ChordalGraphMaxCliqueFinderTest { /** * Tests maximum clique finding on an empty graph. */ @Test public void testGetMaximumClique1() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); Set clique = new ChordalGraphMaxCliqueFinder<>(graph).getClique(); assertNotNull(clique); assertEquals(0, clique.size()); } /** * Tests maximum clique finding on a chordal graph */ @Test public void testGetMaximumClique2() { int[][] edges = { { 1, 2 }, { 3, 4 }, { 3, 5 }, { 3, 6 }, { 4, 5 }, { 4, 6 }, { 5, 6 }, }; Graph graph = TestUtil.createUndirected(edges); Set clique = new ChordalGraphMaxCliqueFinder<>(graph).getClique(); assertNotNull(clique); assertEquals(4, clique.size()); assertIsClique(graph, clique); } /** * Tests maximum clique finding on a non-chordal graph */ @Test public void testGetMaximumClique3() { int[][] edges = { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 1 }, }; Graph graph = TestUtil.createUndirected(edges); Set clique = new ChordalGraphMaxCliqueFinder<>(graph).getClique(); assertNull(clique); } /** * Tests maximum clique finding on a pseudograph */ @Test public void testGetMaximumClique4() { int[][] edges = { { 1, 1 }, { 1, 1 }, { 1, 2 }, { 1, 2 }, { 1, 2 }, { 2, 2 }, { 2, 2 }, }; Graph graph = TestUtil.createPseudograph(edges); Set clique = new ChordalGraphMaxCliqueFinder<>(graph).getClique(); assertNotNull(clique); assertEquals(2, clique.size()); assertIsClique(graph, clique); } /** * Checks whether every two vertices from {@code set} are adjacent. * * @param graph the tested graph. * @param set the tested set of vertices. * @param the graph vertex type. * @param the graph edge type. */ private void assertIsClique(Graph graph, Set set) { ArrayList vertices = new ArrayList<>(set); for (int i = 0; i < vertices.size(); i++) { for (int j = 0; j < i; j++) { assertTrue(graph.containsEdge(vertices.get(i), vertices.get(j))); } } } } CliqueMinimalSeparatorDecomposition1.jpg000066400000000000000000000344171402514743400360310ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cliqueJFIFhExifMM* LGE(12iˆ%B&Nexus 5paint.net 4.0.52015:02:06 11:20:23'0220Ȑܑ   0100 , d2015:02:06 11:20:232015:02:06 11:20:233d610361610361610361R980100 NEM /W' Y' P2015:02:06PX(HHC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222X" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?jG|=;-m4h q@EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP\3o)sxt{n4~8h*f 2I\v]akߜck O _>M*R|-crrD@p}+^oki-N/;(tTQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE,GLZ ݯ>åip ˹|"hP9ٯQijlW[[81pOuQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEs~:·֯Pt ty&K~ei %5Udtm)*cbF ˦'J>^O]d&GǘC@w ۾F;e+3U5K%Դ0<-FR?Sj>n$-fLCs@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQT]J J.N" u>{@>%2kڔvXeO?S8#Ԣ,hTaT =+eŖ%ޢSP7{z+Aַhl-_<]cʘs,Þ|^Hhc.U<#8v}1|V7tQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW)E!!,8dj%zN7M4k7t0}‰O(0Q@s^?e\QlL.[-Dt֌Hr?@5Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@rqOnnbOJR!LE,{ ~5tѴXq%i3@xV5Bo x^J5=HU}doL9(oc/w,fGs5h6~c %C'9֠%ǃN^Yǧ&C?("^ls6U'xLx>h(($ m$ 1ԚTMwRxnh*7 Ү_}U/"6V2Ķ};Gҷ(((((((((((=ψt[k]h\1 W?**dPK1q¢(} "+#즗ACAf&Ǡ~V/]̷7E@I/-Y~hRLYe օh/]QEQEQEQEQX:Ԡ,-Q!`CyVgVQEQEQEQEQEQEQEQEQEQEQEQU5MFHҮ+ok M!eFOj~ X+c/w?ԭjDhӪ޹Y1DQ7axSkڸ7ătK)(:bZ^/o6z|9K8zS} ~\}U:8U+r(?z|KSk EսWgq@zKTPcM$PWEOSl4[ /u+mI+`}\m:n@z4~Xݿk8\xZƁ7y+Ÿ*}[-Z}Nl˦ڤnyf>@$[-cmIKH}>+KxbH8cEb0A`zqRQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@rέjZ BɨjF$[E<7\]j]"8gB ވc=M-J[Dur縙wor{v5۶usƻRx>\;;2oi)xoB'coZAȠ('3h\=IJ1]쨻*I$ȭK+]xUY  GcOEC|t&Z2Уm&A€5o49.beª&߷~'44s.%&(m=jՏw^G?Zn\Al}9op83} Nm>'敉i&~yf&h ( ( ( ( ( ( ( G;QgIWh>5 kU ^] lB)Fpr {|<Үg>}7I׵m_VNYZ\C%p0$f8u{h.0"St3Eq $l2?B) Fnq}o\kx¾%XY!dc xO 8} ip[A:+?(.SV.~f:x: -vY 'pJκ1I[YboI G ^VC41ο/uO (b;Z&T#S7#bqorI2Z~-jյdɉ6mǒqZ ;6+[XK>jQEQEQEQEQEQEQEQEQEQEQEQEQEV$~-Цֿ"K0ńF)IO0 ۜx)m$fcM:/Hޡk g!oԝO1FڋW׵ Yr̦ .'vɏH@oBZ+6X:+)@~o/]OG~Ej\iI5.24r!Fu]xnF{!k#ɗ߁V$_X]$I57)[i^)XcyKC̊UL<>:I}y; RVXW!ʢv5ߦ[]#Rk(=3ƽ=s$q9I}}{<+E[Ba4kd(PzN4ӳ*J}KO=:q$Q3:n Q+KK- [Xav@sk%In{W%a}6UrЃI`q D̲ԝrRXlm%;] wm*Hfw6QG@Km/D'n.)"OWAV:5 !y,ǫ1<䚹T53I[#,G'9'0/\%xAZ ggfkf/qc2A-=N:+/V7i$IIb?He fD}lOgD'MJsg{mp=a_n4Y XjŰ dyb;Utß'}FzeXx_JK V{MDD~.Ym?Vg6~1c>$Ӳu? E3ít&)pObȄ( u5X 5_Q|8@$ PQ@JW/ݒQu()HFHꈀ3RM:Q(EeJG(Οkڌ i4iחqp1]p¿myWXE9F\q;WqII=%i#R^4GV?1vpp,}1qvXiV_>Jz)F0yjeAu"<=:ͬzax"[u9tQ{, -W`Mrt_!l6ԹN f'kTX4MR(%㈣YKx9 dz2kEPEP{1i2 bf P cMk[Wktl:gb??-cYtQEQESխף_[5jEC돵kJp5FA? #aXX{ݢ(}E]A؎3u=p5H~zɣM;b0j&FAu5rM* />"[[N2+O>Kq{ >vIQZۗP1"EI#F,p&ZU >aqg1#[ KjڌeLvq3>OWWQ E ih6"(t+]|+*xk/~O3P2zCy((((##-VMchlGm!.8}Z(ᆉg?mZ%[`%2te ɭ!]: -C[Ps Vr3ңwK]Q-t'??/|F~iL*_ޤ?Tծ,|Mآm*䴵I)h(L'=< mVХjpΟNZmZPEEP\[ 9.D>jѮ3,kn5n\+q@Omc`ŏut]!BO_xIi4)>VHϣ!)Ԭx{DԢ7,r2CccX%5V j( k)'ۍヹ8# h`wQErV^xoX[^ q Ƞ4R*̓z)$l#r}oAk9V? t4g=5;StMB!㔣ĺ͈Wꎆm6E돖OpOE`x÷U6oҷdr*兞 O95| \j3tm\3k?H]p}yEuU /\ҵL^mxC r֯EPEP3SXd}-5W+31YX#PPEPEP%R [m*(@|F1}#Zڢ((O;bÊiz#Pկ.l$8cX罴hwO`vNh^I#of]VeksIzi4H1-^Y>{{ jҢ ( ( ( ( ( ( ( ( ( (*G5Օő&zY0-QEV~i:ٵm:.hm>(KLq6n]OB׷O7Ur~O悲6jR¨0>uM 6Mwܤ^7`}#W$j'I=Թݢ |_$lk8zUkZՏ&_)|٢#``o5Aa8hHdUkoX[)mSJޒOv~ܚtK+S8hg9t+{ӻ&~GAXz4I w]4&7G &|Aq>&-~hXشW1dD*yz?ykdߘnbauNcc2Hwp瓊D9XH28 AT6YFcR*1*yzv(((((((((((}Zxnc4Ȗ}Nu7O&;g:ԀkSG-p5$P~d)'LKpE>'EѢ\]7su1̓>1@:VQEr=OŐeu5xhc~/GWK@Q@Q@s |I8Ehƺ:x}*h((((((((((((׵nbѴYuܥcOnHn Ѣhj gw3-$vV#v>sķW2O(  ۀ1WtMChagi[̸I=]ϯIv4;oSzd``3ޠH<ŞF;A]`x3@ӵ_;;&;LI w?_+z6Յpց <:mՇe _,IsO~kcY1ҵD>!v? t>mn!앧EO"-֓Fwu1w)ٗ7ZtS_ae/1./.5˯&Y EAY~o|-D5[=ЉZ(L;(,"x!mg }$uC5t4{iy}oŨè\ŒMpaj 3۵kQERIlD)(dQ@Q@Q@Q@Q@Q@Q@Q@Q@VW_ x~YYZC:Ļ0>P>!;V/s8P{z 9"dztNwfm 0Ҷ1; ? xn̈́_!?܄ސk֨((nFH+*oWwCҴ{u-GFUI%P1uCƗ p37S \$ o}KB3=0G[Q6cfRG@3wCh֗V~ԟʀ:z¼8gv  *jR.{jV[aO:MLucoz+G} */ public class CliqueMinimalSeparatorDecompositionTest { /** * Test graph:
    * o-o
    * |/|
    * o-o
    */ @Test public void testSimpleGraph1() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(1, 3); g.addEdge(2, 4); // validate graph assertEquals(4, g.vertexSet().size()); assertEquals(5, g.edgeSet().size()); CliqueMinimalSeparatorDecomposition cmsd = new CliqueMinimalSeparatorDecomposition<>(g); // check triangulation assertEquals(4, cmsd.getMinimalTriangulation().vertexSet().size()); assertEquals(5, cmsd.getMinimalTriangulation().edgeSet().size()); // check atoms boolean atom1found = false, atom2found = false; for (Set atom : cmsd.getAtoms()) { if (atom.equals(Set.of(1, 2, 3))) { atom1found = true; } else if (atom.equals(Set.of(2, 3, 4))) { atom2found = true; } } assertEquals(2, cmsd.getAtoms().size()); assertTrue(atom1found); assertTrue(atom2found); // check seprators boolean separator1found = false; for (Set separator : cmsd.getSeparators()) { if (separator.equals(Set.of(2, 3))) { separator1found = true; } } assertEquals(1, cmsd.getSeparators().size()); assertTrue(separator1found); } /** * Test pseudo graph based on:
    * o-o
    * |/|
    * o-o
    */ @Test public void testPseudograph1() { Pseudograph g = new Pseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addEdge(1, 1); g.addEdge(1, 2); g.addEdge(2, 2); g.addEdge(2, 3); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(1, 3); g.addEdge(2, 4); g.addEdge(2, 4); g.addEdge(2, 4); // validate graph assertEquals(4, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); CliqueMinimalSeparatorDecomposition cmsd = new CliqueMinimalSeparatorDecomposition<>(g); // check triangulation assertEquals(4, cmsd.getMinimalTriangulation().vertexSet().size()); assertEquals(5, cmsd.getMinimalTriangulation().edgeSet().size()); // check atoms boolean atom1found = false, atom2found = false; for (Set atom : cmsd.getAtoms()) { if (atom.equals(Set.of(1, 2, 3))) { atom1found = true; } else if (atom.equals(Set.of(2, 3, 4))) { atom2found = true; } } assertEquals(2, cmsd.getAtoms().size()); assertTrue(atom1found); assertTrue(atom2found); // check seprators boolean separator1found = false; for (Set separator : cmsd.getSeparators()) { if (separator.equals(Set.of(2, 3))) { separator1found = true; } } assertEquals(1, cmsd.getSeparators().size()); assertTrue(separator1found); } /** * Test graph:
    * o-o
    * | |
    * o-o
    */ @Test public void testSimpleGraph2() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(4, 1); // validate graph assertEquals(4, g.vertexSet().size()); assertEquals(4, g.edgeSet().size()); CliqueMinimalSeparatorDecomposition cmsd = new CliqueMinimalSeparatorDecomposition<>(g); // check triangulation assertEquals(4, cmsd.getMinimalTriangulation().vertexSet().size()); assertEquals(5, cmsd.getMinimalTriangulation().edgeSet().size()); // check atoms boolean atom1found = false; for (Set atom : cmsd.getAtoms()) { if (atom.equals(Set.of(1, 2, 3, 4))) { atom1found = true; } } assertEquals(1, cmsd.getAtoms().size()); assertTrue(atom1found); // check seprators assertEquals(0, cmsd.getSeparators().size()); } /** * Test graph: An Introduction to Clique Minimal Separator Decomposition, Berry et al. 2010, * Figure 1, DOI:10.3390/a3020197 * http://www.mdpi.com/1999-4893/3/2/197 */ @Test public void testBerry2010() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); g.addVertex("f"); g.addVertex("g"); g.addVertex("h"); g.addVertex("i"); g.addVertex("j"); g.addVertex("k"); g.addEdge("a", "b"); g.addEdge("b", "c"); g.addEdge("c", "d"); g.addEdge("d", "e"); g.addEdge("e", "g"); g.addEdge("f", "g"); g.addEdge("d", "f"); g.addEdge("a", "k"); g.addEdge("k", "j"); g.addEdge("c", "k"); g.addEdge("d", "j"); g.addEdge("c", "j"); g.addEdge("d", "k"); g.addEdge("f", "k"); g.addEdge("f", "j"); g.addEdge("g", "j"); g.addEdge("g", "k"); g.addEdge("h", "j"); g.addEdge("h", "i"); g.addEdge("i", "k"); // validate graph assertEquals(11, g.vertexSet().size()); assertEquals(20, g.edgeSet().size()); CliqueMinimalSeparatorDecomposition cmsd = new CliqueMinimalSeparatorDecomposition<>(g); // check triangulation assertEquals(11, cmsd.getMinimalTriangulation().vertexSet().size()); assertEquals(20 + 3, cmsd.getMinimalTriangulation().edgeSet().size()); // check atoms boolean atom1found = false, atom2found = false, atom3found = false, atom4found = false; for (Set atom : cmsd.getAtoms()) { if (atom.equals(Set.of("a", "b", "c", "k"))) { atom1found = true; } else if (atom.equals(Set.of("c", "d", "j", "k"))) { atom2found = true; } else if (atom.equals(Set.of("h", "i", "j", "k"))) { atom3found = true; } else if (atom.equals(Set.of("d", "e", "f", "g", "j", "k"))) { atom4found = true; } } assertEquals(4, cmsd.getAtoms().size()); assertTrue(atom1found); assertTrue(atom2found); assertTrue(atom3found); assertTrue(atom4found); // check seprators boolean separator1found = false, separator2found = false, separator3found = false; for (Set separator : cmsd.getSeparators()) { if (separator.equals(Set.of("c", "k"))) { separator1found = true; } else if (separator.equals(Set.of("j", "k"))) { separator2found = true; } else if (separator.equals(Set.of("d", "j", "k"))) { separator3found = true; } } assertEquals(3, cmsd.getSeparators().size()); assertTrue(separator1found); assertTrue(separator2found); assertTrue(separator3found); } /** * Test graph: Decomposition by clique separators, Tarjan, 1985, Figure 1, DOI: * 10.1016/0012-365X(85)90051-2 * * http://www.sciencedirect.com/science/article/pii/0012365X85900512 */ @Test public void testTarjan1985() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); g.addVertex("f"); g.addVertex("g"); g.addVertex("h"); g.addVertex("i"); g.addVertex("j"); g.addVertex("k"); g.addEdge("a", "c"); g.addEdge("a", "d"); g.addEdge("a", "f"); g.addEdge("b", "c"); g.addEdge("b", "g"); g.addEdge("c", "d"); g.addEdge("c", "f"); g.addEdge("c", "h"); g.addEdge("d", "e"); g.addEdge("d", "f"); g.addEdge("d", "i"); g.addEdge("e", "j"); g.addEdge("f", "k"); g.addEdge("g", "h"); g.addEdge("h", "k"); g.addEdge("i", "j"); g.addEdge("i", "k"); // validate graph assertEquals(11, g.vertexSet().size()); assertEquals(17, g.edgeSet().size()); CliqueMinimalSeparatorDecomposition cmsd = new CliqueMinimalSeparatorDecomposition<>(g); // check triangulation assertEquals(11, cmsd.getMinimalTriangulation().vertexSet().size()); // disabled: this currently returns 23 instead of 21 /* * assertEquals(17 + 4, cmsd.getMinimalTriangulation().edgeSet().size()); */ // check atoms boolean atom1found = false, atom2found = false, atom3found = false, atom4found = false; for (Set atom : cmsd.getAtoms()) { if (atom.equals(Set.of("a", "c", "d", "f"))) { atom1found = true; } else if (atom.equals(Set.of("b", "c", "g", "h"))) { atom2found = true; } else if (atom.equals(Set.of("d", "e", "i", "j"))) { atom3found = true; } else if (atom.equals(Set.of("c", "d", "f", "h", "i", "k"))) { atom4found = true; } } assertEquals(4, cmsd.getAtoms().size()); assertTrue(atom1found); assertTrue(atom2found); assertTrue(atom3found); assertTrue(atom4found); // check seprators boolean separator1found = false, separator2found = false, separator3found = false; for (Set separator : cmsd.getSeparators()) { if (separator.equals(Set.of("c", "d", "f"))) { separator1found = true; } else if (separator.equals(Set.of("c", "h"))) { separator2found = true; } else if (separator.equals(Set.of("d", "i"))) { separator3found = true; } } assertEquals(3, cmsd.getSeparators().size()); assertTrue(separator1found); assertTrue(separator2found); assertTrue(separator3found); } /** * Test graph: CliqueMinimalSeparatorDecomposition1.jpg *

    * */ @Test public void testGraph1() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); g.addVertex("f"); g.addVertex("g"); g.addVertex("h"); g.addVertex("i"); g.addVertex("j"); g.addVertex("k"); g.addVertex("l"); g.addVertex("m"); g.addVertex("n"); g.addEdge("a", "b"); g.addEdge("a", "d"); g.addEdge("b", "e"); g.addEdge("c", "e"); g.addEdge("d", "e"); g.addEdge("e", "f"); g.addEdge("d", "g"); g.addEdge("d", "h"); g.addEdge("f", "k"); g.addEdge("g", "i"); g.addEdge("h", "i"); g.addEdge("d", "i"); g.addEdge("e", "j"); g.addEdge("i", "j"); g.addEdge("j", "k"); g.addEdge("i", "l"); g.addEdge("i", "m"); g.addEdge("i", "n"); g.addEdge("j", "m"); g.addEdge("j", "n"); // validate graph assertEquals(14, g.vertexSet().size()); assertEquals(20, g.edgeSet().size()); CliqueMinimalSeparatorDecomposition cmsd = new CliqueMinimalSeparatorDecomposition<>(g); // check triangulation assertEquals(14, cmsd.getMinimalTriangulation().vertexSet().size()); assertEquals(20 + 3, cmsd.getMinimalTriangulation().edgeSet().size()); // check atoms assertEquals(9, cmsd.getAtoms().size()); boolean[] atomsFound = new boolean[cmsd.getAtoms().size()]; for (Set atom : cmsd.getAtoms()) { if (atom.equals(Set.of("a", "b", "d", "e"))) { atomsFound[0] = true; } else if (atom.equals(Set.of("c", "e"))) { atomsFound[1] = true; } else if (atom.equals(Set.of("d", "g", "i"))) { atomsFound[2] = true; } else if (atom.equals(Set.of("d", "h", "i"))) { atomsFound[3] = true; } else if (atom.equals(Set.of("d", "e", "i", "j"))) { atomsFound[4] = true; } else if (atom.equals(Set.of("e", "f", "j", "k"))) { atomsFound[5] = true; } else if (atom.equals(Set.of("i", "l"))) { atomsFound[6] = true; } else if (atom.equals(Set.of("i", "j", "m"))) { atomsFound[7] = true; } else if (atom.equals(Set.of("i", "j", "n"))) { atomsFound[8] = true; } } for (int i = 0; i < atomsFound.length; ++i) assertTrue("atom " + i, atomsFound[i]); // check seprators assertEquals(6, cmsd.getSeparators().size()); boolean[] separatorsFound = new boolean[cmsd.getSeparators().size()]; for (Set separator : cmsd.getSeparators()) { if (separator.equals(Set.of("d", "e"))) { separatorsFound[0] = true; } else if (separator.equals(Set.of("e"))) { separatorsFound[1] = true; } else if (separator.equals(Set.of("d", "i"))) { separatorsFound[2] = true; } else if (separator.equals(Set.of("i"))) { separatorsFound[3] = true; } else if (separator.equals(Set.of("e", "j"))) { separatorsFound[4] = true; } else if (separator.equals(Set.of("i", "j"))) { separatorsFound[5] = true; } } for (int i = 0; i < separatorsFound.length; ++i) assertTrue("separator " + i, separatorsFound[i]); // check component counts assertEquals(6, cmsd.getFullComponentCount().size()); assertEquals(2, cmsd.getFullComponentCount().get(Set.of("d", "e")).intValue()); assertEquals(2, cmsd.getFullComponentCount().get(Set.of("e")).intValue()); assertEquals(3, cmsd.getFullComponentCount().get(Set.of("d", "i")).intValue()); assertEquals(2, cmsd.getFullComponentCount().get(Set.of("i")).intValue()); assertEquals(2, cmsd.getFullComponentCount().get(Set.of("e", "j")).intValue()); assertEquals(3, cmsd.getFullComponentCount().get(Set.of("i", "j")).intValue()); } /** * Test random graphs. You may change the number of vertices and edges. */ @Test public void testRandomGraphs() { int rounds = 42; while (rounds-- > 0) { // number of vertices final int n = 80 + rounds; // number of edges, sparse but enough to get a connected graph, // 10% more than phase transition from disconnected to conntected. final int m = (int) Math.ceil(1.1 * Math.log(n) / n * (n * (n - 1) / 2)); // generate a connected random graph with n vertices and m edges GraphGenerator generator; SimpleGraph g; ConnectivityInspector inspector; do { g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator = new GnmRandomGraphGenerator<>(n, m); generator.generateGraph(g); inspector = new ConnectivityInspector<>(g); } while (!inspector.isConnected()); // decompose graph CliqueMinimalSeparatorDecomposition cmsd = new CliqueMinimalSeparatorDecomposition<>(g); // check triangulation assertEquals( cmsd.getMinimalTriangulation().edgeSet().size(), g.edgeSet().size() + cmsd.getFillEdges().size()); // check seprators for (Set separator : cmsd.getSeparators()) { assertTrue(separator.size() >= 1); assertTrue(isClique(g, separator)); } assertTrue(cmsd.getSeparators().size() < cmsd.getAtoms().size()); // check component count assertEquals(cmsd.getSeparators().size(), cmsd.getFullComponentCount().size()); for (Set separator : cmsd.getSeparators()) { assertTrue(cmsd.getFullComponentCount().get(separator) >= 2); } } } /** * Check whether the subgraph of graph induced by the given vertices * is complete, i.e. a clique. * * @param graph the graph. * @param vertices the vertices to induce the subgraph from. * @return true if the induced subgraph is a clique. */ private static boolean isClique(Graph graph, Set vertices) { for (V v1 : vertices) { for (V v2 : vertices) { if (v1 != v2 && graph.getEdge(v1, v2) == null) return false; } } return true; } } DegeneracyBronKerboschCliqueFinderTest.java000066400000000000000000000030561402514743400364400ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clique/* * (C) Copyright 2005-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.concurrent.*; /** * . * * @author John V. Sichi */ public class DegeneracyBronKerboschCliqueFinderTest extends BaseBronKerboschCliqueFinderTest { @Override protected BaseBronKerboschCliqueFinder createFinder1( Graph graph) { return new DegeneracyBronKerboschCliqueFinder<>(graph); } @Override protected BaseBronKerboschCliqueFinder createFinder2( Graph graph) { return new DegeneracyBronKerboschCliqueFinder<>(graph); } @Override protected BaseBronKerboschCliqueFinder createFinder2( Graph graph, long timeout, TimeUnit unit) { return new DegeneracyBronKerboschCliqueFinder<>(graph, timeout, unit); } } PivotBronKerboschCliqueFinderTest.java000066400000000000000000000030321402514743400354650ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clique/* * (C) Copyright 2005-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clique; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.concurrent.*; /** * . * * @author John V. Sichi */ public class PivotBronKerboschCliqueFinderTest extends BaseBronKerboschCliqueFinderTest { @Override protected BaseBronKerboschCliqueFinder createFinder1( Graph graph) { return new PivotBronKerboschCliqueFinder<>(graph); } @Override protected BaseBronKerboschCliqueFinder createFinder2( Graph graph) { return new PivotBronKerboschCliqueFinder<>(graph); } @Override protected BaseBronKerboschCliqueFinder createFinder2( Graph graph, long timeout, TimeUnit unit) { return new PivotBronKerboschCliqueFinder<>(graph, timeout, unit); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/000077500000000000000000000000001402514743400270425ustar00rootroot00000000000000GirvanNewmanClusteringTest.java000066400000000000000000000054131402514743400351250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/* * (C) Copyright 2021-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clustering; import static org.junit.Assert.assertEquals; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.alg.interfaces.ClusteringAlgorithm.Clustering; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for {@link GirvanNewmanClustering}. * * @author Dimitrios Michail */ public class GirvanNewmanClusteringTest { @Test public void testUndirectedGraph() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); g = TestUtil .createUndirected( new int[][] { { 1, 2 }, { 1, 3 }, { 2, 3 }, { 3, 7 }, { 4, 6 }, { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 }, { 8, 9 }, { 8, 12 }, { 9, 10 }, { 9, 11 }, { 12, 13 }, { 12, 14 }, { 10, 11 }, { 13, 14 } }); Clustering c1 = new GirvanNewmanClustering<>(g, 2).getClustering(); assertEquals(c1.getNumberClusters(), 2); assertEquals(Set.of(1, 2, 3, 4, 5, 6, 7), c1.getClusters().get(0)); assertEquals(Set.of(8, 9, 10, 11, 12, 13, 14), c1.getClusters().get(1)); Clustering c2 = new GirvanNewmanClustering<>(g, 6).getClustering(); assertEquals(c2.getNumberClusters(), 6); assertEquals(Set.of(1, 2, 3), c2.getClusters().get(0)); assertEquals(Set.of(7), c2.getClusters().get(1)); assertEquals(Set.of(4, 5, 6), c2.getClusters().get(2)); assertEquals(Set.of(8), c2.getClusters().get(3)); assertEquals(Set.of(9, 10, 11), c2.getClusters().get(4)); assertEquals(Set.of(12, 13, 14), c2.getClusters().get(5)); Clustering c3 = new GirvanNewmanClustering<>(g, g.vertexSet().size()).getClustering(); assertEquals(c3.getNumberClusters(), g.vertexSet().size()); } } KSpanningTreeClusteringTest.java000066400000000000000000000147221402514743400352440ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clustering; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ClusteringAlgorithm.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Tests * * @author Dimitrios Michail */ public class KSpanningTreeClusteringTest { @Test public void test1() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 0; i < 9; i++) g.addVertex(); g.setEdgeWeight(g.addEdge(0, 1), 2d); g.setEdgeWeight(g.addEdge(0, 5), 1d); g.setEdgeWeight(g.addEdge(1, 2), 3d); g.setEdgeWeight(g.addEdge(1, 4), 11d); g.setEdgeWeight(g.addEdge(2, 3), 4d); g.setEdgeWeight(g.addEdge(3, 4), 5d); g.setEdgeWeight(g.addEdge(3, 8), 10d); g.setEdgeWeight(g.addEdge(4, 5), 6d); g.setEdgeWeight(g.addEdge(4, 7), 12d); g.setEdgeWeight(g.addEdge(5, 6), 7d); g.setEdgeWeight(g.addEdge(6, 7), 8d); g.setEdgeWeight(g.addEdge(7, 8), 9d); final int k = 3; KSpanningTreeClustering alg = new KSpanningTreeClustering<>(g, k); Clustering clustering = alg.getClustering(); assertEquals(clustering.getNumberClusters(), k); List> clusters = clustering.getClusters(); assertEquals(Set.of(0, 1, 2, 3, 4, 5, 6), clusters.get(0)); assertEquals(Set.of(7), clusters.get(1)); assertEquals(Set.of(8), clusters.get(2)); } @Test public void test2() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 0; i < 9; i++) g.addVertex(); g.setEdgeWeight(g.addEdge(0, 1), 2d); g.setEdgeWeight(g.addEdge(0, 5), 1d); g.setEdgeWeight(g.addEdge(1, 2), 9d); g.setEdgeWeight(g.addEdge(1, 4), 11d); g.setEdgeWeight(g.addEdge(2, 3), 4d); g.setEdgeWeight(g.addEdge(3, 4), 5d); g.setEdgeWeight(g.addEdge(3, 8), 10d); g.setEdgeWeight(g.addEdge(4, 5), 6d); g.setEdgeWeight(g.addEdge(4, 7), 12d); g.setEdgeWeight(g.addEdge(5, 6), 7d); g.setEdgeWeight(g.addEdge(6, 7), 8d); g.setEdgeWeight(g.addEdge(7, 8), 3d); final int k = 4; KSpanningTreeClustering alg = new KSpanningTreeClustering<>(g, k); Clustering clustering = alg.getClustering(); assertEquals(clustering.getNumberClusters(), k); List> clusters = clustering.getClusters(); assertEquals(Set.of(0, 1, 5), clusters.get(0)); assertEquals(Set.of(2, 3, 4), clusters.get(1)); assertEquals(Set.of(6), clusters.get(2)); assertEquals(Set.of(7, 8), clusters.get(3)); } @Test public void testOneCluster() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 0; i < 9; i++) g.addVertex(); g.setEdgeWeight(g.addEdge(0, 1), 2d); g.setEdgeWeight(g.addEdge(0, 5), 1d); g.setEdgeWeight(g.addEdge(1, 2), 3d); g.setEdgeWeight(g.addEdge(1, 4), 11d); g.setEdgeWeight(g.addEdge(2, 3), 4d); g.setEdgeWeight(g.addEdge(3, 4), 5d); g.setEdgeWeight(g.addEdge(3, 8), 10d); g.setEdgeWeight(g.addEdge(4, 5), 6d); g.setEdgeWeight(g.addEdge(4, 7), 12d); g.setEdgeWeight(g.addEdge(5, 6), 7d); g.setEdgeWeight(g.addEdge(6, 7), 8d); g.setEdgeWeight(g.addEdge(7, 8), 9d); final int k = 1; KSpanningTreeClustering alg = new KSpanningTreeClustering<>(g, k); Clustering clustering = alg.getClustering(); assertEquals(clustering.getNumberClusters(), k); } @Test public void testNClusters() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 0; i < 9; i++) g.addVertex(); g.setEdgeWeight(g.addEdge(0, 1), 2d); g.setEdgeWeight(g.addEdge(0, 5), 1d); g.setEdgeWeight(g.addEdge(1, 2), 3d); g.setEdgeWeight(g.addEdge(1, 4), 11d); g.setEdgeWeight(g.addEdge(2, 3), 4d); g.setEdgeWeight(g.addEdge(3, 4), 5d); g.setEdgeWeight(g.addEdge(3, 8), 10d); g.setEdgeWeight(g.addEdge(4, 5), 6d); g.setEdgeWeight(g.addEdge(4, 7), 12d); g.setEdgeWeight(g.addEdge(5, 6), 7d); g.setEdgeWeight(g.addEdge(6, 7), 8d); g.setEdgeWeight(g.addEdge(7, 8), 9d); final int k = 9; KSpanningTreeClustering alg = new KSpanningTreeClustering<>(g, k); Clustering clustering = alg.getClustering(); assertEquals(clustering.getNumberClusters(), k); } } LabelPropagationClusteringTest.java000066400000000000000000000135171402514743400357600ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/clustering/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.clustering; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ClusteringAlgorithm.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Tests * * @author Dimitrios Michail */ public class LabelPropagationClusteringTest { @Test public void test1() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 0; i < 9; i++) g.addVertex(); g.addEdge(0, 1); g.addEdge(0, 5); g.addEdge(1, 2); g.addEdge(1, 4); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(3, 8); g.addEdge(4, 5); g.addEdge(4, 7); g.addEdge(5, 6); g.addEdge(6, 7); g.addEdge(7, 8); LabelPropagationClustering alg = new LabelPropagationClustering<>(g, 0, new Random(13)); Clustering clustering = alg.getClustering(); assertEquals(1, clustering.getNumberClusters()); List> clusters = clustering.getClusters(); assertEquals(Set.of(0, 1, 2, 3, 4, 5, 6, 7, 8), clusters.get(0)); } @Test public void test2() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 0; i < 8; i++) g.addVertex(); // clique1 g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(0, 3); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 3); // clique2 g.addEdge(4, 5); g.addEdge(4, 6); g.addEdge(4, 7); g.addEdge(5, 6); g.addEdge(5, 7); g.addEdge(6, 7); // one edge between them g.addEdge(3, 4); LabelPropagationClustering alg = new LabelPropagationClustering<>(g, 0, new Random(13)); Clustering clustering = alg.getClustering(); assertEquals(2, clustering.getNumberClusters()); List> clusters = clustering.getClusters(); assertEquals(Set.of(0, 1, 2, 3), clusters.get(0)); assertEquals(Set.of(4, 5, 6, 7), clusters.get(1)); } @Test public void test3() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 0; i < 12; i++) { g.addVertex(); } // clique1 g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(0, 3); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 3); // clique2 g.addEdge(4, 5); g.addEdge(4, 6); g.addEdge(4, 7); g.addEdge(5, 6); g.addEdge(5, 7); g.addEdge(6, 7); // clique3 g.addEdge(8, 9); g.addEdge(8, 10); g.addEdge(8, 11); g.addEdge(9, 10); g.addEdge(9, 11); g.addEdge(10, 11); // one edge between them g.addEdge(3, 4); g.addEdge(7, 8); LabelPropagationClustering alg = new LabelPropagationClustering<>(g, 0, new Random(13)); Clustering clustering = alg.getClustering(); assertEquals(3, clustering.getNumberClusters()); List> clusters = clustering.getClusters(); assertEquals(Set.of(0, 1, 2, 3), clusters.get(0)); assertEquals(Set.of(4, 5, 6, 7), clusters.get(1)); assertEquals(Set.of(8, 9, 10, 11), clusters.get(2)); } @Test public void testWithIsolatedVertex() { Graph graph = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(0, 2); LabelPropagationClustering alg = new LabelPropagationClustering<>(graph, 0, new Random(31)); Clustering clustering = alg.getClustering(); assertEquals(2, clustering.getNumberClusters()); List> clusters = clustering.getClusters(); assertEquals(Set.of(0, 1, 2), clusters.get(0)); assertEquals(Set.of(3), clusters.get(1)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/000077500000000000000000000000001402514743400260015ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/BaseColoringTest.java000066400000000000000000000233661402514743400320650ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.VertexColoringAlgorithm.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.function.*; import java.util.stream.*; import static org.junit.Assert.*; /** * Base class for coloring tests. * * @author Dimitrios Michail */ public abstract class BaseColoringTest { public BaseColoringTest() { super(); } protected abstract VertexColoringAlgorithm getAlgorithm( Graph graph); protected abstract int getExpectedResultOnDSaturNonOptimalGraph(); protected int getExpectedResultOnGraph1() { return 3; } protected int getExpectedResultOnMyceil3Graph() { return 4; } protected int getExpectedResultOnMyceil4Graph() { return 5; } protected void assertColoring( Graph g, Coloring coloring, int expectedColors) { int n = g.vertexSet().size(); assertTrue(coloring.getNumberColors() <= n); assertEquals(expectedColors, coloring.getNumberColors()); Map colors = coloring.getColors(); for (Integer v : g.vertexSet()) { Integer c = colors.get(v); assertNotNull(c); assertTrue(c >= 0); assertTrue(c < n); } for (DefaultEdge e : g.edgeSet()) { assertNotEquals(colors.get(g.getEdgeSource(e)), colors.get(g.getEdgeTarget(e))); } } protected void testRandomGraphColoring(Random rng) { final int tests = 5; final int n = 20; final double p = 0.35; List, VertexColoringAlgorithm>> algs = new ArrayList<>(); algs.add((g) -> getAlgorithm(g)); GraphGenerator gen = new GnpRandomGraphGenerator<>(n, p, rng, false); for (int i = 0; i < tests; i++) { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); for (Function, VertexColoringAlgorithm> algProvider : algs) { VertexColoringAlgorithm alg = algProvider.apply(g); Coloring coloring = alg.getColoring(); assertTrue(coloring.getNumberColors() <= n); Map colors = coloring.getColors(); for (Integer v : g.vertexSet()) { Integer c = colors.get(v); assertNotNull(c); assertTrue(c >= 0); assertTrue(c < n); } for (DefaultEdge e : g.edgeSet()) { assertNotEquals(colors.get(g.getEdgeSource(e)), colors.get(g.getEdgeTarget(e))); } } } } final protected Graph createGraph1() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(1, 3); g.addEdge(1, 4); g.addEdge(1, 5); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(3, 5); return g; } final protected Graph createMyciel3Graph() { // This is a graph from http://mat.gsia.cmu.edu/COLOR/instances/myciel3.col. // SOURCE: Michael Trick (trick@cmu.edu) // DESCRIPTION: Graph based on Mycielski transformation. // Triangle free (clique number 2) but increasing coloring number Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.range(1, 12).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 4); g.addEdge(1, 7); g.addEdge(1, 9); g.addEdge(2, 3); g.addEdge(2, 6); g.addEdge(2, 8); g.addEdge(3, 5); g.addEdge(3, 7); g.addEdge(3, 10); g.addEdge(4, 5); g.addEdge(4, 6); g.addEdge(4, 10); g.addEdge(5, 8); g.addEdge(5, 9); g.addEdge(6, 11); g.addEdge(7, 11); g.addEdge(8, 11); g.addEdge(9, 11); g.addEdge(10, 11); return g; } final protected Graph createMyciel4Graph() { // This is a graph from http://mat.gsia.cmu.edu/COLOR/instances/myciel4.col. // SOURCE: Michael Trick (trick@cmu.edu) // DESCRIPTION: Graph based on Mycielski transformation. // Triangle free (clique number 2) but increasing coloring number Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.range(1, 24).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 4); g.addEdge(1, 7); g.addEdge(1, 9); g.addEdge(1, 13); g.addEdge(1, 15); g.addEdge(1, 18); g.addEdge(1, 20); g.addEdge(2, 3); g.addEdge(2, 6); g.addEdge(2, 8); g.addEdge(2, 12); g.addEdge(2, 14); g.addEdge(2, 17); g.addEdge(2, 19); g.addEdge(3, 5); g.addEdge(3, 7); g.addEdge(3, 10); g.addEdge(3, 13); g.addEdge(3, 16); g.addEdge(3, 18); g.addEdge(3, 21); g.addEdge(4, 5); g.addEdge(4, 6); g.addEdge(4, 10); g.addEdge(4, 12); g.addEdge(4, 16); g.addEdge(4, 17); g.addEdge(4, 21); g.addEdge(5, 8); g.addEdge(5, 9); g.addEdge(5, 14); g.addEdge(5, 15); g.addEdge(5, 19); g.addEdge(5, 20); g.addEdge(6, 11); g.addEdge(6, 13); g.addEdge(6, 15); g.addEdge(6, 22); g.addEdge(7, 11); g.addEdge(7, 12); g.addEdge(7, 14); g.addEdge(7, 22); g.addEdge(8, 11); g.addEdge(8, 13); g.addEdge(8, 16); g.addEdge(8, 22); g.addEdge(9, 11); g.addEdge(9, 12); g.addEdge(9, 16); g.addEdge(9, 22); g.addEdge(10, 11); g.addEdge(10, 14); g.addEdge(10, 15); g.addEdge(10, 22); g.addEdge(11, 17); g.addEdge(11, 18); g.addEdge(11, 19); g.addEdge(11, 20); g.addEdge(11, 21); g.addEdge(12, 23); g.addEdge(13, 23); g.addEdge(14, 23); g.addEdge(15, 23); g.addEdge(16, 23); g.addEdge(17, 23); g.addEdge(18, 23); g.addEdge(19, 23); g.addEdge(20, 23); g.addEdge(21, 23); g.addEdge(22, 23); return g; } final protected Graph createDSaturNonOptimalGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.range(1, 8).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(1, 4); g.addEdge(2, 3); g.addEdge(2, 5); g.addEdge(4, 6); g.addEdge(4, 7); g.addEdge(5, 6); g.addEdge(5, 7); g.addEdge(6, 7); return g; } @Test public void testMyciel3() { Graph g = createMyciel3Graph(); assertColoring(g, getAlgorithm(g).getColoring(), getExpectedResultOnMyceil3Graph()); } @Test public void testMyciel4() { Graph g = createMyciel4Graph(); assertColoring(g, getAlgorithm(g).getColoring(), getExpectedResultOnMyceil4Graph()); } /** * Test instance where DSatur greedy coloring is non-optimal. */ @Test public void testDSaturNonOptimal() { Graph g = createDSaturNonOptimalGraph(); assertColoring( g, getAlgorithm(g).getColoring(), getExpectedResultOnDSaturNonOptimalGraph()); } @Test public void testGraph1() { Graph g = createGraph1(); assertColoring(g, getAlgorithm(g).getColoring(), getExpectedResultOnGraph1()); } @Test public void testCompleteGraph() { final int n = 20; Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator gen = new CompleteGraphGenerator<>(n); gen.generateGraph(g); Coloring coloring = getAlgorithm(g).getColoring(); assertEquals(n, coloring.getNumberColors()); } @Test public void testRandomFixedSeed17() { final long seed = 17; Random rng = new Random(seed); testRandomGraphColoring(rng); } @Test public void testRandom() { Random rng = new Random(); testRandomGraphColoring(rng); } } BrownBacktrackColoringTest.java000066400000000000000000001122011402514743400340140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; /** * Tests for BrownBacktrackColoring * * @author Joris Kinable */ public class BrownBacktrackColoringTest { /** * .Clique of size 6 */ @Test public void testClique() { Graph completeGraph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); CompleteGraphGenerator completeGraphGenerator = new CompleteGraphGenerator<>(6); completeGraphGenerator.generateGraph(completeGraph); BrownBacktrackColoring bbc = new BrownBacktrackColoring<>(completeGraph); assertEquals(6, bbc.getChromaticNumber()); verifyColoring(completeGraph, 6, bbc.getColoring()); } /** * myciel3.col 11 vertices, 20 edges chromatic number: 4 */ @Test public void myciel3Test() { int[][] edges = { { 1, 2 }, { 1, 4 }, { 1, 7 }, { 1, 9 }, { 2, 3 }, { 2, 6 }, { 2, 8 }, { 3, 5 }, { 3, 7 }, { 3, 10 }, { 4, 5 }, { 4, 6 }, { 4, 10 }, { 5, 8 }, { 5, 9 }, { 6, 11 }, { 7, 11 }, { 8, 11 }, { 9, 11 }, { 10, 11 } }; Graph g = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(g, edge[0], edge[1]); BrownBacktrackColoring bbc = new BrownBacktrackColoring<>(g); assertEquals(4, bbc.getChromaticNumber()); verifyColoring(g, 4, bbc.getColoring()); } /** * myciel4.col 23 vertices, 71 edges chromatic number: 5 */ @Test public void myciel4Test() { int[][] edges = { { 1, 2 }, { 1, 4 }, { 1, 7 }, { 1, 9 }, { 1, 13 }, { 1, 15 }, { 1, 18 }, { 1, 20 }, { 2, 3 }, { 2, 6 }, { 2, 8 }, { 2, 12 }, { 2, 14 }, { 2, 17 }, { 2, 19 }, { 3, 5 }, { 3, 7 }, { 3, 10 }, { 3, 13 }, { 3, 16 }, { 3, 18 }, { 3, 21 }, { 4, 5 }, { 4, 6 }, { 4, 10 }, { 4, 12 }, { 4, 16 }, { 4, 17 }, { 4, 21 }, { 5, 8 }, { 5, 9 }, { 5, 14 }, { 5, 15 }, { 5, 19 }, { 5, 20 }, { 6, 11 }, { 6, 13 }, { 6, 15 }, { 6, 22 }, { 7, 11 }, { 7, 12 }, { 7, 14 }, { 7, 22 }, { 8, 11 }, { 8, 13 }, { 8, 16 }, { 8, 22 }, { 9, 11 }, { 9, 12 }, { 9, 16 }, { 9, 22 }, { 10, 11 }, { 10, 14 }, { 10, 15 }, { 10, 22 }, { 11, 17 }, { 11, 18 }, { 11, 19 }, { 11, 20 }, { 11, 21 }, { 12, 23 }, { 13, 23 }, { 14, 23 }, { 15, 23 }, { 16, 23 }, { 17, 23 }, { 18, 23 }, { 19, 23 }, { 20, 23 }, { 21, 23 }, { 22, 23 } }; Graph g = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(g, edge[0], edge[1]); BrownBacktrackColoring bbc = new BrownBacktrackColoring<>(g); assertEquals(5, bbc.getChromaticNumber()); verifyColoring(g, 5, bbc.getColoring()); } /** * queen5_5.col 25 vertices, 320 edges chromatic number: 5 */ @Test public void queen5Test() { int[][] edges = { { 1, 7 }, { 1, 13 }, { 1, 19 }, { 1, 25 }, { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, { 1, 6 }, { 1, 11 }, { 1, 16 }, { 1, 21 }, { 2, 8 }, { 2, 14 }, { 2, 20 }, { 2, 6 }, { 2, 3 }, { 2, 4 }, { 2, 5 }, { 2, 7 }, { 2, 12 }, { 2, 17 }, { 2, 22 }, { 2, 1 }, { 3, 9 }, { 3, 15 }, { 3, 7 }, { 3, 11 }, { 3, 4 }, { 3, 5 }, { 3, 8 }, { 3, 13 }, { 3, 18 }, { 3, 23 }, { 3, 2 }, { 3, 1 }, { 4, 10 }, { 4, 8 }, { 4, 12 }, { 4, 16 }, { 4, 5 }, { 4, 9 }, { 4, 14 }, { 4, 19 }, { 4, 24 }, { 4, 3 }, { 4, 2 }, { 4, 1 }, { 5, 9 }, { 5, 13 }, { 5, 17 }, { 5, 21 }, { 5, 10 }, { 5, 15 }, { 5, 20 }, { 5, 25 }, { 5, 4 }, { 5, 3 }, { 5, 2 }, { 5, 1 }, { 6, 12 }, { 6, 18 }, { 6, 24 }, { 6, 7 }, { 6, 8 }, { 6, 9 }, { 6, 10 }, { 6, 11 }, { 6, 16 }, { 6, 21 }, { 6, 2 }, { 6, 1 }, { 7, 13 }, { 7, 19 }, { 7, 25 }, { 7, 11 }, { 7, 8 }, { 7, 9 }, { 7, 10 }, { 7, 12 }, { 7, 17 }, { 7, 22 }, { 7, 6 }, { 7, 3 }, { 7, 2 }, { 7, 1 }, { 8, 14 }, { 8, 20 }, { 8, 12 }, { 8, 16 }, { 8, 9 }, { 8, 10 }, { 8, 13 }, { 8, 18 }, { 8, 23 }, { 8, 7 }, { 8, 6 }, { 8, 4 }, { 8, 3 }, { 8, 2 }, { 9, 15 }, { 9, 13 }, { 9, 17 }, { 9, 21 }, { 9, 10 }, { 9, 14 }, { 9, 19 }, { 9, 24 }, { 9, 8 }, { 9, 7 }, { 9, 6 }, { 9, 5 }, { 9, 4 }, { 9, 3 }, { 10, 14 }, { 10, 18 }, { 10, 22 }, { 10, 15 }, { 10, 20 }, { 10, 25 }, { 10, 9 }, { 10, 8 }, { 10, 7 }, { 10, 6 }, { 10, 5 }, { 10, 4 }, { 11, 17 }, { 11, 23 }, { 11, 12 }, { 11, 13 }, { 11, 14 }, { 11, 15 }, { 11, 16 }, { 11, 21 }, { 11, 7 }, { 11, 6 }, { 11, 3 }, { 11, 1 }, { 12, 18 }, { 12, 24 }, { 12, 16 }, { 12, 13 }, { 12, 14 }, { 12, 15 }, { 12, 17 }, { 12, 22 }, { 12, 11 }, { 12, 8 }, { 12, 7 }, { 12, 6 }, { 12, 4 }, { 12, 2 }, { 13, 19 }, { 13, 25 }, { 13, 17 }, { 13, 21 }, { 13, 14 }, { 13, 15 }, { 13, 18 }, { 13, 23 }, { 13, 12 }, { 13, 11 }, { 13, 9 }, { 13, 8 }, { 13, 7 }, { 13, 5 }, { 13, 3 }, { 13, 1 }, { 14, 20 }, { 14, 18 }, { 14, 22 }, { 14, 15 }, { 14, 19 }, { 14, 24 }, { 14, 13 }, { 14, 12 }, { 14, 11 }, { 14, 10 }, { 14, 9 }, { 14, 8 }, { 14, 4 }, { 14, 2 }, { 15, 19 }, { 15, 23 }, { 15, 20 }, { 15, 25 }, { 15, 14 }, { 15, 13 }, { 15, 12 }, { 15, 11 }, { 15, 10 }, { 15, 9 }, { 15, 5 }, { 15, 3 }, { 16, 22 }, { 16, 17 }, { 16, 18 }, { 16, 19 }, { 16, 20 }, { 16, 21 }, { 16, 12 }, { 16, 11 }, { 16, 8 }, { 16, 6 }, { 16, 4 }, { 16, 1 }, { 17, 23 }, { 17, 21 }, { 17, 18 }, { 17, 19 }, { 17, 20 }, { 17, 22 }, { 17, 16 }, { 17, 13 }, { 17, 12 }, { 17, 11 }, { 17, 9 }, { 17, 7 }, { 17, 5 }, { 17, 2 }, { 18, 24 }, { 18, 22 }, { 18, 19 }, { 18, 20 }, { 18, 23 }, { 18, 17 }, { 18, 16 }, { 18, 14 }, { 18, 13 }, { 18, 12 }, { 18, 10 }, { 18, 8 }, { 18, 6 }, { 18, 3 }, { 19, 25 }, { 19, 23 }, { 19, 20 }, { 19, 24 }, { 19, 18 }, { 19, 17 }, { 19, 16 }, { 19, 15 }, { 19, 14 }, { 19, 13 }, { 19, 9 }, { 19, 7 }, { 19, 4 }, { 19, 1 }, { 20, 24 }, { 20, 25 }, { 20, 19 }, { 20, 18 }, { 20, 17 }, { 20, 16 }, { 20, 15 }, { 20, 14 }, { 20, 10 }, { 20, 8 }, { 20, 5 }, { 20, 2 }, { 21, 22 }, { 21, 23 }, { 21, 24 }, { 21, 25 }, { 21, 17 }, { 21, 16 }, { 21, 13 }, { 21, 11 }, { 21, 9 }, { 21, 6 }, { 21, 5 }, { 21, 1 }, { 22, 23 }, { 22, 24 }, { 22, 25 }, { 22, 21 }, { 22, 18 }, { 22, 17 }, { 22, 16 }, { 22, 14 }, { 22, 12 }, { 22, 10 }, { 22, 7 }, { 22, 2 }, { 23, 24 }, { 23, 25 }, { 23, 22 }, { 23, 21 }, { 23, 19 }, { 23, 18 }, { 23, 17 }, { 23, 15 }, { 23, 13 }, { 23, 11 }, { 23, 8 }, { 23, 3 }, { 24, 25 }, { 24, 23 }, { 24, 22 }, { 24, 21 }, { 24, 20 }, { 24, 19 }, { 24, 18 }, { 24, 14 }, { 24, 12 }, { 24, 9 }, { 24, 6 }, { 24, 4 }, { 25, 24 }, { 25, 23 }, { 25, 22 }, { 25, 21 }, { 25, 20 }, { 25, 19 }, { 25, 15 }, { 25, 13 }, { 25, 10 }, { 25, 7 }, { 25, 5 }, { 25, 1 } }; Graph g = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(g, edge[0], edge[1]); BrownBacktrackColoring bbc = new BrownBacktrackColoring<>(g); assertEquals(5, bbc.getChromaticNumber()); verifyColoring(g, 5, bbc.getColoring()); } /** * queen6_6.col 36 vertices, 580 edges chromatic number: 7 */ @Test public void queen6Test() { int[][] edges = { { 1, 8 }, { 1, 15 }, { 1, 22 }, { 1, 29 }, { 1, 36 }, { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, { 1, 6 }, { 1, 7 }, { 1, 13 }, { 1, 19 }, { 1, 25 }, { 1, 31 }, { 2, 9 }, { 2, 16 }, { 2, 23 }, { 2, 30 }, { 2, 7 }, { 2, 3 }, { 2, 4 }, { 2, 5 }, { 2, 6 }, { 2, 8 }, { 2, 14 }, { 2, 20 }, { 2, 26 }, { 2, 32 }, { 2, 1 }, { 3, 10 }, { 3, 17 }, { 3, 24 }, { 3, 8 }, { 3, 13 }, { 3, 4 }, { 3, 5 }, { 3, 6 }, { 3, 9 }, { 3, 15 }, { 3, 21 }, { 3, 27 }, { 3, 33 }, { 3, 2 }, { 3, 1 }, { 4, 11 }, { 4, 18 }, { 4, 9 }, { 4, 14 }, { 4, 19 }, { 4, 5 }, { 4, 6 }, { 4, 10 }, { 4, 16 }, { 4, 22 }, { 4, 28 }, { 4, 34 }, { 4, 3 }, { 4, 2 }, { 4, 1 }, { 5, 12 }, { 5, 10 }, { 5, 15 }, { 5, 20 }, { 5, 25 }, { 5, 6 }, { 5, 11 }, { 5, 17 }, { 5, 23 }, { 5, 29 }, { 5, 35 }, { 5, 4 }, { 5, 3 }, { 5, 2 }, { 5, 1 }, { 6, 11 }, { 6, 16 }, { 6, 21 }, { 6, 26 }, { 6, 31 }, { 6, 12 }, { 6, 18 }, { 6, 24 }, { 6, 30 }, { 6, 36 }, { 6, 5 }, { 6, 4 }, { 6, 3 }, { 6, 2 }, { 6, 1 }, { 7, 14 }, { 7, 21 }, { 7, 28 }, { 7, 35 }, { 7, 8 }, { 7, 9 }, { 7, 10 }, { 7, 11 }, { 7, 12 }, { 7, 13 }, { 7, 19 }, { 7, 25 }, { 7, 31 }, { 7, 2 }, { 7, 1 }, { 8, 15 }, { 8, 22 }, { 8, 29 }, { 8, 36 }, { 8, 13 }, { 8, 9 }, { 8, 10 }, { 8, 11 }, { 8, 12 }, { 8, 14 }, { 8, 20 }, { 8, 26 }, { 8, 32 }, { 8, 7 }, { 8, 3 }, { 8, 2 }, { 8, 1 }, { 9, 16 }, { 9, 23 }, { 9, 30 }, { 9, 14 }, { 9, 19 }, { 9, 10 }, { 9, 11 }, { 9, 12 }, { 9, 15 }, { 9, 21 }, { 9, 27 }, { 9, 33 }, { 9, 8 }, { 9, 7 }, { 9, 4 }, { 9, 3 }, { 9, 2 }, { 10, 17 }, { 10, 24 }, { 10, 15 }, { 10, 20 }, { 10, 25 }, { 10, 11 }, { 10, 12 }, { 10, 16 }, { 10, 22 }, { 10, 28 }, { 10, 34 }, { 10, 9 }, { 10, 8 }, { 10, 7 }, { 10, 5 }, { 10, 4 }, { 10, 3 }, { 11, 18 }, { 11, 16 }, { 11, 21 }, { 11, 26 }, { 11, 31 }, { 11, 12 }, { 11, 17 }, { 11, 23 }, { 11, 29 }, { 11, 35 }, { 11, 10 }, { 11, 9 }, { 11, 8 }, { 11, 7 }, { 11, 6 }, { 11, 5 }, { 11, 4 }, { 12, 17 }, { 12, 22 }, { 12, 27 }, { 12, 32 }, { 12, 18 }, { 12, 24 }, { 12, 30 }, { 12, 36 }, { 12, 11 }, { 12, 10 }, { 12, 9 }, { 12, 8 }, { 12, 7 }, { 12, 6 }, { 12, 5 }, { 13, 20 }, { 13, 27 }, { 13, 34 }, { 13, 14 }, { 13, 15 }, { 13, 16 }, { 13, 17 }, { 13, 18 }, { 13, 19 }, { 13, 25 }, { 13, 31 }, { 13, 8 }, { 13, 7 }, { 13, 3 }, { 13, 1 }, { 14, 21 }, { 14, 28 }, { 14, 35 }, { 14, 19 }, { 14, 15 }, { 14, 16 }, { 14, 17 }, { 14, 18 }, { 14, 20 }, { 14, 26 }, { 14, 32 }, { 14, 13 }, { 14, 9 }, { 14, 8 }, { 14, 7 }, { 14, 4 }, { 14, 2 }, { 15, 22 }, { 15, 29 }, { 15, 36 }, { 15, 20 }, { 15, 25 }, { 15, 16 }, { 15, 17 }, { 15, 18 }, { 15, 21 }, { 15, 27 }, { 15, 33 }, { 15, 14 }, { 15, 13 }, { 15, 10 }, { 15, 9 }, { 15, 8 }, { 15, 5 }, { 15, 3 }, { 15, 1 }, { 16, 23 }, { 16, 30 }, { 16, 21 }, { 16, 26 }, { 16, 31 }, { 16, 17 }, { 16, 18 }, { 16, 22 }, { 16, 28 }, { 16, 34 }, { 16, 15 }, { 16, 14 }, { 16, 13 }, { 16, 11 }, { 16, 10 }, { 16, 9 }, { 16, 6 }, { 16, 4 }, { 16, 2 }, { 17, 24 }, { 17, 22 }, { 17, 27 }, { 17, 32 }, { 17, 18 }, { 17, 23 }, { 17, 29 }, { 17, 35 }, { 17, 16 }, { 17, 15 }, { 17, 14 }, { 17, 13 }, { 17, 12 }, { 17, 11 }, { 17, 10 }, { 17, 5 }, { 17, 3 }, { 18, 23 }, { 18, 28 }, { 18, 33 }, { 18, 24 }, { 18, 30 }, { 18, 36 }, { 18, 17 }, { 18, 16 }, { 18, 15 }, { 18, 14 }, { 18, 13 }, { 18, 12 }, { 18, 11 }, { 18, 6 }, { 18, 4 }, { 19, 26 }, { 19, 33 }, { 19, 20 }, { 19, 21 }, { 19, 22 }, { 19, 23 }, { 19, 24 }, { 19, 25 }, { 19, 31 }, { 19, 14 }, { 19, 13 }, { 19, 9 }, { 19, 7 }, { 19, 4 }, { 19, 1 }, { 20, 27 }, { 20, 34 }, { 20, 25 }, { 20, 21 }, { 20, 22 }, { 20, 23 }, { 20, 24 }, { 20, 26 }, { 20, 32 }, { 20, 19 }, { 20, 15 }, { 20, 14 }, { 20, 13 }, { 20, 10 }, { 20, 8 }, { 20, 5 }, { 20, 2 }, { 21, 28 }, { 21, 35 }, { 21, 26 }, { 21, 31 }, { 21, 22 }, { 21, 23 }, { 21, 24 }, { 21, 27 }, { 21, 33 }, { 21, 20 }, { 21, 19 }, { 21, 16 }, { 21, 15 }, { 21, 14 }, { 21, 11 }, { 21, 9 }, { 21, 7 }, { 21, 6 }, { 21, 3 }, { 22, 29 }, { 22, 36 }, { 22, 27 }, { 22, 32 }, { 22, 23 }, { 22, 24 }, { 22, 28 }, { 22, 34 }, { 22, 21 }, { 22, 20 }, { 22, 19 }, { 22, 17 }, { 22, 16 }, { 22, 15 }, { 22, 12 }, { 22, 10 }, { 22, 8 }, { 22, 4 }, { 22, 1 }, { 23, 30 }, { 23, 28 }, { 23, 33 }, { 23, 24 }, { 23, 29 }, { 23, 35 }, { 23, 22 }, { 23, 21 }, { 23, 20 }, { 23, 19 }, { 23, 18 }, { 23, 17 }, { 23, 16 }, { 23, 11 }, { 23, 9 }, { 23, 5 }, { 23, 2 }, { 24, 29 }, { 24, 34 }, { 24, 30 }, { 24, 36 }, { 24, 23 }, { 24, 22 }, { 24, 21 }, { 24, 20 }, { 24, 19 }, { 24, 18 }, { 24, 17 }, { 24, 12 }, { 24, 10 }, { 24, 6 }, { 24, 3 }, { 25, 32 }, { 25, 26 }, { 25, 27 }, { 25, 28 }, { 25, 29 }, { 25, 30 }, { 25, 31 }, { 25, 20 }, { 25, 19 }, { 25, 15 }, { 25, 13 }, { 25, 10 }, { 25, 7 }, { 25, 5 }, { 25, 1 }, { 26, 33 }, { 26, 31 }, { 26, 27 }, { 26, 28 }, { 26, 29 }, { 26, 30 }, { 26, 32 }, { 26, 25 }, { 26, 21 }, { 26, 20 }, { 26, 19 }, { 26, 16 }, { 26, 14 }, { 26, 11 }, { 26, 8 }, { 26, 6 }, { 26, 2 }, { 27, 34 }, { 27, 32 }, { 27, 28 }, { 27, 29 }, { 27, 30 }, { 27, 33 }, { 27, 26 }, { 27, 25 }, { 27, 22 }, { 27, 21 }, { 27, 20 }, { 27, 17 }, { 27, 15 }, { 27, 13 }, { 27, 12 }, { 27, 9 }, { 27, 3 }, { 28, 35 }, { 28, 33 }, { 28, 29 }, { 28, 30 }, { 28, 34 }, { 28, 27 }, { 28, 26 }, { 28, 25 }, { 28, 23 }, { 28, 22 }, { 28, 21 }, { 28, 18 }, { 28, 16 }, { 28, 14 }, { 28, 10 }, { 28, 7 }, { 28, 4 }, { 29, 36 }, { 29, 34 }, { 29, 30 }, { 29, 35 }, { 29, 28 }, { 29, 27 }, { 29, 26 }, { 29, 25 }, { 29, 24 }, { 29, 23 }, { 29, 22 }, { 29, 17 }, { 29, 15 }, { 29, 11 }, { 29, 8 }, { 29, 5 }, { 29, 1 }, { 30, 35 }, { 30, 36 }, { 30, 29 }, { 30, 28 }, { 30, 27 }, { 30, 26 }, { 30, 25 }, { 30, 24 }, { 30, 23 }, { 30, 18 }, { 30, 16 }, { 30, 12 }, { 30, 9 }, { 30, 6 }, { 30, 2 }, { 31, 32 }, { 31, 33 }, { 31, 34 }, { 31, 35 }, { 31, 36 }, { 31, 26 }, { 31, 25 }, { 31, 21 }, { 31, 19 }, { 31, 16 }, { 31, 13 }, { 31, 11 }, { 31, 7 }, { 31, 6 }, { 31, 1 }, { 32, 33 }, { 32, 34 }, { 32, 35 }, { 32, 36 }, { 32, 31 }, { 32, 27 }, { 32, 26 }, { 32, 25 }, { 32, 22 }, { 32, 20 }, { 32, 17 }, { 32, 14 }, { 32, 12 }, { 32, 8 }, { 32, 2 }, { 33, 34 }, { 33, 35 }, { 33, 36 }, { 33, 32 }, { 33, 31 }, { 33, 28 }, { 33, 27 }, { 33, 26 }, { 33, 23 }, { 33, 21 }, { 33, 19 }, { 33, 18 }, { 33, 15 }, { 33, 9 }, { 33, 3 }, { 34, 35 }, { 34, 36 }, { 34, 33 }, { 34, 32 }, { 34, 31 }, { 34, 29 }, { 34, 28 }, { 34, 27 }, { 34, 24 }, { 34, 22 }, { 34, 20 }, { 34, 16 }, { 34, 13 }, { 34, 10 }, { 34, 4 }, { 35, 36 }, { 35, 34 }, { 35, 33 }, { 35, 32 }, { 35, 31 }, { 35, 30 }, { 35, 29 }, { 35, 28 }, { 35, 23 }, { 35, 21 }, { 35, 17 }, { 35, 14 }, { 35, 11 }, { 35, 7 }, { 35, 5 }, { 36, 35 }, { 36, 34 }, { 36, 33 }, { 36, 32 }, { 36, 31 }, { 36, 30 }, { 36, 29 }, { 36, 24 }, { 36, 22 }, { 36, 18 }, { 36, 15 }, { 36, 12 }, { 36, 8 }, { 36, 6 }, { 36, 1 } }; Graph g = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(g, edge[0], edge[1]); BrownBacktrackColoring bbc = new BrownBacktrackColoring<>(g); assertEquals(7, bbc.getChromaticNumber()); verifyColoring(g, 7, bbc.getColoring()); } /** * mug100_1.col 100 vertices, 166 edges chromatic number: 4 */ @Test @Category(OptionalTests.class) public void insertions2_3Test() { int[][] edges = { { 1, 3 }, { 1, 4 }, { 1, 9 }, { 1, 23 }, { 2, 6 }, { 2, 8 }, { 2, 11 }, { 2, 32 }, { 3, 4 }, { 3, 5 }, { 4, 21 }, { 4, 22 }, { 5, 7 }, { 5, 45 }, { 5, 46 }, { 6, 18 }, { 6, 19 }, { 6, 44 }, { 7, 12 }, { 7, 13 }, { 7, 17 }, { 8, 9 }, { 8, 10 }, { 9, 10 }, { 10, 24 }, { 10, 25 }, { 11, 15 }, { 11, 41 }, { 11, 62 }, { 12, 13 }, { 12, 26 }, { 13, 64 }, { 13, 65 }, { 14, 15 }, { 14, 16 }, { 14, 53 }, { 14, 59 }, { 15, 16 }, { 16, 43 }, { 16, 47 }, { 17, 19 }, { 17, 30 }, { 17, 31 }, { 18, 29 }, { 18, 50 }, { 19, 51 }, { 19, 52 }, { 20, 21 }, { 20, 22 }, { 20, 33 }, { 20, 34 }, { 21, 22 }, { 23, 24 }, { 23, 25 }, { 24, 25 }, { 26, 27 }, { 26, 28 }, { 27, 28 }, { 27, 54 }, { 27, 55 }, { 28, 36 }, { 28, 37 }, { 29, 30 }, { 29, 31 }, { 30, 31 }, { 32, 33 }, { 32, 34 }, { 33, 34 }, { 35, 36 }, { 35, 37 }, { 35, 60 }, { 35, 61 }, { 36, 71 }, { 37, 39 }, { 37, 40 }, { 38, 39 }, { 38, 40 }, { 38, 73 }, { 38, 95 }, { 39, 40 }, { 41, 42 }, { 41, 78 }, { 41, 83 }, { 42, 43 }, { 42, 48 }, { 42, 49 }, { 43, 77 }, { 44, 46 }, { 44, 87 }, { 44, 88 }, { 45, 46 }, { 45, 86 }, { 47, 48 }, { 47, 57 }, { 47, 58 }, { 48, 49 }, { 49, 99 }, { 49, 100 }, { 50, 52 }, { 50, 93 }, { 50, 94 }, { 51, 52 }, { 51, 92 }, { 53, 54 }, { 53, 55 }, { 54, 55 }, { 56, 58 }, { 56, 69 }, { 56, 70 }, { 56, 98 }, { 57, 58 }, { 57, 68 }, { 59, 60 }, { 59, 61 }, { 60, 75 }, { 60, 76 }, { 61, 74 }, { 62, 63 }, { 62, 64 }, { 63, 64 }, { 63, 66 }, { 63, 67 }, { 65, 66 }, { 65, 67 }, { 66, 67 }, { 68, 69 }, { 68, 70 }, { 69, 70 }, { 71, 72 }, { 71, 73 }, { 72, 73 }, { 72, 96 }, { 72, 97 }, { 74, 75 }, { 74, 76 }, { 75, 76 }, { 77, 78 }, { 77, 79 }, { 78, 90 }, { 78, 91 }, { 79, 81 }, { 79, 82 }, { 79, 89 }, { 80, 81 }, { 80, 82 }, { 80, 84 }, { 80, 85 }, { 81, 82 }, { 83, 84 }, { 83, 85 }, { 84, 85 }, { 86, 87 }, { 86, 88 }, { 87, 88 }, { 89, 90 }, { 89, 91 }, { 90, 91 }, { 92, 93 }, { 92, 94 }, { 93, 94 }, { 95, 96 }, { 95, 97 }, { 96, 97 }, { 98, 99 }, { 98, 100 }, { 99, 100 } }; Graph g = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(g, edge[0], edge[1]); BrownBacktrackColoring bbc = new BrownBacktrackColoring<>(g); assertEquals(4, bbc.getChromaticNumber()); verifyColoring(g, 4, bbc.getColoring()); } /** * jean.col 80 vertices, 508 edges chromatic number: 10 */ @Test @Category(OptionalTests.class) public void jeanTest() { int[][] edges = { { 1, 14 }, { 2, 37 }, { 2, 75 }, { 2, 14 }, { 3, 54 }, { 3, 46 }, { 3, 37 }, { 3, 28 }, { 3, 5 }, { 3, 60 }, { 3, 57 }, { 3, 44 }, { 3, 63 }, { 3, 40 }, { 3, 69 }, { 3, 25 }, { 3, 27 }, { 3, 73 }, { 3, 33 }, { 4, 50 }, { 4, 79 }, { 4, 7 }, { 4, 72 }, { 4, 47 }, { 4, 19 }, { 4, 34 }, { 4, 68 }, { 4, 9 }, { 4, 66 }, { 5, 22 }, { 5, 10 }, { 5, 20 }, { 5, 39 }, { 5, 17 }, { 5, 37 }, { 5, 28 }, { 5, 3 }, { 6, 57 }, { 6, 16 }, { 6, 48 }, { 6, 72 }, { 6, 37 }, { 6, 35 }, { 6, 55 }, { 6, 58 }, { 6, 28 }, { 7, 15 }, { 7, 47 }, { 7, 50 }, { 7, 4 }, { 7, 79 }, { 7, 9 }, { 7, 66 }, { 7, 38 }, { 7, 34 }, { 7, 72 }, { 7, 68 }, { 7, 19 }, { 8, 72 }, { 8, 56 }, { 9, 37 }, { 9, 38 }, { 9, 35 }, { 9, 28 }, { 9, 47 }, { 9, 50 }, { 9, 19 }, { 9, 79 }, { 9, 66 }, { 9, 7 }, { 9, 72 }, { 9, 4 }, { 9, 68 }, { 9, 15 }, { 9, 34 }, { 10, 5 }, { 10, 37 }, { 10, 22 }, { 10, 20 }, { 10, 39 }, { 10, 17 }, { 11, 42 }, { 11, 72 }, { 12, 14 }, { 13, 43 }, { 14, 37 }, { 14, 80 }, { 14, 41 }, { 14, 65 }, { 14, 32 }, { 14, 24 }, { 14, 12 }, { 14, 2 }, { 14, 75 }, { 14, 1 }, { 15, 79 }, { 15, 72 }, { 15, 7 }, { 15, 19 }, { 15, 48 }, { 15, 57 }, { 15, 33 }, { 15, 37 }, { 15, 59 }, { 15, 68 }, { 15, 9 }, { 15, 34 }, { 15, 66 }, { 15, 26 }, { 15, 38 }, { 15, 76 }, { 15, 23 }, { 15, 77 }, { 15, 29 }, { 16, 6 }, { 16, 35 }, { 16, 48 }, { 16, 72 }, { 16, 57 }, { 16, 55 }, { 16, 58 }, { 17, 22 }, { 17, 10 }, { 17, 20 }, { 17, 5 }, { 17, 39 }, { 17, 37 }, { 18, 44 }, { 18, 57 }, { 18, 48 }, { 19, 15 }, { 19, 38 }, { 19, 68 }, { 19, 50 }, { 19, 79 }, { 19, 9 }, { 19, 34 }, { 19, 72 }, { 19, 47 }, { 19, 4 }, { 19, 66 }, { 19, 7 }, { 20, 5 }, { 20, 37 }, { 20, 22 }, { 20, 10 }, { 20, 39 }, { 20, 17 }, { 22, 5 }, { 22, 37 }, { 22, 10 }, { 22, 20 }, { 22, 39 }, { 22, 17 }, { 23, 15 }, { 23, 77 }, { 24, 14 }, { 25, 3 }, { 25, 63 }, { 25, 40 }, { 25, 69 }, { 25, 27 }, { 25, 73 }, { 25, 33 }, { 26, 59 }, { 26, 77 }, { 26, 15 }, { 26, 29 }, { 27, 3 }, { 27, 63 }, { 27, 40 }, { 27, 69 }, { 27, 25 }, { 27, 73 }, { 27, 33 }, { 28, 30 }, { 28, 59 }, { 28, 72 }, { 28, 9 }, { 28, 6 }, { 28, 35 }, { 28, 55 }, { 28, 44 }, { 28, 58 }, { 28, 64 }, { 28, 57 }, { 28, 46 }, { 28, 31 }, { 28, 3 }, { 28, 5 }, { 28, 37 }, { 28, 43 }, { 29, 37 }, { 29, 59 }, { 29, 15 }, { 29, 77 }, { 29, 26 }, { 29, 36 }, { 29, 45 }, { 30, 37 }, { 30, 28 }, { 30, 59 }, { 31, 28 }, { 31, 37 }, { 32, 14 }, { 33, 59 }, { 33, 15 }, { 33, 3 }, { 33, 63 }, { 33, 40 }, { 33, 69 }, { 33, 25 }, { 33, 27 }, { 33, 73 }, { 34, 47 }, { 34, 50 }, { 34, 19 }, { 34, 79 }, { 34, 48 }, { 34, 38 }, { 34, 7 }, { 34, 72 }, { 34, 4 }, { 34, 68 }, { 34, 9 }, { 34, 66 }, { 34, 15 }, { 35, 9 }, { 35, 16 }, { 35, 48 }, { 35, 6 }, { 35, 28 }, { 35, 44 }, { 35, 37 }, { 35, 55 }, { 35, 58 }, { 35, 57 }, { 36, 29 }, { 37, 29 }, { 37, 77 }, { 37, 66 }, { 37, 9 }, { 37, 72 }, { 37, 30 }, { 37, 6 }, { 37, 35 }, { 37, 55 }, { 37, 58 }, { 37, 15 }, { 37, 78 }, { 37, 64 }, { 37, 57 }, { 37, 44 }, { 37, 59 }, { 37, 22 }, { 37, 10 }, { 37, 20 }, { 37, 5 }, { 37, 39 }, { 37, 17 }, { 37, 31 }, { 37, 61 }, { 37, 46 }, { 37, 3 }, { 37, 28 }, { 37, 43 }, { 37, 53 }, { 37, 70 }, { 37, 14 }, { 37, 75 }, { 37, 2 }, { 37, 67 }, { 37, 60 }, { 37, 62 }, { 38, 79 }, { 38, 72 }, { 38, 19 }, { 38, 66 }, { 38, 9 }, { 38, 7 }, { 38, 34 }, { 38, 68 }, { 38, 48 }, { 38, 52 }, { 38, 15 }, { 39, 22 }, { 39, 10 }, { 39, 20 }, { 39, 5 }, { 39, 17 }, { 39, 37 }, { 40, 3 }, { 40, 63 }, { 40, 69 }, { 40, 25 }, { 40, 27 }, { 40, 73 }, { 40, 33 }, { 41, 14 }, { 42, 11 }, { 42, 72 }, { 43, 13 }, { 43, 78 }, { 43, 28 }, { 43, 37 }, { 44, 74 }, { 44, 28 }, { 44, 35 }, { 44, 55 }, { 44, 58 }, { 44, 18 }, { 44, 48 }, { 44, 37 }, { 44, 59 }, { 44, 57 }, { 44, 3 }, { 45, 76 }, { 45, 29 }, { 46, 28 }, { 46, 3 }, { 46, 37 }, { 46, 54 }, { 47, 9 }, { 47, 72 }, { 47, 34 }, { 47, 7 }, { 47, 4 }, { 47, 19 }, { 47, 66 }, { 48, 34 }, { 48, 6 }, { 48, 35 }, { 48, 55 }, { 48, 16 }, { 48, 58 }, { 48, 38 }, { 48, 15 }, { 48, 57 }, { 48, 44 }, { 48, 18 }, { 50, 68 }, { 50, 4 }, { 50, 19 }, { 50, 79 }, { 50, 9 }, { 50, 66 }, { 50, 34 }, { 50, 7 }, { 50, 72 }, { 51, 57 }, { 52, 38 }, { 53, 37 }, { 54, 3 }, { 54, 46 }, { 55, 48 }, { 55, 72 }, { 55, 16 }, { 55, 6 }, { 55, 28 }, { 55, 44 }, { 55, 37 }, { 55, 35 }, { 55, 58 }, { 55, 57 }, { 56, 8 }, { 57, 6 }, { 57, 72 }, { 57, 16 }, { 57, 35 }, { 57, 55 }, { 57, 58 }, { 57, 18 }, { 57, 59 }, { 57, 48 }, { 57, 15 }, { 57, 28 }, { 57, 37 }, { 57, 51 }, { 57, 76 }, { 57, 3 }, { 57, 44 }, { 58, 48 }, { 58, 72 }, { 58, 16 }, { 58, 6 }, { 58, 28 }, { 58, 44 }, { 58, 37 }, { 58, 35 }, { 58, 55 }, { 58, 57 }, { 59, 29 }, { 59, 77 }, { 59, 28 }, { 59, 30 }, { 59, 26 }, { 59, 57 }, { 59, 33 }, { 59, 15 }, { 59, 64 }, { 59, 37 }, { 59, 44 }, { 60, 3 }, { 60, 37 }, { 61, 37 }, { 62, 37 }, { 63, 3 }, { 63, 40 }, { 63, 69 }, { 63, 25 }, { 63, 27 }, { 63, 73 }, { 63, 33 }, { 64, 28 }, { 64, 59 }, { 64, 37 }, { 65, 14 }, { 66, 37 }, { 66, 38 }, { 66, 68 }, { 66, 50 }, { 66, 79 }, { 66, 9 }, { 66, 7 }, { 66, 72 }, { 66, 47 }, { 66, 19 }, { 66, 4 }, { 66, 34 }, { 66, 15 }, { 67, 37 }, { 68, 19 }, { 68, 66 }, { 68, 79 }, { 68, 50 }, { 68, 38 }, { 68, 7 }, { 68, 72 }, { 68, 4 }, { 68, 15 }, { 68, 9 }, { 68, 34 }, { 69, 3 }, { 69, 63 }, { 69, 40 }, { 69, 25 }, { 69, 27 }, { 69, 73 }, { 69, 33 }, { 70, 37 }, { 72, 37 }, { 72, 15 }, { 72, 38 }, { 72, 28 }, { 72, 47 }, { 72, 50 }, { 72, 4 }, { 72, 19 }, { 72, 79 }, { 72, 9 }, { 72, 66 }, { 72, 7 }, { 72, 34 }, { 72, 68 }, { 72, 57 }, { 72, 55 }, { 72, 16 }, { 72, 58 }, { 72, 6 }, { 72, 11 }, { 72, 42 }, { 72, 8 }, { 73, 3 }, { 73, 63 }, { 73, 40 }, { 73, 69 }, { 73, 25 }, { 73, 27 }, { 73, 33 }, { 74, 44 }, { 74, 77 }, { 75, 37 }, { 75, 2 }, { 75, 14 }, { 76, 15 }, { 76, 45 }, { 76, 57 }, { 77, 37 }, { 77, 59 }, { 77, 26 }, { 77, 23 }, { 77, 15 }, { 77, 29 }, { 77, 74 }, { 78, 37 }, { 78, 43 }, { 79, 15 }, { 79, 38 }, { 79, 68 }, { 79, 50 }, { 79, 4 }, { 79, 19 }, { 79, 9 }, { 79, 66 }, { 79, 34 }, { 79, 7 }, { 79, 72 }, { 80, 14 } }; Graph g = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(g, edge[0], edge[1]); BrownBacktrackColoring bbc = new BrownBacktrackColoring<>(g); assertEquals(10, bbc.getChromaticNumber()); verifyColoring(g, 10, bbc.getColoring()); } /** * huck.col 74 vertices, 602 edges chromatic number: 11 */ @Test public void huckTest() { int[][] edges = { { 1, 44 }, { 1, 4 }, { 1, 69 }, { 1, 59 }, { 1, 13 }, { 1, 29 }, { 1, 40 }, { 1, 11 }, { 1, 50 }, { 1, 5 }, { 1, 25 }, { 1, 10 }, { 1, 63 }, { 1, 22 }, { 1, 9 }, { 1, 55 }, { 1, 72 }, { 1, 49 }, { 2, 46 }, { 2, 55 }, { 2, 62 }, { 2, 4 }, { 2, 74 }, { 2, 43 }, { 2, 51 }, { 2, 57 }, { 2, 41 }, { 3, 53 }, { 3, 52 }, { 3, 55 }, { 3, 9 }, { 4, 69 }, { 4, 1 }, { 4, 9 }, { 4, 22 }, { 4, 49 }, { 4, 63 }, { 4, 68 }, { 4, 60 }, { 4, 19 }, { 4, 56 }, { 4, 27 }, { 4, 46 }, { 4, 55 }, { 4, 2 }, { 4, 73 }, { 4, 43 }, { 4, 62 }, { 4, 47 }, { 4, 67 }, { 4, 12 }, { 4, 57 }, { 4, 32 }, { 4, 71 }, { 4, 45 }, { 4, 41 }, { 5, 59 }, { 5, 13 }, { 5, 29 }, { 5, 40 }, { 5, 11 }, { 5, 50 }, { 5, 25 }, { 5, 49 }, { 5, 1 }, { 5, 55 }, { 6, 64 }, { 6, 23 }, { 6, 21 }, { 6, 17 }, { 6, 34 }, { 6, 39 }, { 6, 20 }, { 6, 55 }, { 7, 70 }, { 8, 14 }, { 8, 44 }, { 8, 9 }, { 8, 52 }, { 8, 38 }, { 8, 31 }, { 8, 18 }, { 9, 69 }, { 9, 4 }, { 9, 72 }, { 9, 10 }, { 9, 66 }, { 9, 1 }, { 9, 49 }, { 9, 8 }, { 9, 14 }, { 9, 44 }, { 9, 38 }, { 9, 31 }, { 9, 18 }, { 9, 3 }, { 9, 53 }, { 9, 52 }, { 9, 22 }, { 9, 55 }, { 10, 72 }, { 10, 1 }, { 10, 49 }, { 10, 55 }, { 10, 9 }, { 11, 59 }, { 11, 13 }, { 11, 29 }, { 11, 40 }, { 11, 50 }, { 11, 5 }, { 11, 25 }, { 11, 49 }, { 11, 1 }, { 11, 55 }, { 12, 55 }, { 12, 73 }, { 12, 43 }, { 12, 62 }, { 12, 4 }, { 12, 47 }, { 12, 67 }, { 12, 57 }, { 12, 41 }, { 13, 59 }, { 13, 29 }, { 13, 40 }, { 13, 11 }, { 13, 50 }, { 13, 5 }, { 13, 25 }, { 13, 49 }, { 13, 1 }, { 13, 55 }, { 14, 8 }, { 14, 44 }, { 14, 9 }, { 14, 52 }, { 14, 38 }, { 14, 31 }, { 14, 18 }, { 15, 22 }, { 15, 55 }, { 16, 65 }, { 16, 55 }, { 17, 28 }, { 17, 30 }, { 17, 64 }, { 17, 23 }, { 17, 21 }, { 17, 6 }, { 17, 34 }, { 17, 39 }, { 17, 20 }, { 17, 55 }, { 18, 22 }, { 18, 8 }, { 18, 14 }, { 18, 44 }, { 18, 9 }, { 18, 52 }, { 18, 38 }, { 18, 24 }, { 18, 31 }, { 18, 55 }, { 19, 68 }, { 19, 55 }, { 19, 60 }, { 19, 73 }, { 19, 4 }, { 19, 56 }, { 19, 27 }, { 19, 57 }, { 19, 41 }, { 20, 64 }, { 20, 23 }, { 20, 21 }, { 20, 17 }, { 20, 6 }, { 20, 34 }, { 20, 39 }, { 20, 55 }, { 21, 64 }, { 21, 23 }, { 21, 17 }, { 21, 6 }, { 21, 34 }, { 21, 39 }, { 21, 20 }, { 21, 55 }, { 22, 18 }, { 22, 44 }, { 22, 69 }, { 22, 49 }, { 22, 4 }, { 22, 66 }, { 22, 63 }, { 22, 1 }, { 22, 61 }, { 22, 26 }, { 22, 57 }, { 22, 41 }, { 22, 15 }, { 22, 42 }, { 22, 55 }, { 22, 9 }, { 23, 30 }, { 23, 64 }, { 23, 21 }, { 23, 17 }, { 23, 6 }, { 23, 34 }, { 23, 39 }, { 23, 20 }, { 23, 55 }, { 24, 18 }, { 24, 31 }, { 24, 42 }, { 24, 55 }, { 25, 59 }, { 25, 13 }, { 25, 29 }, { 25, 40 }, { 25, 11 }, { 25, 50 }, { 25, 5 }, { 25, 49 }, { 25, 1 }, { 25, 55 }, { 26, 22 }, { 27, 68 }, { 27, 55 }, { 27, 60 }, { 27, 19 }, { 27, 73 }, { 27, 57 }, { 27, 41 }, { 27, 4 }, { 27, 56 }, { 27, 62 }, { 28, 17 }, { 29, 59 }, { 29, 13 }, { 29, 40 }, { 29, 11 }, { 29, 50 }, { 29, 5 }, { 29, 25 }, { 29, 49 }, { 29, 1 }, { 29, 55 }, { 30, 23 }, { 30, 55 }, { 30, 17 }, { 31, 8 }, { 31, 14 }, { 31, 44 }, { 31, 9 }, { 31, 52 }, { 31, 38 }, { 31, 24 }, { 31, 18 }, { 31, 55 }, { 32, 4 }, { 32, 71 }, { 33, 49 }, { 34, 64 }, { 34, 23 }, { 34, 21 }, { 34, 17 }, { 34, 6 }, { 34, 39 }, { 34, 20 }, { 34, 55 }, { 35, 48 }, { 35, 58 }, { 36, 60 }, { 36, 55 }, { 36, 57 }, { 36, 41 }, { 37, 72 }, { 37, 49 }, { 38, 8 }, { 38, 14 }, { 38, 44 }, { 38, 9 }, { 38, 52 }, { 38, 31 }, { 38, 18 }, { 39, 64 }, { 39, 23 }, { 39, 21 }, { 39, 17 }, { 39, 6 }, { 39, 34 }, { 39, 20 }, { 39, 55 }, { 40, 59 }, { 40, 13 }, { 40, 29 }, { 40, 11 }, { 40, 50 }, { 40, 5 }, { 40, 25 }, { 40, 49 }, { 40, 1 }, { 40, 55 }, { 41, 68 }, { 41, 60 }, { 41, 19 }, { 41, 73 }, { 41, 56 }, { 41, 27 }, { 41, 46 }, { 41, 2 }, { 41, 74 }, { 41, 43 }, { 41, 51 }, { 41, 62 }, { 41, 47 }, { 41, 67 }, { 41, 12 }, { 41, 36 }, { 41, 4 }, { 41, 57 }, { 41, 22 }, { 41, 55 }, { 42, 22 }, { 42, 55 }, { 42, 24 }, { 43, 47 }, { 43, 67 }, { 43, 12 }, { 43, 73 }, { 43, 4 }, { 43, 2 }, { 43, 74 }, { 43, 51 }, { 43, 57 }, { 43, 41 }, { 44, 22 }, { 44, 1 }, { 44, 55 }, { 44, 49 }, { 44, 8 }, { 44, 14 }, { 44, 9 }, { 44, 52 }, { 44, 38 }, { 44, 31 }, { 44, 18 }, { 45, 4 }, { 45, 71 }, { 46, 55 }, { 46, 62 }, { 46, 4 }, { 46, 57 }, { 46, 41 }, { 46, 2 }, { 47, 55 }, { 47, 73 }, { 47, 43 }, { 47, 62 }, { 47, 4 }, { 47, 67 }, { 47, 12 }, { 47, 57 }, { 47, 41 }, { 48, 35 }, { 48, 58 }, { 49, 44 }, { 49, 22 }, { 49, 69 }, { 49, 59 }, { 49, 13 }, { 49, 29 }, { 49, 40 }, { 49, 11 }, { 49, 50 }, { 49, 5 }, { 49, 25 }, { 49, 4 }, { 49, 33 }, { 49, 10 }, { 49, 9 }, { 49, 37 }, { 49, 1 }, { 49, 55 }, { 49, 72 }, { 50, 59 }, { 50, 13 }, { 50, 29 }, { 50, 40 }, { 50, 11 }, { 50, 5 }, { 50, 25 }, { 50, 49 }, { 50, 1 }, { 50, 55 }, { 51, 2 }, { 51, 74 }, { 51, 43 }, { 51, 57 }, { 51, 41 }, { 52, 8 }, { 52, 14 }, { 52, 44 }, { 52, 38 }, { 52, 31 }, { 52, 18 }, { 52, 3 }, { 52, 53 }, { 52, 55 }, { 52, 9 }, { 53, 3 }, { 53, 52 }, { 53, 55 }, { 53, 9 }, { 54, 55 }, { 55, 44 }, { 55, 59 }, { 55, 13 }, { 55, 29 }, { 55, 40 }, { 55, 11 }, { 55, 50 }, { 55, 5 }, { 55, 25 }, { 55, 69 }, { 55, 10 }, { 55, 66 }, { 55, 1 }, { 55, 72 }, { 55, 49 }, { 55, 68 }, { 55, 60 }, { 55, 19 }, { 55, 73 }, { 55, 56 }, { 55, 27 }, { 55, 46 }, { 55, 4 }, { 55, 2 }, { 55, 62 }, { 55, 47 }, { 55, 67 }, { 55, 12 }, { 55, 36 }, { 55, 57 }, { 55, 41 }, { 55, 15 }, { 55, 30 }, { 55, 64 }, { 55, 23 }, { 55, 21 }, { 55, 17 }, { 55, 6 }, { 55, 34 }, { 55, 39 }, { 55, 20 }, { 55, 65 }, { 55, 16 }, { 55, 54 }, { 55, 18 }, { 55, 31 }, { 55, 3 }, { 55, 53 }, { 55, 52 }, { 55, 22 }, { 55, 42 }, { 55, 24 }, { 55, 9 }, { 56, 68 }, { 56, 55 }, { 56, 60 }, { 56, 19 }, { 56, 73 }, { 56, 57 }, { 56, 41 }, { 56, 4 }, { 56, 27 }, { 57, 68 }, { 57, 60 }, { 57, 19 }, { 57, 73 }, { 57, 56 }, { 57, 27 }, { 57, 46 }, { 57, 2 }, { 57, 74 }, { 57, 43 }, { 57, 51 }, { 57, 62 }, { 57, 47 }, { 57, 67 }, { 57, 12 }, { 57, 36 }, { 57, 4 }, { 57, 41 }, { 57, 22 }, { 57, 55 }, { 58, 35 }, { 58, 48 }, { 59, 13 }, { 59, 29 }, { 59, 40 }, { 59, 11 }, { 59, 50 }, { 59, 5 }, { 59, 25 }, { 59, 49 }, { 59, 1 }, { 59, 55 }, { 60, 36 }, { 60, 68 }, { 60, 55 }, { 60, 19 }, { 60, 73 }, { 60, 4 }, { 60, 56 }, { 60, 27 }, { 60, 57 }, { 60, 41 }, { 61, 22 }, { 62, 46 }, { 62, 2 }, { 62, 55 }, { 62, 4 }, { 62, 47 }, { 62, 67 }, { 62, 12 }, { 62, 57 }, { 62, 41 }, { 62, 27 }, { 63, 4 }, { 63, 1 }, { 63, 22 }, { 64, 23 }, { 64, 21 }, { 64, 17 }, { 64, 6 }, { 64, 34 }, { 64, 39 }, { 64, 20 }, { 64, 55 }, { 65, 16 }, { 65, 55 }, { 66, 22 }, { 66, 55 }, { 66, 9 }, { 67, 55 }, { 67, 73 }, { 67, 43 }, { 67, 62 }, { 67, 4 }, { 67, 47 }, { 67, 12 }, { 67, 57 }, { 67, 41 }, { 68, 55 }, { 68, 60 }, { 68, 19 }, { 68, 73 }, { 68, 4 }, { 68, 56 }, { 68, 27 }, { 68, 57 }, { 68, 41 }, { 69, 4 }, { 69, 22 }, { 69, 9 }, { 69, 1 }, { 69, 49 }, { 69, 55 }, { 70, 7 }, { 71, 32 }, { 71, 4 }, { 71, 45 }, { 72, 10 }, { 72, 9 }, { 72, 37 }, { 72, 1 }, { 72, 55 }, { 72, 49 }, { 73, 68 }, { 73, 55 }, { 73, 60 }, { 73, 19 }, { 73, 56 }, { 73, 27 }, { 73, 57 }, { 73, 41 }, { 73, 47 }, { 73, 67 }, { 73, 12 }, { 73, 43 }, { 73, 4 }, { 74, 2 }, { 74, 43 }, { 74, 51 }, { 74, 57 }, { 74, 41 } }; Graph g = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(g, edge[0], edge[1]); BrownBacktrackColoring bbc = new BrownBacktrackColoring<>(g); assertEquals(11, bbc.getChromaticNumber()); verifyColoring(g, 11, bbc.getColoring()); } /** * 2-Insertions_3.col 37 vertices, 72 edges chromatic number: 4 */ @Test public void mugg100Test() { int[][] edges = { { 1, 2 }, { 1, 4 }, { 1, 11 }, { 1, 13 }, { 2, 3 }, { 2, 10 }, { 2, 12 }, { 3, 6 }, { 3, 11 }, { 3, 15 }, { 4, 5 }, { 4, 10 }, { 4, 14 }, { 5, 8 }, { 5, 13 }, { 5, 17 }, { 6, 7 }, { 6, 12 }, { 6, 16 }, { 7, 9 }, { 7, 15 }, { 7, 18 }, { 8, 9 }, { 8, 14 }, { 8, 18 }, { 9, 16 }, { 9, 17 }, { 10, 20 }, { 10, 22 }, { 11, 19 }, { 11, 21 }, { 12, 20 }, { 12, 24 }, { 13, 19 }, { 13, 23 }, { 14, 22 }, { 14, 26 }, { 15, 21 }, { 15, 25 }, { 16, 24 }, { 16, 27 }, { 17, 23 }, { 17, 27 }, { 18, 25 }, { 18, 26 }, { 19, 29 }, { 19, 31 }, { 20, 28 }, { 20, 30 }, { 21, 29 }, { 21, 33 }, { 22, 28 }, { 22, 32 }, { 23, 31 }, { 23, 35 }, { 24, 30 }, { 24, 34 }, { 25, 33 }, { 25, 36 }, { 26, 32 }, { 26, 36 }, { 27, 34 }, { 27, 35 }, { 28, 37 }, { 29, 37 }, { 30, 37 }, { 31, 37 }, { 32, 37 }, { 33, 37 }, { 34, 37 }, { 35, 37 }, { 36, 37 } }; Graph g = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(g, edge[0], edge[1]); BrownBacktrackColoring bbc = new BrownBacktrackColoring<>(g); assertEquals(4, bbc.getChromaticNumber()); verifyColoring(g, 4, bbc.getColoring()); } private static void verifyColoring( Graph g, int chromaticNumber, VertexColoringAlgorithm.Coloring coloring) { Map colorAssignment = coloring.getColors(); List> colorClasses = coloring.getColorClasses(); // check chromatic number assertEquals(chromaticNumber, coloring.getNumberColors()); assertEquals(chromaticNumber, new HashSet<>(colorAssignment.values()).size()); assertEquals(chromaticNumber, colorClasses.size()); // All vertices are assigned a color assertEquals(g.vertexSet(), colorAssignment.keySet()); // Neighbors cannot have the same color for (E e : g.edgeSet()) assertNotEquals( colorAssignment.get(g.getEdgeSource(e)), colorAssignment.get(g.getEdgeTarget(e))); // All vertices in the same color class must have the same color for (Set colorClass : colorClasses) assertEquals(1, colorClass.stream().mapToInt(colorAssignment::get).distinct().count()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/ChordalGraphColoringTest.java000066400000000000000000000106451402514743400335450ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for the {@link ChordalGraphColoring} * * @author Timofey Chudakov */ public class ChordalGraphColoringTest { /** * Tests coloring of an empty graph */ @Test public void testGetColoring1() { int[][] edges = {}; Graph graph = TestUtil.createUndirected(edges); VertexColoringAlgorithm.Coloring coloring = new ChordalGraphColoring<>(graph).getColoring(); assertNotNull(coloring); assertEquals(0, coloring.getNumberColors()); assertEquals(0, coloring.getColors().size()); assertEquals(0, coloring.getColorClasses().size()); } /** * Tests coloring on a small clique */ @Test public void testGetColoring2() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 3 }, }; Graph graph = TestUtil.createUndirected(edges); VertexColoringAlgorithm.Coloring coloring = new ChordalGraphColoring<>(graph).getColoring(); assertNotNull(coloring); assertEquals(3, coloring.getNumberColors()); assertIsColoring(graph, coloring); } /** * Tests coloring on a non-chordal graph. */ @Test public void testGetColoring3() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); VertexColoringAlgorithm.Coloring coloring = new ChordalGraphColoring<>(graph).getColoring(); assertNull(coloring); } /** * Tests coloring of the big graph */ @Test public void testGetColoring4() { int[][] edges = { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 }, { 8, 9 }, { 9, 10 }, { 10, 1 }, { 2, 4 }, { 4, 6 }, { 6, 8 }, { 8, 10 }, { 10, 2 }, { 2, 6 }, { 2, 8 }, { 4, 8 }, { 4, 10 }, { 6, 10 }, }; Graph graph = TestUtil.createUndirected(edges); VertexColoringAlgorithm.Coloring coloring = new ChordalGraphColoring<>(graph).getColoring(); assertNotNull(coloring); assertIsColoring(graph, coloring); assertEquals(5, coloring.getNumberColors()); } /** * Tests coloring of a pseudograph */ @Test public void testGetColoring5() { int[][] edges = { { 1, 1 }, { 2, 2 }, { 2, 3 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 4 }, { 3, 4 }, { 4, 4 }, { 4, 4 }, { 5, 5 }, { 5, 5 }, }; Graph graph = TestUtil.createPseudograph(edges); VertexColoringAlgorithm.Coloring coloring = new ChordalGraphColoring<>(graph).getColoring(); assertNotNull(coloring); assertIsColoring(graph, coloring); assertEquals(3, coloring.getNumberColors()); } /** * Checks whether the {@code coloring} is a valid vertex coloring. * * @param graph the tested graph. * @param coloring the tested coloring. * @param the graph vertex type. * @param the graph edge type. */ private void assertIsColoring(Graph graph, VertexColoringAlgorithm.Coloring coloring) { Map colors = coloring.getColors(); for (V vertex : graph.vertexSet()) { for (E edge : graph.edgesOf(vertex)) { V opposite = Graphs.getOppositeVertex(graph, edge, vertex); if (!vertex.equals(opposite)) { assertNotEquals(colors.get(vertex), colors.get(opposite)); } } } } } ColorRefinementAlgorithmTest.java000066400000000000000000000134651402514743400344000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/* * (C) Copyright 2018-2021, by Oliver Feith and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; /** * Tests for the color-refinement algorithm. * * @author Oliver Feith */ public class ColorRefinementAlgorithmTest { @Test public void testTree() { Graph tree = new SimpleGraph<>(DefaultEdge.class); // Tree has the form ._._|_._. Graphs.addAllVertices(tree, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); tree.addEdge(1, 2); tree.addEdge(2, 3); tree.addEdge(3, 4); tree.addEdge(4, 5); tree.addEdge(3, 6); tree.addEdge(6, 7); tree.addEdge(6, 8); ColorRefinementAlgorithm CR = new ColorRefinementAlgorithm<>(tree); Map colors = CR.getColoring().getColors(); // symmetric pairs around 3 should have the same color and different colors otherwise assertEquals(colors.get(1).intValue(), colors.get(5).intValue()); assertNotEquals(colors.get(1).intValue(), colors.get(7).intValue()); assertEquals(colors.get(2).intValue(), colors.get(4).intValue()); assertEquals(colors.get(7).intValue(), colors.get(8).intValue()); assertNotEquals(colors.get(1).intValue(), colors.get(2).intValue()); assertNotEquals(colors.get(2).intValue(), colors.get(3).intValue()); assertNotEquals(colors.get(3).intValue(), colors.get(6).intValue()); } @Test public void testRegular() { Graph regularGraph = new SimpleGraph<>(DefaultEdge.class); // Graph should be the disjoint union of 2 triangles Graphs.addAllVertices(regularGraph, Arrays.asList(1, 2, 3, 4, 5, 6)); regularGraph.addEdge(1, 2); regularGraph.addEdge(2, 3); regularGraph.addEdge(3, 1); regularGraph.addEdge(4, 5); regularGraph.addEdge(5, 6); regularGraph.addEdge(6, 4); ColorRefinementAlgorithm CR = new ColorRefinementAlgorithm<>(regularGraph); Map colors = CR.getColoring().getColors(); // all vertices should have the same color assertEquals(colors.get(1).intValue(), colors.get(2).intValue()); assertEquals(colors.get(1).intValue(), colors.get(3).intValue()); assertEquals(colors.get(1).intValue(), colors.get(4).intValue()); assertEquals(colors.get(1).intValue(), colors.get(5).intValue()); assertEquals(colors.get(1).intValue(), colors.get(6).intValue()); } @Test public void testGraph1() { Graph graph1 = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); graph1.addEdge(1, 2); graph1.addEdge(2, 3); graph1.addEdge(2, 4); graph1.addEdge(2, 6); graph1.addEdge(2, 11); graph1.addEdge(3, 4); graph1.addEdge(4, 6); graph1.addEdge(5, 6); graph1.addEdge(6, 7); graph1.addEdge(7, 8); graph1.addEdge(8, 9); graph1.addEdge(8, 10); graph1.addEdge(8, 11); graph1.addEdge(9, 10); graph1.addEdge(9, 11); graph1.addEdge(10, 11); ColorRefinementAlgorithm CR = new ColorRefinementAlgorithm<>(graph1); Map colors = CR.getColoring().getColors(); // 9 and 10 should have the same color, all others should have distinct colors for (int i = 1; i < 11; i++) { for (int j = i + 1; j <= 11; j++) { if (i != 9 || j != 10) { assertNotEquals(colors.get(i).intValue(), colors.get(j).intValue()); } } } assertEquals(colors.get(9).intValue(), colors.get(10).intValue()); } @Test public void testDirectedGraph1() { Graph graph1 = new DefaultDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); graph1.addEdge(1, 2); graph1.addEdge(2, 4); graph1.addEdge(3, 2); graph1.addEdge(4, 2); graph1.addEdge(4, 5); graph1.addEdge(4, 6); graph1.addEdge(5, 8); graph1.addEdge(6, 7); graph1.addEdge(7, 8); graph1.addEdge(8, 4); graph1.addEdge(8, 6); ColorRefinementAlgorithm CR = new ColorRefinementAlgorithm<>(graph1); Map colors = CR.getColoring().getColors(); // 1 and 3 should have the same color, all others should have distinct colors for (int i = 1; i < 9; i++) { for (int j = i + 1; j < 9; j++) { if ((i == 1 && j == 3) || (i == 5 && j == 7)) { assertEquals(colors.get(i).intValue(), colors.get(j).intValue()); } else { assertNotEquals(colors.get(i).intValue(), colors.get(j).intValue()); } } } assertEquals(colors.get(1).intValue(), colors.get(3).intValue()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/GreedyColoringTest.java000066400000000000000000000035131402514743400324220ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.VertexColoringAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Coloring tests * * @author Dimitrios Michail */ public class GreedyColoringTest extends BaseColoringTest { @Override protected VertexColoringAlgorithm getAlgorithm(Graph graph) { return new GreedyColoring<>(graph); } @Override protected int getExpectedResultOnDSaturNonOptimalGraph() { return 4; } @Test public void testGreedy() { Graph g = createGraph1(); Coloring coloring = new GreedyColoring<>(g).getColoring(); assertEquals(3, coloring.getNumberColors()); Map colors = coloring.getColors(); assertEquals(0, colors.get(1).intValue()); assertEquals(1, colors.get(2).intValue()); assertEquals(2, colors.get(3).intValue()); assertEquals(1, colors.get(4).intValue()); assertEquals(1, colors.get(5).intValue()); } } LargestDegreeFirstColoringTest.java000066400000000000000000000117661402514743400346620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.VertexColoringAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Coloring tests * * @author Dimitrios Michail */ public class LargestDegreeFirstColoringTest extends BaseColoringTest { @Override protected VertexColoringAlgorithm getAlgorithm(Graph graph) { return new LargestDegreeFirstColoring<>(graph); } @Test public void testMyciel3() { Graph g = createMyciel3Graph(); assertColoring(g, getAlgorithm(g).getColoring(), 4); } @Override protected int getExpectedResultOnDSaturNonOptimalGraph() { return 4; } @Test public void testLargestDegreeFirstColoring() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(1, 3); g.addEdge(1, 4); g.addEdge(1, 5); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(3, 5); Coloring coloring = new LargestDegreeFirstColoring<>(g).getColoring(); assertEquals(3, coloring.getNumberColors()); Map colors = coloring.getColors(); assertEquals(0, colors.get(1).intValue()); assertEquals(2, colors.get(2).intValue()); assertEquals(1, colors.get(3).intValue()); assertEquals(2, colors.get(4).intValue()); assertEquals(2, colors.get(5).intValue()); } @Test public void testLargestDegreeFirstColoring1() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(1, 3); g.addEdge(1, 4); g.addEdge(1, 5); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(3, 5); g.addEdge(3, 6); g.addEdge(5, 6); Coloring coloring = new LargestDegreeFirstColoring<>(g).getColoring(); assertEquals(3, coloring.getNumberColors()); Map colors = coloring.getColors(); assertEquals(1, colors.get(1).intValue()); assertEquals(2, colors.get(2).intValue()); assertEquals(0, colors.get(3).intValue()); assertEquals(2, colors.get(4).intValue()); assertEquals(2, colors.get(5).intValue()); assertEquals(1, colors.get(6).intValue()); } @Test public void testLargestDegreeFirstColoringNonSimple() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(2, 3); g.addEdge(4, 5); g.addEdge(4, 6); for (int i = 0; i < 20000; i++) { g.addEdge(5, 6); } Coloring coloring = new LargestDegreeFirstColoring<>(g).getColoring(); assertEquals(3, coloring.getNumberColors()); Map colors = coloring.getColors(); assertEquals(0, colors.get(1).intValue()); assertEquals(0, colors.get(2).intValue()); assertEquals(1, colors.get(3).intValue()); assertEquals(2, colors.get(4).intValue()); assertEquals(0, colors.get(5).intValue()); assertEquals(1, colors.get(6).intValue()); } @Test public void testLargestDegreeFirstColoringSimple() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(2, 3); g.addEdge(4, 5); g.addEdge(4, 6); g.addEdge(5, 6); g.addEdge(5, 3); Coloring coloring = new LargestDegreeFirstColoring<>(g).getColoring(); assertEquals(3, coloring.getNumberColors()); Map colors = coloring.getColors(); assertEquals(0, colors.get(1).intValue()); assertEquals(0, colors.get(2).intValue()); assertEquals(1, colors.get(3).intValue()); assertEquals(1, colors.get(4).intValue()); assertEquals(0, colors.get(5).intValue()); assertEquals(2, colors.get(6).intValue()); } } RandomGreedyColoring2Test.java000066400000000000000000000023341402514743400335660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * Coloring tests * * @author Dimitrios Michail */ public class RandomGreedyColoring2Test extends BaseColoringTest { final long seed = 15; @Override protected VertexColoringAlgorithm getAlgorithm(Graph graph) { return new RandomGreedyColoring<>(graph, new Random(seed)); } @Override protected int getExpectedResultOnDSaturNonOptimalGraph() { return 4; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/RandomGreedyColoringTest.java000066400000000000000000000023331402514743400335620ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * Coloring tests * * @author Dimitrios Michail */ public class RandomGreedyColoringTest extends BaseColoringTest { final long seed = 13; @Override protected VertexColoringAlgorithm getAlgorithm(Graph graph) { return new RandomGreedyColoring<>(graph, new Random(seed)); } @Override protected int getExpectedResultOnDSaturNonOptimalGraph() { return 3; } } SaturationDegreeColoringTest.java000066400000000000000000000041741402514743400343750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.VertexColoringAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Coloring tests * * @author Dimitrios Michail */ public class SaturationDegreeColoringTest extends BaseColoringTest { @Override protected VertexColoringAlgorithm getAlgorithm(Graph graph) { return new SaturationDegreeColoring<>(graph); } @Override protected int getExpectedResultOnDSaturNonOptimalGraph() { return 4; } @Test public void testSaturationDegree() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(2, 3); g.addEdge(4, 5); g.addEdge(4, 6); g.addEdge(5, 6); g.addEdge(5, 3); Coloring coloring = new SaturationDegreeColoring<>(g).getColoring(); assertEquals(3, coloring.getNumberColors()); Map colors = coloring.getColors(); assertEquals(0, colors.get(1).intValue()); assertEquals(0, colors.get(2).intValue()); assertEquals(1, colors.get(3).intValue()); assertEquals(1, colors.get(4).intValue()); assertEquals(0, colors.get(5).intValue()); assertEquals(2, colors.get(6).intValue()); } } SmallestDegreeLastColoringTest.java000066400000000000000000000055651402514743400346610ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/color/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.color; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.VertexColoringAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Coloring tests * * @author Dimitrios Michail */ public class SmallestDegreeLastColoringTest extends BaseColoringTest { @Override protected VertexColoringAlgorithm getAlgorithm(Graph graph) { return new SmallestDegreeLastColoring<>(graph); } @Override protected int getExpectedResultOnDSaturNonOptimalGraph() { return 3; } @Test public void testSmallestDegreeLastColoring() { Graph g = createGraph1(); Coloring coloring = new SmallestDegreeLastColoring<>(g).getColoring(); assertEquals(3, coloring.getNumberColors()); Map colors = coloring.getColors(); assertEquals(2, colors.get(1).intValue()); assertEquals(0, colors.get(2).intValue()); assertEquals(1, colors.get(3).intValue()); assertEquals(0, colors.get(4).intValue()); assertEquals(0, colors.get(5).intValue()); } @Test public void testSmallestDegreeLastColoringNonSimple() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(2, 3); g.addEdge(4, 5); g.addEdge(4, 6); g.addEdge(5, 6); g.addEdge(5, 6); g.addEdge(5, 6); g.addEdge(5, 6); g.addEdge(5, 6); g.addEdge(5, 6); g.addEdge(5, 6); g.addEdge(5, 6); Coloring coloring = new SmallestDegreeLastColoring<>(g).getColoring(); assertEquals(3, coloring.getNumberColors()); Map colors = coloring.getColors(); assertEquals(0, colors.get(1).intValue()); assertEquals(1, colors.get(2).intValue()); assertEquals(0, colors.get(3).intValue()); assertEquals(2, colors.get(4).intValue()); assertEquals(1, colors.get(5).intValue()); assertEquals(0, colors.get(6).intValue()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/000077500000000000000000000000001402514743400274015ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/BiconnectedGraph.java000066400000000000000000000031601402514743400334430ustar00rootroot00000000000000/* * (C) Copyright 2007-2021, by France Telecom and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.graph.*; /** */ public class BiconnectedGraph extends SimpleGraph { // ~ Static fields/initializers --------------------------------------------- /** */ private static final long serialVersionUID = 6007460525580983710L; // ~ Constructors ----------------------------------------------------------- public BiconnectedGraph() { super(DefaultEdge.class); addVertices(); addEdges(); } // ~ Methods ---------------------------------------------------------------- private void addEdges() { addEdge("0", "1"); addEdge("1", "2"); addEdge("2", "3"); addEdge("3", "4"); addEdge("4", "5"); addEdge("5", "0"); } private void addVertices() { addVertex("0"); addVertex("1"); addVertex("2"); addVertex("3"); addVertex("4"); addVertex("5"); } } BiconnectivityInspectorTest.java000066400000000000000000000251161402514743400357120ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2007-2021, by France Telecom and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * @author Joris Kinable */ public class BiconnectivityInspectorTest { @Test public void testBiconnected() { BiconnectedGraph graph = new BiconnectedGraph(); BiconnectivityInspector inspector = new BiconnectivityInspector<>(graph); assertTrue(inspector.isBiconnected()); assertEquals(0, inspector.getCutpoints().size()); } @Test public void testLinearGraph() { int nbVertices = 5; Graph graph = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); LinearGraphGenerator generator = new LinearGraphGenerator<>(nbVertices); generator.generateGraph(graph); BiconnectivityInspector inspector = new BiconnectivityInspector<>(graph); assertEquals(nbVertices - 2, inspector.getCutpoints().size()); } @Test public void testNotBiconnected() { NotBiconnectedGraph graph = new NotBiconnectedGraph(); BiconnectivityInspector inspector = new BiconnectivityInspector<>(graph); assertEquals(2, inspector.getCutpoints().size()); } @Test public void testBorderCases() { Graph g = new SimpleGraph<>(DefaultEdge.class); assertFalse(new BiconnectivityInspector<>(g).isBiconnected()); // empty graph g.addVertex(0); assertFalse(new BiconnectivityInspector<>(g).isBiconnected()); // graph on 1 vertex g.addVertex(1); assertFalse(new BiconnectivityInspector<>(g).isBiconnected()); // graph on 2 vertices // without edges g.addEdge(0, 1); assertTrue(new BiconnectivityInspector<>(g).isBiconnected()); // graph with one edge } @Test public void testConnectedComponents1() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(4, 5); BiconnectivityInspector inspector = new BiconnectivityInspector<>(g); assertEquals(2, inspector.getConnectedComponents().size()); assertFalse(inspector.isConnected()); Graph g1 = new AsSubgraph<>(g, Set.of(1, 2, 3)); Graph g2 = new AsSubgraph<>(g, Set.of(4, 5)); for (Integer v : g1.vertexSet()) assertEquals(g1, inspector.getConnectedComponent(v)); for (Integer v : g2.vertexSet()) assertEquals(g2, inspector.getConnectedComponent(v)); } @Test public void testWikiGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)); int[][] edges = { { 1, 3 }, { 1, 2 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 }, { 7, 9 }, { 9, 10 }, { 9, 11 }, { 11, 12 }, { 12, 13 }, { 13, 14 }, { 12, 14 }, { 7, 14 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); BiconnectivityInspector inspector = new BiconnectivityInspector<>(g); assertTrue(inspector.isConnected()); Set expectedCutpoints = Set.of(4, 5, 6, 7, 9); assertEquals(expectedCutpoints, inspector.getCutpoints()); Set expectedBridges = new HashSet<>(); expectedBridges.add(g.getEdge(4, 5)); expectedBridges.add(g.getEdge(5, 6)); expectedBridges.add(g.getEdge(6, 7)); expectedBridges.add(g.getEdge(7, 8)); expectedBridges.add(g.getEdge(9, 10)); assertEquals(expectedBridges, inspector.getBridges()); // Check vertex to block mapping List> blocks = new ArrayList<>(); blocks.add(new AsSubgraph<>(g, Set.of(1, 2, 3, 4))); // 0 blocks.add(new AsSubgraph<>(g, Set.of(4, 5))); // 1 blocks.add(new AsSubgraph<>(g, Set.of(5, 6))); // 2 blocks.add(new AsSubgraph<>(g, Set.of(6, 7))); // 3 blocks.add(new AsSubgraph<>(g, Set.of(7, 8))); // 4 blocks.add(new AsSubgraph<>(g, Set.of(9, 10))); // 5 blocks.add(new AsSubgraph<>(g, Set.of(7, 9, 11, 12, 13, 14))); // 6 for (int v : Arrays.asList(1, 2, 3)) assertEquals(Collections.singleton(blocks.get(0)), inspector.getBlocks(v)); assertEquals(Collections.singleton(blocks.get(4)), inspector.getBlocks(8)); for (int v : Arrays.asList(11, 12, 13, 14)) { assertEquals(Collections.singleton(blocks.get(6)), inspector.getBlocks(v)); } // cutpoints reside in multiple blocks assertEquals(Set.of(blocks.get(0), blocks.get(1)), inspector.getBlocks(4)); assertEquals(Set.of(blocks.get(1), blocks.get(2)), inspector.getBlocks(5)); assertEquals(Set.of(blocks.get(2), blocks.get(3)), inspector.getBlocks(6)); assertEquals(Set.of(blocks.get(3), blocks.get(4), blocks.get(6)), inspector.getBlocks(7)); assertEquals(Set.of(blocks.get(5), blocks.get(6)), inspector.getBlocks(9)); } @Test public void testMultiGraph() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2)); DefaultEdge bridge = g.addEdge(0, 1); g.addEdge(1, 1); g.addEdge(1, 2); g.addEdge(1, 2); BiconnectivityInspector inspector = new BiconnectivityInspector<>(g); assertEquals(Collections.singleton(1), inspector.getCutpoints()); assertEquals(Collections.singleton(bridge), inspector.getBridges()); List> blocks = new ArrayList<>(); blocks.add(new AsSubgraph<>(g, Set.of(0, 1))); // 0 blocks.add(new AsSubgraph<>(g, Set.of(1, 2))); // 1 assertEquals(new HashSet<>(blocks), inspector.getBlocks()); } @Test public void testMultiGraph2() { Graph g = new Multigraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)); int[][] edges = { { 1, 3 }, { 1, 2 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 }, { 7, 9 }, { 9, 10 }, { 9, 11 }, { 11, 12 }, { 12, 13 }, { 13, 14 }, { 12, 14 }, { 7, 14 }, { 1, 3 }, { 1, 2 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 6, 7 }, { 7, 8 }, { 7, 9 }, { 9, 10 }, { 9, 11 }, { 11, 12 }, { 12, 13 }, { 13, 14 }, { 12, 14 }, { 7, 14 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); BiconnectivityInspector inspector = new BiconnectivityInspector<>(g); assertTrue(inspector.isConnected()); Set expectedCutpoints = Set.of(4, 5, 6, 7, 9); assertEquals(expectedCutpoints, inspector.getCutpoints()); assertEquals(Collections.emptySet(), inspector.getBridges()); // Check vertex to block mapping List> blocks = new ArrayList<>(); blocks.add(new AsSubgraph<>(g, Set.of(1, 2, 3, 4))); // 0 blocks.add(new AsSubgraph<>(g, Set.of(4, 5))); // 1 blocks.add(new AsSubgraph<>(g, Set.of(5, 6))); // 2 blocks.add(new AsSubgraph<>(g, Set.of(6, 7))); // 3 blocks.add(new AsSubgraph<>(g, Set.of(7, 8))); // 4 blocks.add(new AsSubgraph<>(g, Set.of(9, 10))); // 5 blocks.add(new AsSubgraph<>(g, Set.of(7, 9, 11, 12, 13, 14))); // 6 for (int v : Arrays.asList(1, 2, 3)) assertEquals(Collections.singleton(blocks.get(0)), inspector.getBlocks(v)); assertEquals(Collections.singleton(blocks.get(4)), inspector.getBlocks(8)); for (int v : Arrays.asList(11, 12, 13, 14)) { assertEquals(Collections.singleton(blocks.get(6)), inspector.getBlocks(v)); } // cutpoints reside in multiple blocks assertEquals(Set.of(blocks.get(0), blocks.get(1)), inspector.getBlocks(4)); assertEquals(Set.of(blocks.get(1), blocks.get(2)), inspector.getBlocks(5)); assertEquals(Set.of(blocks.get(2), blocks.get(3)), inspector.getBlocks(6)); assertEquals(Set.of(blocks.get(3), blocks.get(4), blocks.get(6)), inspector.getBlocks(7)); assertEquals(Set.of(blocks.get(5), blocks.get(6)), inspector.getBlocks(9)); } @Test public void testGithubIssueBug798() { Graph g = GraphTypeBuilder .undirected().allowingSelfLoops(false).allowingMultipleEdges(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); g.addVertex(0); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(0, 2); DefaultEdge e03 = g.addEdge(0, 3); BiconnectivityInspector bi = new BiconnectivityInspector<>(g); assertFalse(bi.isBiconnected()); Set cutpoints = bi.getCutpoints(); assertTrue(cutpoints.size() == 1); assertTrue(cutpoints.contains(0)); Set bridges = bi.getBridges(); assertTrue(bridges.size() == 1); assertTrue(bridges.contains(e03)); assertTrue(bi.getBlocks(0).size() == 2); assertTrue(bi.getBlocks(1).size() == 1); assertTrue(bi.getBlocks(2).size() == 1); assertTrue(bi.getBlocks(3).size() == 1); assertTrue(bi.getBlocks().size() == 2); } } BlockCutpointGraphTest.java000066400000000000000000000065441402514743400346000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Joris Kinable */ public class BlockCutpointGraphTest { @Test public void randomGraphTest() { GnpRandomGraphGenerator gen = new GnpRandomGraphGenerator<>(50, .5, 0); for (int i = 0; i < 5; i++) { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); this.validateGraph(g, new BlockCutpointGraph<>(g)); } } @Test public void randomDirectedGraphTest() { GnpRandomGraphGenerator gen = new GnpRandomGraphGenerator<>(50, .5, 0); for (int i = 0; i < 5; i++) { Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); this.validateGraph(g, new BlockCutpointGraph<>(g)); } } private void validateGraph(Graph graph, BlockCutpointGraph bcGraph) { assertTrue(GraphTests.isBipartite(bcGraph)); assertTrue(GraphTests.isForest(bcGraph)); assertEquals( new ConnectivityInspector<>(graph).connectedSets().size(), new ConnectivityInspector<>(bcGraph).connectedSets().size()); BiconnectivityInspector inspector = new BiconnectivityInspector<>(graph); Set> blocks = inspector.getBlocks(); Set cutpoints = inspector.getCutpoints(); assertEquals(blocks.size() + cutpoints.size(), bcGraph.vertexSet().size()); // assert that every cutpoint is contained in the block it is attached to for (V cutpoint : cutpoints) { Graph cpblock = bcGraph.getBlock(cutpoint); assertEquals(1, cpblock.vertexSet().size()); assertTrue(cpblock.containsVertex(cutpoint)); for (Graph block : Graphs.neighborListOf(bcGraph, cpblock)) assertTrue(block.containsVertex(cutpoint)); } // assert that the edge set is complete, i.e. there are edges between a block and all its // cutpoints for (Graph block : bcGraph.getBlocks()) { long nrCutpointInBlock = block.vertexSet().stream().filter(cutpoints::contains).count(); assertEquals(nrCutpointInBlock, bcGraph.degreeOf(block)); } } } ConnectivityInspectorTest.java000066400000000000000000000074211402514743400353760ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * . * * @author Barak Naveh */ public class ConnectivityInspectorTest { // ~ Static fields/initializers --------------------------------------------- private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String V4 = "v4"; // ~ Instance fields -------------------------------------------------------- // DefaultEdge e1; DefaultEdge e2; DefaultEdge e3; DefaultEdge e3_b; DefaultEdge u; // ~ Methods ---------------------------------------------------------------- /** * . * * @return a graph */ public Pseudograph create() { Pseudograph g = new Pseudograph<>(DefaultEdge.class); assertEquals(0, g.vertexSet().size()); g.addVertex(V1); assertEquals(1, g.vertexSet().size()); g.addVertex(V2); assertEquals(2, g.vertexSet().size()); g.addVertex(V3); assertEquals(3, g.vertexSet().size()); g.addVertex(V4); assertEquals(4, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); e1 = g.addEdge(V1, V2); assertEquals(1, g.edgeSet().size()); e2 = g.addEdge(V2, V3); assertEquals(2, g.edgeSet().size()); e3 = g.addEdge(V3, V1); assertEquals(3, g.edgeSet().size()); e3_b = g.addEdge(V3, V1); assertEquals(4, g.edgeSet().size()); assertNotNull(e3_b); u = g.addEdge(V1, V1); assertEquals(5, g.edgeSet().size()); u = g.addEdge(V1, V1); assertEquals(6, g.edgeSet().size()); return g; } /** * . */ @Test public void testDirectedGraph() { ListenableGraph g = new DefaultListenableGraph<>(new DefaultDirectedGraph<>(DefaultEdge.class)); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addEdge(V1, V2); ConnectivityInspector inspector = new ConnectivityInspector<>(g); g.addGraphListener(inspector); assertEquals(false, inspector.isConnected()); g.addEdge(V1, V3); assertEquals(true, inspector.isConnected()); } /** * . */ @Test public void testIsGraphConnected() { Pseudograph g = create(); ConnectivityInspector inspector = new ConnectivityInspector<>(g); assertEquals(false, inspector.isConnected()); g.removeVertex(V4); inspector = new ConnectivityInspector<>(g); assertEquals(true, inspector.isConnected()); g.removeVertex(V1); assertEquals(1, g.edgeSet().size()); g.removeEdge(e2); g.addEdge(V2, V2); assertEquals(1, g.edgeSet().size()); inspector = new ConnectivityInspector<>(g); assertEquals(false, inspector.isConnected()); } } NotBiconnectedGraph.java000066400000000000000000000031661402514743400340530ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2007-2021, by France Telecom and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.graph.*; /** */ public class NotBiconnectedGraph extends SimpleGraph { // ~ Static fields/initializers --------------------------------------------- /** */ private static final long serialVersionUID = 6518961051694377584L; // ~ Constructors ----------------------------------------------------------- public NotBiconnectedGraph() { super(DefaultEdge.class); addVertices(); addEdges(); } // ~ Methods ---------------------------------------------------------------- private void addEdges() { addEdge("0", "2"); addEdge("0", "3"); addEdge("3", "1"); addEdge("1", "4"); addEdge("4", "5"); addEdge("5", "3"); } private void addVertices() { addVertex("0"); addVertex("1"); addVertex("2"); addVertex("3"); addVertex("4"); addVertex("5"); } } StrongConnectivityAlgorithmTest.java000066400000000000000000000344421402514743400365560ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2003-2021, by Sarah Komla-Ebri and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.runner.*; import org.junit.runners.*; import org.junit.runners.Parameterized.*; import java.util.*; import java.util.function.*; import java.util.stream.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** * Test cases for the GabowStrongConnectivityInspector. Tests are identical to the tests for the * KosarajuStrongConnectivityInspector as provided in the ConnectivityInspectorTest class. * * @author Sarah Komla-Ebri * @author Joris Kinable * @author Hannes Wellmann */ @RunWith(Parameterized.class) public class StrongConnectivityAlgorithmTest { // ~ Static fields/initializers --------------------------------------------- private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String V4 = "v4"; private static final String V5 = "v5"; private static final String V6 = "v6"; private static final String V7 = "v7"; private static final String V8 = "v8"; private static final String V9 = "v9"; private static final String V10 = "v10"; private static final String V11 = "v11"; private static final List VERTICES = List.of(V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11); // ~ Instance fields -------------------------------------------------------- @Parameter() public String name; @Parameter(1) public Function, StrongConnectivityAlgorithm> algorithmFactory; @Parameters(name = "{0}") @SuppressWarnings("unchecked") public static List getAlgorithmFactory() { return List .of( new Object[] { GabowStrongConnectivityInspector.class.getSimpleName(), (Function, ?>) GabowStrongConnectivityInspector::new }, new Object[] { KosarajuStrongConnectivityInspector.class.getSimpleName(), (Function, ?>) KosarajuStrongConnectivityInspector::new }); } @Test public void testStronglyConnected1() { Graph g = createDirectedGraphWithVertices(4); g.addEdge(V1, V2); g.addEdge(V2, V1); // strongly connected g.addEdge(V3, V4); // only weakly connected assertStronglyConnectedSets(g, Set.of(V1, V2), Set.of(V3), Set.of(V4)); } @Test public void testStronglyConnected2() { Graph g = createDirectedGraphWithVertices(4); g.addEdge(V1, V2); g.addEdge(V2, V1); // strongly connected g.addEdge(V4, V3); // only weakly connected g.addEdge(V3, V2); // only weakly connected assertStronglyConnectedSets(g, Set.of(V1, V2), Set.of(V3), Set.of(V4)); } @Test public void testStronglyConnected3() { Graph g = createDirectedGraphWithVertices(4); g.addEdge(V1, V2); g.addEdge(V2, V3); g.addEdge(V3, V1); // strongly connected g.addEdge(V1, V4); g.addEdge(V2, V4); g.addEdge(V3, V4); // weakly connected assertStronglyConnectedSets(g, Set.of(V1, V2, V3), Set.of(V4)); } @Test public void testStronglyConnected4() { Graph graph = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createStringSupplier(), false); new RingGraphGenerator(3).generateGraph(graph); assertStronglyConnectedSets(graph, Set.of(0, 1, 2)); } @Test public void testStronglyConnected5() { // example from paper "Path-based depth-first search for strong and biconnected components" // of Harold N. Gabow (2000) Graph graph = createDirectedGraphWithVertices(6); graph.addEdge(V1, V2); graph.addEdge(V1, V3); graph.addEdge(V2, V3); graph.addEdge(V2, V4); graph.addEdge(V4, V3); graph.addEdge(V4, V5); graph.addEdge(V5, V2); graph.addEdge(V5, V6); graph.addEdge(V6, V3); graph.addEdge(V6, V4); assertStronglyConnectedSets(graph, Set.of(V1), Set.of(V2, V4, V5, V6), Set.of(V3)); } @Test public void testStronglyConnected6() { // example from // https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm Graph graph = createDirectedGraphWithVertices(8); graph.addEdge(V1, V5); graph.addEdge(V2, V1); graph.addEdge(V3, V2); graph.addEdge(V3, V4); graph.addEdge(V4, V3); graph.addEdge(V5, V2); graph.addEdge(V6, V2); graph.addEdge(V6, V5); graph.addEdge(V6, V7); graph.addEdge(V7, V3); graph.addEdge(V7, V6); graph.addEdge(V8, V4); graph.addEdge(V8, V7); assertStronglyConnectedSets( graph, Set.of(V1, V2, V5), Set.of(V3, V4), Set.of(V6, V7), Set.of(V8)); } @Test public void testStronglyConnected7() { Graph graph = createDirectedGraphWithVertices(5); graph.addEdge(V1, V2); graph.addEdge(V2, V3); graph.addEdge(V3, V4); graph.addEdge(V3, V5); graph.addEdge(V4, V1); graph.addEdge(V5, V3); assertStronglyConnectedSets(graph, Set.of(V1, V2, V3, V4, V5)); } @Test public void testStronglyConnected8() { Graph graph = createDirectedGraphWithVertices(11); graph.addEdge(V1, V2); graph.addEdge(V1, V4); graph.addEdge(V2, V3); graph.addEdge(V2, V5); graph.addEdge(V3, V1); graph.addEdge(V3, V7); graph.addEdge(V4, V3); graph.addEdge(V5, V6); graph.addEdge(V5, V7); graph.addEdge(V6, V7); graph.addEdge(V6, V8); graph.addEdge(V6, V9); graph.addEdge(V6, V10); graph.addEdge(V7, V5); graph.addEdge(V8, V10); graph.addEdge(V9, V10); graph.addEdge(V10, V9); assertStronglyConnectedSets( graph, Set.of(V1, V2, V3, V4), Set.of(V5, V6, V7), Set.of(V8), Set.of(V9, V10), Set.of(V11)); } @Test @SuppressWarnings("unchecked") public void testCondensation() { Graph g = createDirectedGraphWithVertices(5); g.addEdge(V1, V2); g.addEdge(V2, V1); // strongly connected g.addEdge(V3, V4); // only weakly connected g.addEdge(V5, V4); // only weakly connected StrongConnectivityAlgorithm inspector = getStrongConnectivityInspector(g); Graph, DefaultEdge> condensation = inspector.getCondensation(); // assert that the condensation is as expected assertThat( condensation.vertexSet().stream().map(Graph::vertexSet).collect(Collectors.toList()), containsInAnyOrder(Set.of(V1, V2), Set.of(V3), Set.of(V4), Set.of(V5))); Graph g1 = getOnlyGraphWithVertices(condensation, Set.of(V1, V2)); Graph g2 = getOnlyGraphWithVertices(condensation, Set.of(V3)); Graph g3 = getOnlyGraphWithVertices(condensation, Set.of(V4)); Graph g4 = getOnlyGraphWithVertices(condensation, Set.of(V5)); // Check edges inside condensed graphs assertThat(g1.edgeSet(), is(equalTo(Set.of(g.getEdge(V1, V2), g.getEdge(V2, V1))))); assertThat(g2.edgeSet(), empty()); assertThat(g3.edgeSet(), empty()); assertThat(g4.edgeSet(), empty()); // check edges between SCCs Set interSCCEdges = Set.of(condensation.getEdge(g2, g3), condensation.getEdge(g4, g3)); assertThat(condensation.edgeSet(), is(equalTo(interSCCEdges))); } @Test @SuppressWarnings("unchecked") public void testCondensation2() { Graph g = createDirectedGraphWithVertices(4); g.addEdge(V1, V2); g.addEdge(V2, V1); g.addEdge(V3, V4); g.addEdge(V4, V3); g.addEdge(V1, V3); g.addEdge(V2, V4); StrongConnectivityAlgorithm inspector = getStrongConnectivityInspector(g); Graph, DefaultEdge> condensation = inspector.getCondensation(); // assert that the condensation is as expected assertThat( condensation.vertexSet().stream().map(Graph::vertexSet).collect(Collectors.toList()), containsInAnyOrder(Set.of(V1, V2), Set.of(V3, V4))); Graph g1 = getOnlyGraphWithVertices(condensation, Set.of(V1, V2)); Graph g2 = getOnlyGraphWithVertices(condensation, Set.of(V3, V4)); // Check edges inside condensed graphs assertThat(g1.edgeSet(), is(equalTo(Set.of(g.getEdge(V1, V2), g.getEdge(V2, V1))))); assertThat(g2.edgeSet(), is(equalTo(Set.of(g.getEdge(V3, V4), g.getEdge(V4, V3))))); // check edges between SCCs assertThat(condensation.edgeSet(), is(equalTo(Set.of(condensation.getEdge(g1, g2))))); } // --- utility methods --- private static Graph createDirectedGraphWithVertices(int vertexCount) { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); VERTICES.subList(0, vertexCount).forEach(g::addVertex); return g; } @SafeVarargs private void assertStronglyConnectedSets(Graph graph, Set... expectedSets) { // Test the SCC algorithm for each vertex of the graph as start vertex int vertices = graph.vertexSet().size(); for (int i = 0; i < vertices; i++) { Graph g = createRotatedGraphCopy(graph, i); Set[] expectedVertices = createdRotatedSets(expectedSets, i, graph); StrongConnectivityAlgorithm inspector = getStrongConnectivityInspector(g); assertThat(inspector.stronglyConnectedSets(), containsInAnyOrder(expectedVertices)); Set> actualSets = new HashSet<>(); for (Graph sg : inspector.getStronglyConnectedComponents()) { actualSets.add(sg.vertexSet()); StrongConnectivityAlgorithm ci = getStrongConnectivityInspector(sg); assertTrue(ci.isStronglyConnected()); } assertThat(actualSets, containsInAnyOrder(expectedVertices)); } } private static Graph createRotatedGraphCopy(Graph graph, int shift) { // Because the algorithm implementations don't use linked Maps it is not sufficient to just // add vertices in a different order to achieve different start vertices. Instead the // vertices are added to a graph-copy in the same order every-time but they are logically // shifted by the given shift-length. // This logical shift is achieved by shifting the touching vertices of each edges. // So if the shift is 1 a edge from v1 to v2 is now going from v2 to v3 and so on. List vertexList = new ArrayList<>(graph.vertexSet()); Graph g = GraphTypeBuilder.forGraph(graph).buildGraph(); vertexList.forEach(g::addVertex); // add all vertices for (E edge : graph.edgeSet()) { // Apply shift to edges V source = graph.getEdgeSource(edge); source = getShiftedCounterpart(source, shift, vertexList); V target = graph.getEdgeTarget(edge); target = getShiftedCounterpart(target, shift, vertexList); g.addEdge(source, target); } return g; } private static V getShiftedCounterpart(V v, int shift, List vertexList) { int index = vertexList.indexOf(v); int shiftedIndex = (index + shift) % vertexList.size(); return vertexList.get(shiftedIndex); } private static Set[] createdRotatedSets(Set[] sets, int shift, Graph graph) { List vertexList = new ArrayList<>(graph.vertexSet()); List> rotatedSets = new ArrayList<>(); for (Set set : sets) { Set newSet = CollectionUtil.newHashSetWithExpectedSize(set.size()); for (V v : set) { v = getShiftedCounterpart(v, shift, vertexList); newSet.add(v); } rotatedSets.add(newSet); } @SuppressWarnings("unchecked") Set[] rotatedSet = (Set[]) rotatedSets.toArray(i -> new Set[i]); return rotatedSet; } private static Graph getOnlyGraphWithVertices( Graph, DefaultEdge> graph, Set vertices) { return getOnlyMatch(graph.vertexSet(), g -> g.vertexSet().equals(vertices)); } private static T getOnlyMatch(Collection c, Predicate p) { List matches = c.stream().filter(p).collect(Collectors.toList()); if (matches.size() == 1) { return matches.get(0); } else { fail("Not exactly one match: " + matches); return null; // never executed, fail() throws } } @SuppressWarnings("unchecked") private StrongConnectivityAlgorithm getStrongConnectivityInspector(Graph g) { return (StrongConnectivityAlgorithm) algorithmFactory.apply(g); } } TreeDynamicConnectivityTest.java000066400000000000000000000117441402514743400356370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/connectivity/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.connectivity; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * Tests for {@link TreeDynamicConnectivity} * * @author Timofey Chudakov */ public class TreeDynamicConnectivityTest { private static final Random RANDOM = new Random(17L); @Test public void testTreeDynamicConnectivity_addNode_removeNode() { for (int treeSize = 1; treeSize < 50; treeSize++) { TreeDynamicConnectivity connectivity = new TreeDynamicConnectivity<>(); for (int node = 0; node < treeSize; node++) { assertFalse(connectivity.contains(node)); assertTrue(connectivity.add(node)); assertTrue(connectivity.contains(node)); assertFalse(connectivity.add(node)); assertTrue(connectivity.remove(node)); assertFalse(connectivity.contains(node)); assertFalse(connectivity.remove(node)); } } } @Test public void testTreeDynamicConnectivity_2Trees() { for (int firstTreeSize = 1; firstTreeSize < 50; firstTreeSize++) { for (int secondTreeSize = 1; secondTreeSize < 50; secondTreeSize++) { // System.out.printf("First size = %d, second size = %d\n", firstTreeSize, // secondTreeSize); Graph firstTree = generateTree(firstTreeSize); Graph secondTree = generateTree(secondTreeSize, firstTreeSize); TreeDynamicConnectivity connectivity = new TreeDynamicConnectivity<>(); connectTree(firstTree, connectivity); connectTree(secondTree, connectivity); for (int v1 : firstTree.vertexSet()) { for (int v2 : secondTree.vertexSet()) { assertFalse(connectivity.connected(v1, v2)); assertTrue(connectivity.link(v1, v2)); assertTrue(connectivity.connected(v1, v2)); assertFalse(connectivity.link(v1, v2)); assertTrue(connectivity.connected(v1, v2)); assertTrue(connectivity.cut(v1, v2)); assertFalse(connectivity.connected(v1, v2)); assertFalse(connectivity.cut(v1, v2)); assertFalse(connectivity.connected(v1, v2)); } } destroyTree(firstTree, connectivity); destroyTree(secondTree, connectivity); } } } private void destroyTree( Graph graph, TreeDynamicConnectivity connectivity) { for (int v : graph.vertexSet()) { assertTrue(connectivity.contains(v)); assertTrue(connectivity.remove(v)); assertFalse(connectivity.contains(v)); } } private void connectTree( Graph graph, TreeDynamicConnectivity connectivity) { for (Integer v : graph.vertexSet()) { assertFalse(connectivity.contains(v)); assertTrue(connectivity.add(v)); assertTrue(connectivity.contains(v)); } for (DefaultEdge e : graph.edgeSet()) { int source = graph.getEdgeSource(e), target = graph.getEdgeTarget(e); assertFalse(connectivity.connected(source, target)); assertTrue(connectivity.link(source, target)); assertTrue(connectivity.connected(source, target)); } } private Graph generateTree(int nodeNum) { return generateTree(nodeNum, 0); } private Graph generateTree(int nodeNum, int start) { Graph tree = new DefaultUndirectedGraph<>( SupplierUtil.createIntegerSupplier(start), SupplierUtil.createDefaultEdgeSupplier(), false); BarabasiAlbertForestGenerator gen = new BarabasiAlbertForestGenerator<>(1, nodeNum, RANDOM); gen.generateGraph(tree); return tree; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/000077500000000000000000000000001402514743400257625ustar00rootroot00000000000000AhujaOrlinSharmaCyclicExchangeLocalAugmentationTest.java000066400000000000000000000454231402514743400407270ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/* * (C) Copyright 2018-2021, by Christoph Grüne and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; /** * Unit tests for {@link AhujaOrlinSharmaCyclicExchangeLocalAugmentation}. * * @author Christoph Grüne */ public class AhujaOrlinSharmaCyclicExchangeLocalAugmentationTest { @Test public void testImprovementGraph1() { LinkedList cycle = new LinkedList<>(); cycle.add(0); cycle.add(4); cycle.add(0); Map labels = new HashMap<>(); for (int i = 0; i < 3; ++i) { labels.put(i, 1); labels.put(i + 3, 2); labels.put(i + 6, 3); } Graph graph = generateImprovementGraphForPartitionProblem(labels, 1); graph.setEdgeWeight(graph.getEdge(0, 4), -5); graph.setEdgeWeight(graph.getEdge(4, 8), -5); int lengthBound = 2; GraphWalk calculatedCycle = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, false) .getLocalAugmentationCycle(); assertEquals(cycle, calculatedCycle.getVertexList()); assertEquals(-4, calculatedCycle.getWeight(), 0.0000001); } @Test public void testImprovementGraph2() { LinkedList cycle = new LinkedList<>(); for (int i = 0; i <= 20; i += 5) { cycle.add(i); } cycle.add(0); Map labels = new HashMap<>(); for (int i = 0; i < 5; ++i) { labels.put(i, 1); labels.put(i + 5, 2); labels.put(i + 10, 3); labels.put(i + 15, 4); labels.put(i + 20, 5); } Graph graph = generateImprovementGraphForPartitionProblem(labels, 4.9); graph.setEdgeWeight(graph.getEdge(0, 5), -1); graph.setEdgeWeight(graph.getEdge(5, 10), -1); graph.setEdgeWeight(graph.getEdge(10, 15), -1); graph.setEdgeWeight(graph.getEdge(15, 20), -1); graph.setEdgeWeight(graph.getEdge(20, 0), -1); int lengthBound = 5; GraphWalk calculatedCycle = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, false) .getLocalAugmentationCycle(); assertEquals(cycle, calculatedCycle.getVertexList()); assertEquals(-5, calculatedCycle.getWeight(), 0.0000001); } @Test public void testImprovementGraph3() { LinkedList cycle = new LinkedList<>(); for (int i = 0; i <= 20; i += 5) { cycle.add(i); } cycle.add(0); Map labels = new HashMap<>(); for (int i = 0; i < 5; ++i) { labels.put(i, 1); labels.put(i + 5, 2); labels.put(i + 10, 3); labels.put(i + 15, 4); labels.put(i + 20, 5); } Graph graph = generateImprovementGraphForPartitionProblem(labels, 3.9); graph.setEdgeWeight(graph.getEdge(0, 5), -1); graph.setEdgeWeight(graph.getEdge(5, 10), -1); graph.setEdgeWeight(graph.getEdge(10, 15), -1); graph.setEdgeWeight(graph.getEdge(15, 20), -1); int lengthBound = 5; GraphWalk calculatedCycle = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, false) .getLocalAugmentationCycle(); assertEquals(cycle, calculatedCycle.getVertexList()); assertEquals(3.9 - 4, calculatedCycle.getWeight(), 0.0000001); } @Test public void testImprovementGraph4() { LinkedList cycle1 = new LinkedList<>(); cycle1.add(1); cycle1.add(4); cycle1.add(5); cycle1.add(0); cycle1.add(1); LinkedList cycle2 = new LinkedList<>(); cycle2.add(3); cycle2.add(4); cycle2.add(5); cycle2.add(0); cycle2.add(1); cycle2.add(2); cycle2.add(3); Map labels = new HashMap<>(); labels.put(0, 0); labels.put(1, 1); labels.put(2, 2); labels.put(3, 3); labels.put(4, 4); labels.put(5, 5); Graph graph = new DefaultDirectedGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), true); for (int i = 0; i < 6; ++i) { graph.addVertex(i); } for (int i = 0; i < 6; ++i) { graph.setEdgeWeight(graph.addEdge(i, (i + 1) % 6), 1); } graph.setEdgeWeight(graph.addEdge(1, 4), -3); graph.setEdgeWeight(graph.getEdge(3, 4), -6); int lengthBound1 = 4; GraphWalk calculatedCycle1 = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>( graph, lengthBound1, labels, false).getLocalAugmentationCycle(); assertEquals(0, calculatedCycle1.getWeight(), 0.0000001); assertEquals(cycle1, calculatedCycle1.getVertexList()); int lengthBound2 = 6; GraphWalk calculatedCycle2 = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>( graph, lengthBound2, labels, false).getLocalAugmentationCycle(); assertEquals(cycle2, calculatedCycle2.getVertexList()); assertEquals(-1, calculatedCycle2.getWeight(), 0.0000001); } @Test public void testImprovementGraph5() { LinkedList cycle = new LinkedList<>(); cycle.add(0); cycle.add(0); Map labels = new HashMap<>(); labels.put(0, 0); labels.put(1, 1); labels.put(2, 2); labels.put(3, 3); labels.put(4, 4); labels.put(5, 5); Graph graph = new DefaultDirectedGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), true); for (int i = 0; i < 6; ++i) { graph.addVertex(i); DefaultEdge e = graph.addEdge(i, i); graph.setEdgeWeight(e, -1); } for (int i = 0; i < 6; ++i) { graph.setEdgeWeight(graph.addEdge(i, (i + 1) % 6), 1); } graph.setEdgeWeight(graph.addEdge(1, 4), 1); graph.setEdgeWeight(graph.getEdge(1, 4), -3); graph.setEdgeWeight(graph.getEdge(3, 4), -6); int lengthBound = 6; GraphWalk calculatedCycle1 = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, false) .getLocalAugmentationCycle(); assertEquals(-2, calculatedCycle1.getWeight(), 0.0000001); assertEquals(cycle, calculatedCycle1.getVertexList()); assertEquals(Integer.valueOf(0), calculatedCycle1.getStartVertex()); assertEquals(Integer.valueOf(0), calculatedCycle1.getEndVertex()); } @Test public void testImprovementGraph6() { LinkedList cycle = new LinkedList<>(); cycle.add(0); cycle.add(5); cycle.add(2); cycle.add(9); cycle.add(10); cycle.add(0); Map labels = new HashMap<>(); labels.put(0, 0); labels.put(1, 1); labels.put(2, 2); labels.put(3, 1); labels.put(4, 2); labels.put(5, 1); labels.put(6, 2); labels.put(7, 1); labels.put(8, 2); labels.put(9, 3); labels.put(10, 4); Graph graph = new DefaultDirectedGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), true); for (int i = 0; i < 11; ++i) { graph.addVertex(i); } graph.setEdgeWeight(graph.addEdge(0, 1), -2); graph.setEdgeWeight(graph.addEdge(0, 3), -1); graph.setEdgeWeight(graph.addEdge(0, 5), -3); graph.setEdgeWeight(graph.addEdge(0, 7), -2); graph.setEdgeWeight(graph.addEdge(1, 2), 1); graph.setEdgeWeight(graph.addEdge(1, 4), 0); graph.setEdgeWeight(graph.addEdge(1, 6), 1); graph.setEdgeWeight(graph.addEdge(1, 8), 0); graph.setEdgeWeight(graph.addEdge(2, 9), 0); graph.setEdgeWeight(graph.addEdge(3, 2), 0); graph.setEdgeWeight(graph.addEdge(3, 4), 1); graph.setEdgeWeight(graph.addEdge(3, 6), 1); graph.setEdgeWeight(graph.addEdge(3, 8), 0); graph.setEdgeWeight(graph.addEdge(4, 9), 0); graph.setEdgeWeight(graph.addEdge(5, 2), 0); graph.setEdgeWeight(graph.addEdge(5, 4), 1); graph.setEdgeWeight(graph.addEdge(5, 6), 1); graph.setEdgeWeight(graph.addEdge(5, 8), 1); graph.setEdgeWeight(graph.addEdge(6, 9), 0); graph.setEdgeWeight(graph.addEdge(7, 2), 0); graph.setEdgeWeight(graph.addEdge(7, 4), 1); graph.setEdgeWeight(graph.addEdge(7, 6), 0); graph.setEdgeWeight(graph.addEdge(7, 8), 0); graph.setEdgeWeight(graph.addEdge(8, 9), 0); graph.setEdgeWeight(graph.addEdge(9, 10), 0); graph.setEdgeWeight(graph.addEdge(10, 0), 0); int lengthBound = 6; GraphWalk calculatedCycle = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, false) .getLocalAugmentationCycle(); assertNotNull(calculatedCycle); assertEquals(cycle, calculatedCycle.getVertexList()); assertEquals(-3.0, calculatedCycle.getWeight(), 0.00000001); } @Test public void testImprovementGraph7() { LinkedList cycle = new LinkedList<>(); cycle.add(0); cycle.add(6); cycle.add(7); cycle.add(8); cycle.add(0); Map labels = new HashMap<>(); labels.put(0, 0); labels.put(1, 1); labels.put(2, 2); labels.put(3, 1); labels.put(4, 2); labels.put(5, 1); labels.put(6, 1); labels.put(7, 3); labels.put(8, 4); Graph graph = new DefaultDirectedGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), true); for (int i = 0; i < 9; ++i) { graph.addVertex(i); } graph.setEdgeWeight(graph.addEdge(0, 1), -2); graph.setEdgeWeight(graph.addEdge(0, 3), -1); graph.setEdgeWeight(graph.addEdge(0, 5), -3); graph.setEdgeWeight(graph.addEdge(0, 6), -3); graph.setEdgeWeight(graph.addEdge(1, 2), 1); graph.setEdgeWeight(graph.addEdge(1, 4), 0); graph.setEdgeWeight(graph.addEdge(2, 7), 0); graph.setEdgeWeight(graph.addEdge(3, 2), 0); graph.setEdgeWeight(graph.addEdge(3, 4), 1); graph.setEdgeWeight(graph.addEdge(4, 7), 0); graph.setEdgeWeight(graph.addEdge(5, 2), 0); graph.setEdgeWeight(graph.addEdge(5, 4), 1); graph.setEdgeWeight(graph.addEdge(5, 7), 1); graph.setEdgeWeight(graph.addEdge(6, 2), 0); graph.setEdgeWeight(graph.addEdge(6, 4), 1); graph.setEdgeWeight(graph.addEdge(6, 7), 0); graph.setEdgeWeight(graph.addEdge(7, 8), 0); graph.setEdgeWeight(graph.addEdge(8, 0), 0); int lengthBound = 6; GraphWalk calculatedCycle = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, false) .getLocalAugmentationCycle(); assertNotNull(calculatedCycle); assertEquals(cycle, calculatedCycle.getVertexList()); assertEquals(-3.0, calculatedCycle.getWeight(), 0.00000001); } @Test public void testImprovementGraph8() { LinkedList cycle = new LinkedList<>(); cycle.add(0); cycle.add(6); cycle.add(7); cycle.add(8); cycle.add(0); Map labels = new HashMap<>(); labels.put(0, 0); labels.put(1, 1); labels.put(2, 2); labels.put(3, 1); labels.put(4, 2); labels.put(5, 1); labels.put(6, 1); labels.put(7, 3); labels.put(8, 4); Graph graph = new DefaultDirectedGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), true); for (int i = 0; i < 9; ++i) { graph.addVertex(i); } graph.setEdgeWeight(graph.addEdge(0, 1), -4); graph.setEdgeWeight(graph.addEdge(0, 3), -4); graph.setEdgeWeight(graph.addEdge(0, 5), -3); graph.setEdgeWeight(graph.addEdge(0, 6), -4); graph.setEdgeWeight(graph.addEdge(1, 2), 1); graph.setEdgeWeight(graph.addEdge(1, 4), 1); graph.setEdgeWeight(graph.addEdge(2, 7), 0); graph.setEdgeWeight(graph.addEdge(3, 2), 1); graph.setEdgeWeight(graph.addEdge(3, 4), 0); graph.setEdgeWeight(graph.addEdge(4, 7), 0); graph.setEdgeWeight(graph.addEdge(5, 7), 0); graph.setEdgeWeight(graph.addEdge(6, 7), 0); graph.setEdgeWeight(graph.addEdge(7, 8), 0); graph.setEdgeWeight(graph.addEdge(8, 0), 0); int lengthBound = 6; GraphWalk calculatedCycle = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, false) .getLocalAugmentationCycle(); assertNotNull(calculatedCycle); assertEquals(cycle, calculatedCycle.getVertexList()); assertEquals(-4.0, calculatedCycle.getWeight(), 0.00000001); } @Test public void testImprovementGraph9() { LinkedList cycle = new LinkedList<>(); Map labels = new HashMap<>(); labels.put(0, 0); labels.put(1, 0); labels.put(2, 0); Graph graph = new DefaultDirectedGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), true); for (int i = 0; i < 3; ++i) { graph.addVertex(i); } graph.setEdgeWeight(graph.addEdge(0, 1), -4); graph.setEdgeWeight(graph.addEdge(1, 2), 1); int lengthBound = 6; GraphWalk calculatedCycle = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, true) .getLocalAugmentationCycle(); assertNotNull(calculatedCycle); assertEquals(cycle, calculatedCycle.getVertexList()); assertEquals(Double.MAX_VALUE, calculatedCycle.getWeight(), 0.00000001); } @Test public void testImprovementGraph10() { LinkedList cycle = new LinkedList<>(); cycle.add(1); cycle.add(2); cycle.add(3); cycle.add(4); cycle.add(5); cycle.add(6); cycle.add(1); Map labels = new HashMap<>(); labels.put(0, 0); labels.put(1, 1); labels.put(2, 2); labels.put(3, 3); labels.put(4, 4); labels.put(5, 5); labels.put(6, 6); Graph graph = new DefaultDirectedGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), true); for (int i = 0; i < 7; ++i) { graph.addVertex(i); } for (int i = 0; i < 7; ++i) { for (int j = 0; j < 7; ++j) { if (i == j) { graph.setEdgeWeight(graph.addEdge(i, j), 1); } else if ((i + j) % 2 == 0) { graph.setEdgeWeight(graph.addEdge(i, j), i + j); } else if (j == i + 1) { graph.setEdgeWeight(graph.addEdge(i, j), -i - j); } else if (i == 6 && j == 1) { graph.setEdgeWeight(graph.addEdge(i, j), -i - j); } else { graph.setEdgeWeight(graph.addEdge(i, j), -i - j + 1); } } } int lengthBound = 8; GraphWalk calculatedCycle = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, true) .getLocalAugmentationCycle(); assertNotNull(calculatedCycle); assertEquals(cycle, calculatedCycle.getVertexList()); assertEquals(-42.0, calculatedCycle.getWeight(), 0.00000001); } @Test public void testImprovementGraph11() { LinkedList cycle = new LinkedList<>(); cycle.add(5); cycle.add(5); Map labels = new HashMap<>(); labels.put(0, 0); labels.put(1, 1); labels.put(2, 2); labels.put(3, 3); labels.put(4, 4); labels.put(5, 5); Graph graph = new DefaultDirectedGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), true); for (int i = 0; i < 6; ++i) { graph.addVertex(i); } for (int i = 0; i < 6; ++i) { graph.setEdgeWeight(graph.addEdge(i, i), -i * i); } int lengthBound = 4; GraphWalk calculatedCycle = new AhujaOrlinSharmaCyclicExchangeLocalAugmentation<>(graph, lengthBound, labels, true) .getLocalAugmentationCycle(); assertNotNull(calculatedCycle); assertEquals(cycle, calculatedCycle.getVertexList()); assertEquals(-50.0, calculatedCycle.getWeight(), 0.00000001); } private Graph generateImprovementGraphForPartitionProblem( Map labels, double initialWeight) { Graph graph = new DefaultDirectedGraph<>(null, SupplierUtil.createDefaultEdgeSupplier(), true); for (Integer v1 : labels.keySet()) { graph.addVertex(v1); } for (Integer v1 : labels.keySet()) { for (Integer v2 : labels.keySet()) { if (!labels.get(v1).equals(labels.get(v2))) { graph.addEdge(v1, v2); graph.setEdgeWeight(graph.getEdge(v1, v2), initialWeight); graph.addEdge(v2, v1); graph.setEdgeWeight(graph.getEdge(v1, v2), initialWeight); } } } return graph; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/BergeGraphInspectorTest.java000066400000000000000000000465771402514743400334050ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Philipp S. Kaesgen and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import static org.junit.Assert.*; public class BergeGraphInspectorTest { private SimpleGraph stimulus; private BergeGraphInspector dut = new BergeGraphInspector<>(); private void reset() { stimulus = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createIntegerSupplier(), false); } private boolean verifyCertificate(GraphPath certificate) { if (certificate == null) return false; Set set = new HashSet<>(); set.addAll(certificate.getVertexList()); Graph subg = new AsSubgraph<>(certificate.getGraph(), set); return subg.vertexSet().size() == subg.edgeSet().size() && subg.edgeSet().size() == subg.vertexSet().size() && subg.vertexSet().size() % 2 == 1 && subg.vertexSet().stream().allMatch(t -> subg.edgesOf(t).size() == 2); } private int maximalNumberOfVertices = 17, minimalNumberOfVertices = 14; private int repititionsPerTestCase = 1; @Test public void checkPyramid() { reset(); stimulus.addVertex(1);// b1 stimulus.addVertex(2);// b2 stimulus.addVertex(3);// b3 stimulus.addEdge(1, 2);// 0 stimulus.addEdge(2, 3);// 1 stimulus.addEdge(3, 1);// 2 stimulus.addVertex(4);// s1 stimulus.addVertex(5);// s2 stimulus.addVertex(6);// s3 stimulus.addVertex(7);// a stimulus.addEdge(4, 7);// 3 stimulus.addEdge(5, 7);// 4 stimulus.addEdge(6, 7);// 5 /* * optional either stimulus.addEdge(7,1);iff 1 in {4,5,6} stimulus.addEdge(7,2);iff 2 in * {4,5,6} stimulus.addEdge(7,3);iff 3 in {4,5,6} */ stimulus.addVertex(8);// m1 stimulus.addVertex(9);// m2 stimulus.addVertex(10);// m3 stimulus.addVertex(11);// S1 stimulus.addVertex(12);// S2 stimulus.addVertex(13);// S3 stimulus.addVertex(14);// T1 stimulus.addVertex(15);// T2 stimulus.addVertex(16);// T3 stimulus.addEdge(8, 11);// 6 stimulus.addEdge(11, 4);// 7 stimulus.addEdge(9, 12);// 8 stimulus.addEdge(12, 5);// 9 stimulus.addEdge(10, 13);// 10 stimulus.addEdge(13, 6);// 11 stimulus.addEdge(1, 14);// 12 stimulus.addEdge(14, 8);// 13 stimulus.addEdge(2, 15);// 14 stimulus.addEdge(15, 9);// 15 stimulus.addEdge(3, 16);// 16 stimulus.addEdge(16, 10);// 17 assertTrue(dut.containsPyramid(stimulus)); dut.isBerge(stimulus, true); assertTrue(verifyCertificate(dut.getCertificate())); stimulus.addEdge(8, 2); dut.isBerge(stimulus, true); assertTrue(verifyCertificate(dut.getCertificate())); stimulus.removeEdge(8, 2); stimulus.addEdge(9, 3); dut.isBerge(stimulus, true); assertTrue(verifyCertificate(dut.getCertificate())); stimulus.removeEdge(9, 3); stimulus.addEdge(10, 1); dut.isBerge(stimulus, true); assertTrue(verifyCertificate(dut.getCertificate())); stimulus.addEdge(11, 2); assertFalse(dut.containsPyramid(stimulus)); } @Test public void checkJewel() { reset(); stimulus.addVertex(1); stimulus.addVertex(2); stimulus.addVertex(3); stimulus.addVertex(4); stimulus.addVertex(5); stimulus.addEdge(1, 2); stimulus.addEdge(2, 3); stimulus.addEdge(3, 4); stimulus.addEdge(4, 5); stimulus.addEdge(5, 1); /* * non-edges: v1v3 v2v4 v1v4 */ stimulus.addVertex(6); stimulus.addVertex(7); stimulus.addVertex(8); stimulus.addEdge(1, 6); stimulus.addEdge(6, 7); stimulus.addEdge(7, 8); stimulus.addEdge(8, 4); assertTrue(dut.containsJewel(stimulus)); dut.isBerge(stimulus, true); assertTrue(verifyCertificate(dut.getCertificate())); stimulus.addEdge(1, 3); assertFalse(dut.containsJewel(stimulus)); } @Test public void checkIsYXComplete() { reset(); stimulus.addVertex(1); stimulus.addVertex(2); stimulus.addVertex(3); stimulus.addVertex(4); stimulus.addEdge(1, 4); stimulus.addEdge(1, 2); stimulus.addEdge(1, 3); Set X = new HashSet<>(); X.add(2); X.add(3); X.add(4); assertTrue(dut.isYXComplete(stimulus, 1, X)); stimulus.removeEdge(1, 4); assertFalse(dut.isYXComplete(stimulus, 1, X)); stimulus.addEdge(1, 4); X.clear(); X.add(2); X.add(1); assertFalse(dut.isYXComplete(stimulus, 3, X)); } @Test public void checkConfigurationType2() { reset(); stimulus.addVertex(1); stimulus.addVertex(2); stimulus.addVertex(3); stimulus.addVertex(4); stimulus.addVertex(5);// p1 stimulus.addVertex(6);// x stimulus.addVertex(7);// p2=P* stimulus.addVertex(8);// p3 stimulus.addEdge(1, 2); stimulus.addEdge(2, 3); stimulus.addEdge(3, 4); stimulus.addEdge(1, 6); stimulus.addEdge(2, 6); stimulus.addEdge(4, 6); stimulus.addEdge(1, 5); stimulus.addEdge(5, 7); stimulus.addEdge(7, 8); stimulus.addEdge(4, 8); assertTrue(dut.hasConfigurationType2(stimulus)); stimulus.addEdge(3, 6); assertTrue(dut.hasConfigurationType2(stimulus)); stimulus.addEdge(7, 6); assertFalse(dut.hasConfigurationType2(stimulus)); stimulus.removeEdge(3, 6); stimulus.removeEdge(4, 8); assertFalse(dut.hasConfigurationType2(stimulus)); } @Test public void checkConfigurationType3() { reset(); stimulus.addVertex(1); stimulus.addVertex(2); stimulus.addVertex(3); stimulus.addVertex(4); stimulus.addVertex(5); stimulus.addVertex(6); stimulus.addEdge(1, 2); stimulus.addEdge(3, 4); stimulus.addEdge(1, 4); stimulus.addEdge(2, 3); stimulus.addEdge(3, 5); stimulus.addEdge(4, 6); /* * Non-edges: stimulus.addEdge(1,3); stimulus.addEdge(2,4); stimulus.addEdge(1,5); * stimulus.addEdge(2,5); stimulus.addEdge(1,6); stimulus.addEdge(2,6); * stimulus.addEdge(4,5); * * Optional edges: stimulus.addEdge(3,5); stimulus.addEdge(3,6); * * stimulus.addEdge(5,6); implies non-edge stimulus.addEdge(6,7); */ stimulus.addVertex(7);// x stimulus.addEdge(1, 7); stimulus.addEdge(2, 7); stimulus.addEdge(5, 7); /* * Non-edges either: stimulus.addEdge(3,7); or stimulus.addEdge(4,7); !! Note: one is to * choose, otherwise it is a 5-Cycle !! * * Optional edges if non-edge stimulus.addEdge(5,6); stimulus.addEdge(6,7); */ stimulus.addVertex(8);// p1 stimulus.addVertex(9);// p2 stimulus.addVertex(10);// p3 stimulus.addEdge(5, 8); stimulus.addEdge(8, 9); stimulus.addEdge(9, 10); stimulus.addEdge(10, 6); /* * Non-edges: stimulus.addEdge(1,9); stimulus.addEdge(2,9); stimulus.addEdge(7,9); * * Optional edges: stimulus.addEdge(1,8); stimulus.addEdge(2,8); stimulus.addEdge(3,8); * stimulus.addEdge(4,8); stimulus.addEdge(6,8); stimulus.addEdge(7,8); * stimulus.addEdge(8,10); stimulus.addEdge(3,9); stimulus.addEdge(4,9); * stimulus.addEdge(5,9); stimulus.addEdge(6,9); * */ assertTrue(dut.hasConfigurationType3(stimulus)); stimulus.addEdge(4, 7); assertFalse(dut.hasConfigurationType3(stimulus)); } @Test public void checkCleanOddHole() { reset(); stimulus.addVertex(1); stimulus.addVertex(2); stimulus.addVertex(3); stimulus.addVertex(4); stimulus.addVertex(5); stimulus.addVertex(6); stimulus.addVertex(7); stimulus.addEdge(1, 2); stimulus.addEdge(3, 2); stimulus.addEdge(4, 3); stimulus.addEdge(4, 5); stimulus.addEdge(6, 5); stimulus.addEdge(6, 7); stimulus.addEdge(7, 1); assertTrue(dut.containsCleanShortestOddHole(stimulus)); stimulus.addEdge(3, 7); stimulus.addEdge(4, 7); assertFalse(dut.containsCleanShortestOddHole(stimulus)); } @Test public void checkContainsShortestOddHole() { reset(); stimulus.addVertex(1); stimulus.addVertex(2); stimulus.addVertex(3); stimulus.addVertex(4); stimulus.addVertex(5); stimulus.addVertex(6); stimulus.addVertex(7); stimulus.addEdge(1, 2); stimulus.addEdge(3, 2); stimulus.addEdge(4, 3); stimulus.addEdge(4, 5); stimulus.addEdge(6, 5); stimulus.addEdge(6, 7); stimulus.addEdge(7, 1); stimulus.addVertex(8);// Cleaner stimulus.addEdge(3, 8); stimulus.addEdge(8, 7); stimulus.addEdge(8, 5); assertTrue(dut.containsCleanShortestOddHole(stimulus)); List golden = new LinkedList<>(); golden.add(1); golden.add(2); golden.add(3); golden.add(8); golden.add(7); golden.add(1); // assertEquals(golden,certificate.getVertexList()); stimulus.removeVertex(8); assertTrue(dut.containsCleanShortestOddHole(stimulus)); } @Test public void checkRoutine3() { reset(); stimulus.addVertex(1);// u stimulus.addVertex(2);// v stimulus.addVertex(3); stimulus.addVertex(4); stimulus.addEdge(1, 2); stimulus.addEdge(2, 3); stimulus.addEdge(2, 4); stimulus.addEdge(1, 3); stimulus.addEdge(1, 4); Set> golden = new HashSet<>(); Set golden1 = new HashSet<>(), golden2 = new HashSet<>(); golden1.add(1); golden1.add(2); golden2.add(1); golden2.add(2); golden2.add(3); golden2.add(4); golden.add(golden1); golden.add(golden2); assertEquals(golden, dut.routine3(stimulus)); } @Test public void checkPetersenGraph() { reset(); new NamedGraphGenerator().generatePetersenGraph(stimulus); assertFalse(dut.isBerge(stimulus, true)); assertTrue(verifyCertificate(dut.getCertificate())); } @Test public void checkDodecahedronGraph() { reset(); new NamedGraphGenerator().generateDodecahedronGraph(stimulus); assertFalse(dut.isBerge(stimulus, true)); assertTrue(verifyCertificate(dut.getCertificate())); } @Test @Category(OptionalTests.class) public void checkMöbiusKantorGraph() { reset(); new NamedGraphGenerator().generateMöbiusKantorGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test public void checkBullGraph() { reset(); new NamedGraphGenerator().generateBullGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test public void checkButterflyGraph() { reset(); new NamedGraphGenerator().generateButterflyGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test public void checkClawGraph() { reset(); new NamedGraphGenerator().generateClawGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test public void checkGrötzschGraph() { reset(); new NamedGraphGenerator().generateGrötzschGraph(stimulus); assertFalse(dut.isBerge(stimulus, true)); assertTrue(verifyCertificate(dut.getCertificate())); } @Test public void checkDiamondGraph() { reset(); new NamedGraphGenerator().generateDiamondGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test @Category(SlowTests.class) public void checkFranklinGraph() { reset(); new NamedGraphGenerator().generateFranklinGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test public void checkFruchtGraph() { reset(); new NamedGraphGenerator().generateFruchtGraph(stimulus); assertFalse(dut.isBerge(stimulus, true)); assertTrue(verifyCertificate(dut.getCertificate())); } @Test @Category(SlowTests.class) public void checkGoldnerHararyGraph() { reset(); new NamedGraphGenerator().generateGoldnerHararyGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test @Category(SlowTests.class) public void checkHeawoodGraph() { reset(); new NamedGraphGenerator().generateHeawoodGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test @Category(SlowTests.class) public void checkHerschelGraph() { reset(); new NamedGraphGenerator().generateHerschelGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test @Category(SlowTests.class) public void checkKrackhardtKiteGraph() { reset(); new NamedGraphGenerator().generateKrackhardtKiteGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test public void checkMoserSpindleGraph() { reset(); new NamedGraphGenerator().generateMoserSpindleGraph(stimulus); assertFalse(dut.isBerge(stimulus, true)); assertTrue(verifyCertificate(dut.getCertificate())); } @Test @Category(OptionalTests.class) public void checkPappusGraph() { reset(); new NamedGraphGenerator().generatePappusGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test public void checkTietzeGraph() { reset(); new NamedGraphGenerator().generateTietzeGraph(stimulus); assertFalse(dut.isBerge(stimulus, true)); assertTrue(verifyCertificate(dut.getCertificate())); } @Test public void checkThomsenGraph() { reset(); new NamedGraphGenerator().generateThomsenGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test public void checkTutteGraph() { reset(); new NamedGraphGenerator().generateTutteGraph(stimulus); assertFalse(dut.isBerge(stimulus, true)); assertTrue(verifyCertificate(dut.getCertificate())); } @Test public void checkEmptyGraph() { reset(); int numberOfVertices = new Random().nextInt(maximalNumberOfVertices - minimalNumberOfVertices) + minimalNumberOfVertices; new EmptyGraphGenerator(numberOfVertices).generateGraph(stimulus); assertTrue(dut.isBerge(stimulus, true)); assertFalse(verifyCertificate(dut.getCertificate())); } @Test @Category(OptionalTests.class) public void checkBipartiteGraphs() { int repititions = repititionsPerTestCase; reset(); while (repititions-- > 0) { int n1 = new Random().nextInt(maximalNumberOfVertices - minimalNumberOfVertices) / 2 + minimalNumberOfVertices / 2, n2 = maximalNumberOfVertices - n1; int maximalNumberOfEdges = n1 * n2; int numberOfEdges = new Random().nextInt(maximalNumberOfEdges); reset(); new GnmRandomBipartiteGraphGenerator(n1, n2, numberOfEdges) .generateGraph(stimulus); assertTrue(dut.isBerge(stimulus)); } } @Test @Category(OptionalTests.class) public void checkWheelGraphs() { int repititions = repititionsPerTestCase; while (repititions-- > 0) { int numberOfVertices = new Random().nextInt(maximalNumberOfVertices - minimalNumberOfVertices) + minimalNumberOfVertices; if (numberOfVertices % 2 == 0) numberOfVertices += 1; assertTrue(maximalNumberOfVertices > minimalNumberOfVertices); reset(); new WheelGraphGenerator(numberOfVertices).generateGraph(stimulus); assertTrue(dut.isBerge(stimulus)); } repititions = repititionsPerTestCase; while (repititions-- > 0) { int numberOfVertices = new Random().nextInt(maximalNumberOfVertices - minimalNumberOfVertices) + minimalNumberOfVertices; if (numberOfVertices % 2 == 1) numberOfVertices += 1; assertTrue(maximalNumberOfVertices > minimalNumberOfVertices); reset(); new WheelGraphGenerator(numberOfVertices).generateGraph(stimulus); assertFalse(dut.isBerge(stimulus)); } } @Test @Category(OptionalTests.class) public void checkWindmillGraphs() { int repititions = repititionsPerTestCase; while (repititions-- > 0) { int m = 2; int numberOfVertices = new Random().nextInt(maximalNumberOfVertices - 3) + 3; reset(); new WindmillGraphsGenerator( WindmillGraphsGenerator.Mode.WINDMILL, m, numberOfVertices).generateGraph(stimulus); assertTrue(dut.isBerge(stimulus)); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChinesePostmanTest.java000066400000000000000000000267471402514743400324250ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; /** * @author Joris Kinable */ public class ChinesePostmanTest { @Test(expected = IllegalArgumentException.class) public void testGraphNoVertices() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); ChinesePostman alg = new ChinesePostman<>(); alg.getCPPSolution(g); } @Test public void testSingleEdgeGraph() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(0); g.addVertex(1); Graphs.addEdge(g, 0, 1, 10); this.verifyClosedPath(g, 20, 2); } @Test public void testGraphWithSelfloop() { Graph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex(0); g.addVertex(1); Graphs.addEdge(g, 0, 1, 10); Graphs.addEdge(g, 0, 0, 20); this.verifyClosedPath(g, 40, 3); } @Test public void testGraphWithMultipleEdges() { Graph g = new WeightedMultigraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); Graphs.addEdge(g, 1, 2, 1); Graphs.addEdge(g, 1, 4, 3); Graphs.addEdge(g, 2, 3, 20); Graphs.addEdge(g, 2, 3, 10); Graphs.addEdge(g, 3, 4, 2); this.verifyClosedPath(g, 42, 8); } @Test public void testUndirectedGraph1() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H')); Graphs.addEdge(g, 'A', 'B', 50); Graphs.addEdge(g, 'A', 'C', 50); Graphs.addEdge(g, 'A', 'D', 50); Graphs.addEdge(g, 'B', 'D', 50); Graphs.addEdge(g, 'B', 'E', 70); Graphs.addEdge(g, 'B', 'F', 50); Graphs.addEdge(g, 'C', 'D', 70); Graphs.addEdge(g, 'C', 'G', 70); Graphs.addEdge(g, 'C', 'H', 120); Graphs.addEdge(g, 'D', 'F', 60); Graphs.addEdge(g, 'E', 'F', 70); Graphs.addEdge(g, 'F', 'H', 60); Graphs.addEdge(g, 'G', 'H', 70); this.verifyClosedPath(g, 1000, 16); } @Test public void testUndirectedGraph2() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList('A', 'B', 'C', 'D', 'E')); Graphs.addEdge(g, 'A', 'B', 8); Graphs.addEdge(g, 'A', 'C', 5); Graphs.addEdge(g, 'A', 'D', 6); Graphs.addEdge(g, 'B', 'C', 5); Graphs.addEdge(g, 'B', 'E', 6); Graphs.addEdge(g, 'C', 'D', 5); Graphs.addEdge(g, 'C', 'E', 5); Graphs.addEdge(g, 'D', 'E', 8); this.verifyClosedPath(g, 60, 10); } @Test public void testUndirectedGraph3() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7)); Graphs.addEdge(g, 1, 2, 5); Graphs.addEdge(g, 1, 4, 4); Graphs.addEdge(g, 1, 5, 1); Graphs.addEdge(g, 2, 3, 3); Graphs.addEdge(g, 2, 5, 1); Graphs.addEdge(g, 2, 7, 1); Graphs.addEdge(g, 3, 4, 2); Graphs.addEdge(g, 3, 5, 3); Graphs.addEdge(g, 3, 6, 1); Graphs.addEdge(g, 3, 7, 2); Graphs.addEdge(g, 4, 5, 1); Graphs.addEdge(g, 6, 7, 3); this.verifyClosedPath(g, 31, 15); } @Test public void testUndirectedGraph4() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); Graphs.addEdge(g, 1, 2, 100); Graphs.addEdge(g, 1, 3, 150); Graphs.addEdge(g, 1, 4, 200); Graphs.addEdge(g, 2, 3, 120); Graphs.addEdge(g, 2, 5, 250); Graphs.addEdge(g, 3, 6, 200); Graphs.addEdge(g, 4, 5, 100); Graphs.addEdge(g, 4, 7, 80); Graphs.addEdge(g, 4, 8, 160); Graphs.addEdge(g, 5, 6, 100); Graphs.addEdge(g, 5, 7, 100); Graphs.addEdge(g, 6, 7, 150); Graphs.addEdge(g, 6, 10, 160); Graphs.addEdge(g, 7, 9, 100); Graphs.addEdge(g, 8, 9, 40); Graphs.addEdge(g, 9, 10, 80); this.verifyClosedPath(g, 2590, 20); } @Test public void testUndirectedGraph5() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); Graphs.addEdge(g, 1, 2, 1); Graphs.addEdge(g, 2, 3, 1); Graphs.addEdge(g, 3, 4, 1); Graphs.addEdge(g, 2, 5, 1); Graphs.addEdge(g, 3, 6, 1); this.verifyClosedPath(g, 10, 10); } // ---------------------Directed graph tests -------------------------- @Test public void testDirectedGraphWithMultipleEdgesAndSelfLoop() { Graph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); Graphs.addEdge(g, 1, 2, 1); Graphs.addEdge(g, 2, 3, 3); Graphs.addEdge(g, 2, 3, 20); Graphs.addEdge(g, 3, 4, 10); Graphs.addEdge(g, 4, 4, 5); Graphs.addEdge(g, 4, 1, 2); this.verifyClosedPath(g, 54, 9); } @Test public void testDirectedGraph1() { Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); Graphs.addEdge(g, 1, 2, 1); Graphs.addEdge(g, 1, 4, 5); Graphs.addEdge(g, 2, 3, 2); Graphs.addEdge(g, 3, 1, 3); Graphs.addEdge(g, 4, 3, 4); this.verifyClosedPath(g, 18, 6); } @Test public void testDirectedGraph2() { Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); Graphs.addEdge(g, 1, 2, 8); Graphs.addEdge(g, 1, 3, 3); Graphs.addEdge(g, 2, 3, 10); Graphs.addEdge(g, 2, 4, 5); Graphs.addEdge(g, 3, 4, 15); Graphs.addEdge(g, 4, 1, 4); this.verifyClosedPath(g, 76, 10); } @Test public void testDirectedGraph3() { Graph g = new DirectedWeightedMultigraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); Graphs.addEdge(g, 1, 2, 21); Graphs.addEdge(g, 2, 3, 8); Graphs.addEdge(g, 3, 1, 5); Graphs.addEdge(g, 3, 4, 20); Graphs.addEdge(g, 4, 2, 12); Graphs.addEdge(g, 4, 2, 2); this.verifyClosedPath(g, 104, 9); } @Test public void testDirectedGraph4() { Graph g = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); Graphs.addEdge(g, 1, 2, 10); Graphs.addEdge(g, 1, 3, 20); Graphs.addEdge(g, 2, 4, 50); Graphs.addEdge(g, 2, 5, 10); Graphs.addEdge(g, 3, 4, 20); Graphs.addEdge(g, 3, 5, 33); Graphs.addEdge(g, 4, 5, 5); Graphs.addEdge(g, 4, 6, 12); Graphs.addEdge(g, 5, 1, 12); Graphs.addEdge(g, 5, 6, 1); Graphs.addEdge(g, 6, 3, 22); this.verifyClosedPath(g, 276, 17); } @Test public void testDirectedGraph5() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); g.addEdge(1, 2); g.addEdge(1, 11); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(3, 5); g.addEdge(4, 9); g.addEdge(5, 7); g.addEdge(6, 9); g.addEdge(7, 6); g.addEdge(8, 7); g.addEdge(9, 8); g.addEdge(9, 10); g.addEdge(10, 1); g.addEdge(11, 9); this.verifyClosedPath(g, 22, 22); } @Test public void temp() { ChinesePostman alg = new ChinesePostman<>(); Graph g = new SimpleGraph<>(DefaultEdge.class); // Graph g=new DefaultDirectedGraph(DefaultEdge.class); g.addVertex(0); g.addVertex(1); g.addEdge(0, 1); g.addEdge(1, 0); GraphPath path = alg.getCPPSolution(g); } private void verifyClosedPath(Graph graph, double expectedWeight, int expectedLength) { ChinesePostman alg = new ChinesePostman<>(); GraphPath path = alg.getCPPSolution(graph); Assert.assertEquals(expectedLength, path.getLength()); Assert.assertEquals(expectedLength, path.getEdgeList().size()); Assert.assertEquals(expectedWeight, path.getWeight(), 0.00000001); Assert .assertEquals( expectedWeight, path.getEdgeList().stream().mapToDouble(graph::getEdgeWeight).sum(), 0.00000001); // all edges of the graph must be visited at least once Assert.assertTrue(path.getEdgeList().containsAll(graph.edgeSet())); Assert.assertTrue(graph.containsVertex(path.getStartVertex())); Assert.assertEquals(path.getStartVertex(), path.getEndVertex()); // Verify that the path is an actual path in the graph Assert.assertEquals(path.getEdgeList().size() + 1, path.getVertexList().size()); List vertexList = path.getVertexList(); List edgeList = path.getEdgeList(); // Check start and end vertex Assert.assertEquals(vertexList.get(0), path.getStartVertex()); Assert.assertEquals(vertexList.get(vertexList.size() - 1), path.getEndVertex()); // All vertices and edges in the path must be contained in the graph Assert.assertTrue(graph.vertexSet().containsAll(vertexList)); Assert.assertTrue(graph.edgeSet().containsAll(edgeList)); for (int i = 0; i < vertexList.size() - 1; i++) { V u = vertexList.get(i); V v = vertexList.get(i + 1); E edge = edgeList.get(i); if (graph.getType().isUndirected()) { Assert.assertEquals(Graphs.getOppositeVertex(graph, edge, u), v); } else { // Directed Assert.assertEquals(graph.getEdgeSource(edge), u); Assert.assertEquals(graph.getEdgeTarget(edge), v); } } } } ChordalGraphMinimalVertexSeparatorFinderTest.java000066400000000000000000000146071402514743400374720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Tests for the {@link ChordalGraphMinimalVertexSeparatorFinder} * * @author Timofey Chudakov */ public class ChordalGraphMinimalVertexSeparatorFinderTest { /** * Test on empty graph */ @Test public void testGetMinimalSeparators1() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); ChordalGraphMinimalVertexSeparatorFinder finder = new ChordalGraphMinimalVertexSeparatorFinder<>(graph); Set> separators = finder.getMinimalSeparators(); Map, Integer> separatorsAndMultiplicities = finder.getMinimalSeparatorsWithMultiplicities(); assertEquals(0, separators.size()); assertEquals(0, separatorsAndMultiplicities.size()); } /** * Test on small chordal graph */ @Test public void testGetMinimalSeparators2() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalGraphMinimalVertexSeparatorFinder finder = new ChordalGraphMinimalVertexSeparatorFinder<>(graph); Set> separators = finder.getMinimalSeparators(); Map, Integer> separatorsAndMultiplicities = finder.getMinimalSeparatorsWithMultiplicities(); Map, Integer> expected = new HashMap<>(); expected.put(Set.of(2, 3), 1); assertEquals(expected.keySet(), separators); assertEquals(expected, separatorsAndMultiplicities); } /** * Test on big chordal graph (example from original article) */ @Test public void testGetMinimalSeparators3() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 3 }, { 3, 4 }, { 3, 5 }, { 3, 6 }, { 3, 8 }, { 3, 10 }, { 3, 11 }, { 4, 5 }, { 4, 6 }, { 5, 6 }, { 6, 7 }, { 6, 8 }, { 6, 10 }, { 6, 11 }, { 7, 8 }, { 7, 10 }, { 8, 9 }, { 8, 10 }, { 9, 10 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalGraphMinimalVertexSeparatorFinder finder = new ChordalGraphMinimalVertexSeparatorFinder<>(graph); Set> separators = finder.getMinimalSeparators(); Map, Integer> separatorsAndMultiplicities = finder.getMinimalSeparatorsWithMultiplicities(); Map, Integer> expected = new HashMap<>(); expected.put(Set.of(3), 1); expected.put(Set.of(3, 6), 2); expected.put(Set.of(8, 10), 1); expected.put(Set.of(6, 8, 10), 1); assertEquals(expected.keySet(), separators); assertEquals(expected, separatorsAndMultiplicities); } /** * Test on big chordal graph (example from original article) */ @Test public void testGetMinimalSeparators4() { int[][] edges = { { 1, 2 }, { 2, 8 }, { 2, 9 }, { 3, 8 }, { 3, 9 }, { 4, 6 }, { 4, 8 }, { 5, 6 }, { 5, 8 }, { 6, 7 }, { 6, 8 }, { 6, 9 }, { 7, 8 }, { 7, 9 }, { 8, 9 }, { 8, 10 }, { 8, 11 }, { 8, 12 }, { 9, 10 }, { 9, 11 }, { 9, 12 }, { 10, 11 }, { 11, 12 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalGraphMinimalVertexSeparatorFinder finder = new ChordalGraphMinimalVertexSeparatorFinder<>(graph); Set> separators = finder.getMinimalSeparators(); Map, Integer> separatorsAndMultiplicities = finder.getMinimalSeparatorsWithMultiplicities(); Map, Integer> expected = new HashMap<>(); expected.put(Set.of(2), 1); expected.put(Set.of(6, 8), 2); expected.put(Set.of(8, 9), 3); expected.put(Set.of(8, 9, 11), 1); assertEquals(expected.keySet(), separators); assertEquals(expected, separatorsAndMultiplicities); } /** * Test on not chordal graph */ @Test public void testGetMinimalSeparators5() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalGraphMinimalVertexSeparatorFinder finder = new ChordalGraphMinimalVertexSeparatorFinder<>(graph); Set> separators = finder.getMinimalSeparators(); Map, Integer> separatorsAndMultiplicities = finder.getMinimalSeparatorsWithMultiplicities(); assertNull(separators); assertNull(separatorsAndMultiplicities); } /** * Test on pseudograph */ @Test public void testGetMinimalSeparators6() { int[][] edges = { { 1, 1 }, { 1, 1 }, { 1, 2 }, { 2, 3 }, { 2, 3 }, { 2, 3 }, { 2, 5 }, { 3, 3 }, { 3, 4 }, { 5, 3 }, { 5, 3 }, { 5, 4 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalGraphMinimalVertexSeparatorFinder finder = new ChordalGraphMinimalVertexSeparatorFinder<>(graph); Set> separators = finder.getMinimalSeparators(); Map, Integer> separatorsAndMultiplicities = finder.getMinimalSeparatorsWithMultiplicities(); Map, Integer> expected = new HashMap<>(); expected.put(Set.of(2), 1); expected.put(Set.of(3, 5), 1); assertEquals(expected.keySet(), separators); assertEquals(expected, separatorsAndMultiplicities); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/ChordalityInspectorTest.java000066400000000000000000000354301402514743400334630ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.junit.*; import org.junit.runner.*; import org.junit.runners.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for the {@link ChordalityInspector} * * @author Timofey Chudakov */ @RunWith(Parameterized.class) public class ChordalityInspectorTest { private ChordalityInspector.IterationOrder iterationOrder; public ChordalityInspectorTest(ChordalityInspector.IterationOrder iterationOrder) { this.iterationOrder = iterationOrder; } @Parameterized.Parameters public static Object[] params() { return new Object[] { ChordalityInspector.IterationOrder.MCS, ChordalityInspector.IterationOrder.LEX_BFS }; } /** * Tests usage of the correct iteration order */ @Test public void testIterationOrder() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); ChordalityInspector inspector = new ChordalityInspector<>(graph, iterationOrder); assertEquals(iterationOrder, inspector.getIterationOrder()); } /** * Test on the big cycle */ @Test public void testGetChordlessCycle() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); int upperBound = 100; for (int i = 0; i < upperBound; i++) { Graphs.addEdgeWithVertices(graph, i, i + 1); } Graphs.addEdgeWithVertices(graph, 0, upperBound); ChordalityInspector inspector = new ChordalityInspector<>(graph); GraphPath path = inspector.getHole(); assertNotNull(path); assertIsHole(graph, path); } /** * Tests whether repeated calls to the {@link ChordalityInspector#getPerfectEliminationOrder()} * return the same vertex order. */ @Test public void testPerfectEliminationOrder() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalityInspector inspector = new ChordalityInspector<>(graph, iterationOrder); List order1 = inspector.getPerfectEliminationOrder(); assertNull(inspector.getHole()); graph.removeVertex(1); List order2 = inspector.getPerfectEliminationOrder(); assertEquals(order1, order2); assertNull(inspector.getHole()); } /** * Tests whether returned list is unmodifiable */ @Test(expected = UnsupportedOperationException.class) public void testUnmodifiableList() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); ChordalityInspector inspector = new ChordalityInspector<>(graph); List perfectEliminationOrder = inspector.getPerfectEliminationOrder(); perfectEliminationOrder.add(0); } /** * Test chordality inspection of an empty graph */ @Test public void testIsChordal1() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); ChordalityInspector inspector = new ChordalityInspector<>(graph); assertTrue(inspector.isChordal()); List perfectEliminationOrder = inspector.getPerfectEliminationOrder(); assertNotNull(perfectEliminationOrder); } /** * Test on chordal graph with 4 vertices:
    * 1--2
    * | \|
    * 3--4
    */ @Test public void testIsChordal2() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalityInspector inspector = new ChordalityInspector<>(graph); assertTrue(inspector.isChordal()); assertNull(inspector.getHole()); assertNotNull(inspector.getPerfectEliminationOrder()); } /** * Test on chordal graph with two connected components:
    * 1-2-3-1 and 4-5-6-4
    */ @Test public void testIsChordal3() { int[][] edges = { { 1, 2 }, { 2, 3 }, { 3, 1 }, { 4, 5 }, { 5, 6 }, { 6, 4 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalityInspector inspector = new ChordalityInspector<>(graph); assertTrue(inspector.isChordal()); assertNull(inspector.getHole()); assertNotNull(inspector.getPerfectEliminationOrder()); } /** * Test on chordal connected graph with 10 vertices */ @Test public void testIsChordal4() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 3 }, { 3, 4 }, { 3, 5 }, { 4, 5 }, { 5, 6 }, { 5, 7 }, { 6, 7 }, { 7, 8 }, { 7, 9 }, { 8, 9 }, { 9, 1 }, { 10, 1 }, { 3, 7 }, { 1, 7 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalityInspector inspector = new ChordalityInspector<>(graph); assertTrue(inspector.isChordal()); assertNull(inspector.getHole()); assertNotNull(inspector.getPerfectEliminationOrder()); } /** * Test on graph with 4-vertex cycle: 1-2-3-4-1 */ @Test public void testIsChordal5() { int[][] edges = { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 1 }, { 1, 5 }, { 5, 2 }, { 2, 6 }, { 6, 3 }, { 3, 7 }, { 7, 4 }, { 4, 8 }, { 8, 1 }, { 5, 6 }, { 6, 7 }, { 7, 8 }, { 8, 5 }, { 5, 7 }, { 6, 8 }, }; Graph graph = TestUtil.createUndirected(edges); ChordalityInspector inspector = new ChordalityInspector<>(graph); assertFalse(inspector.isChordal()); GraphPath path = inspector.getHole(); assertNotNull(path); assertIsHole(graph, path); assertNull(inspector.getPerfectEliminationOrder()); } /** * Test on the chordal pseudograph */ @Test public void testIsChordal6() { int[][] edges = { { 1, 1 }, { 1, 2 }, { 1, 2 }, { 1, 3 }, { 3, 1 }, { 2, 3 }, }; Graph graph = TestUtil.createPseudograph(edges); ChordalityInspector inspector = new ChordalityInspector<>(graph); assertTrue(inspector.isChordal()); assertNull(inspector.getHole()); assertNotNull(inspector.getPerfectEliminationOrder()); } /** * Test of non-chordal pseudograph (cycle 2-3-4-5-2) */ @Test public void testIsChordal7() { int[][] edges = { { 1, 1 }, { 1, 2 }, { 2, 1 }, { 2, 2 }, { 3, 3 }, { 4, 4 }, { 2, 3 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 2 }, }; Graph graph = TestUtil.createPseudograph(edges); ChordalityInspector inspector = new ChordalityInspector<>(graph); assertFalse(inspector.isChordal()); GraphPath path = inspector.getHole(); assertNotNull(path); assertIsHole(graph, path); assertNull(inspector.getPerfectEliminationOrder()); } /** * Test for correct hole detection */ @Test public void testIsChordal8() { Graph ellinghamHorton78 = NamedGraphGenerator.ellinghamHorton78Graph(); ChordalityInspector inspector = new ChordalityInspector<>(ellinghamHorton78); assertFalse(inspector.isChordal()); assertIsHole(ellinghamHorton78, inspector.getHole()); } /** * Test for correct hole detection */ @Test public void testIsChordal9() { Graph gosset = NamedGraphGenerator.gossetGraph(); ChordalityInspector inspector = new ChordalityInspector<>(gosset); assertFalse(inspector.isChordal()); assertIsHole(gosset, inspector.getHole()); } /** * Test for correct hole detection */ @Test public void testIsChordal10() { Graph klein = NamedGraphGenerator.klein3RegularGraph(); ChordalityInspector inspector = new ChordalityInspector<>(klein); assertFalse(inspector.isChordal()); assertIsHole(klein, inspector.getHole()); } /** * Test for correct hole detection */ @Test public void testIsChordal11() { Graph schläfli = NamedGraphGenerator.schläfliGraph(); ChordalityInspector inspector = new ChordalityInspector<>(schläfli); assertFalse(inspector.isChordal()); assertIsHole(schläfli, inspector.getHole()); } @Test public void testIsChordal12() { Graph buckyBall = NamedGraphGenerator.buckyBallGraph(); ChordalityInspector inspector = new ChordalityInspector<>(buckyBall); assertFalse(inspector.isChordal()); assertIsHole(buckyBall, inspector.getHole()); } /** * Basic test for {@link ChordalityInspector#isPerfectEliminationOrder(List)} */ @Test public void testIsPerfectEliminationOrder1() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 1, 4 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); List order = Arrays.asList(1, 2, 3, 4); assertFalse( new ChordalityInspector<>(graph, iterationOrder).isPerfectEliminationOrder(order)); } /** * First test on 4-vertex cycle: 1-2-3-4-1
    * Second test with chord 2-4 added, so that the graph becomes chordal */ @Test public void testIsPerfectEliminationOrder2() { int[][] edges = { { 1, 2 }, { 1, 4 }, { 2, 3 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); List order = Arrays.asList(1, 2, 4, 3); ChordalityInspector inspector = new ChordalityInspector<>(graph, iterationOrder); assertFalse( "Not a perfect elimination order: cycle 1->2->3->4->1 has non chord", inspector.isPerfectEliminationOrder(order)); graph.addEdge(2, 4); assertTrue( "Valid perfect elimination order: no induced cycles of length > 3", inspector.isPerfectEliminationOrder(order)); } /** * Test on chordal graph:
    * .......5
    * ...../.|.\
    * ....4--3--6--7
    * ....|./.|.|\.|
    * ....1--2..9--8
    * ...........\.|
    * ............10
    */ @Test public void testIsPerfectEliminationOrder3() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 1, 4 }, { 2, 3 }, { 3, 4 }, { 3, 5 }, { 3, 6 }, { 4, 5 }, { 5, 6 }, { 6, 7 }, { 6, 8 }, { 6, 9 }, { 7, 8 }, { 8, 9 }, { 8, 10 }, { 9, 10 }, }; Graph graph = TestUtil.createUndirected(edges); List order = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); assertTrue( new ChordalityInspector<>(graph, iterationOrder).isPerfectEliminationOrder(order)); } /** * Test on big chordal graph with valid perfect elimination order */ @Test public void testIsPerfectEliminationOrder4() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 1, 4 }, { 2, 3 }, { 3, 4 }, { 3, 5 }, { 3, 6 }, { 3, 7 }, { 4, 5 }, { 5, 6 }, { 5, 7 }, { 6, 7 }, { 6, 8 }, { 7, 9 }, { 7, 10 }, { 7, 11 }, { 9, 10 }, { 9, 11 }, { 9, 12 }, { 10, 11 }, { 11, 12 }, }; Graph graph = TestUtil.createUndirected(edges); List order = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12); assertTrue( "Valid perfect elimination order", new ChordalityInspector<>(graph, iterationOrder).isPerfectEliminationOrder(order)); } /** * Test on chordal graph with invalid perfect elimination order */ @Test public void testIsPerfectEliminationOrder5() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 }, { 4, 6 }, { 5, 6 }, }; Graph graph = TestUtil.createUndirected(edges); List order = Arrays.asList(1, 2, 5, 6, 4, 3); assertFalse( "Graph is chordal, order isn't perfect elimination order", new ChordalityInspector<>(graph, iterationOrder).isPerfectEliminationOrder(order)); } /** * Test on graph with 5-vertex cycle 2-4-6-8-10-2 with no chords */ @Test public void testIsPerfectEliminationOrder6() { int[][] edges = { { 1, 2 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 4, 5 }, { 4, 6 }, { 5, 6 }, { 6, 7 }, { 6, 8 }, { 7, 8 }, { 8, 9 }, { 8, 10 }, { 9, 10 }, { 10, 1 }, { 10, 2 }, }; Graph graph = TestUtil.createUndirected(edges); List order = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); assertFalse( "Cycle 2->4->6->8->10->2 has no chords => no perfect elimination order", new ChordalityInspector<>(graph, iterationOrder).isPerfectEliminationOrder(order)); } /** * Checks whether {@code cycle} is a hole in {@code graph} * * @param graph the tested graph. * @param path the tested cycle. * @param graph vertex type. * @param graph edge type. */ private void assertIsHole(Graph graph, GraphPath path) { List cycle = path.getVertexList(); assertTrue(cycle.size() > 4); for (int i = 0; i < cycle.size() - 1; i++) { assertTrue(graph.containsEdge(cycle.get(i), cycle.get(i + 1))); } for (int i = 0; i < cycle.size() - 2; i++) { for (int j = 0; j < cycle.size() - 2; j++) { if (Math.abs(i - j) > 1) { assertFalse(graph.containsEdge(cycle.get(i), cycle.get(j))); } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/CycleDetectorTest.java000066400000000000000000000133231402514743400322200ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * . * * @author John V. Sichi */ public class CycleDetectorTest { // ~ Static fields/initializers --------------------------------------------- private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String V4 = "v4"; private static final String V5 = "v5"; private static final String V6 = "v6"; private static final String V7 = "v7"; // ~ Methods ---------------------------------------------------------------- /** * . * * @param g */ public void createGraph(Graph g) { g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); g.addVertex(V6); g.addVertex(V7); g.addEdge(V1, V2); g.addEdge(V2, V3); g.addEdge(V3, V4); g.addEdge(V4, V1); g.addEdge(V4, V5); g.addEdge(V5, V6); g.addEdge(V1, V6); // test an edge which leads into a cycle, but where the source // is not itself part of a cycle g.addEdge(V7, V1); } /** * . */ @Test public void testDirectedWithCycle() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); createGraph(g); Set cyclicSet = new HashSet<>(); cyclicSet.add(V1); cyclicSet.add(V2); cyclicSet.add(V3); cyclicSet.add(V4); Set acyclicSet = new HashSet<>(); acyclicSet.add(V5); acyclicSet.add(V6); acyclicSet.add(V7); runTest(g, cyclicSet, acyclicSet); } /** * . */ @Test public void testDirectedWithDoubledCycle() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); // build the graph: vertex order is chosen specifically // to exercise old bug-cases in CycleDetector g.addVertex(V2); g.addVertex(V1); g.addVertex(V3); g.addEdge(V1, V2); g.addEdge(V2, V3); g.addEdge(V3, V1); g.addEdge(V2, V1); Set cyclicSet = new HashSet<>(); cyclicSet.add(V1); cyclicSet.add(V2); cyclicSet.add(V3); Set acyclicSet = new HashSet<>(); runTest(g, cyclicSet, acyclicSet); } /** * . */ @SuppressWarnings("unchecked") @Test public void testDirectedWithoutCycle() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); createGraph(g); g.removeVertex(V2); Set cyclicSet = Collections.EMPTY_SET; // hb: I would like // EMPTY_SET to be typed // as well... Set acyclicSet = g.vertexSet(); runTest(g, cyclicSet, acyclicSet); } private void runTest( Graph g, Set cyclicSet, Set acyclicSet) { CycleDetector detector = new CycleDetector<>(g); Set emptySet = Collections.emptySet(); assertEquals(!cyclicSet.isEmpty(), detector.detectCycles()); assertEquals(cyclicSet, detector.findCycles()); for (String v : cyclicSet) { assertEquals(true, detector.detectCyclesContainingVertex(v)); assertEquals(cyclicSet, detector.findCyclesContainingVertex(v)); } for (String v : acyclicSet) { assertEquals(false, detector.detectCyclesContainingVertex(v)); assertEquals(emptySet, detector.findCyclesContainingVertex(v)); } } @Test public void testVertexEquals() { DefaultDirectedGraph graph = new DefaultDirectedGraph<>(DefaultEdge.class); assertEquals(0, graph.edgeSet().size()); String vertexA = "A"; String vertexB = "B"; String vertexC = new String("A"); assertNotSame(vertexA, vertexC); graph.addVertex(vertexA); graph.addVertex(vertexB); graph.addEdge(vertexA, vertexB); graph.addEdge(vertexB, vertexC); assertEquals(2, graph.edgeSet().size()); assertEquals(2, graph.vertexSet().size()); CycleDetector cycleDetector = new CycleDetector<>(graph); Set cycleVertices = cycleDetector.findCycles(); boolean foundCycle = cycleDetector.detectCyclesContainingVertex(vertexA); boolean foundVertex = graph.containsVertex(vertexA); Set subCycle = cycleDetector.findCyclesContainingVertex(vertexA); assertEquals(2, cycleVertices.size()); assertEquals(2, subCycle.size()); // fails with zero items assertTrue(foundCycle); // fails with no cycle found which includes // vertexA assertTrue(foundVertex); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/CyclesTest.java000066400000000000000000000133431402514743400307130ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit tests for {@link Cycles}. * * @author Dimitrios Michail */ public class CyclesTest { @Test public void testUndirected1() { Graph graph = new SimpleGraph<>(DefaultEdge.class); List cycle = new ArrayList<>(); cycle.add(Graphs.addEdgeWithVertices(graph, 0, 1)); cycle.add(Graphs.addEdgeWithVertices(graph, 1, 2)); cycle.add(Graphs.addEdgeWithVertices(graph, 2, 0)); GraphPath graphPath = Cycles.simpleCycleToGraphPath(graph, cycle); assertEquals(graphPath.getStartVertex(), graphPath.getEndVertex()); assertUndirectedCycle(graphPath.getGraph(), graphPath.getEdgeList()); } @Test public void testUndirected2() { Graph graph = new SimpleGraph<>(DefaultEdge.class); List cycle = new ArrayList<>(); cycle.add(Graphs.addEdgeWithVertices(graph, 1, 2)); cycle.add(Graphs.addEdgeWithVertices(graph, 3, 4)); cycle.add(Graphs.addEdgeWithVertices(graph, 0, 1)); cycle.add(Graphs.addEdgeWithVertices(graph, 4, 5)); cycle.add(Graphs.addEdgeWithVertices(graph, 5, 0)); cycle.add(Graphs.addEdgeWithVertices(graph, 2, 3)); Graphs.addEdgeWithVertices(graph, 5, 6); Graphs.addEdgeWithVertices(graph, 6, 7); GraphPath graphPath = Cycles.simpleCycleToGraphPath(graph, cycle); assertEquals(graphPath.getStartVertex(), graphPath.getEndVertex()); assertUndirectedCycle(graphPath.getGraph(), graphPath.getEdgeList()); } @Test public void testSelfLoop1() { Graph graph = new Pseudograph<>(DefaultEdge.class); List cycle = new ArrayList<>(); cycle.add(Graphs.addEdgeWithVertices(graph, 0, 0)); GraphPath graphPath = Cycles.simpleCycleToGraphPath(graph, cycle); assertEquals(graphPath.getStartVertex(), graphPath.getEndVertex()); assertUndirectedCycle(graphPath.getGraph(), graphPath.getEdgeList()); } @Test public void testDirected1() { Graph graph = new DirectedPseudograph<>(DefaultEdge.class); List cycle = new ArrayList<>(); cycle.add(Graphs.addEdgeWithVertices(graph, 0, 1)); cycle.add(Graphs.addEdgeWithVertices(graph, 3, 2)); cycle.add(Graphs.addEdgeWithVertices(graph, 3, 4)); cycle.add(Graphs.addEdgeWithVertices(graph, 1, 2)); cycle.add(Graphs.addEdgeWithVertices(graph, 0, 4)); GraphPath graphPath = Cycles.simpleCycleToGraphPath(graph, cycle); assertEquals(graphPath.getStartVertex(), graphPath.getEndVertex()); assertUndirectedCycle(graphPath.getGraph(), graphPath.getEdgeList()); } @Test(expected = IllegalArgumentException.class) public void testUndirectedNotSimple1() { Graph graph = new SimpleGraph<>(DefaultEdge.class); List cycle = new ArrayList<>(); cycle.add(Graphs.addEdgeWithVertices(graph, 0, 1)); cycle.add(Graphs.addEdgeWithVertices(graph, 1, 2)); cycle.add(Graphs.addEdgeWithVertices(graph, 2, 0)); cycle.add(Graphs.addEdgeWithVertices(graph, 2, 3)); cycle.add(Graphs.addEdgeWithVertices(graph, 3, 4)); cycle.add(Graphs.addEdgeWithVertices(graph, 4, 2)); Cycles.simpleCycleToGraphPath(graph, cycle); } // assert that a list of edges is a cycle (without respecting edge directions) private void assertUndirectedCycle(Graph g, List edges) { if (edges.isEmpty()) { return; } DefaultEdge prev = null; DefaultEdge first = null, last = null; Iterator it = edges.iterator(); Set dupCheck = new HashSet<>(); while (it.hasNext()) { DefaultEdge cur = it.next(); assertTrue(dupCheck.add(cur)); if (prev == null) { first = cur; } else { assertTrue( g.getEdgeSource(cur).equals(g.getEdgeSource(prev)) || g.getEdgeSource(cur).equals(g.getEdgeTarget(prev)) || g.getEdgeTarget(cur).equals(g.getEdgeSource(prev)) || g.getEdgeTarget(cur).equals(g.getEdgeTarget(prev))); } if (!it.hasNext()) { last = cur; } prev = cur; } if (edges.size() > 1) { assertTrue( g.getEdgeSource(first).equals(g.getEdgeSource(last)) || g.getEdgeSource(first).equals(g.getEdgeTarget(last)) || g.getEdgeTarget(first).equals(g.getEdgeSource(last)) || g.getEdgeTarget(first).equals(g.getEdgeTarget(last))); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/DirectedSimpleCyclesTest.java000066400000000000000000000120661402514743400335320ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.function.*; import static org.junit.Assert.assertTrue; public class DirectedSimpleCyclesTest { private static int MAX_SIZE = 9; private static int[] RESULTS = { 0, 1, 3, 8, 24, 89, 415, 2372, 16072, 125673 }; @Test public void test() { testAlgorithm(g -> new TiernanSimpleCycles(g)); testAlgorithm(g -> new TarjanSimpleCycles(g)); testAlgorithm(g -> new JohnsonSimpleCycles(g)); testAlgorithm(g -> new SzwarcfiterLauerSimpleCycles(g)); testAlgorithm(g -> new HawickJamesSimpleCycles(g)); testAlgorithmWithWeightedGraph( g -> new TiernanSimpleCycles(g)); testAlgorithmWithWeightedGraph( g -> new TarjanSimpleCycles(g)); testAlgorithmWithWeightedGraph( g -> new JohnsonSimpleCycles(g)); testAlgorithmWithWeightedGraph( g -> new SzwarcfiterLauerSimpleCycles(g)); testAlgorithmWithWeightedGraph( g -> new HawickJamesSimpleCycles(g)); } private void testAlgorithm( Function, DirectedSimpleCycles> algProvider) { Graph graph = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); for (int i = 0; i < 7; i++) { graph.addVertex(i); } DirectedSimpleCycles alg = algProvider.apply(graph); graph.addEdge(0, 0); assertTrue(alg.findSimpleCycles().size() == 1); graph.addEdge(1, 1); assertTrue(alg.findSimpleCycles().size() == 2); graph.addEdge(0, 1); graph.addEdge(1, 0); assertTrue(alg.findSimpleCycles().size() == 3); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 0); assertTrue(alg.findSimpleCycles().size() == 4); graph.addEdge(6, 6); assertTrue(alg.findSimpleCycles().size() == 5); for (int size = 1; size <= MAX_SIZE; size++) { graph = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); for (int i = 0; i < size; i++) { graph.addVertex(i); } for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { graph.addEdge(i, j); } } alg = algProvider.apply(graph); assertTrue(alg.findSimpleCycles().size() == RESULTS[size]); } } private void testAlgorithmWithWeightedGraph( Function, DirectedSimpleCycles> algProvider) { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); for (int i = 0; i < 7; i++) { graph.addVertex(i); } DirectedSimpleCycles alg = algProvider.apply(graph); graph.addEdge(0, 0); assertTrue(alg.findSimpleCycles().size() == 1); graph.addEdge(1, 1); assertTrue(alg.findSimpleCycles().size() == 2); graph.addEdge(0, 1); graph.addEdge(1, 0); assertTrue(alg.findSimpleCycles().size() == 3); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 0); assertTrue(alg.findSimpleCycles().size() == 4); graph.addEdge(6, 6); assertTrue(alg.findSimpleCycles().size() == 5); for (int size = 1; size <= MAX_SIZE; size++) { graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); for (int i = 0; i < size; i++) { graph.addVertex(i); } for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { graph.addEdge(i, j); } } alg = algProvider.apply(graph); assertTrue(alg.findSimpleCycles().size() == RESULTS[size]); } } } HawickJamesSimpleCyclesTest.java000066400000000000000000000252641402514743400341220ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/* * (C) Copyright 2013-2021, by Nikolay Ognyanov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit tests for class {@link HawickJamesSimpleCycles}. * * @author Edwin Ouwehand */ public class HawickJamesSimpleCyclesTest { @Test public void noCyclesCount() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addEdge("A", "B"); graph.addEdge("B", "C"); assertEquals(0, new HawickJamesSimpleCycles<>(graph).countSimpleCycles()); } @Test public void reflexiveCycleCount() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addEdge("A", "A"); assertEquals(1, new HawickJamesSimpleCycles<>(graph).countSimpleCycles()); } @Test public void singleDirectCycleCount() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addEdge("A", "B"); graph.addEdge("B", "A"); assertEquals(1, new HawickJamesSimpleCycles<>(graph).countSimpleCycles()); } @Test public void indirectCycleCount() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addEdge("A", "B"); graph.addEdge("B", "C"); graph.addEdge("C", "A"); assertEquals(1, new HawickJamesSimpleCycles<>(graph).countSimpleCycles()); } @Test public void noCyclesFind() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addEdge("A", "B"); graph.addEdge("B", "C"); assertTrue(new HawickJamesSimpleCycles<>(graph).findSimpleCycles().isEmpty()); } @Test public void reflexiveCycleFind() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addEdge("A", "A"); List> cycles = new HawickJamesSimpleCycles<>(graph).findSimpleCycles(); assertEquals(1, cycles.size()); assertEquals(singletonList("A"), cycles.get(0)); } @Test public void singleDirectFind() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addEdge("A", "B"); graph.addEdge("B", "A"); List> cycles = new HawickJamesSimpleCycles<>(graph).findSimpleCycles(); assertEquals(1, cycles.size()); assertTrue(cycles.get(0).containsAll(asList("A", "B"))); } @Test public void indirectCycleFind() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addEdge("A", "B"); graph.addEdge("B", "C"); graph.addEdge("C", "A"); List> cycles = new HawickJamesSimpleCycles<>(graph).findSimpleCycles(); assertEquals(1, cycles.size()); assertTrue(cycles.get(0).containsAll(asList("A", "B", "C"))); } @Test public void twoCycles() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addEdge("A", "B"); graph.addEdge("B", "A"); graph.addEdge("B", "C"); graph.addEdge("C", "A"); List> cycles = new HawickJamesSimpleCycles<>(graph).findSimpleCycles(); assertEquals(2, cycles.size()); assertTrue(cycles.get(0).containsAll(asList("A", "B"))); assertTrue(cycles.get(1).containsAll(asList("A", "B", "C"))); } @Test public void twoSharingEdge() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addVertex("D"); graph.addEdge("B", "C"); // Shared graph.addEdge("A", "B"); graph.addEdge("C", "A"); graph.addEdge("D", "B"); graph.addEdge("C", "D"); List> cycles = new HawickJamesSimpleCycles<>(graph).findSimpleCycles(); assertEquals(2, cycles.size()); assertTrue(cycles.get(0).containsAll(asList("A", "B", "C"))); assertTrue(cycles.get(1).containsAll(asList("D", "B", "C"))); } @Test public void simplestCycles() { // We do NOT want to find A -> B, B -> B, B -> A as an additional cycle here, // nor B -> A, A -> A, A -> B for that matter. Only the most simple ones. Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addEdge("A", "B"); graph.addEdge("B", "A"); graph.addEdge("A", "A"); graph.addEdge("B", "B"); List> cycles = new HawickJamesSimpleCycles<>(graph).findSimpleCycles(); assertEquals(3, cycles.size()); assertEquals(asList("A", "B"), cycles.get(0)); assertEquals(singletonList("A"), cycles.get(1)); assertEquals(singletonList("B"), cycles.get(2)); } @Test public void complexGraph() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, asList("A", "B", "C", "D", "E", "F")); graph.addEdge("A", "B"); graph.addEdge("B", "C"); graph.addEdge("B", "E"); graph.addEdge("C", "D"); graph.addEdge("D", "E"); graph.addEdge("E", "F"); graph.addEdge("F", "A"); List> cycles = new HawickJamesSimpleCycles<>(graph).findSimpleCycles(); assertEquals(2, cycles.size()); List cycle0 = cycles.get(0); assertTrue(cycle0.containsAll(asList("A", "B", "C", "D", "E", "F"))); List cycle1 = cycles.get(1); assertTrue(cycle1.containsAll(asList("A", "B", "E", "F"))); } @Test public void consecutiveRuns() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addEdge("A", "B"); graph.addEdge("B", "C"); graph.addEdge("C", "A"); HawickJamesSimpleCycles hjsc = new HawickJamesSimpleCycles<>(graph); List> run1 = hjsc.findSimpleCycles(); assertEquals(1, run1.size()); assertTrue(run1.get(0).containsAll(asList("A", "B", "C"))); List> run2 = hjsc.findSimpleCycles(); assertEquals(1, run2.size()); assertTrue(run2.get(0).containsAll(asList("A", "B", "C"))); } @Test public void limitPaths1() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addEdge("A", "B"); graph.addEdge("B", "A"); HawickJamesSimpleCycles hjsc = new HawickJamesSimpleCycles<>(graph); hjsc.setPathLimit(1); assertTrue(hjsc.findSimpleCycles().isEmpty()); } @Test public void limitPaths2() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addEdge("A", "B"); graph.addEdge("B", "C"); graph.addEdge("C", "A"); HawickJamesSimpleCycles hjsc = new HawickJamesSimpleCycles<>(graph); hjsc.setPathLimit(2); assertTrue(hjsc.findSimpleCycles().isEmpty()); } @Test public void limitPathsTwoCycles() { // Two smaller cycles are still found Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addVertex("D"); graph.addEdge("A", "B"); graph.addEdge("B", "A"); graph.addEdge("C", "D"); graph.addEdge("D", "C"); HawickJamesSimpleCycles hjsc = new HawickJamesSimpleCycles<>(graph); hjsc.setPathLimit(2); List> cycles = hjsc.findSimpleCycles(); assertEquals(2, cycles.size()); assertTrue(cycles.get(0).containsAll(asList("A", "B"))); assertTrue(cycles.get(1).containsAll(asList("C", "D"))); } @Test public void testOrder() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("0"); graph.addVertex("1"); graph.addVertex("2"); graph.addVertex("3"); graph.addVertex("4"); graph.addVertex("5"); graph.addEdge("0", "1"); graph.addEdge("1", "2"); graph.addEdge("2", "3"); graph.addEdge("3", "0"); graph.addEdge("1", "4"); graph.addEdge("4", "5"); graph.addEdge("5", "2"); HawickJamesSimpleCycles hjsc = new HawickJamesSimpleCycles<>(graph); List> cycles = hjsc.findSimpleCycles(); assertEquals(2, cycles.size()); String cycle0 = cycles.get(0).stream().collect(Collectors.joining(",")); String cycle1 = cycles.get(1).stream().collect(Collectors.joining(",")); assertEquals(cycle0, "0,1,2,3"); assertEquals(cycle1, "0,1,4,5,2,3"); } } HierholzerEulerianCycleTest.java000066400000000000000000000660741402514743400342030ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.*; /** * Tests for class {@link HierholzerEulerianCycle}. * * @author Dimitrios Michail */ public class HierholzerEulerianCycleTest { @Test public void testNullEulerian() { Graph g1 = new Pseudograph<>(DefaultEdge.class); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g1)); Graph g2 = new DirectedPseudograph<>(DefaultEdge.class); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g2)); } @Test public void testEmptyEulerian() { Graph g1 = new Pseudograph<>(DefaultEdge.class); g1.addVertex(1); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g1)); g1.addVertex(2); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g1)); g1.addVertex(3); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g1)); Graph g2 = new DirectedPseudograph<>(DefaultEdge.class); g2.addVertex(1); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g2)); g2.addVertex(2); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g2)); g2.addVertex(3); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g2)); } @Test public void testUndirectedDisconnectedEulerian() { Graph g = new Pseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addVertex(6); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(4, 2); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testUndirectedDisconnectedNonEulerian() { Graph g = new Pseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addVertex(6); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(4, 2); g.addEdge(5, 6); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testDirectedDisconnectedEulerian() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addVertex(6); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(4, 2); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testDirectedDisconnectedNonEulerian() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addVertex(6); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(4, 2); g.addEdge(5, 6); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testUndirectedEulerian1() { // complete graph of 6 vertices Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator gen = new CompleteGraphGenerator<>(6); gen.generateGraph(g); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testUndirectedEulerian2() { // even degrees but disconnected Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testUndirectedEulerian3() { // even degrees Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); g.addEdge(3, 4); g.addEdge(3, 4); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testUndirectedEulerian4() { // even degrees Graph g = new Pseudograph<>(DefaultEdge.class); g.addVertex(1); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testUndirectedEulerian5() { // with loops Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); g.addEdge(3, 4); g.addEdge(3, 4); IntStream.rangeClosed(1, 6).forEach(i -> g.addEdge(i, i)); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testUndirectedEulerian6() { // with loops Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); IntStream.rangeClosed(1, 6).forEach(i -> g.addEdge(i, i)); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testUndirectedEulerian7() { // complete graph of 5 vertices Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator gen = new CompleteGraphGenerator<>(5); gen.generateGraph(g); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testDirectedEulerian1() { // complete graph of 6 vertices Graph g1 = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator gen1 = new CompleteGraphGenerator<>(6); gen1.generateGraph(g1); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g1)); // complete graph of 7 vertices Graph g2 = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator gen2 = new CompleteGraphGenerator<>(7); gen2.generateGraph(g2); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g2)); } @Test public void testDirectedEulerian2() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1)); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); g.addEdge(1, 1); g.addEdge(1, 1); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); Graphs.addAllVertices(g, Arrays.asList(2)); g.addEdge(2, 1); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testDirectedEulerian3() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); g.addEdge(2, 1); g.addEdge(3, 2); g.addEdge(4, 3); g.addEdge(5, 4); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g)); g.addEdge(1, 1); g.addEdge(2, 2); g.addEdge(3, 3); g.addEdge(4, 4); g.addEdge(5, 5); Assert.assertFalse(new HierholzerEulerianCycle().isEulerian(g)); g.addEdge(1, 5); Assert.assertTrue(new HierholzerEulerianCycle().isEulerian(g)); } @Test public void testEmptyWithSingleVertexUndirected() { Graph g = new Pseudograph<>(DefaultEdge.class); g.addVertex(1); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEmptyMultipleVerticesUndirected() { Graph g = new Pseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEmptyWithSingleVertexDirected() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEmptyMultipleVerticesDirected() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEulerianCycleUndirected1() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); g.addEdge(3, 4); g.addEdge(3, 4); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEulerianCycleUndirected2() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); g.addEdge(3, 4); g.addEdge(3, 4); g.addEdge(5, 7); g.addEdge(5, 7); g.addEdge(7, 8); g.addEdge(7, 8); g.addEdge(5, 8); g.addEdge(5, 8); g.addEdge(8, 8); g.addEdge(8, 8); g.addEdge(3, 3); g.addEdge(3, 3); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEulerianCycleUndirected3() { final long seed = 17; Random rng = new Random(seed); for (int size = 13; size < 52; size += 2) { Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator gen = new CompleteGraphGenerator<>(size); gen.generateGraph(g); for (Integer v : g.vertexSet()) { IntStream.rangeClosed(0, rng.nextInt(10)).forEach(i -> g.addEdge(v, v)); } List edges = new ArrayList<>(g.edgeSet()); for (DefaultEdge e : edges) { IntStream .rangeClosed(0, 2 * rng.nextInt(10)) .forEach(i -> g.addEdge(g.getEdgeSource(e), g.getEdgeTarget(e))); } GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } } @Test public void testEulerianCycleUndirected4() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); g.addEdge(3, 4); g.addEdge(3, 4); g.addEdge(5, 7); g.addEdge(5, 7); g.addEdge(7, 8); g.addEdge(7, 8); g.addEdge(5, 8); g.addEdge(5, 8); g.addEdge(8, 8); g.addEdge(8, 8); g.addEdge(3, 3); g.addEdge(3, 3); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testRandomUndirected() { final int tests = 100; final int size = 50; final double p = 0.7; Random rng = new Random(); GnpRandomGraphGenerator rgg = new GnpRandomGraphGenerator<>(size, p, rng, true); for (int i = 0; i < tests; i++) { Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); rgg.generateGraph(g); // add one extra copy for each edge List edges = new ArrayList<>(g.edgeSet()); for (DefaultEdge e : edges) { g.addEdge(g.getEdgeTarget(e), g.getEdgeSource(e)); } // randomly add more loops for (Integer v : g.vertexSet()) { IntStream.rangeClosed(0, rng.nextInt(10)).forEach(j -> g.addEdge(v, v)); } GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } } @Test public void testRandomUndirectedFixedSeed() { final int tests = 100; final int size = 50; final long seed = 17; final double p = 0.7; GnpRandomGraphGenerator rgg = new GnpRandomGraphGenerator<>(size, p, seed); for (int i = 0; i < tests; i++) { Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); rgg.generateGraph(g); List edges = new ArrayList<>(g.edgeSet()); for (DefaultEdge e : edges) { g.addEdge(g.getEdgeTarget(e), g.getEdgeSource(e)); } GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } } @Test public void testEulerianCycleUndirectedVertexList() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(4, 3, 2)); DefaultEdge e42 = g.addEdge(4, 2); DefaultEdge e34 = g.addEdge(3, 4); DefaultEdge e32 = g.addEdge(3, 2); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEquals(e32, cycle.getEdgeList().get(0)); assertEquals(e34, cycle.getEdgeList().get(1)); assertEquals(e42, cycle.getEdgeList().get(2)); List vl = cycle.getVertexList(); assertEquals(2, vl.get(0).intValue()); assertEquals(3, vl.get(1).intValue()); assertEquals(4, vl.get(2).intValue()); assertEquals(2, vl.get(3).intValue()); assertEulerian(cycle); } @Test public void testEulerianCycleDirected1() { // even degrees Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); g.addEdge(3, 4); g.addEdge(4, 3); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEulerianCycleDirected2() { // even degrees Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); g.addEdge(3, 4); g.addEdge(4, 3); g.addEdge(5, 7); g.addEdge(7, 8); g.addEdge(8, 5); g.addEdge(5, 7); g.addEdge(7, 8); g.addEdge(8, 5); g.addEdge(8, 8); g.addEdge(8, 8); g.addEdge(8, 8); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEulerianCycleDirected3() { // even degrees Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(2, 4); g.addEdge(4, 2); g.addEdge(3, 6); g.addEdge(3, 5); g.addEdge(5, 3); g.addEdge(6, 8); g.addEdge(6, 7); g.addEdge(7, 6); g.addEdge(8, 1); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEulerianCycleDirected4() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)); g.addEdge(1, 2); g.addEdge(2, 4); g.addEdge(2, 3); g.addEdge(4, 2); g.addEdge(3, 5); g.addEdge(3, 6); g.addEdge(5, 3); g.addEdge(6, 7); g.addEdge(6, 8); g.addEdge(7, 6); g.addEdge(8, 1); assertEulerian(new HierholzerEulerianCycle().getEulerianCycle(g)); } @Test public void testEulerianCycleDirected5() { // even degrees Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(3, 1); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 4); g.addEdge(3, 4); g.addEdge(4, 3); GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } @Test public void testEulerianCycleDirected() { final long seed = 17; Random rng = new Random(seed); for (int size = 5; size < 52; size += 2) { Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator gen = new CompleteGraphGenerator<>(size); gen.generateGraph(g); for (Integer v : g.vertexSet()) { IntStream.rangeClosed(0, rng.nextInt(10)).forEach(i -> g.addEdge(v, v)); } List edges = new ArrayList<>(g.edgeSet()); for (DefaultEdge e : edges) { IntStream.rangeClosed(0, 2 * rng.nextInt(10)).forEach(i -> { g.addEdge(g.getEdgeSource(e), g.getEdgeTarget(e)); g.addEdge(g.getEdgeTarget(e), g.getEdgeSource(e)); }); } GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } } @Test public void testRandomDirected() { final int tests = 100; final int size = 50; final double p = 0.7; Random rng = new Random(); GnpRandomGraphGenerator rgg = new GnpRandomGraphGenerator<>(size, p, rng, true); for (int i = 0; i < tests; i++) { Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); rgg.generateGraph(g); List edges = new ArrayList<>(g.edgeSet()); for (DefaultEdge e : edges) { g.addEdge(g.getEdgeTarget(e), g.getEdgeSource(e)); } // randomly add more loops for (Integer v : g.vertexSet()) { IntStream.rangeClosed(0, rng.nextInt(10)).forEach(j -> g.addEdge(v, v)); } GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } } @Test public void testRandomDirectedFixedSeed() { final int tests = 100; final int size = 50; final long seed = 17; final double p = 0.7; GnpRandomGraphGenerator rgg = new GnpRandomGraphGenerator<>(size, p, seed); for (int i = 0; i < tests; i++) { Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); rgg.generateGraph(g); List edges = new ArrayList<>(g.edgeSet()); for (DefaultEdge e : edges) { g.addEdge(g.getEdgeTarget(e), g.getEdgeSource(e)); } GraphPath cycle = new HierholzerEulerianCycle().getEulerianCycle(g); assertEulerian(cycle); } } @Test public void testPseudograph() { /* * Test for issue 388 on github. */ Graph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList('A', 'B', 'C', 'D', 'E')); Graphs.addEdge(g, 'A', 'B', 8); Graphs.addEdge(g, 'A', 'C', 5); Graphs.addEdge(g, 'A', 'D', 6); Graphs.addEdge(g, 'B', 'C', 5); Graphs.addEdge(g, 'B', 'E', 6); Graphs.addEdge(g, 'C', 'D', 5); Graphs.addEdge(g, 'C', 'E', 5); Graphs.addEdge(g, 'D', 'E', 8); Graphs.addEdge(g, 'A', 'D', 8); Graphs.addEdge(g, 'B', 'E', 8); GraphPath gp = new HierholzerEulerianCycle().getEulerianCycle(g); assertEquals('E', gp.getStartVertex().charValue()); assertEquals("[E, B, E, D, A, D, C, B, A, C, E]", gp.getVertexList().toString()); assertEulerian(gp); } // assert that a cycle is Eulerian private static void assertEulerian(GraphPath cycle) { assertNotNull(cycle.getGraph()); Graph g = cycle.getGraph(); assertTrue(GraphTests.isEulerian(g)); if (g.vertexSet().isEmpty()) { // we do not consider the null-graph to be connected assertTrue(false); } else if (GraphTests.isEmpty(g)) { assertTrue(cycle.getStartVertex() == null); assertTrue(cycle.getEndVertex() == null); assertTrue(cycle.getEdgeList().isEmpty()); } else { boolean isDirected = g.getType().isDirected(); assertNotNull(cycle.getStartVertex()); assertEquals(cycle.getStartVertex(), cycle.getEndVertex()); assertEquals(g.edgeSet().size(), cycle.getLength()); E prev = null; Iterator it = cycle.getEdgeList().iterator(); Set dupCheck = new HashSet<>(); while (it.hasNext()) { E cur = it.next(); assertTrue(dupCheck.add(cur)); if (prev != null) { if (isDirected) { assertTrue(g.getEdgeSource(cur).equals(g.getEdgeTarget(prev))); } else { assertTrue( g.getEdgeSource(cur).equals(g.getEdgeSource(prev)) || g.getEdgeSource(cur).equals(g.getEdgeTarget(prev)) || g.getEdgeTarget(cur).equals(g.getEdgeSource(prev)) || g.getEdgeTarget(cur).equals(g.getEdgeTarget(prev))); } } prev = cur; } } } } HowardMinimumMeanCycleTest.java000066400000000000000000000203131402514743400337460ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.Graph; import org.jgrapht.GraphPath; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.DirectedWeightedPseudograph; import org.jgrapht.graph.GraphWalk; import org.junit.Test; import java.util.ArrayList; import java.util.List; import static org.junit.Assert.assertEquals; /** * Test for {@link HowardMinimumMeanCycle}. */ public class HowardMinimumMeanCycleTest { // test graph instances private int[][] graph1 = { { 1, 2, 1 }, { 1, 3, 10 }, { 2, 3, 3 }, { 3, 4, 2 }, { 4, 1, 8 }, { 4, 2, 0 } }; private int[][] graph2 = { { 1, 3, 7 }, { 3, 2, 3 }, { 2, 0, 7 }, { 2, 1, 5 } }; private int[][] graph3 = { { 0, 2, 16 }, { 0, 3, 0 }, { 3, 0, 14 }, { 5, 0, 16 }, { 0, 8, 12 }, { 5, 1, 13 }, { 1, 6, 4 }, { 6, 1, 15 }, { 7, 1, 2 }, { 1, 9, 8 }, { 2, 6, 3 }, { 7, 2, 15 }, { 9, 2, 10 }, { 3, 6, 1 }, { 3, 8, 8 }, { 8, 3, 18 }, { 4, 6, 7 }, { 4, 9, 13 }, { 5, 6, 3 }, { 8, 6, 7 }, { 7, 8, 7 }, { 9, 7, 17 } }; private int[][] graph4 = { { 0, 3, 19 }, { 4, 0, 0 }, { 0, 5, 8 }, { 5, 0, 17 }, { 0, 7, 10 }, { 8, 0, 15 }, { 1, 4, 14 }, { 7, 1, 10 }, { 3, 2, 14 }, { 2, 4, 3 }, { 2, 5, 1 }, { 2, 9, 1 }, { 5, 3, 18 }, { 6, 3, 4 }, { 3, 7, 2 }, { 8, 3, 8 }, { 5, 4, 17 }, { 6, 4, 5 }, { 8, 4, 15 }, { 9, 4, 17 }, { 6, 5, 1 }, { 5, 7, 19 }, { 9, 5, 12 }, { 6, 8, 15 }, { 8, 6, 19 }, { 7, 9, 6 } }; private int[][] graph5 = { { 6, 0, 11 }, { 8, 0, 5 }, { 4, 1, 3 }, { 2, 3, 6 }, { 7, 2, 9 }, { 3, 4, 19 }, { 3, 9, 6 }, { 4, 9, 8 }, { 6, 5, 5 }, { 5, 9, 16 }, { 6, 7, 16 }, { 6, 8, 12 }, { 8, 9, 12 } }; private int[][] graph6 = { { 0, 2, 16 }, { 0, 3, 0 }, { 3, 0, 14 }, { 5, 0, 16 }, { 0, 8, 12 }, { 13, 0, 13 }, { 0, 14, 4 }, { 14, 0, 15 }, { 2, 1, 2 }, { 1, 4, 8 }, { 1, 8, 3 }, { 9, 1, 15 }, { 11, 1, 10 }, { 1, 14, 1 }, { 2, 4, 8 }, { 4, 2, 18 }, { 2, 7, 7 }, { 2, 10, 13 }, { 2, 11, 3 }, { 5, 3, 7 }, { 3, 7, 7 }, { 8, 3, 17 }, { 3, 12, 19 }, { 13, 3, 0 }, { 3, 14, 8 }, { 14, 3, 17 }, { 4, 6, 10 }, { 7, 4, 15 }, { 4, 11, 14 }, { 14, 4, 10 }, { 8, 5, 14 }, { 5, 9, 3 }, { 5, 10, 1 }, { 5, 14, 1 }, { 8, 6, 18 }, { 9, 6, 4 }, { 6, 10, 2 }, { 11, 6, 8 }, { 13, 6, 17 }, { 14, 6, 5 }, { 9, 7, 15 }, { 10, 7, 17 }, { 11, 7, 1 }, { 7, 12, 19 }, { 14, 7, 12 }, { 8, 10, 15 }, { 10, 8, 19 }, { 8, 13, 6 }, { 9, 10, 2 }, { 12, 9, 17 }, { 13, 9, 8 }, { 10, 13, 3 }, { 11, 14, 2 }, { 12, 14, 5 }, { 14, 12, 13 }, { 14, 13, 5 } }; private int[][] graph7 = { { 0, 1, 5 }, { 2, 0, 8 }, { 0, 3, 8 }, { 4, 0, 10 }, { 0, 5, 7 }, { 5, 0, 8 }, { 6, 0, 2 }, { 8, 0, 7 }, { 11, 0, 3 }, { 0, 14, 1 }, { 2, 1, 12 }, { 1, 6, 16 }, { 1, 12, 11 }, { 12, 1, 18 }, { 1, 13, 10 }, { 2, 3, 9 }, { 2, 4, 9 }, { 4, 2, 12 }, { 5, 2, 15 }, { 7, 2, 10 }, { 2, 8, 10 }, { 8, 2, 8 }, { 2, 10, 12 }, { 2, 12, 6 }, { 12, 2, 10 }, { 3, 5, 13 }, { 3, 9, 8 }, { 11, 3, 8 }, { 13, 3, 2 }, { 7, 4, 17 }, { 8, 4, 17 }, { 12, 4, 11 }, { 5, 6, 13 }, { 8, 5, 8 }, { 9, 5, 2 }, { 5, 10, 11 }, { 5, 11, 6 }, { 5, 12, 12 }, { 12, 5, 17 }, { 5, 13, 13 }, { 6, 8, 7 }, { 6, 9, 17 }, { 6, 13, 4 }, { 6, 14, 15 }, { 14, 6, 19 }, { 7, 8, 18 }, { 8, 7, 19 }, { 7, 11, 18 }, { 11, 7, 8 }, { 12, 7, 10 }, { 7, 13, 4 }, { 13, 7, 17 }, { 8, 9, 15 }, { 9, 8, 8 }, { 9, 12, 11 }, { 9, 14, 3 }, { 10, 11, 1 }, { 11, 10, 12 }, { 10, 13, 17 }, { 11, 12, 2 }, { 12, 11, 18 }, { 11, 13, 9 }, { 13, 11, 5 }, { 11, 14, 3 }, { 14, 12, 8 }, { 14, 13, 9 } }; // expected mean values private double expectedMean1 = 1.6666666666666667; private double expectedMean2 = 5.0; private double expectedMean3 = 7; private double expectedMean4 = 8.25; private double expectedMean5 = Double.POSITIVE_INFINITY; private double expectedMean6 = 3.6000000000000001; private double expectedMean7 = 4.4285714285714288; // expected minimum mean path for graph instance private int[][] expectedCycle1 = { { 2, 3 }, { 3, 4 }, { 4, 2 } }; private int[][] expectedCycle2 = { { 1, 3 }, { 3, 2 }, { 2, 1 } }; private int[][] expectedCycle3 = { { 0, 3 }, { 3, 0 } }; private int[][] expectedCycle4 = { { 0, 7 }, { 7, 9 }, { 9, 4, }, { 4, 0 } }; private int[][] expectedCycle5 = null; private int[][] expectedCycle6 = { { 14, 6 }, { 6, 10 }, { 10, 13 }, { 13, 3 }, { 3, 14 } }; private int[][] expectedCycle7 = { { 0, 14 }, { 14, 13 }, { 13, 3 }, { 3, 9 }, { 9, 5 }, { 5, 11 }, { 11, 0 } }; @Test public void testGraph1() { testOnGraph(graph1, expectedMean1, expectedCycle1); } @Test public void testGraph2() { testOnGraph(graph2, expectedMean2, expectedCycle2); } @Test public void testGraph3() { testOnGraph(graph3, expectedMean3, expectedCycle3); } @Test public void testGraph4() { testOnGraph(graph4, expectedMean4, expectedCycle4); } @Test public void testGraph5() { testOnGraph(graph5, expectedMean5, expectedCycle5); } @Test public void testGraph6() { testOnGraph(graph6, expectedMean6, expectedCycle6); } @Test public void testGraph7() { testOnGraph(graph7, expectedMean7, expectedCycle7); } /** * Tests the algorithm on the graph instance {@code graphArray} using {@code expectedMean} and * {@code expectedCycleArray} to check correctness. * * @param graphArray graph instance * @param expectedMean mean value * @param expectedCycleArray minimum mean cycle */ private void testOnGraph(int[][] graphArray, double expectedMean, int[][] expectedCycleArray) { Graph graph = new DirectedWeightedPseudograph<>(DefaultEdge.class); TestUtil.constructGraph(graph, graphArray); GraphPath expectedPath; if (expectedCycleArray == null) { expectedPath = null; } else { expectedPath = readPath(expectedCycleArray, graph); } HowardMinimumMeanCycle mmc = new HowardMinimumMeanCycle<>(graph); GraphPath actualPath = mmc.getCycle(); double actualMean = mmc.getCycleMean(); assertEquals(expectedMean, actualMean, 1e-9); assertEquals(expectedPath, actualPath); } /** * Constructs path stored in {@code path}. * * @param path path * @param graph graph * @return constructed path instance */ private GraphPath readPath( int[][] path, Graph graph) { int startVertex = path[0][0]; int endVertex = path[path.length - 1][1]; List edges = new ArrayList<>(path.length); double pathWeight = 0.0; for (int[] edgeArray : path) { int source = edgeArray[0]; int target = edgeArray[1]; double minimumWeight = Double.POSITIVE_INFINITY; DefaultEdge minimumWeightEdge = null; for (DefaultEdge edge : graph.getAllEdges(source, target)) { double edgeWeight = graph.getEdgeWeight(edge); if (edgeWeight < minimumWeight) { minimumWeight = edgeWeight; minimumWeightEdge = edge; } } edges.add(minimumWeightEdge); pathWeight += minimumWeight; } return new GraphWalk<>(graph, startVertex, endVertex, edges, pathWeight); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/JohnsonSimpleCyclesTest.java000066400000000000000000000032561402514743400334260ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Simple tests for JohnsonSimpleCycles. * * @author Dimitrios Michail */ public class JohnsonSimpleCyclesTest { @Test public void testSmallExample() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5, 6)); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(2, 5); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 1); List> cycles = new JohnsonSimpleCycles<>(g).findSimpleCycles(); assertTrue(cycles.size() == 2); List cycle0 = cycles.get(0); assertEquals(cycle0, Arrays.asList(1, 2, 3, 4, 5, 6)); List cycle1 = cycles.get(1); assertEquals(cycle1, Arrays.asList(1, 2, 5, 6)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/PatonCycleBaseTest.java000066400000000000000000000505071402514743400323300ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Nikolay Ognyanov, Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.CycleBasisAlgorithm.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class PatonCycleBaseTest { private static int MAX_SIZE = 10; private static int[] RESULTS = { 0, 0, 0, 1, 3, 6, 10, 15, 21, 28, 36 }; @Test public void testAlgorithm() { SimpleGraph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); for (int i = 0; i < 7; i++) { graph.addVertex(i); } CycleBasisAlgorithm finder = new PatonCycleBase<>(graph); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 0); checkResult(finder, 1); graph.addEdge(2, 3); graph.addEdge(3, 0); checkResult(finder, 2); graph.addEdge(3, 1); checkResult(finder, 3); graph.addEdge(3, 4); graph.addEdge(4, 2); checkResult(finder, 4); graph.addEdge(4, 5); checkResult(finder, 4); graph.addEdge(5, 2); checkResult(finder, 5); graph.addEdge(5, 6); graph.addEdge(6, 4); checkResult(finder, 6); for (int size = 1; size <= MAX_SIZE; size++) { graph = new SimpleGraph<>(DefaultEdge.class); for (int i = 0; i < size; i++) { graph.addVertex(i); } for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (i != j) { graph.addEdge(i, j); } } } finder = new PatonCycleBase<>(graph); checkResult(finder, RESULTS[size]); } } private void checkResult(CycleBasisAlgorithm finder, int size) { assertTrue(finder.getCycleBasis().getCycles().size() == size); } @Test public void testPatonCycleBasis() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.rangeClosed(1, 7).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 4); g.addEdge(2, 5); g.addEdge(3, 6); g.addEdge(3, 7); g.addEdge(4, 5); g.addEdge(6, 7); g.addEdge(4, 6); // @formatter:off // // 1 // / \ // 2 3 // | \ // 4 - 5 6 7 // | | // --------- // // @formatter:on Set> ucb = new PatonCycleBase<>(g).getCycleBasis().getCycles(); int[] cyclesSizes = { 3, 5, 3 }; Iterator> it = ucb.iterator(); for (int i = 0; i < 3; i++) { List cycle = it.next(); assertEquals(cyclesSizes[i], cycle.size()); } } @Test public void testPatonCycleBasis1() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.rangeClosed(1, 15).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(1, 4); g.addEdge(1, 12); g.addEdge(3, 5); g.addEdge(3, 6); g.addEdge(12, 13); g.addEdge(6, 7); g.addEdge(6, 8); g.addEdge(13, 14); g.addEdge(7, 9); g.addEdge(8, 10); g.addEdge(14, 15); g.addEdge(10, 11); g.addEdge(2, 11); g.addEdge(5, 4); g.addEdge(5, 9); g.addEdge(9, 10); g.addEdge(9, 11); g.addEdge(10, 14); g.addEdge(11, 15); CycleBasis ucb = new PatonCycleBase<>(g).getCycleBasis(); int[] cyclesSizes = { 3, 8, 8, 9, 5, 7, 4 }; Iterator> it = ucb.getCycles().iterator(); for (int i = 0; i < 7; i++) { List cycle = it.next(); assertEquals(cyclesSizes[i], cycle.size()); assertCycle(g, cycle); } assertEquals(44, ucb.getLength()); assertEquals(44d, ucb.getWeight(), 1e-9); } @Test public void testPatonCycleBasis2() { SimpleGraph graph = new SimpleGraph<>(DefaultEdge.class); for (int i = 0; i < 7; i++) { graph.addVertex(i); } CycleBasisAlgorithm finder = new PatonCycleBase<>(graph); CycleBasis basis; graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 0); basis = finder.getCycleBasis(); assertEquals(1, basis.getCycles().size()); assertEquals(3, basis.getLength()); graph.addEdge(2, 3); graph.addEdge(3, 0); basis = finder.getCycleBasis(); assertEquals(2, basis.getCycles().size()); assertEquals(6, basis.getLength()); graph.addEdge(3, 1); basis = finder.getCycleBasis(); assertEquals(3, basis.getCycles().size()); assertEquals(9, basis.getLength()); graph.addEdge(3, 4); graph.addEdge(4, 2); basis = finder.getCycleBasis(); assertEquals(4, basis.getCycles().size()); assertEquals(12, basis.getLength()); graph.addEdge(4, 5); basis = finder.getCycleBasis(); assertEquals(4, basis.getCycles().size()); assertEquals(12, basis.getLength()); graph.addEdge(5, 2); basis = finder.getCycleBasis(); assertEquals(5, basis.getCycles().size()); assertEquals(15, basis.getLength()); graph.addEdge(5, 6); graph.addEdge(6, 4); basis = finder.getCycleBasis(); assertEquals(6, basis.getCycles().size()); assertEquals(18, basis.getLength()); for (int size = 1; size <= MAX_SIZE; size++) { graph = new SimpleGraph<>(DefaultEdge.class); finder = new PatonCycleBase<>(graph); for (int i = 0; i < size; i++) { graph.addVertex(i); } for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { if (i != j) { graph.addEdge(i, j); } } } basis = finder.getCycleBasis(); assertEquals(RESULTS[size], basis.getCycles().size()); assertEquals(3 * RESULTS[size], basis.getLength()); assertEquals(3.0 * RESULTS[size], basis.getWeight(), 1e-9); for (List c : basis.getCycles()) { assertCycle(graph, c); } } } @Test public void testPatonCycleBasis3() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.rangeClosed(1, 15).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 4); g.addEdge(2, 5); g.addEdge(3, 6); g.addEdge(3, 7); g.addEdge(4, 8); g.addEdge(4, 9); g.addEdge(5, 10); g.addEdge(5, 11); g.addEdge(6, 12); g.addEdge(6, 13); g.addEdge(7, 14); g.addEdge(7, 15); g.addEdge(8, 9); g.addEdge(10, 11); g.addEdge(12, 13); g.addEdge(14, 15); g.addEdge(8, 10); g.addEdge(9, 11); g.addEdge(10, 12); g.addEdge(11, 13); g.addEdge(12, 14); g.addEdge(8, 11); g.addEdge(9, 12); g.addEdge(10, 13); g.addEdge(11, 14); g.addEdge(12, 15); g.addEdge(8, 12); g.addEdge(9, 13); g.addEdge(10, 14); g.addEdge(11, 15); g.addEdge(8, 13); g.addEdge(9, 14); g.addEdge(10, 15); g.addEdge(8, 14); g.addEdge(9, 15); g.addEdge(8, 15); CycleBasis ucb = new PatonCycleBase<>(g).getCycleBasis(); Iterator> it = ucb.getCycles().iterator(); for (int i = 0; i < 24; i++) { List cycle = it.next(); assertCycle(g, cycle); } assertEquals(85, ucb.getLength()); assertEquals(85d, ucb.getWeight(), 1e-9); } @Test public void testPatonCycleBasis4() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.rangeClosed(1, 7).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 4); g.addEdge(2, 5); g.addEdge(3, 6); g.addEdge(3, 7); g.addEdge(4, 5); g.addEdge(6, 7); g.addEdge(4, 6); // @formatter:off // // 1 // / \ // 2 3 // | \ // 4 - 5 6 7 // | | // --------- // // @formatter:on CycleBasis ucb = new PatonCycleBase<>(g).getCycleBasis(); Iterator> it = ucb.getCycles().iterator(); for (int i = 0; i < 3; i++) { List cycle = it.next(); assertCycle(g, cycle); } assertEquals(11, ucb.getLength()); assertEquals(11d, ucb.getWeight(), 1e-9); } @Test public void testPatonCycleBasis5() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.rangeClosed(1, 15).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 4); g.addEdge(2, 5); g.addEdge(3, 6); g.addEdge(3, 7); g.addEdge(4, 8); g.addEdge(4, 9); g.addEdge(5, 10); g.addEdge(5, 11); g.addEdge(6, 12); g.addEdge(6, 13); g.addEdge(7, 14); g.addEdge(7, 15); g.addEdge(8, 9); g.addEdge(10, 11); g.addEdge(12, 13); g.addEdge(14, 15); g.addEdge(8, 10); CycleBasis ucb = new PatonCycleBase<>(g).getCycleBasis(); int[] cyclesSizes = { 3, 3, 3, 5, 3 }; Iterator> it = ucb.getCycles().iterator(); for (int i = 0; i < 5; i++) { List cycle = it.next(); assertCycle(g, cycle); assertEquals(cyclesSizes[i], cycle.size()); } assertEquals(17, ucb.getLength()); assertEquals(17d, ucb.getWeight(), 1e-9); } @Test public void testPatonCycleBasis6() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.rangeClosed(1, 7).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 4); g.addEdge(2, 5); g.addEdge(2, 3); g.addEdge(3, 6); g.addEdge(3, 7); g.addEdge(4, 6); g.addEdge(5, 7); CycleBasis ucb = new PatonCycleBase<>(g).getCycleBasis(); int[] cyclesSizes = { 3, 4, 4 }; Iterator> it = ucb.getCycles().iterator(); for (int i = 0; i < 3; i++) { List cycle = it.next(); assertEquals(cyclesSizes[i], cycle.size()); } } @Test public void testPatonCycleBasis7() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.rangeClosed(1, 7).boxed().collect(Collectors.toList())); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 4); g.addEdge(2, 5); g.addEdge(4, 5); g.addEdge(4, 3); // @formatter:off // // 1 // / \ // 2 3 // | // 4 - 5 | // | | // --------- // // @formatter:on CycleBasis ucb = new PatonCycleBase<>(g).getCycleBasis(); Iterator> it = ucb.getCycles().iterator(); for (int i = 0; i < 2; i++) { List cycle = it.next(); assertCycle(g, cycle); } assertEquals(7, ucb.getLength()); assertEquals(7d, ucb.getWeight(), 1e-9); } @Test public void testPatonCycleBasis8() { final int n = 200; final double p = 0.7; final int graphs = 10; GnpRandomGraphGenerator gen = new GnpRandomGraphGenerator<>(n, p); for (int i = 0; i < graphs; i++) { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); CycleBasis ucb = new PatonCycleBase<>(g).getCycleBasis(); int k = new ConnectivityInspector<>(g).connectedSets().size(); int cycleSpaceDimension = g.edgeSet().size() - g.vertexSet().size() + k; assertEquals(cycleSpaceDimension, ucb.getCycles().size()); for (List cycle : ucb.getCycles()) { assertCycle(g, cycle); } } } @Test public void testZeroCycleSpaceDimension() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addEdge(0, 1); graph.addEdge(2, 3); CycleBasisAlgorithm fcb = new PatonCycleBase<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(0, cb.getCycles().size()); assertEquals(0, cb.getLength()); assertEquals(0d, cb.getWeight(), 1e-9); } @Test public void testWithLoops() { Graph graph = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); DefaultEdge e01 = graph.addEdge(0, 1); DefaultEdge e12 = graph.addEdge(1, 2); DefaultEdge e23 = graph.addEdge(2, 3); DefaultEdge e30 = graph.addEdge(3, 0); DefaultEdge e00 = graph.addEdge(0, 0); DefaultEdge e11 = graph.addEdge(1, 1); DefaultEdge e22 = graph.addEdge(2, 2); DefaultEdge e33 = graph.addEdge(3, 3); CycleBasisAlgorithm fcb = new PatonCycleBase<>(graph); CycleBasis cb = fcb.getCycleBasis(); int dimension = 5; Iterator> it = cb.getCycles().iterator(); for (int i = 0; i < dimension; i++) { List c = it.next(); assertCycle(graph, c); switch (i) { case 0: assertEquals(Collections.singletonList(e00), c); break; case 1: assertEquals(Collections.singletonList(e33), c); break; case 2: assertEquals(Arrays.asList(e12, e23, e30, e01), c); break; case 3: assertEquals(Collections.singletonList(e22), c); break; case 4: assertEquals(Collections.singletonList(e11), c); break; } } assertEquals(5, cb.getCycles().size()); assertEquals(8, cb.getLength()); assertEquals(8d, cb.getWeight(), 1e-9); } @Test public void testSingleLoops() { Graph graph = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Collections.singletonList(0)); DefaultEdge e1 = graph.addEdge(0, 0); CycleBasisAlgorithm fcb = new PatonCycleBase<>(graph); CycleBasis cb = fcb.getCycleBasis(); int dimension = 1; Iterator> it = cb.getCycles().iterator(); for (int i = 0; i < dimension; i++) { List c = it.next(); assertCycle(graph, c); switch (i) { case 0: assertEquals(Collections.singletonList(e1), c); break; } } assertEquals(1, cb.getCycles().size()); assertEquals(1, cb.getLength()); assertEquals(1d, cb.getWeight(), 1e-9); } @Test(expected = IllegalArgumentException.class) public void testMultipleEdges() { Graph graph = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Collections.singletonList(0)); graph.addEdge(0, 0); graph.addEdge(0, 0); new PatonCycleBase<>(graph).getCycleBasis(); } @Test public void testDisconnectedAndWeights() { WeightedPseudograph graph = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3, 4, 5)); graph.setEdgeWeight(graph.addEdge(0, 1), 2.0); graph.setEdgeWeight(graph.addEdge(1, 2), 7.0); graph.setEdgeWeight(graph.addEdge(2, 0), 13.0); graph.setEdgeWeight(graph.addEdge(3, 4), 102.0); graph.setEdgeWeight(graph.addEdge(4, 5), 107.0); graph.setEdgeWeight(graph.addEdge(5, 3), 113.0); CycleBasisAlgorithm fcb = new PatonCycleBase<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(2, cb.getCycles().size()); assertEquals(6, cb.getLength()); assertEquals(344d, cb.getWeight(), 1e-9); } // assert that a list of edges is a cycle private void assertCycle(Graph g, List edges) { if (edges.isEmpty()) { return; } boolean isDirected = g.getType().isDirected(); DefaultEdge prev = null; DefaultEdge first = null, last = null; Iterator it = edges.iterator(); Set dupCheck = new HashSet<>(); while (it.hasNext()) { DefaultEdge cur = it.next(); assertTrue(dupCheck.add(cur)); if (prev == null) { first = cur; } else { if (isDirected) { assertTrue(g.getEdgeSource(cur).equals(g.getEdgeTarget(prev))); } else { assertTrue( g.getEdgeSource(cur).equals(g.getEdgeSource(prev)) || g.getEdgeSource(cur).equals(g.getEdgeTarget(prev)) || g.getEdgeTarget(cur).equals(g.getEdgeSource(prev)) || g.getEdgeTarget(cur).equals(g.getEdgeTarget(prev))); } } if (!it.hasNext()) { last = cur; } prev = cur; } if (edges.size() > 1) { if (isDirected) { assertTrue(g.getEdgeSource(first).equals(g.getEdgeTarget(last))); } else { assertTrue( g.getEdgeSource(first).equals(g.getEdgeSource(last)) || g.getEdgeSource(first).equals(g.getEdgeTarget(last)) || g.getEdgeTarget(first).equals(g.getEdgeSource(last)) || g.getEdgeTarget(first).equals(g.getEdgeTarget(last))); } } } } QueueBFSFundamentalCycleBasisTest.java000066400000000000000000000522311402514743400351510ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.CycleBasisAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit tests for {@link QueueBFSFundamentalCycleBasis}. * * @author Dimitrios Michail */ public class QueueBFSFundamentalCycleBasisTest { @Test public void testSimple() { Graph graph = new SimpleGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, 0, 1); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 2, 0); CycleBasisAlgorithm fcb = new QueueBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); List> cycles = new ArrayList<>(cb.getCycles()); assertEquals(1, cb.getCycles().size()); List c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(0, 1))); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertEquals(3, c1.size()); assertEquals(3, cb.getLength()); assertEquals(3.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 2, 3); Graphs.addEdgeWithVertices(graph, 3, 0); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(2, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); List c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); assertEquals(6, cb.getLength()); assertEquals(6.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 3, 1); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(3, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); List c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); assertEquals(9, cb.getLength()); assertEquals(9.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 3, 4); Graphs.addEdgeWithVertices(graph, 4, 2); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(4, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); List c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(3, 4))); assertTrue(c4.contains(graph.getEdge(0, 3))); assertTrue(c4.contains(graph.getEdge(0, 2))); assertTrue(c4.contains(graph.getEdge(2, 4))); assertEquals(4, c4.size()); assertEquals(13, cb.getLength()); assertEquals(13.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 4, 5); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(4, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(3, 4))); assertTrue(c4.contains(graph.getEdge(0, 3))); assertTrue(c4.contains(graph.getEdge(0, 2))); assertTrue(c4.contains(graph.getEdge(2, 4))); assertEquals(4, c4.size()); assertEquals(13, cb.getLength()); assertEquals(13.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 5, 2); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(5, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(3, 4))); assertTrue(c4.contains(graph.getEdge(0, 3))); assertTrue(c4.contains(graph.getEdge(0, 2))); assertTrue(c4.contains(graph.getEdge(2, 4))); assertEquals(4, c4.size()); List c5 = cycles.get(4); assertTrue(c5.contains(graph.getEdge(4, 5))); assertTrue(c5.contains(graph.getEdge(2, 4))); assertTrue(c5.contains(graph.getEdge(2, 5))); assertEquals(3, c5.size()); assertEquals(16, cb.getLength()); assertEquals(16.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 5, 6); Graphs.addEdgeWithVertices(graph, 6, 4); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(6, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(3, 4))); assertTrue(c4.contains(graph.getEdge(0, 3))); assertTrue(c4.contains(graph.getEdge(0, 2))); assertTrue(c4.contains(graph.getEdge(2, 4))); assertEquals(4, c4.size()); c5 = cycles.get(4); assertTrue(c5.contains(graph.getEdge(4, 5))); assertTrue(c5.contains(graph.getEdge(2, 4))); assertTrue(c5.contains(graph.getEdge(2, 5))); assertEquals(3, c5.size()); List c6 = cycles.get(5); assertTrue(c6.contains(graph.getEdge(5, 6))); assertTrue(c6.contains(graph.getEdge(5, 2))); assertTrue(c6.contains(graph.getEdge(2, 4))); assertTrue(c6.contains(graph.getEdge(4, 6))); assertEquals(4, c6.size()); assertEquals(20, cb.getLength()); assertEquals(20.0, cb.getWeight(), 0.0001); } @Test public void testMultigraphsWithLoops() { Graph graph = new Pseudograph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, 0, 1); Graphs.addEdgeWithVertices(graph, 0, 2); Graphs.addEdgeWithVertices(graph, 0, 3); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 2, 3); Graphs.addEdgeWithVertices(graph, 1, 4); Graphs.addEdgeWithVertices(graph, 2, 5); Graphs.addEdgeWithVertices(graph, 3, 6); Graphs.addEdgeWithVertices(graph, 4, 5); Graphs.addEdgeWithVertices(graph, 5, 6); Graphs.addEdgeWithVertices(graph, 4, 7); Graphs.addEdgeWithVertices(graph, 5, 8); Graphs.addEdgeWithVertices(graph, 6, 9); Graphs.addEdgeWithVertices(graph, 7, 8); DefaultEdge e89_1 = graph.addEdge(8, 9); Graphs.addEdgeWithVertices(graph, 7, 9); DefaultEdge e89_2 = graph.addEdge(8, 9); DefaultEdge e89_3 = graph.addEdge(8, 9); DefaultEdge e89_4 = graph.addEdge(8, 9); DefaultEdge e77_1 = graph.addEdge(7, 7); DefaultEdge e77_2 = graph.addEdge(7, 7); DefaultEdge e77_3 = graph.addEdge(7, 7); CycleBasisAlgorithm fcb = new QueueBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); List> cycles = new ArrayList<>(cb.getCycles()); assertEquals(13, cb.getCycles().size()); List c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(0, 1))); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertEquals(3, c1.size()); List c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertTrue(c2.contains(graph.getEdge(2, 3))); assertEquals(3, c2.size()); List c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(1, 4))); assertTrue(c3.contains(graph.getEdge(4, 5))); assertTrue(c3.contains(graph.getEdge(5, 2))); assertTrue(c3.contains(graph.getEdge(2, 0))); assertEquals(5, c3.size()); List c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(0, 2))); assertTrue(c4.contains(graph.getEdge(2, 5))); assertTrue(c4.contains(graph.getEdge(5, 6))); assertTrue(c4.contains(graph.getEdge(6, 3))); assertTrue(c4.contains(graph.getEdge(3, 0))); assertEquals(5, c4.size()); List c5 = cycles.get(4); assertTrue(c5.contains(graph.getEdge(0, 1))); assertTrue(c5.contains(graph.getEdge(1, 4))); assertTrue(c5.contains(graph.getEdge(4, 7))); assertTrue(c5.contains(graph.getEdge(7, 8))); assertTrue(c5.contains(graph.getEdge(8, 5))); assertTrue(c5.contains(graph.getEdge(5, 2))); assertTrue(c5.contains(graph.getEdge(2, 0))); assertEquals(7, c5.size()); List c6 = cycles.get(5); assertTrue(c6.contains(graph.getEdge(0, 2))); assertTrue(c6.contains(graph.getEdge(2, 5))); assertTrue(c6.contains(graph.getEdge(5, 8))); assertTrue(c6.contains(e89_1)); assertTrue(c6.contains(graph.getEdge(9, 6))); assertTrue(c6.contains(graph.getEdge(6, 3))); assertTrue(c6.contains(graph.getEdge(3, 0))); assertEquals(7, c6.size()); List c7 = cycles.get(6); assertTrue(c7.contains(graph.getEdge(0, 1))); assertTrue(c7.contains(graph.getEdge(1, 4))); assertTrue(c7.contains(graph.getEdge(4, 7))); assertTrue(c7.contains(graph.getEdge(7, 9))); assertTrue(c7.contains(graph.getEdge(9, 6))); assertTrue(c7.contains(graph.getEdge(6, 3))); assertTrue(c7.contains(graph.getEdge(3, 0))); assertEquals(7, c7.size()); List c8 = cycles.get(7); assertTrue(c8.contains(graph.getEdge(0, 2))); assertTrue(c8.contains(graph.getEdge(2, 5))); assertTrue(c8.contains(graph.getEdge(5, 8))); assertTrue(c8.contains(e89_2)); assertTrue(c8.contains(graph.getEdge(9, 6))); assertTrue(c8.contains(graph.getEdge(6, 3))); assertTrue(c8.contains(graph.getEdge(3, 0))); assertEquals(7, c8.size()); List c9 = cycles.get(8); assertTrue(c9.contains(graph.getEdge(0, 2))); assertTrue(c9.contains(graph.getEdge(2, 5))); assertTrue(c9.contains(graph.getEdge(5, 8))); assertTrue(c9.contains(e89_3)); assertTrue(c9.contains(graph.getEdge(9, 6))); assertTrue(c9.contains(graph.getEdge(6, 3))); assertTrue(c9.contains(graph.getEdge(3, 0))); assertEquals(7, c9.size()); List c10 = cycles.get(9); assertTrue(c10.contains(graph.getEdge(0, 2))); assertTrue(c10.contains(graph.getEdge(2, 5))); assertTrue(c10.contains(graph.getEdge(5, 8))); assertTrue(c10.contains(e89_4)); assertTrue(c10.contains(graph.getEdge(9, 6))); assertTrue(c10.contains(graph.getEdge(6, 3))); assertTrue(c10.contains(graph.getEdge(3, 0))); assertEquals(7, c10.size()); List c11 = cycles.get(10); assertTrue(c11.contains(e77_1)); assertEquals(1, c11.size()); List c12 = cycles.get(11); assertTrue(c12.contains(e77_2)); assertEquals(1, c12.size()); List c13 = cycles.get(12); assertTrue(c13.contains(e77_3)); assertEquals(1, c13.size()); assertEquals(61, cb.getLength()); assertEquals(61.0, cb.getWeight(), 0.0001); } @Test public void testMultiGraphWithMultipleComponentsWithLoops() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addEdge(0, 1); graph.addEdge(0, 2); DefaultEdge e12_1 = graph.addEdge(1, 2); DefaultEdge e12_2 = graph.addEdge(1, 2); DefaultEdge e11_1 = graph.addEdge(1, 1); DefaultEdge e11_2 = graph.addEdge(1, 1); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); graph.addEdge(3, 4); graph.addEdge(3, 5); DefaultEdge e45_1 = graph.addEdge(4, 5); DefaultEdge e45_2 = graph.addEdge(4, 5); DefaultEdge e55_1 = graph.addEdge(5, 5); DefaultEdge e55_2 = graph.addEdge(5, 5); CycleBasisAlgorithm fcb = new QueueBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); List> cycles = new ArrayList<>(cb.getCycles()); assertEquals(8, cb.getCycles().size()); List c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(0, 1))); assertTrue(c1.contains(e12_1)); assertTrue(c1.contains(graph.getEdge(2, 0))); assertEquals(3, c1.size()); List c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(0, 1))); assertTrue(c2.contains(e12_2)); assertTrue(c2.contains(graph.getEdge(2, 0))); assertEquals(3, c2.size()); List c3 = cycles.get(2); assertTrue(c3.contains(e11_1)); assertEquals(1, c3.size()); List c4 = cycles.get(3); assertTrue(c4.contains(e11_2)); assertEquals(1, c4.size()); List c5 = cycles.get(4); assertTrue(c5.contains(graph.getEdge(3, 4))); assertTrue(c5.contains(e45_1)); assertTrue(c5.contains(graph.getEdge(5, 3))); assertEquals(3, c5.size()); List c6 = cycles.get(5); assertTrue(c6.contains(graph.getEdge(3, 4))); assertTrue(c6.contains(e45_2)); assertTrue(c6.contains(graph.getEdge(5, 3))); assertEquals(3, c6.size()); List c7 = cycles.get(6); assertTrue(c7.contains(e55_1)); assertEquals(1, c7.size()); List c8 = cycles.get(7); assertTrue(c8.contains(e55_2)); assertEquals(1, c8.size()); assertEquals(16, cb.getLength()); assertEquals(16.0, cb.getWeight(), 0.0001); } @Test public void testTwoParallelEdges() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); DefaultEdge e1 = graph.addEdge(0, 1); DefaultEdge e2 = graph.addEdge(0, 1); CycleBasisAlgorithm fcb = new QueueBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); List> cycles = new ArrayList<>(cb.getCycles()); assertEquals(1, cb.getCycles().size()); List c1 = cycles.get(0); assertTrue(c1.contains(e1)); assertTrue(c1.contains(e2)); assertEquals(2, c1.size()); assertEquals(2, cb.getLength()); assertEquals(2.0, cb.getWeight(), 0.0001); } @Test public void testMoreParallelEdges() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); DefaultEdge e01_1 = graph.addEdge(0, 1); DefaultEdge e01_2 = graph.addEdge(0, 1); DefaultEdge e12 = graph.addEdge(1, 2); DefaultEdge e23_1 = graph.addEdge(2, 3); DefaultEdge e23_2 = graph.addEdge(2, 3); DefaultEdge e30 = graph.addEdge(3, 0); CycleBasisAlgorithm fcb = new QueueBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); List> cycles = new ArrayList<>(cb.getCycles()); assertEquals(3, cb.getCycles().size()); List c1 = cycles.get(0); assertTrue(c1.contains(e01_2)); assertTrue(c1.contains(e01_1)); assertEquals(2, c1.size()); List c2 = cycles.get(1); assertTrue(c2.contains(e23_1)); assertTrue(c2.contains(e30)); assertTrue(c2.contains(e01_1)); assertTrue(c2.contains(e12)); assertEquals(4, c2.size()); List c3 = cycles.get(2); assertTrue(c3.contains(e23_2)); assertTrue(c3.contains(e30)); assertTrue(c3.contains(e01_1)); assertTrue(c3.contains(e12)); assertEquals(4, c3.size()); assertEquals(10, cb.getLength()); assertEquals(10.0, cb.getWeight(), 0.0001); } @Test public void testZeroCycleSpaceDimension() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addEdge(0, 1); graph.addEdge(2, 3); CycleBasisAlgorithm fcb = new QueueBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(0, cb.getCycles().size()); assertEquals(0, cb.getLength()); assertEquals(0d, cb.getWeight(), 0.0001); } @Test public void testEmptyGraph() { Graph graph = new Pseudograph<>(DefaultEdge.class); CycleBasisAlgorithm fcb = new QueueBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(0, cb.getCycles().size()); assertEquals(0, cb.getLength()); assertEquals(0d, cb.getWeight(), 0.0001); } } StackBFSFundamentalCycleBasisTest.java000066400000000000000000000562571402514743400351460ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.CycleBasisAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit tests for {@link StackBFSFundamentalCycleBasis}. * * @author Dimitrios Michail */ public class StackBFSFundamentalCycleBasisTest { @Test public void testSimple() { Graph graph = new SimpleGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, 0, 1); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 2, 0); CycleBasisAlgorithm fcb = new StackBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(1, cb.getCycles().size()); List> cycles = new ArrayList<>(cb.getCycles()); List c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(0, 1))); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertEquals(3, c1.size()); assertEquals(3, cb.getLength()); assertEquals(3.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 2, 3); Graphs.addEdgeWithVertices(graph, 3, 0); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(2, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); List c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); assertEquals(6, cb.getLength()); assertEquals(6.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 3, 1); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(3, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); List c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); assertEquals(9, cb.getLength()); assertEquals(9.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 3, 4); Graphs.addEdgeWithVertices(graph, 4, 2); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(4, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); List c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(3, 4))); assertTrue(c4.contains(graph.getEdge(0, 3))); assertTrue(c4.contains(graph.getEdge(0, 2))); assertTrue(c4.contains(graph.getEdge(2, 4))); assertEquals(4, c4.size()); assertEquals(13, cb.getLength()); assertEquals(13.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 4, 5); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(4, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(3, 4))); assertTrue(c4.contains(graph.getEdge(0, 3))); assertTrue(c4.contains(graph.getEdge(0, 2))); assertTrue(c4.contains(graph.getEdge(2, 4))); assertEquals(4, c4.size()); assertEquals(13, cb.getLength()); assertEquals(13.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 5, 2); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(5, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(4, 2))); assertTrue(c4.contains(graph.getEdge(2, 0))); assertTrue(c4.contains(graph.getEdge(0, 3))); assertTrue(c4.contains(graph.getEdge(3, 4))); assertEquals(4, c4.size()); List c5 = cycles.get(4); assertTrue(c5.contains(graph.getEdge(5, 2))); assertTrue(c5.contains(graph.getEdge(2, 0))); assertTrue(c5.contains(graph.getEdge(0, 3))); assertTrue(c5.contains(graph.getEdge(3, 4))); assertTrue(c5.contains(graph.getEdge(4, 5))); assertEquals(5, c5.size()); assertEquals(18, cb.getLength()); assertEquals(18.0, cb.getWeight(), 0.0001); Graphs.addEdgeWithVertices(graph, 5, 6); Graphs.addEdgeWithVertices(graph, 6, 4); cb = fcb.getCycleBasis(); cycles = new ArrayList<>(cb.getCycles()); assertEquals(6, cb.getCycles().size()); c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(1, 0))); assertEquals(3, c1.size()); c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertTrue(c2.contains(graph.getEdge(0, 3))); assertEquals(3, c2.size()); c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 3))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertTrue(c3.contains(graph.getEdge(0, 3))); assertEquals(3, c3.size()); c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(4, 2))); assertTrue(c4.contains(graph.getEdge(2, 0))); assertTrue(c4.contains(graph.getEdge(0, 3))); assertTrue(c4.contains(graph.getEdge(3, 4))); assertEquals(4, c4.size()); c5 = cycles.get(4); assertTrue(c5.contains(graph.getEdge(5, 2))); assertTrue(c5.contains(graph.getEdge(2, 0))); assertTrue(c5.contains(graph.getEdge(0, 3))); assertTrue(c5.contains(graph.getEdge(3, 4))); assertTrue(c5.contains(graph.getEdge(4, 5))); assertEquals(5, c5.size()); List c6 = cycles.get(5); assertTrue(c6.contains(graph.getEdge(5, 6))); assertTrue(c6.contains(graph.getEdge(6, 4))); assertTrue(c6.contains(graph.getEdge(4, 5))); assertEquals(3, c6.size()); assertEquals(21, cb.getLength()); assertEquals(21.0, cb.getWeight(), 0.0001); } @Test public void testMultigraphsWithLoops() { Graph graph = new Pseudograph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, 0, 1); Graphs.addEdgeWithVertices(graph, 0, 2); Graphs.addEdgeWithVertices(graph, 0, 3); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 2, 3); Graphs.addEdgeWithVertices(graph, 1, 4); Graphs.addEdgeWithVertices(graph, 2, 5); Graphs.addEdgeWithVertices(graph, 3, 6); Graphs.addEdgeWithVertices(graph, 4, 5); Graphs.addEdgeWithVertices(graph, 5, 6); Graphs.addEdgeWithVertices(graph, 4, 7); Graphs.addEdgeWithVertices(graph, 5, 8); Graphs.addEdgeWithVertices(graph, 6, 9); Graphs.addEdgeWithVertices(graph, 7, 8); DefaultEdge e89_1 = graph.addEdge(8, 9); Graphs.addEdgeWithVertices(graph, 7, 9); DefaultEdge e89_2 = graph.addEdge(8, 9); DefaultEdge e89_3 = graph.addEdge(8, 9); DefaultEdge e89_4 = graph.addEdge(8, 9); DefaultEdge e77_1 = graph.addEdge(7, 7); DefaultEdge e77_2 = graph.addEdge(7, 7); DefaultEdge e77_3 = graph.addEdge(7, 7); CycleBasisAlgorithm fcb = new StackBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(13, cb.getCycles().size()); List> cycles = new ArrayList<>(cb.getCycles()); List c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(1, 2))); assertTrue(c1.contains(graph.getEdge(2, 0))); assertTrue(c1.contains(graph.getEdge(0, 1))); assertEquals(3, c1.size()); List c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(2, 3))); assertTrue(c2.contains(graph.getEdge(3, 0))); assertTrue(c2.contains(graph.getEdge(0, 2))); assertEquals(3, c2.size()); List c3 = cycles.get(2); assertTrue(c3.contains(graph.getEdge(1, 4))); assertTrue(c3.contains(graph.getEdge(4, 7))); assertTrue(c3.contains(graph.getEdge(7, 9))); assertTrue(c3.contains(graph.getEdge(9, 6))); assertTrue(c3.contains(graph.getEdge(6, 3))); assertTrue(c3.contains(graph.getEdge(3, 0))); assertTrue(c3.contains(graph.getEdge(0, 1))); assertEquals(7, c3.size()); List c4 = cycles.get(3); assertTrue(c4.contains(graph.getEdge(2, 5))); assertTrue(c4.contains(graph.getEdge(5, 6))); assertTrue(c4.contains(graph.getEdge(6, 3))); assertTrue(c4.contains(graph.getEdge(3, 0))); assertTrue(c4.contains(graph.getEdge(0, 2))); assertEquals(5, c4.size()); List c5 = cycles.get(4); assertTrue(c5.contains(graph.getEdge(4, 5))); assertTrue(c5.contains(graph.getEdge(5, 6))); assertTrue(c5.contains(graph.getEdge(6, 9))); assertTrue(c5.contains(graph.getEdge(9, 7))); assertTrue(c5.contains(graph.getEdge(7, 4))); assertEquals(5, c5.size()); List c6 = cycles.get(5); assertTrue(c6.contains(graph.getEdge(5, 8))); assertTrue(c6.contains(graph.getEdge(8, 9))); assertTrue(c6.contains(graph.getEdge(9, 6))); assertTrue(c6.contains(graph.getEdge(6, 5))); assertEquals(4, c6.size()); List c7 = cycles.get(6); assertTrue(c7.contains(graph.getEdge(7, 8))); assertTrue(c7.contains(e89_1)); assertTrue(c7.contains(graph.getEdge(9, 7))); assertEquals(3, c7.size()); List c8 = cycles.get(7); assertTrue(c8.contains(e89_2)); assertTrue(c8.contains(e89_1)); assertEquals(2, c8.size()); List c9 = cycles.get(8); assertTrue(c9.contains(e89_3)); assertTrue(c9.contains(e89_1)); assertEquals(2, c9.size()); List c10 = cycles.get(9); assertTrue(c10.contains(e89_4)); assertTrue(c10.contains(e89_1)); assertEquals(2, c10.size()); List c11 = cycles.get(10); assertTrue(c11.contains(e77_1)); assertEquals(1, c11.size()); List c12 = cycles.get(11); assertTrue(c12.contains(e77_2)); assertEquals(1, c12.size()); List c13 = cycles.get(12); assertTrue(c13.contains(e77_3)); assertEquals(1, c13.size()); for (List c : cb.getCycles()) { assertCycle(graph, c); } assertEquals(39, cb.getLength()); assertEquals(39.0, cb.getWeight(), 0.0001); } @Test public void testMultiGraphWithMultipleComponentsWithLoops() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addEdge(0, 1); graph.addEdge(0, 2); DefaultEdge e12_1 = graph.addEdge(1, 2); DefaultEdge e12_2 = graph.addEdge(1, 2); DefaultEdge e11_1 = graph.addEdge(1, 1); DefaultEdge e11_2 = graph.addEdge(1, 1); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); graph.addEdge(3, 4); graph.addEdge(3, 5); DefaultEdge e45_1 = graph.addEdge(4, 5); DefaultEdge e45_2 = graph.addEdge(4, 5); DefaultEdge e55_1 = graph.addEdge(5, 5); DefaultEdge e55_2 = graph.addEdge(5, 5); CycleBasisAlgorithm fcb = new StackBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(8, cb.getCycles().size()); List> cycles = new ArrayList<>(cb.getCycles()); List c1 = cycles.get(0); assertTrue(c1.contains(graph.getEdge(0, 1))); assertTrue(c1.contains(e12_1)); assertTrue(c1.contains(graph.getEdge(2, 0))); assertEquals(3, c1.size()); List c2 = cycles.get(1); assertTrue(c2.contains(graph.getEdge(0, 1))); assertTrue(c2.contains(e12_2)); assertTrue(c2.contains(graph.getEdge(2, 0))); assertEquals(3, c2.size()); List c3 = cycles.get(2); assertTrue(c3.contains(e11_1)); assertEquals(1, c3.size()); List c4 = cycles.get(3); assertTrue(c4.contains(e11_2)); assertEquals(1, c4.size()); List c5 = cycles.get(4); assertTrue(c5.contains(graph.getEdge(3, 4))); assertTrue(c5.contains(e45_1)); assertTrue(c5.contains(graph.getEdge(5, 3))); assertEquals(3, c5.size()); List c6 = cycles.get(5); assertTrue(c6.contains(graph.getEdge(3, 4))); assertTrue(c6.contains(e45_2)); assertTrue(c6.contains(graph.getEdge(5, 3))); assertEquals(3, c6.size()); List c7 = cycles.get(6); assertTrue(c7.contains(e55_1)); assertEquals(1, c7.size()); List c8 = cycles.get(7); assertTrue(c8.contains(e55_2)); assertEquals(1, c8.size()); assertEquals(16, cb.getLength()); assertEquals(16.0, cb.getWeight(), 0.0001); } @Test public void testTwoParallelEdges() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); DefaultEdge e1 = graph.addEdge(0, 1); DefaultEdge e2 = graph.addEdge(0, 1); CycleBasisAlgorithm fcb = new StackBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(1, cb.getCycles().size()); List c1 = cb.getCycles().stream().findFirst().get(); assertTrue(c1.contains(e1)); assertTrue(c1.contains(e2)); assertEquals(2, c1.size()); assertEquals(2, cb.getLength()); assertEquals(2.0, cb.getWeight(), 0.0001); } @Test public void testMoreParallelEdges() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); DefaultEdge e01_1 = graph.addEdge(0, 1); DefaultEdge e01_2 = graph.addEdge(0, 1); DefaultEdge e12 = graph.addEdge(1, 2); DefaultEdge e23_1 = graph.addEdge(2, 3); DefaultEdge e23_2 = graph.addEdge(2, 3); DefaultEdge e30 = graph.addEdge(3, 0); CycleBasisAlgorithm fcb = new StackBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(3, cb.getCycles().size()); List> cycles = new ArrayList<>(cb.getCycles()); List c1 = cycles.get(0); assertTrue(c1.contains(e01_2)); assertTrue(c1.contains(e01_1)); assertEquals(2, c1.size()); List c2 = cycles.get(1); assertTrue(c2.contains(e01_1)); assertTrue(c2.contains(e12)); assertTrue(c2.contains(e23_1)); assertTrue(c2.contains(e30)); assertEquals(4, c2.size()); List c3 = cycles.get(2); assertTrue(c3.contains(e23_2)); assertTrue(c3.contains(e23_1)); assertEquals(2, c3.size()); assertEquals(8, cb.getLength()); assertEquals(8.0, cb.getWeight(), 0.0001); } @Test public void testZeroCycleSpaceDimension() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addEdge(0, 1); graph.addEdge(2, 3); CycleBasisAlgorithm fcb = new StackBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(0, cb.getCycles().size()); assertEquals(0, cb.getLength()); assertEquals(0d, cb.getWeight(), 1e-9); } @Test public void testEmptyGraph() { Graph graph = new Pseudograph<>(DefaultEdge.class); CycleBasisAlgorithm fcb = new StackBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); assertEquals(0, cb.getCycles().size()); assertEquals(0, cb.getLength()); assertEquals(0d, cb.getWeight(), 1e-9); } @Test public void test1() { Graph graph = new SimpleGraph<>(DefaultEdge.class); Graphs .addAllVertices( graph, IntStream.rangeClosed(1, 7).boxed().collect(Collectors.toList())); graph.addEdge(1, 2); graph.addEdge(1, 3); graph.addEdge(2, 4); graph.addEdge(2, 5); graph.addEdge(3, 6); graph.addEdge(3, 7); graph.addEdge(4, 5); graph.addEdge(6, 7); graph.addEdge(4, 6); // @formatter:off // // 1 // / \ // 2 3 // | \ // 4 - 5 6 7 // | | // --------- // // @formatter:on CycleBasisAlgorithm fcb = new StackBFSFundamentalCycleBasis<>(graph); CycleBasis cb = fcb.getCycleBasis(); int[] cyclesSizes = { 5, 6, 3 }; Iterator> it = cb.getCycles().iterator(); for (int i = 0; i < 3; i++) { List cycle = it.next(); assertCycle(graph, cycle); assertEquals(cyclesSizes[i], cycle.size()); } assertEquals(14, cb.getLength()); assertEquals(14d, cb.getWeight(), 1e-9); } // assert that a list of edges is a cycle private void assertCycle(Graph g, List edges) { if (edges.isEmpty()) { return; } boolean isDirected = g.getType().isDirected(); DefaultEdge prev = null; DefaultEdge first = null, last = null; Iterator it = edges.iterator(); Set dupCheck = new HashSet<>(); while (it.hasNext()) { DefaultEdge cur = it.next(); assertTrue(dupCheck.add(cur)); if (prev == null) { first = cur; } else { if (isDirected) { assertTrue(g.getEdgeSource(cur).equals(g.getEdgeTarget(prev))); } else { assertTrue( g.getEdgeSource(cur).equals(g.getEdgeSource(prev)) || g.getEdgeSource(cur).equals(g.getEdgeTarget(prev)) || g.getEdgeTarget(cur).equals(g.getEdgeSource(prev)) || g.getEdgeTarget(cur).equals(g.getEdgeTarget(prev))); } } if (!it.hasNext()) { last = cur; } prev = cur; } if (edges.size() > 1) { if (isDirected) { assertTrue(g.getEdgeSource(first).equals(g.getEdgeTarget(last))); } else { assertTrue( g.getEdgeSource(first).equals(g.getEdgeSource(last)) || g.getEdgeSource(first).equals(g.getEdgeTarget(last)) || g.getEdgeTarget(first).equals(g.getEdgeSource(last)) || g.getEdgeTarget(first).equals(g.getEdgeTarget(last))); } } } } WeakChordalityInspectorTest.java000066400000000000000000000425351402514743400342200ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/cycle/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.cycle; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for {@link WeakChordalityInspector} * * @author Timofey Chudakov */ public class WeakChordalityInspectorTest { /** * Test on empty graph */ @Test public void testIsWeaklyChordal1() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); WeakChordalityInspector inspector = new WeakChordalityInspector<>(graph); assertTrue(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNull(graphPath); } /** * Test on small chordal graph */ @Test public void testIsWeaklyChordal2() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); WeakChordalityInspector inspector = new WeakChordalityInspector<>(graph); assertTrue(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNull(graphPath); } /** * Test on small weakly chordal graph */ @Test public void testIsWeaklyChordal3() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); WeakChordalityInspector inspector = new WeakChordalityInspector<>(graph); assertTrue(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNull(graphPath); } /** * Test on hole */ @Test public void testIsWeaklyChordal4() { int[][] edges = { { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 1 }, }; Graph graph = TestUtil.createUndirected(edges); WeakChordalityInspector inspector = new WeakChordalityInspector<>(graph); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(graph, graphPath); } /** * Test on anti hole */ @Test public void testIsWeaklyChordal5() { int[][] edges = { { 1, 3 }, { 1, 4 }, { 1, 5 }, { 1, 6 }, { 2, 4 }, { 2, 5 }, { 2, 6 }, { 2, 7 }, { 3, 5 }, { 3, 6 }, { 3, 7 }, { 4, 6 }, { 4, 7 }, { 5, 7 }, }; Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); TestUtil.constructGraph(graph, edges); WeakChordalityInspector inspector = new WeakChordalityInspector<>(graph); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(graph, graphPath); } /** * Test on weakly chordal pseudograph */ @Test public void testIsWeaklyChordal6() { int[][] edges = { { 1, 1 }, { 1, 1 }, { 1, 2 }, { 1, 2 }, { 1, 2 }, { 1, 3 }, { 2, 4 }, { 2, 4 }, { 2, 4 }, { 3, 4 }, { 4, 4 }, { 4, 4 }, { 4, 4 }, }; Graph graph = TestUtil.createUndirected(edges); WeakChordalityInspector inspector = new WeakChordalityInspector<>(graph); assertTrue(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNull(graphPath); } /** * Test on big not weakly chordal graph */ @Test public void testIsWeaklyChordal7() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 4 }, { 2, 7 }, { 2, 8 }, { 2, 10 }, { 2, 5 }, { 3, 5 }, { 3, 6 }, { 4, 7 }, { 5, 8 }, { 5, 9 }, { 5, 6 }, { 6, 9 }, { 7, 8 }, { 7, 10 }, { 8, 9 }, { 8, 10 }, { 9, 10 }, }; Graph graph = TestUtil.createUndirected(edges); WeakChordalityInspector inspector = new WeakChordalityInspector<>(graph); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(graph, graphPath); } /** * Test on big chordless cycle */ @Test public void testIsWeaklyChordal8() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); int bound = 100; for (int i = 0; i < bound; i++) { Graphs.addEdgeWithVertices(graph, i, i + 1); } Graphs.addEdgeWithVertices(graph, 0, bound); WeakChordalityInspector inspector = new WeakChordalityInspector<>(graph); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(graph, graphPath); } /** * Test on big complete graph */ @Test public void testIsWeaklyChordal9() { Graph graph = new DefaultUndirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); CompleteGraphGenerator generator = new CompleteGraphGenerator<>(50); generator.generateGraph(graph); WeakChordalityInspector inspector = new WeakChordalityInspector<>(graph); assertTrue(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNull(graphPath); } @Test public void testIsWeaklyChordal10() { Graph dodecahedron = NamedGraphGenerator.dodecahedronGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(dodecahedron); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(dodecahedron, graphPath); } @Test public void testIsWeaklyChordal11() { Graph bull = NamedGraphGenerator.bullGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(bull); assertTrue(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNull(graphPath); } @Test public void testIsWeaklyChordal12() { Graph buckyBall = NamedGraphGenerator.buckyBallGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(buckyBall); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(buckyBall, graphPath); } @Test public void testIsWeaklyChordal13() { Graph clebsch = NamedGraphGenerator.clebschGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(clebsch); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(clebsch, graphPath); } @Test public void testIsWeaklyChordal14() { Graph grötzsch = NamedGraphGenerator.grötzschGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(grötzsch); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(grötzsch, graphPath); } @Test public void testIsWeaklyChordal15() { Graph bidiakis = NamedGraphGenerator.bidiakisCubeGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(bidiakis); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(bidiakis, graphPath); } @Test public void testIsWeaklyChordal16() { Graph blanusaFirstSnark = NamedGraphGenerator.blanusaFirstSnarkGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(blanusaFirstSnark); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(blanusaFirstSnark, graphPath); } @Test public void testIsWeaklyChordal17() { Graph doubleStarSnark = NamedGraphGenerator.doubleStarSnarkGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(doubleStarSnark); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(doubleStarSnark, graphPath); } @Test public void testIsWeaklyChordal18() { Graph brinkmann = NamedGraphGenerator.brinkmannGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(brinkmann); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(brinkmann, graphPath); } @Test public void testIsWeaklyChordal19() { Graph gosset = NamedGraphGenerator.gossetGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(gosset); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(gosset, graphPath); } @Test public void testIsWeaklyChordal20() { Graph chvatal = NamedGraphGenerator.chvatalGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(chvatal); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(chvatal, graphPath); } @Test public void testIsWeaklyChordal21() { Graph kittell = NamedGraphGenerator.kittellGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(kittell); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(kittell, graphPath); } @Test public void testIsWeaklyChordal22() { Graph coxeter = NamedGraphGenerator.coxeterGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(coxeter); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(coxeter, graphPath); } @Test public void testIsWeaklyChordal23() { Graph ellinghamHorton78 = NamedGraphGenerator.ellinghamHorton78Graph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(ellinghamHorton78); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(ellinghamHorton78, graphPath); } @Test public void testIsWeaklyChordal24() { Graph errera = NamedGraphGenerator.erreraGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(errera); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(errera, graphPath); } @Test public void testIsWeaklyChordal25() { Graph folkman = NamedGraphGenerator.folkmanGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(folkman); assertFalse(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNotNull(graphPath); assertIsHoleOrAntiHole(folkman, graphPath); } @Test public void testIsWeaklyChordal26() { Graph krackhardtKite = NamedGraphGenerator.krackhardtKiteGraph(); WeakChordalityInspector inspector = new WeakChordalityInspector<>(krackhardtKite); assertTrue(inspector.isWeaklyChordal()); GraphPath graphPath = inspector.getCertificate(); assertNull(graphPath); } /** * Asserts that the specified {@code path} forms a hole or anti-hole in the {@code graph} * * @param graph the graph that should contain a hole or anti-hole * @param path a path in the {@code graph} * @param the graph vertex type * @param the graph edge type */ private void assertIsHoleOrAntiHole(Graph graph, GraphPath path) { assertTrue(isHole(graph, path) || isAntiHole(graph, path)); } /** * Checks whether specified {@code path} forms a hole in the {@code graph} * * @param graph the graph that should contain a hole * @param path a path in the {@code graph} * @param the graph vertex type * @param the graph edge type * @return true is the {@code path} forms a hole in the {@code graph}, false otherwise */ private boolean isHole(Graph graph, GraphPath path) { List vertices = path.getVertexList(); if (vertices.size() < 6 || !vertices.get(0).equals(vertices.get(vertices.size() - 1))) { return false; } for (int i = 0; i < vertices.size() - 1; i++) { if (!graph.containsEdge(vertices.get(i), vertices.get(i + 1))) { return false; } } for (int i = 0; i < vertices.size() - 2; i++) { for (int j = 0; j < vertices.size() - 2; j++) { if (Math.abs(i - j) > 1 && graph.containsEdge(vertices.get(i), vertices.get(j))) { return false; } } } return true; } /** * Checks whether specified {@code path} forms an anti-hole in the {@code graph} * * @param graph the graph that should contain an anti-hole * @param path a path in the {@code graph} * @param the graph vertex type * @param the graph edge type * @return true is the {@code path} forms an anti-hole in the {@code graph}, false otherwise */ private boolean isAntiHole(Graph graph, GraphPath path) { List vertices = path.getVertexList(); if (vertices.size() < 6 || !vertices.get(0).equals(vertices.get(vertices.size() - 1))) { return false; } for (int i = 0; i < vertices.size() - 1; i++) { if (graph.containsEdge(vertices.get(i), vertices.get(i + 1))) { return false; } } for (int i = 0; i < vertices.size() - 2; i++) { for (int j = 0; j < vertices.size() - 2; j++) { if (Math.abs(i - j) > 1 && !graph.containsEdge(vertices.get(i), vertices.get(j))) { return false; } } } return true; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/decomposition/000077500000000000000000000000001402514743400275375ustar00rootroot00000000000000DulmageMendelsohnDecompositionTest.java000066400000000000000000000157111402514743400373200ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/decomposition/* * (C) Copyright 2018-2021, by CAE Tech Limited and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.decomposition; import org.jgrapht.*; import org.jgrapht.alg.interfaces.MatchingAlgorithm.*; import org.jgrapht.alg.matching.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.runner.*; import org.junit.runners.*; import org.junit.runners.Parameterized.*; import java.util.*; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * Test for Dulmage-Mendelsohn, based on MaximumCardinailityBipartiteMatchingTest * * @author Peter Harman * @author Joris Kinable */ @RunWith(Parameterized.class) public class DulmageMendelsohnDecompositionTest { private final GnmRandomBipartiteGraphGenerator generator; public DulmageMendelsohnDecompositionTest( GnmRandomBipartiteGraphGenerator generator) { this.generator = generator; } @Parameters public static Collection generators() { Collection out = new ArrayList<>(); Random random = new Random(1); for (int vertices = 20; vertices < 120; vertices++) { int edges = random.nextInt(maxEdges(vertices) / 2); int imbalance = randomImbalance(random, vertices); GnmRandomBipartiteGraphGenerator generator = new GnmRandomBipartiteGraphGenerator<>( vertices - imbalance, vertices + imbalance, edges, 0); out.add(new Object[] { generator }); } return out; } @Test public void testGeneratedGraph() { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(graph); DulmageMendelsohnDecomposition dm = new DulmageMendelsohnDecomposition<>( graph, generator.getFirstPartition(), generator.getSecondPartition()); assertValidDecomposition( graph, dm.getDecomposition(true), generator.getFirstPartition(), generator.getSecondPartition()); assertValidDecomposition( graph, dm.getDecomposition(false), generator.getFirstPartition(), generator.getSecondPartition()); } /** * Assert that the structure of the decomposition is valid * * @param * @param * @param graph * @param decomposition * @param partition1 * @param partition2 */ private static void assertValidDecomposition( Graph graph, DulmageMendelsohnDecomposition.Decomposition decomposition, Set partition1, Set partition2) { // Is the perfect matched set actually perfectly matched? Set allPerfectlyMatched = new HashSet<>(); Set partition1PerfectlyMatched = new HashSet<>(); Set partition2PerfectlyMatched = new HashSet<>(); for (Set set : decomposition.getPerfectMatchedSets()) { allPerfectlyMatched.addAll(set); for (V v : set) { if (partition1.contains(v)) { partition1PerfectlyMatched.add(v); } if (partition2.contains(v)) { partition2PerfectlyMatched.add(v); } } ; } ; Matching perfectMatching = new HopcroftKarpMaximumCardinalityBipartiteMatching<>( new AsSubgraph<>(graph, allPerfectlyMatched), partition1PerfectlyMatched, partition2PerfectlyMatched).getMatching(); assertTrue("Core of decomposition must perfectly match", perfectMatching.isPerfect()); // Do all the vertices in the graph appear in the decomposition, and only in one part of it? for (V v : graph.vertexSet()) { if (allPerfectlyMatched.contains(v)) { assertFalse( "Vertex appears in multiple sets in decomposition", decomposition.getPartition1DominatedSet().contains(v)); assertFalse( "Vertex appears in multiple sets in decomposition", decomposition.getPartition2DominatedSet().contains(v)); } else if (decomposition.getPartition1DominatedSet().contains(v)) { assertFalse( "Vertex appears in multiple sets in decomposition", allPerfectlyMatched.contains(v)); assertFalse( "Vertex appears in multiple sets in decomposition", decomposition.getPartition2DominatedSet().contains(v)); } else { assertTrue( "Vertex appears in multiple sets in decomposition", decomposition.getPartition2DominatedSet().contains(v)); } } ; // Are the partition1/2 dominated sets dominated as expected? int n1 = 0; int n2 = 0; for (V v : decomposition.getPartition1DominatedSet()) { if (partition1.contains(v)) { n1++; } else { n2++; } } assertTrue( "Partition 1 dominated set is not dominated by partition 1", n1 > n2 || (n1 == 0 && n2 == 0)); n1 = 0; n2 = 0; for (V v : decomposition.getPartition2DominatedSet()) { if (partition1.contains(v)) { n1++; } else { n2++; } } assertTrue( "Partition 2 dominated set is not dominated by partition 2", n1 < n2 || (n1 == 0 && n2 == 0)); } /** * Calculate the maximum number of edges for number of vertices * * @param n * @return */ private static int maxEdges(int n) { if (n % 2 == 0) { return Math.multiplyExact(n / 2, n - 1); } else { return Math.multiplyExact(n, (n - 1) / 2); } } /** * Generate a random difference between the size of partition1 and partition2 * * @param random * @param n * @return */ private static int randomImbalance(Random random, int n) { int max = Math.floorDiv(n, 4); return random.nextInt(max * 2) - max; } } HeavyPathDecompositionTest.java000066400000000000000000000331331402514743400356140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/decomposition/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.decomposition; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import java.util.stream.*; import static org.jgrapht.util.MathUtil.log2; /** * Tests for {@link HeavyPathDecomposition} * * @author Alexandru Valeanu */ public class HeavyPathDecompositionTest { // Count the maximum number of light edges on any root-to-leaf path public static int countMaxPath(Graph graph, HeavyPathDecomposition decomposition) { Set> paths = decomposition.getPathDecomposition().getPaths(); Map whichPath = new HashMap<>(); int i = 0; for (GraphPath path : paths) { List vertexList = path.getVertexList(); for (int j = 0; j < vertexList.size(); j++) { whichPath.put(vertexList.get(j), i); } i++; } int maxim = 0; HeavyPathDecomposition.InternalState state = decomposition.getInternalState(); for (V v : graph.vertexSet()) { if (whichPath.containsKey(v)) { int cnt = 0; while (true) { V u = state.getParent(v); if (u != null) { E edge = graph.getEdge(u, v); if (decomposition.getLightEdges().contains(edge)) { cnt++; } v = u; } else break; } maxim = Math.max(maxim, cnt); } } return maxim; } public static boolean isValidDecomposition( Graph graph, Set roots, HeavyPathDecomposition decomposition) { Set heavyEdges = decomposition.getHeavyEdges(); Set lightEdges = decomposition.getLightEdges(); Set allEdges = new HashSet<>(heavyEdges); allEdges.addAll(lightEdges); // Check that heavyEdges + lightEdges = allEdges if (!allEdges.equals(graph.edgeSet())) return false; Set> paths = decomposition.getPathDecomposition().getPaths(); Map whichPath = new HashMap<>(); int i = 0; for (GraphPath path : paths) { List vertexList = path.getVertexList(); for (int j = 0; j < vertexList.size(); j++) { // Check if a vertex appear more than once in the decomposition if (whichPath.containsKey(vertexList.get(j))) return false; whichPath.put(vertexList.get(j), i); // Check if the path is actually a valid path in the graph if (j > 0) { if (!graph.containsEdge(vertexList.get(j - 1), vertexList.get(j))) return false; } } i++; } ConnectivityInspector connectivityInspector = new ConnectivityInspector<>(graph); // Check if every reachable vertex from a root is in a path for (V root : roots) { for (V v : connectivityInspector.connectedSetOf(root)) if (!whichPath.containsKey(v)) { return false; } } for (V root : roots) { BreadthFirstIterator bfs = new BreadthFirstIterator<>(graph, root); List postOrder = new ArrayList<>(); while (bfs.hasNext()) { V v = bfs.next(); postOrder.add(v); } Collections.reverse(postOrder); Map sizeSubtree = CollectionUtil.newHashMapWithExpectedSize(graph.vertexSet().size()); for (V v : postOrder) { sizeSubtree.put(v, 1); for (E edge : graph.edgesOf(v)) { V u = Graphs.getOppositeVertex(graph, edge, v); if (!u.equals(bfs.getParent(v))) { int sizeU = sizeSubtree.get(u); sizeSubtree.put(v, sizeSubtree.get(v) + sizeU); } } for (E edge : graph.edgesOf(v)) { if (lightEdges.contains(edge)) { V u = Graphs.getOppositeVertex(graph, edge, v); if (!u.equals(bfs.getParent(v)) && 2 * sizeSubtree.get(u) > sizeSubtree.get(v)) { return false; } } else { // edge is heavy V u = Graphs.getOppositeVertex(graph, edge, v); if (!u.equals(bfs.getParent(v)) && 2 * sizeSubtree.get(u) <= sizeSubtree.get(v)) { return false; } } } } } return countMaxPath(graph, decomposition) <= log2(graph.vertexSet().size()); } @Test(expected = NullPointerException.class) public void testNullGraph() { HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(null, 1); } @Test(expected = NullPointerException.class) public void testNullRoot() { Graph graph = new SimpleGraph<>(DefaultEdge.class); String s = null; HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, s); } @Test(expected = IllegalArgumentException.class) public void testRootNotInTree() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex("a"); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, "b"); } @Test public void testNoHeavyEdges() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex("1"); graph.addVertex("2"); graph.addVertex("3"); graph.addVertex("4"); graph.addEdge("1", "2"); graph.addEdge("1", "3"); graph.addEdge("1", "4"); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, "1"); Assert.assertTrue(heavyPathDecomposition.getHeavyEdges().isEmpty()); Assert .assertTrue( isValidDecomposition(graph, Collections.singleton("1"), heavyPathDecomposition)); } @Test public void testOneVertex() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex("a"); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, "a"); Assert.assertEquals(1, heavyPathDecomposition.getPathDecomposition().numberOfPaths()); Assert .assertTrue( isValidDecomposition(graph, Collections.singleton("a"), heavyPathDecomposition)); } @Test public void testLineGraph() { Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= 11; i++) graph.addVertex(i); for (int i = 1; i < 11; i++) graph.addEdge(i, i + 1); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, 1); Assert.assertEquals(1, heavyPathDecomposition.getPathDecomposition().numberOfPaths()); Assert .assertTrue( isValidDecomposition(graph, Collections.singleton(1), heavyPathDecomposition)); } @Test public void testLineGraph2() { Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= 11; i++) graph.addVertex(i); for (int i = 1; i < 11; i++) graph.addEdge(i, i + 1); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, 5); Assert.assertEquals(2, heavyPathDecomposition.getPathDecomposition().numberOfPaths()); Assert .assertTrue( isValidDecomposition(graph, Collections.singleton(5), heavyPathDecomposition)); } @Test public void testSmallTree() { Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= 11; i++) graph.addVertex(i); graph.addEdge(1, 2); graph.addEdge(2, 4); graph.addEdge(2, 5); graph.addEdge(2, 6); graph.addEdge(4, 7); graph.addEdge(4, 8); graph.addEdge(6, 9); graph.addEdge(1, 3); graph.addEdge(3, 10); graph.addEdge(3, 11); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, 1); Assert .assertTrue( isValidDecomposition(graph, Collections.singleton(1), heavyPathDecomposition)); } @Test public void testDisconnectedSmallGraph() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addEdge(1, 2); graph.addVertex(3); graph.addVertex(4); graph.addEdge(3, 4); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, 1); Assert.assertEquals(1, heavyPathDecomposition.getPathDecomposition().numberOfPaths()); Assert.assertTrue(heavyPathDecomposition.getHeavyEdges().isEmpty()); Assert.assertEquals(1, heavyPathDecomposition.getLightEdges().size()); } @Test @Category(SlowTests.class) public void testRandomTrees() { final int NUM_TESTS = 100; Random random = new Random(0x2882); for (int test = 0; test < NUM_TESTS; test++) { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(0), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>(1, 1024 + random.nextInt(1 << 12), random); generator.generateGraph(graph, null); Set roots = Collections.singleton(graph.vertexSet().iterator().next()); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, roots); Assert.assertTrue(isValidDecomposition(graph, roots, heavyPathDecomposition)); } } @Test @Category(SlowTests.class) public void testRandomForests() { final int NUM_TESTS = 1000; Random random = new Random(0x1881); for (int test = 0; test < NUM_TESTS; test++) { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(0), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>( 1 + random.nextInt(20), 50 + random.nextInt(1 << 11), random); generator.generateGraph(graph, null); ConnectivityInspector connectivityInspector = new ConnectivityInspector<>(graph); List> connectedComponents = connectivityInspector.connectedSets(); Set roots = connectedComponents .stream().map(component -> component.iterator().next()).collect(Collectors.toSet()); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, roots); Assert.assertTrue(isValidDecomposition(graph, roots, heavyPathDecomposition)); } } @Test @Category(SlowTests.class) public void testHugeTree() { Random random = new Random(0x118811); Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(0), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>(1, 1 << 19, random); generator.generateGraph(graph, null); Set roots = Collections.singleton(graph.vertexSet().iterator().next()); HeavyPathDecomposition heavyPathDecomposition = new HeavyPathDecomposition<>(graph, roots); Assert.assertTrue(isValidDecomposition(graph, roots, heavyPathDecomposition)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/000077500000000000000000000000001402514743400275155ustar00rootroot00000000000000GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsPerEdgeTest.java000066400000000000000000000144671402514743400445400ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/* * (C) Copyright 2018-2021, by Andre Immig and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.densesubgraph; import org.jgrapht.*; import org.jgrapht.alg.flow.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.function.*; import static java.util.Arrays.asList; /** * Tests for {@link GoldbergMaximumDensitySubgraphAlgorithm} * * @author Andre Immig */ public class GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsPerEdgeTest extends GoldbergMaximumDensitySubgraphTestBase, DefaultEdge> { @Override protected MaximumDensitySubgraphAlgorithm, DefaultEdge> constructSolver( Graph, DefaultEdge> g, Function, DefaultWeightedEdge>, MinimumSTCutAlgorithm, DefaultWeightedEdge>> alg) { return new GoldbergMaximumDensitySubgraphAlgorithmNodeWeightPerEdgeWeight<>( g, s, t, DEFAULT_EPS, alg); } @Override protected Pair getAdditionalSink() { return new Pair<>(-1, 0.0); } @Override protected Pair getAdditionalSource() { return new Pair<>(-2, 0.0); } @Test public void testEmpty1() { WeightedMultigraph, DefaultEdge> g = new WeightedMultigraph<>(DefaultEdge.class); test(g, constructSolver(g, PushRelabelMFImpl::new), 0, new ArrayList<>()); } @Test public void testEmpty2() { WeightedMultigraph, DefaultEdge> g = new WeightedMultigraph<>(DefaultEdge.class); Pair p1 = new Pair<>(0, 1.3); Pair p2 = new Pair<>(1, 2.1); addVertices(g, asList(p1, p2)); test(g, constructSolver(g, PushRelabelMFImpl::new), 0, new ArrayList<>()); } @Test public void testMinimal() { SimpleDirectedWeightedGraph, DefaultEdge> g = new SimpleDirectedWeightedGraph<>(DefaultEdge.class); Pair v1 = new Pair<>(1, 1.5); Pair v2 = new Pair<>(0, 2.5); addVertices(g, asList(v1, v2)); addEdgesAndWeights( g, Collections.singletonList(new Pair<>(v1, v2)), Collections.singletonList(10.0)); test(g, constructSolver(g, PushRelabelMFImpl::new), 2.5, asList(v1, v2)); } @Test public void testSmall1() { SimpleWeightedGraph, DefaultEdge> g = new SimpleWeightedGraph<>(DefaultEdge.class); ArrayList> vertices = new ArrayList<>(); vertices.add(new Pair<>(0, 1.51)); vertices.add(new Pair<>(1, 1.0)); vertices.add(new Pair<>(2, 1.0)); addVertices(g, vertices); addEdgesAndWeights( g, asList( new Pair<>(vertices.get(0), vertices.get(1)), new Pair<>(vertices.get(0), vertices.get(2))), asList(4.0, 2.0)); test( g, constructSolver(g, PushRelabelMFImpl::new), 1.709401, getByIndices(vertices, asList(0, 1, 2))); } @Test public void testSmall2() { SimpleWeightedGraph, DefaultEdge> g = new SimpleWeightedGraph<>(DefaultEdge.class); ArrayList> vertices = new ArrayList<>(); for (int i = 0; i <= 7; i++) { vertices.add(new Pair<>(i, 1.1)); } addVertices(g, vertices); List, Pair>> edges = asList( new Pair<>(vertices.get(0), vertices.get(1)), new Pair<>(vertices.get(1), vertices.get(2)), new Pair<>(vertices.get(2), vertices.get(3)), new Pair<>(vertices.get(3), vertices.get(4)), new Pair<>(vertices.get(4), vertices.get(5)), new Pair<>(vertices.get(5), vertices.get(6)), new Pair<>(vertices.get(6), vertices.get(7)), new Pair<>(vertices.get(1), vertices.get(7)), new Pair<>(vertices.get(2), vertices.get(7)), new Pair<>(vertices.get(3), vertices.get(7)), new Pair<>(vertices.get(4), vertices.get(2))); List weights = asList(3.0, 2.0, 1.0, 2.0, 1.0, 3.0, 1.0, 2.0, 1.0, 4.0, 1.0); addEdgesAndWeights(g, edges, weights); test( g, constructSolver(g, PushRelabelMFImpl::new), 2.424242, getByIndices(vertices, asList(0, 1, 2, 3, 4, 7))); } @Test public void testMedium() { DirectedWeightedMultigraph, DefaultEdge> g = new DirectedWeightedMultigraph<>(DefaultEdge.class); List> vertices = new ArrayList<>(); List weights = new ArrayList<>(); List, Pair>> edges = new ArrayList<>(); for (int i = 0; i <= 100; i++) { vertices.add(new Pair<>(i, 1.0)); } addVertices(g, vertices); for (int i = 1; i <= 50; i++) { edges.add(new Pair<>(vertices.get(i), vertices.get(i / 2))); weights.add(1 / Math.log10(i + 1)); } for (int j = 50; j <= 100; j++) { edges.add(new Pair<>(vertices.get(j), vertices.get(1))); weights.add(100 / (double) j); } List> expected = vertices.subList(50, 101); expected.add(vertices.get(0)); expected.add(vertices.get(1)); expected.add(vertices.get(2)); addEdgesAndWeights(g, edges, weights); test(g, constructSolver(g, PushRelabelMFImpl::new), 1.411760, expected); } } GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsTest.java000066400000000000000000000144371402514743400433010ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/* * (C) Copyright 2018-2021, by Andre Immig and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.densesubgraph; import org.jgrapht.*; import org.jgrapht.alg.flow.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.function.*; import static java.util.Arrays.asList; /** * Tests for {@link GoldbergMaximumDensitySubgraphAlgorithm} * * @author Andre Immig */ public class GoldbergMaximumDensitySubgraphAlgorithmNodeWeightsTest extends GoldbergMaximumDensitySubgraphTestBase, DefaultEdge> { @Override protected MaximumDensitySubgraphAlgorithm, DefaultEdge> constructSolver( Graph, DefaultEdge> g, Function, DefaultWeightedEdge>, MinimumSTCutAlgorithm, DefaultWeightedEdge>> alg) { return new GoldbergMaximumDensitySubgraphAlgorithmNodeWeights<>(g, s, t, DEFAULT_EPS, alg); } @Override protected Pair getAdditionalSink() { return new Pair<>(-1, 0.0); } @Override protected Pair getAdditionalSource() { return new Pair<>(-2, 0.0); } @Test public void testEmpty1() { WeightedMultigraph, DefaultEdge> g = new WeightedMultigraph<>(DefaultEdge.class); test(g, constructSolver(g, PushRelabelMFImpl::new), 0, new ArrayList<>()); } @Test public void testEmpty2() { WeightedMultigraph, DefaultEdge> g = new WeightedMultigraph<>(DefaultEdge.class); Pair p1 = new Pair<>(0, 1.3); Pair p2 = new Pair<>(1, 2.1); addVertices(g, asList(p1, p2)); test(g, constructSolver(g, PushRelabelMFImpl::new), 2.1, Collections.singletonList(p2)); } @Test public void testMinimal() { SimpleDirectedWeightedGraph, DefaultEdge> g = new SimpleDirectedWeightedGraph<>(DefaultEdge.class); Pair v1 = new Pair<>(1, 1.5); Pair v2 = new Pair<>(0, 2.5); addVertices(g, asList(v1, v2)); addEdgesAndWeights( g, Collections.singletonList(new Pair<>(v1, v2)), Collections.singletonList(10.0)); test(g, constructSolver(g, PushRelabelMFImpl::new), 7, asList(v1, v2)); } @Test public void testSmall1() { SimpleWeightedGraph, DefaultEdge> g = new SimpleWeightedGraph<>(DefaultEdge.class); ArrayList> vertices = new ArrayList<>(); vertices.add(new Pair<>(0, 1.51)); vertices.add(new Pair<>(1, 1.0)); vertices.add(new Pair<>(2, 1.0)); addVertices(g, vertices); addEdgesAndWeights( g, asList( new Pair<>(vertices.get(0), vertices.get(1)), new Pair<>(vertices.get(0), vertices.get(2))), asList(4.0, 2.0)); test( g, constructSolver(g, PushRelabelMFImpl::new), 3.255, getByIndices(vertices, asList(0, 1))); } @Test public void testSmall2() { SimpleWeightedGraph, DefaultEdge> g = new SimpleWeightedGraph<>(DefaultEdge.class); ArrayList> vertices = new ArrayList<>(); for (int i = 0; i <= 7; i++) { vertices.add(new Pair<>(i, 1.1)); } addVertices(g, vertices); List, Pair>> edges = asList( new Pair<>(vertices.get(0), vertices.get(1)), new Pair<>(vertices.get(1), vertices.get(2)), new Pair<>(vertices.get(2), vertices.get(3)), new Pair<>(vertices.get(3), vertices.get(4)), new Pair<>(vertices.get(4), vertices.get(5)), new Pair<>(vertices.get(5), vertices.get(6)), new Pair<>(vertices.get(6), vertices.get(7)), new Pair<>(vertices.get(1), vertices.get(7)), new Pair<>(vertices.get(2), vertices.get(7)), new Pair<>(vertices.get(3), vertices.get(7)), new Pair<>(vertices.get(4), vertices.get(2))); List weights = asList(3.0, 2.0, 1.0, 2.0, 1.0, 3.0, 1.0, 2.0, 1.0, 4.0, 1.0); addEdgesAndWeights(g, edges, weights); test( g, constructSolver(g, PushRelabelMFImpl::new), 3.76666666, getByIndices(vertices, asList(0, 1, 2, 3, 4, 7))); } @Test public void testMedium() { DirectedWeightedMultigraph, DefaultEdge> g = new DirectedWeightedMultigraph<>(DefaultEdge.class); List> vertices = new ArrayList<>(); List weights = new ArrayList<>(); List, Pair>> edges = new ArrayList<>(); for (int i = 0; i <= 100; i++) { vertices.add(new Pair<>(i, 1.0)); } addVertices(g, vertices); for (int i = 1; i <= 50; i++) { edges.add(new Pair<>(vertices.get(i), vertices.get(i / 2))); weights.add(1 / Math.log10(i + 1)); } for (int j = 50; j <= 100; j++) { edges.add(new Pair<>(vertices.get(j), vertices.get(1))); weights.add(100 / (double) j); } List> expected = vertices.subList(50, 101); expected.add(vertices.get(0)); expected.add(vertices.get(1)); expected.add(vertices.get(2)); addEdgesAndWeights(g, edges, weights); test(g, constructSolver(g, PushRelabelMFImpl::new), 2.411760, expected); } } GoldbergMaximumDensitySubgraphAlgorithmTest.java000066400000000000000000000126731402514743400411400ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/* * (C) Copyright 2018-2021, by Andre Immig and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.densesubgraph; import org.jgrapht.*; import org.jgrapht.alg.flow.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.function.*; import static java.util.Arrays.asList; /** * Tests for {@link GoldbergMaximumDensitySubgraphAlgorithm} * * @author Andre Immig */ public class GoldbergMaximumDensitySubgraphAlgorithmTest extends GoldbergMaximumDensitySubgraphTestBase { @Override protected MaximumDensitySubgraphAlgorithm constructSolver( Graph g, Function, MinimumSTCutAlgorithm> alg) { return new GoldbergMaximumDensitySubgraphAlgorithm<>(g, s, t, DEFAULT_EPS, alg); } @Override protected Integer getAdditionalSource() { return -1; } @Override protected Integer getAdditionalSink() { return -2; } @Test public void testEmpty1() { WeightedMultigraph g = new WeightedMultigraph<>(DefaultEdge.class); test(g, constructSolver(g, PushRelabelMFImpl::new), 0, new ArrayList<>()); } @Test public void testEmpty2() { WeightedMultigraph g = new WeightedMultigraph<>(DefaultEdge.class); addVertices(g, asList(0, 1)); test(g, constructSolver(g, PushRelabelMFImpl::new), 0, new ArrayList<>()); } @Test public void testMinimal() { WeightedMultigraph g = new WeightedMultigraph<>(DefaultEdge.class); addVertices(g, asList(0, 1)); addEdgesAndWeights( g, Collections.singletonList(new Pair<>(0, 1)), Collections.singletonList(10.0)); test(g, constructSolver(g, PushRelabelMFImpl::new), 5, asList(0, 1)); } @Test public void testSmall1() { WeightedMultigraph g = new WeightedMultigraph<>(DefaultEdge.class); addVertices(g, asList(0, 1, 2, 3, 4)); List> edges = asList( new Pair<>(0, 3), new Pair<>(0, 1), new Pair<>(0, 2), new Pair<>(4, 2), new Pair<>(0, 4), new Pair<>(2, 3)); List weights = asList(2.0, 1.0, 1.0, 1.0, 3.0, 1.0); addEdgesAndWeights(g, edges, weights); test(g, constructSolver(g, PushRelabelMFImpl::new), 2, asList(0, 2, 3, 4)); } @Test public void testSmall2() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultEdge.class); addVertices(g, asList(0, 1, 2, 3, 4, 5, 6, 7)); List> edges = asList( new Pair<>(0, 1), new Pair<>(1, 2), new Pair<>(2, 3), new Pair<>(3, 4), new Pair<>(4, 5), new Pair<>(5, 6), new Pair<>(6, 7), new Pair<>(1, 7), new Pair<>(2, 7), new Pair<>(3, 7), new Pair<>(4, 2)); List weights = asList(3.0, 2.0, 1.0, 2.0, 1.0, 3.0, 1.0, 2.0, 1.0, 4.0, 1.0); addEdgesAndWeights(g, edges, weights); test(g, constructSolver(g, PushRelabelMFImpl::new), 2.66666666, asList(0, 1, 2, 3, 4, 7)); } @Test public void testSmallWeights() { SimpleDirectedWeightedGraph g = new SimpleDirectedWeightedGraph<>(DefaultEdge.class); addVertices(g, asList(0, 1, 2, 3, 4)); List> edges = asList( new Pair<>(0, 3), new Pair<>(0, 1), new Pair<>(0, 2), new Pair<>(4, 2), new Pair<>(0, 4), new Pair<>(2, 3)); List weights = asList(0.0002, 0.00000001, 0.001, 0.0009, 0.003, 0.001); addEdgesAndWeights(g, edges, weights); test(g, constructSolver(g, PushRelabelMFImpl::new), 0.001633333, asList(0, 2, 4)); } @Test public void testMedium() { DirectedWeightedMultigraph g = new DirectedWeightedMultigraph<>(DefaultEdge.class); List vertices = new ArrayList<>(); List weights = new ArrayList<>(); List> edges = new ArrayList<>(); for (int i = 0; i <= 100; i++) { vertices.add(i); } addVertices(g, vertices); for (int i = 1; i <= 50; i++) { edges.add(new Pair<>(i, i / 2)); weights.add(1 / Math.log10(i + 1)); } for (int j = 50; j <= 100; j++) { edges.add(new Pair<>(j, 1)); weights.add(100 / (double) j); } List expected = vertices.subList(50, 101); expected.add(0); expected.add(1); expected.add(2); addEdgesAndWeights(g, edges, weights); test(g, constructSolver(g, PushRelabelMFImpl::new), 1.411760, expected); } } GoldbergMaximumDensitySubgraphTestBase.java000066400000000000000000000051431402514743400400560ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/densesubgraph/* * (C) Copyright 2018-2021, by Andre Immig and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.densesubgraph; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import java.util.*; import java.util.function.*; import java.util.stream.*; import static org.junit.Assert.assertEquals; /** * base class for {@link GoldbergMaximumDensitySubgraphAlgorithm} testing * * @author Andre Immig */ public abstract class GoldbergMaximumDensitySubgraphTestBase { protected final double DEFAULT_EPS = Math.pow(10, -5); protected V s, t; public GoldbergMaximumDensitySubgraphTestBase() { s = this.getAdditionalSource(); t = this.getAdditionalSink(); } protected abstract MaximumDensitySubgraphAlgorithm constructSolver( Graph g, Function, MinimumSTCutAlgorithm> alg); protected abstract V getAdditionalSource(); protected abstract V getAdditionalSink(); protected void addVertices(Graph g, List vertices) { for (V v : vertices) { g.addVertex(v); } } protected List getByIndices(List list, List indexes) { return indexes.stream().map(list::get).collect(Collectors.toList()); } protected void addEdgesAndWeights(Graph g, List> edges, List weights) { for (int i = 0; i < edges.size(); i++) { Pair e = edges.get(i); g.setEdgeWeight(g.addEdge(e.getFirst(), e.getSecond()), weights.get(i)); } } public void test( Graph g, MaximumDensitySubgraphAlgorithm solver, double expectedDensity, List expectedVertices) { Graph computed = solver.calculateDensest(); assertEquals(expectedDensity, solver.getDensity(), DEFAULT_EPS); Graph expected = new AsSubgraph<>(g, new LinkedHashSet<>(expectedVertices)); assertEquals(expected, computed); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/000077500000000000000000000000001402514743400263165ustar00rootroot00000000000000CircularLayoutAlgorithm2DTest.java000066400000000000000000000057371402514743400347550ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.model.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertTrue; /** * Test {@link CircularLayoutAlgorithm2D}. * * @author Dimitrios Michail */ public class CircularLayoutAlgorithm2DTest { @Test public void testSimple() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); CircularLayoutAlgorithm2D alg = new CircularLayoutAlgorithm2D<>(1d); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 2d, 2d)); alg.layout(graph, model); assertTrue(Points.equals(Point2D.of(2d, 1d), model.get(v1))); assertTrue(Points.equals(Point2D.of(1d, 2d), model.get(v2))); assertTrue(Points.equals(Point2D.of(0d, 1d), model.get(v3))); assertTrue(Points.equals(Point2D.of(1d, 0d), model.get(v4))); } @Test public void testWithOrder() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = "4"; graph.addVertex(v1); String v2 = "3"; graph.addVertex(v2); String v3 = "2"; graph.addVertex(v3); String v4 = "1"; graph.addVertex(v4); CircularLayoutAlgorithm2D alg = new CircularLayoutAlgorithm2D<>(1d, (a, b) -> a.compareTo(b)); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 2d, 2d)); alg.layout(graph, model); assertTrue(Points.equals(Point2D.of(2d, 1d), model.get(v4))); assertTrue(Points.equals(Point2D.of(1d, 2d), model.get(v3))); assertTrue(Points.equals(Point2D.of(0d, 1d), model.get(v2))); assertTrue(Points.equals(Point2D.of(1d, 0d), model.get(v1))); } } FRLayoutAlgorithm2DTest.java000066400000000000000000000135511402514743400335110ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.Map; import java.util.Random; import java.util.function.Function; import org.jgrapht.Graph; import org.jgrapht.alg.drawing.model.Box2D; import org.jgrapht.alg.drawing.model.MapLayoutModel2D; import org.jgrapht.alg.drawing.model.Point2D; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Test {@link FRLayoutAlgorithm2D}. * * @author Dimitrios Michail */ public class FRLayoutAlgorithm2DTest { @Test public void testGraph1() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); graph.addEdge(v1, v2); graph.addEdge(v3, v1); graph.addEdge(v4, v1); graph.addEdge(v5, v2); graph.addEdge(v6, v2); final Random rng = new Random(17); final int iterations = 100; final double normalizationFactor = 0.5; FRLayoutAlgorithm2D alg = new FRLayoutAlgorithm2D<>(iterations, normalizationFactor, rng); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 100d, 100d)); alg.layout(graph, model); Map result = model.collect(); // @formatter:off // 6 4 // \ / // 2 -- 1 // / \ // 5 3 // @formatter:on assertTrue(result.get(v1).getX() > result.get(v2).getX()); assertTrue(result.get(v1).getY() > result.get(v2).getY()); assertTrue(result.get(v3).getX() > result.get(v1).getX()); assertTrue(result.get(v3).getY() < result.get(v1).getY()); assertTrue(result.get(v4).getX() > result.get(v1).getX()); assertTrue(result.get(v4).getY() > result.get(v1).getY()); assertTrue(result.get(v5).getX() < result.get(v2).getX()); assertTrue(result.get(v5).getY() < result.get(v2).getY()); assertTrue(result.get(v6).getX() < result.get(v2).getX()); assertTrue(result.get(v6).getY() > result.get(v2).getY()); } @Test public void testInitializerSamePosition() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); graph.addEdge(v1, v2); graph.addEdge(v3, v1); graph.addEdge(v4, v1); graph.addEdge(v5, v2); graph.addEdge(v6, v2); final Random rng = new Random(17); final int iterations = 100; final double normalizationFactor = 0.5; FRLayoutAlgorithm2D alg = new FRLayoutAlgorithm2D<>(iterations, normalizationFactor, rng); Function init = (v) -> { if (v1.equals(v) || v2.equals(v)) { // two points with same position return Point2D.of(1d, 1d); } return Point2D.of(rng.nextDouble(), rng.nextDouble()); }; alg.setInitializer(init); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 100d, 100d)); alg.layout(graph, model); model.collect(); } @Test public void testGraphWithIsolatedVertex() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); String v7 = graph.addVertex(); graph.addEdge(v1, v2); graph.addEdge(v3, v1); graph.addEdge(v4, v1); graph.addEdge(v5, v2); graph.addEdge(v6, v2); final Random rng = new Random(17); final int iterations = 100; final double normalizationFactor = 0.5; FRLayoutAlgorithm2D alg = new FRLayoutAlgorithm2D<>(iterations, normalizationFactor, rng); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 100d, 100d)); alg.layout(graph, model); Map result = model.collect(); assertEquals(result.get(v7).getX(), 100d, 1e-9); assertEquals(result.get(v7).getY(), 100d, 1e-9); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/FRQuadTreeTest.java000066400000000000000000000046731402514743400317750ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.alg.drawing.FRQuadTree.*; import org.jgrapht.alg.drawing.model.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test {@link FRQuadTree}. * * @author Dimitrios Michail */ public class FRQuadTreeTest { @Test public void testQuadTree() { double width = 100; double height = 100; int points = 10000; Box2D region = Box2D.of(0, 0, width, height); FRQuadTree tree = new FRQuadTree(region); Random rng = new Random(17); for (int i = 0; i < points; i++) { Point2D p = Point2D.of(rng.nextDouble() * width, rng.nextDouble() * height); tree.insert(p); } Deque queue = new ArrayDeque<>(); Node root = tree.getRoot(); assertEquals(root.getNumberOfPoints(), points); queue.addLast(root); while (!queue.isEmpty()) { Node cur = queue.poll(); if (cur.hasPoints()) { assertTrue(Points.equals(cur.getCentroid(), centroid(cur.getPoints()))); } int totalPoints = cur.getNumberOfPoints(); if (!cur.isLeaf()) { int childrenPoints = 0; for (Node c : cur.getChildren()) { queue.addLast(c); childrenPoints += c.getNumberOfPoints(); } assertEquals(totalPoints, childrenPoints); } } } private Point2D centroid(List points) { double x = 0d; double y = 0d; for (Point2D p : points) { x += p.getX(); y += p.getY(); } int n = points.size(); return Point2D.of(x / n, y / n); } } GreedyTwoLayeredBipartiteLayout2DTest.java000066400000000000000000000115501402514743400364130ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import static org.junit.Assert.assertEquals; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.alg.drawing.model.Box2D; import org.jgrapht.alg.drawing.model.MapLayoutModel2D; import org.jgrapht.alg.drawing.model.Point2D; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Test {@link MedianGreedyTwoLayeredBipartiteLayout2D} and * {@link BarycenterGreedyTwoLayeredBipartiteLayout2D}. * * @author Dimitrios Michail */ public class GreedyTwoLayeredBipartiteLayout2DTest { @Test public void testMedian() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); String v7 = graph.addVertex(); String v8 = graph.addVertex(); String v9 = graph.addVertex(); graph.addEdge(v1, v4); graph.addEdge(v1, v5); graph.addEdge(v1, v6); graph.addEdge(v1, v7); graph.addEdge(v1, v8); graph.addEdge(v2, v4); graph.addEdge(v2, v5); graph.addEdge(v2, v7); graph.addEdge(v3, v5); graph.addEdge(v3, v6); graph.addEdge(v3, v8); MedianGreedyTwoLayeredBipartiteLayout2D alg = new MedianGreedyTwoLayeredBipartiteLayout2D<>(); alg.withFirstPartition(Set.of(v1, v2, v3)); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 3d, 10d)); alg.layout(graph, model); assertEquals(Point2D.of(0.0, 0.0), model.get(v1)); assertEquals(Point2D.of(0.0, 5.0), model.get(v2)); assertEquals(Point2D.of(0.0, 10.0), model.get(v3)); assertEquals(Point2D.of(3.0, 0.0), model.get(v9)); assertEquals(Point2D.of(3.0, 2.0), model.get(v5)); assertEquals(Point2D.of(3.0, 4.0), model.get(v4)); assertEquals(Point2D.of(3.0, 6.0), model.get(v6)); assertEquals(Point2D.of(3.0, 8.0), model.get(v7)); assertEquals(Point2D.of(3.0, 10.0), model.get(v8)); } @Test public void testBarycenter() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); String v7 = graph.addVertex(); String v8 = graph.addVertex(); String v9 = graph.addVertex(); graph.addEdge(v1, v4); graph.addEdge(v1, v5); graph.addEdge(v1, v6); graph.addEdge(v1, v7); graph.addEdge(v1, v8); graph.addEdge(v2, v4); graph.addEdge(v2, v5); graph.addEdge(v2, v7); graph.addEdge(v3, v5); graph.addEdge(v3, v6); graph.addEdge(v3, v8); BarycenterGreedyTwoLayeredBipartiteLayout2D alg = new BarycenterGreedyTwoLayeredBipartiteLayout2D<>(); alg.withFirstPartition(Set.of(v1, v2, v3)); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 3d, 10d)); alg.layout(graph, model); assertEquals(Point2D.of(0.0, 0.0), model.get(v1)); assertEquals(Point2D.of(0.0, 5.0), model.get(v2)); assertEquals(Point2D.of(0.0, 10.0), model.get(v3)); assertEquals(Point2D.of(3.0, 0.0), model.get(v9)); assertEquals(Point2D.of(3.0, 2.0), model.get(v5)); assertEquals(Point2D.of(3.0, 4.0), model.get(v4)); assertEquals(Point2D.of(3.0, 6.0), model.get(v6)); assertEquals(Point2D.of(3.0, 8.0), model.get(v7)); assertEquals(Point2D.of(3.0, 10.0), model.get(v8)); } } IndexedFRLayoutAlgorithm2DTest.java000066400000000000000000000147201402514743400350110ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.model.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test {@link IndexedFRLayoutAlgorithm2D}. * * @author Dimitrios Michail */ public class IndexedFRLayoutAlgorithm2DTest { @Test public void testGraph1() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); graph.addEdge(v1, v2); graph.addEdge(v3, v1); graph.addEdge(v4, v1); graph.addEdge(v5, v2); graph.addEdge(v6, v2); final Random rng = new Random(17); final int iterations = 100; final double normalizationFactor = 0.5; final double theta = 0.5; IndexedFRLayoutAlgorithm2D alg = new IndexedFRLayoutAlgorithm2D<>(iterations, theta, normalizationFactor, rng); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 100d, 100d)); alg.layout(graph, model); Map result = model.collect(); // @formatter:off // 6 4 // \ / // 2 -- 1 // / \ // 5 3 // @formatter:on assertTrue(result.get(v1).getX() > result.get(v2).getX()); assertTrue(result.get(v1).getY() > result.get(v2).getY()); assertTrue(result.get(v3).getX() > result.get(v1).getX()); assertTrue(result.get(v3).getY() < result.get(v1).getY()); assertTrue(result.get(v4).getX() > result.get(v1).getX()); assertTrue(result.get(v4).getY() > result.get(v1).getY()); assertTrue(result.get(v5).getX() < result.get(v2).getX()); assertTrue(result.get(v5).getY() < result.get(v2).getY()); assertTrue(result.get(v6).getX() < result.get(v2).getX()); assertTrue(result.get(v6).getY() > result.get(v2).getY()); assertEquals(80, alg.getSavedComparisons()); } @Test public void testGraphZeroTheta() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); graph.addEdge(v1, v2); graph.addEdge(v3, v1); graph.addEdge(v4, v1); graph.addEdge(v5, v2); graph.addEdge(v6, v2); final Random rng = new Random(17); final int iterations = 100; final double normalizationFactor = 0.5; final double theta = 0; IndexedFRLayoutAlgorithm2D alg = new IndexedFRLayoutAlgorithm2D<>(iterations, theta, normalizationFactor, rng); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 100d, 100d)); alg.layout(graph, model); Map result = model.collect(); // @formatter:off // 6 4 // \ / // 2 -- 1 // / \ // 5 3 // @formatter:on assertTrue(result.get(v1).getX() > result.get(v2).getX()); assertTrue(result.get(v1).getY() > result.get(v2).getY()); assertTrue(result.get(v3).getX() > result.get(v1).getX()); assertTrue(result.get(v3).getY() < result.get(v1).getY()); assertTrue(result.get(v4).getX() > result.get(v1).getX()); assertTrue(result.get(v4).getY() > result.get(v1).getY()); assertTrue(result.get(v5).getX() < result.get(v2).getX()); assertTrue(result.get(v5).getY() < result.get(v2).getY()); assertTrue(result.get(v6).getX() < result.get(v2).getX()); assertTrue(result.get(v6).getY() > result.get(v2).getY()); assertEquals(0, alg.getSavedComparisons()); } @Test public void testGraphWithIsolatedVertex() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); String v7 = graph.addVertex(); graph.addEdge(v1, v2); graph.addEdge(v3, v1); graph.addEdge(v4, v1); graph.addEdge(v5, v2); graph.addEdge(v6, v2); final Random rng = new Random(17); final int iterations = 100; final double normalizationFactor = 0.5; final double theta = 0; IndexedFRLayoutAlgorithm2D alg = new IndexedFRLayoutAlgorithm2D<>(iterations, theta, normalizationFactor, rng); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 100d, 100d)); alg.layout(graph, model); Map result = model.collect(); assertEquals(result.get(v7).getX(), 100d, 1e-9); assertEquals(result.get(v7).getY(), 100d, 1e-9); } } RandomLayoutAlgorithm2DTest.java000066400000000000000000000041311402514743400344140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.model.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Test {@link RandomLayoutAlgorithm2D}. * * @author Dimitrios Michail */ public class RandomLayoutAlgorithm2DTest { @Test public void testRandom() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); RandomLayoutAlgorithm2D alg = new RandomLayoutAlgorithm2D<>(5L); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 10d, 20d)); alg.layout(graph, model); Random rng = new Random(5L); assertEquals(Point2D.of(10 * rng.nextDouble(), 20 * rng.nextDouble()), model.get(v1)); assertEquals(Point2D.of(10 * rng.nextDouble(), 20 * rng.nextDouble()), model.get(v2)); assertEquals(Point2D.of(10 * rng.nextDouble(), 20 * rng.nextDouble()), model.get(v3)); assertEquals(Point2D.of(10 * rng.nextDouble(), 20 * rng.nextDouble()), model.get(v4)); } } RescaleLayoutAlgorithm2DTest.java000066400000000000000000000107331402514743400345570ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import org.jgrapht.*; import org.jgrapht.alg.drawing.model.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertTrue; /** * Test {@link CircularLayoutAlgorithm2D}. * * @author Dimitrios Michail */ public class RescaleLayoutAlgorithm2DTest { @Test public void testSimple() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); CircularLayoutAlgorithm2D alg = new CircularLayoutAlgorithm2D<>(1d); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 2d, 2d)); alg.layout(graph, model); assertTrue(Points.equals(Point2D.of(2d, 1d), model.get(v1))); assertTrue(Points.equals(Point2D.of(1d, 2d), model.get(v2))); assertTrue(Points.equals(Point2D.of(0d, 1d), model.get(v3))); assertTrue(Points.equals(Point2D.of(1d, 0d), model.get(v4))); new RescaleLayoutAlgorithm2D(4.0).layout(graph, model); assertTrue(Points.equals(Point2D.of(5d, 1.0), model.get(v1))); assertTrue(Points.equals(Point2D.of(1d, 5d), model.get(v2))); assertTrue(Points.equals(Point2D.of(-3.0d, 1d), model.get(v3))); assertTrue(Points.equals(Point2D.of(1d, -3d), model.get(v4))); } @Test public void testWithDifferentAspectRatio() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(-1d, -1d, 2d, 2d)); model.put(v1, Point2D.of(1.0, 0.0)); model.put(v2, Point2D.of(-1.0, 0.0)); model.put(v3, Point2D.of(0, 0.5)); model.put(v4, Point2D.of(0, -0.5)); new RescaleLayoutAlgorithm2D(3.0).layout(graph, model); assertTrue(Points.equals(Point2D.of(3d, 0.0), model.get(v1))); assertTrue(Points.equals(Point2D.of(-3d, 0.0), model.get(v2))); assertTrue(Points.equals(Point2D.of(0, 1.5d), model.get(v3))); assertTrue(Points.equals(Point2D.of(0, -1.5d), model.get(v4))); } @Test public void testBadInput() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(-1d, -1d, 2d, 2d)); model.put(v1, Point2D.of(0.0, 0.0)); new RescaleLayoutAlgorithm2D(3.0).layout(graph, model); assertTrue(Points.equals(Point2D.of(0d, 0.0), model.get(v1))); } @Test public void testBadInput1() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(-1d, -1d, 2d, 2d)); new RescaleLayoutAlgorithm2D(3.0).layout(graph, model); } } TwoLayeredBipartiteLayout2DTest.java000066400000000000000000000124631402514743400352570ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing; import static org.junit.Assert.assertEquals; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.alg.drawing.model.Box2D; import org.jgrapht.alg.drawing.model.MapLayoutModel2D; import org.jgrapht.alg.drawing.model.Point2D; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Test {@link TwoLayeredBipartiteLayout2D}. * * @author Dimitrios Michail */ public class TwoLayeredBipartiteLayout2DTest { @Test public void testVertical() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); graph.addEdge(v1, v4); graph.addEdge(v1, v5); graph.addEdge(v1, v6); graph.addEdge(v2, v4); graph.addEdge(v2, v5); graph.addEdge(v3, v5); graph.addEdge(v3, v6); TwoLayeredBipartiteLayout2D alg = new TwoLayeredBipartiteLayout2D<>(); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 3d, 10d)); alg.layout(graph, model); assertEquals(Point2D.of(0.0, 0.0), model.get(v1)); assertEquals(Point2D.of(0.0, 5.0), model.get(v2)); assertEquals(Point2D.of(0.0, 10.0), model.get(v3)); assertEquals(Point2D.of(3.0, 0.0), model.get(v4)); assertEquals(Point2D.of(3.0, 5.0), model.get(v5)); assertEquals(Point2D.of(3.0, 10.0), model.get(v6)); } @Test public void testHorizontal() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); graph.addEdge(v1, v4); graph.addEdge(v1, v5); graph.addEdge(v1, v6); graph.addEdge(v2, v4); graph.addEdge(v2, v5); graph.addEdge(v3, v5); graph.addEdge(v3, v6); TwoLayeredBipartiteLayout2D alg = new TwoLayeredBipartiteLayout2D<>(); alg.withVertical(false); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 10d, 3d)); alg.layout(graph, model); assertEquals(Point2D.of(0.0, 0.0), model.get(v1)); assertEquals(Point2D.of(5.0, 0.0), model.get(v2)); assertEquals(Point2D.of(10.0, 0.0), model.get(v3)); assertEquals(Point2D.of(0.0, 3.0), model.get(v4)); assertEquals(Point2D.of(5.0, 3.0), model.get(v5)); assertEquals(Point2D.of(10.0, 3.0), model.get(v6)); } @Test public void testWithPartition() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); String v3 = graph.addVertex(); String v4 = graph.addVertex(); String v5 = graph.addVertex(); String v6 = graph.addVertex(); String v7 = graph.addVertex(); graph.addEdge(v1, v4); graph.addEdge(v1, v5); graph.addEdge(v1, v6); graph.addEdge(v2, v4); graph.addEdge(v2, v5); graph.addEdge(v3, v5); graph.addEdge(v3, v6); TwoLayeredBipartiteLayout2D alg = new TwoLayeredBipartiteLayout2D<>(); alg.withFirstPartition(Set.of(v1, v2, v3, v7)); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0d, 0d, 3d, 10d)); alg.layout(graph, model); assertEquals(Point2D.of(0.0, 0.0), model.get(v1)); assertEquals(Point2D.of(0.0, 3.3333333333333335), model.get(v2)); assertEquals(Point2D.of(0.0, 6.666666666666667), model.get(v3)); assertEquals(Point2D.of(0.0, 10.0), model.get(v7)); assertEquals(Point2D.of(3.0, 0.0), model.get(v4)); assertEquals(Point2D.of(3.0, 5.0), model.get(v5)); assertEquals(Point2D.of(3.0, 10.0), model.get(v6)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/000077500000000000000000000000001402514743400274165ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/Box2DTest.java000066400000000000000000000044671402514743400320520ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import org.junit.*; import static org.junit.Assert.assertEquals; /** * Test {@link Box2D}. * * @author Dimitrios Michail */ public class Box2DTest { private static final double EPS = 1e-9; @Test public void testConstructor1() { Box2D p = new Box2D(5, 10); assertEquals(p.getWidth(), 5d, EPS); assertEquals(p.getHeight(), 10d, EPS); assertEquals(p.getMinX(), 0d, EPS); assertEquals(p.getMinY(), 0d, EPS); assertEquals(p.getMaxX(), 5d, EPS); assertEquals(p.getMaxY(), 10d, EPS); } @Test public void testConstructor2() { Box2D p = new Box2D(5, 4, 7, 8); assertEquals(p.getWidth(), 7d, EPS); assertEquals(p.getHeight(), 8d, EPS); assertEquals(p.getMinX(), 5d, EPS); assertEquals(p.getMinY(), 4d, EPS); assertEquals(p.getMaxX(), 12d, EPS); assertEquals(p.getMaxY(), 12d, EPS); } @Test public void testFactoryMethod1() { Box2D p = Box2D.of(5, 10); assertEquals(p.getWidth(), 5d, EPS); assertEquals(p.getHeight(), 10d, EPS); assertEquals(p.getMinX(), 0d, EPS); assertEquals(p.getMinY(), 0d, EPS); assertEquals(p.getMaxX(), 5d, EPS); assertEquals(p.getMaxY(), 10d, EPS); } @Test public void testFactoryMethod2() { Box2D p = Box2D.of(5, 4, 7, 8); assertEquals(p.getWidth(), 7d, EPS); assertEquals(p.getHeight(), 8d, EPS); assertEquals(p.getMinX(), 5d, EPS); assertEquals(p.getMinY(), 4d, EPS); assertEquals(p.getMaxX(), 5d + 7d, EPS); assertEquals(p.getMaxY(), 4d + 8d, EPS); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/BoxesTest.java000066400000000000000000000055711402514743400322110ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import org.jgrapht.alg.util.*; import org.junit.*; import static org.junit.Assert.*; /** * Test {@link Boxes}. * * @author Dimitrios Michail */ public class BoxesTest { @Test public void testContainsPoint2D() { Box2D region = Box2D.of(0, 0, 10, 10); assertTrue(Boxes.containsPoint(region, Point2D.of(5, 5))); assertTrue(Boxes.containsPoint(region, Point2D.of(10, 5))); assertTrue(Boxes.containsPoint(region, Point2D.of(10, 10))); assertTrue(Boxes.containsPoint(region, Point2D.of(0, 0))); assertTrue(Boxes.containsPoint(region, Point2D.of(0, 10))); assertTrue(Boxes.containsPoint(region, Point2D.of(10, 0))); assertFalse(Boxes.containsPoint(region, Point2D.of(11, 0))); assertFalse(Boxes.containsPoint(region, Point2D.of(0, 11))); } @Test public void testSplitAlongXAxis() { Box2D region = Box2D.of(5, 5, 10, 10); Pair pair = Boxes.splitAlongXAxis(region); Box2D westRegion = pair.getFirst(); Box2D eastRegion = pair.getSecond(); assertEquals(5d, westRegion.getMinX(), 1e-9); assertEquals(5d, westRegion.getMinY(), 1e-9); assertEquals(10d, westRegion.getHeight(), 1e-9); assertEquals(5d, westRegion.getWidth(), 1e-9); assertEquals(10d, eastRegion.getMinX(), 1e-9); assertEquals(5d, eastRegion.getMinY(), 1e-9); assertEquals(10d, eastRegion.getHeight(), 1e-9); assertEquals(5d, eastRegion.getWidth(), 1e-9); } @Test public void testSplitAlongYAxis() { Box2D region = Box2D.of(5, 5, 10, 10); Pair pair = Boxes.splitAlongYAxis(region); Box2D southRegion = pair.getFirst(); Box2D northRegion = pair.getSecond(); assertEquals(5d, southRegion.getMinX(), 1e-9); assertEquals(5d, southRegion.getMinY(), 1e-9); assertEquals(5d, southRegion.getHeight(), 1e-9); assertEquals(10d, southRegion.getWidth(), 1e-9); assertEquals(5d, northRegion.getMinX(), 1e-9); assertEquals(10d, northRegion.getMinY(), 1e-9); assertEquals(5d, northRegion.getHeight(), 1e-9); assertEquals(10d, northRegion.getWidth(), 1e-9); } } ListenableLayoutModel2DTest.java000066400000000000000000000072641402514743400355020ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.*; /** * Test {@link ListenableLayoutModel2D}. * * @author Dimitrios Michail */ public class ListenableLayoutModel2DTest { @Test public void testGeneral() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); MapLayoutModel2D delegate = new MapLayoutModel2D<>(Box2D.of(0, 0, 2d, 2d)); ListenableLayoutModel2D model = new ListenableLayoutModel2D<>(delegate); CountListener listener = new CountListener(); model.addListener(listener); assertEquals(Box2D.of(0d, 0d, 2d, 2d), model.getDrawableArea()); assertNull(model.get(v1)); model.put(v1, Point2D.of(3, 5)); assertEquals(model.get(v1), Point2D.of(3, 5)); assertFalse(model.isFixed(v1)); assertEquals(1, listener.getCalled()); assertEquals(Point2D.of(3, 5), listener.getLastPoint()); assertEquals(v1, listener.getLastVertex()); model.setFixed(v1, true); assertTrue(model.isFixed(v1)); model.put(v1, Point2D.of(10, 20)); assertEquals(model.get(v1), Point2D.of(3, 5)); assertEquals(1, listener.getCalled()); assertEquals(Point2D.of(3, 5), listener.getLastPoint()); assertEquals(v1, listener.getLastVertex()); model.setFixed(v1, false); assertFalse(model.isFixed(v1)); model.put(v1, Point2D.of(10, 20)); assertEquals(model.get(v1), Point2D.of(10, 20)); assertEquals(2, listener.getCalled()); assertEquals(Point2D.of(10, 20), listener.getLastPoint()); assertEquals(v1, listener.getLastVertex()); model.put(v2, Point2D.of(5, 7)); assertEquals(3, listener.getCalled()); assertEquals(Point2D.of(5, 7), listener.getLastPoint()); assertEquals(v2, listener.getLastVertex()); Map all = model.collect(); assertEquals(all.get(v1), Point2D.of(10, 20)); assertEquals(all.get(v2), Point2D.of(5, 7)); } private static class CountListener implements BiConsumer { private int called = 0; private Point2D lastPoint; private String lastVertex; @Override public void accept(String t, Point2D u) { lastPoint = u; lastVertex = t; called++; } public int getCalled() { return called; } public Point2D getLastPoint() { return lastPoint; } public String getLastVertex() { return lastVertex; } } } MapLayoutModel2DTest.java000066400000000000000000000043051402514743400341260ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Test {@link MapLayoutModel2D}. * * @author Dimitrios Michail */ public class MapLayoutModel2DTest { @Test public void testGeneral() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); String v1 = graph.addVertex(); String v2 = graph.addVertex(); MapLayoutModel2D model = new MapLayoutModel2D<>(Box2D.of(0, 0, 2d, 2d)); assertEquals(Box2D.of(0d, 0d, 2d, 2d), model.getDrawableArea()); assertNull(model.get(v1)); model.put(v1, Point2D.of(3, 5)); assertEquals(model.get(v1), Point2D.of(3, 5)); assertFalse(model.isFixed(v1)); model.setFixed(v1, true); assertTrue(model.isFixed(v1)); model.put(v1, Point2D.of(10, 20)); assertEquals(model.get(v1), Point2D.of(3, 5)); model.setFixed(v1, false); assertFalse(model.isFixed(v1)); model.put(v1, Point2D.of(10, 20)); assertEquals(model.get(v1), Point2D.of(10, 20)); model.put(v2, Point2D.of(5, 7)); Map all = model.collect(); assertEquals(all.get(v1), Point2D.of(10, 20)); assertEquals(all.get(v2), Point2D.of(5, 7)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/Point2DTest.java000066400000000000000000000023141402514743400324000ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import org.junit.*; import static org.junit.Assert.assertEquals; /** * Test {@link Point2D}. * * @author Dimitrios Michail */ public class Point2DTest { @Test public void testDefaultConstructor() { Point2D p = new Point2D(0d, 0d); assertEquals(p.getX(), 0d, 1e-9); assertEquals(p.getY(), 0d, 1e-9); } @Test public void testConstructorAndGetters() { Point2D p = new Point2D(3d, 2d); assertEquals(p.getX(), 3d, 1e-9); assertEquals(p.getY(), 2d, 1e-9); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/drawing/model/PointsTest.java000066400000000000000000000036541402514743400324050ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.drawing.model; import org.junit.*; import static org.junit.Assert.assertEquals; /** * Test {@link Points}. * * @author Dimitrios Michail */ public class PointsTest { @Test public void testLength() { Point2D p = Point2D.of(5, 5); assertEquals(Math.sqrt(50), Points.length(p), 1e-9); } @Test public void testAdd() { Point2D p1 = Point2D.of(5, 5); Point2D p2 = Point2D.of(3, 4); Point2D p3 = Points.add(p1, p2); assertEquals(8d, p3.getX(), 1e-9); assertEquals(9d, p3.getY(), 1e-9); } @Test public void testSub() { Point2D p1 = Point2D.of(5, 5); Point2D p2 = Point2D.of(3, 4); Point2D p3 = Points.subtract(p1, p2); assertEquals(2d, p3.getX(), 1e-9); assertEquals(1d, p3.getY(), 1e-9); } @Test public void testMinus() { Point2D p1 = Point2D.of(5, 3); Point2D p2 = Points.negate(p1); assertEquals(-5d, p2.getX(), 1e-9); assertEquals(-3d, p2.getY(), 1e-9); } @Test public void testScalarMultiply() { Point2D p1 = Point2D.of(5, 3); Point2D p2 = Points.scalarMultiply(p1, 2); assertEquals(10d, p2.getX(), 1e-9); assertEquals(6d, p2.getY(), 1e-9); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/000077500000000000000000000000001402514743400256325ustar00rootroot00000000000000BoykovKolmogorovMFImplTest.java000066400000000000000000000647001402514743400336620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.MaximumFlowAlgorithm; import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultUndirectedWeightedGraph; import org.jgrapht.graph.DefaultWeightedEdge; import org.junit.Test; import static junit.framework.TestCase.assertEquals; /** * Tests for {@link BoykovKolmogorovMFImpl}. *

    * The directed networks used for the tests were generated using the NetworkGenerator. The * undirected networks were obtained from directed networks by using the same edge weights. In the * case of parallel edges, their weights were summed. * * @author Timofey Chudakov */ public class BoykovKolmogorovMFImplTest extends MaximumFlowAlgorithmTest { private static final double EPS = 1e-9; private Graph constructDirected(int[][] edges) { return constructGraph(new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class), edges); } private Graph constructUndirected(int[][] edges) { return constructGraph( new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class), edges); } private Graph constructGraph( Graph graph, int[][] edges) { for (int[] edge : edges) { Graphs.addEdgeWithVertices(graph, edge[0], edge[1], edge[2]); } return graph; } private void testOnGraph( Graph graph, int source, int sink, int expectedFlow) { MaximumFlowAlgorithm flow = createSolver(graph); double maxFlow = flow.getMaximumFlowValue(source, sink); assertEquals(expectedFlow, maxFlow, EPS); } private void testDirectedUndirected( int[][] edges, int source, int sink, int expectedDirectedFlow, int expectedUndirectedFlow) { Graph directed = constructDirected(edges); Graph undirected = constructUndirected(edges); testOnGraph(directed, source, sink, expectedDirectedFlow); testOnGraph(undirected, source, sink, expectedUndirectedFlow); } /** * Network generator parameters: nodeNum = 10 arcNum = 30 supply = 10 sourceNum = 1 sinkNum = 1 * minCap = 1 maxCap = 50 seed = 1 */ @Test public void test1() { int[][] edges = { { 1, 5, 10 }, { 5, 9, 20 }, { 9, 8, 35 }, { 8, 3, 27 }, { 3, 2, 49 }, { 2, 4, 10 }, { 4, 6, 26 }, { 6, 7, 28 }, { 2, 10, 34 }, { 1, 7, 15 }, { 1, 10, 9 }, { 3, 7, 26 }, { 3, 4, 25 }, { 3, 9, 25 }, { 3, 8, 18 }, { 4, 9, 28 }, { 4, 3, 4 }, { 4, 7, 22 }, { 4, 8, 1 }, { 6, 2, 42 }, { 6, 4, 13 }, { 6, 8, 43 }, { 6, 5, 48 }, { 8, 9, 8 }, { 8, 5, 34 }, { 9, 4, 36 }, { 9, 3, 47 }, { 3, 10, 10 }, { 4, 10, 39 }, { 5, 10, 16 }, }; testDirectedUndirected(edges, 1, 10, 19, 34); } /** * Network generator parameters: nodeNum = 10 arcNum = 30 supply = 10 sourceNum = 1 sinkNum = 1 * minCap = 1 maxCap = 50 seed = 2 */ @Test public void test2() { int[][] edges = { { 1, 2, 10 }, { 2, 7, 45 }, { 7, 9, 17 }, { 9, 8, 29 }, { 8, 5, 13 }, { 5, 3, 10 }, { 3, 6, 18 }, { 6, 4, 43 }, { 7, 10, 17 }, { 1, 8, 27 }, { 2, 5, 3 }, { 3, 8, 44 }, { 3, 5, 17 }, { 3, 4, 19 }, { 4, 8, 48 }, { 6, 2, 29 }, { 6, 8, 16 }, { 6, 3, 25 }, { 6, 5, 32 }, { 7, 8, 21 }, { 7, 6, 25 }, { 7, 5, 36 }, { 7, 4, 44 }, { 9, 7, 37 }, { 9, 2, 25 }, { 9, 3, 15 }, { 9, 6, 33 }, { 3, 10, 7 }, { 4, 10, 23 }, { 5, 10, 48 }, }; testDirectedUndirected(edges, 1, 10, 23, 37); } /** * Network generator parameters: nodeNum = 20 arcNum = 40 supply = 20 sourceNum = 2 sinkNum = 2 * minCap = 1 maxCap = 50 seed = 2 */ @Test public void test3() { int[][] edges = { { 1, 16, 20 }, { 2, 17, 35 }, { 16, 11, 50 }, { 17, 12, 50 }, { 11, 9, 36 }, { 12, 13, 8 }, { 9, 7, 19 }, { 13, 5, 9 }, { 7, 4, 34 }, { 5, 14, 32 }, { 14, 3, 24 }, { 4, 15, 19 }, { 3, 8, 37 }, { 15, 18, 24 }, { 18, 6, 43 }, { 6, 10, 19 }, { 1, 19, 47 }, { 10, 20, 19 }, { 3, 19, 48 }, { 3, 6, 14 }, { 4, 14, 27 }, { 4, 16, 3 }, { 5, 18, 18 }, { 5, 16, 42 }, { 6, 16, 31 }, { 6, 18, 8 }, { 7, 8, 37 }, { 7, 17, 17 }, { 7, 12, 32 }, { 9, 15, 32 }, { 9, 18, 18 }, { 10, 18, 11 }, { 10, 12, 23 }, { 10, 16, 25 }, { 13, 17, 37 }, { 13, 8, 17 }, { 3, 20, 28 }, { 6, 20, 2 }, { 7, 19, 40 }, { 8, 20, 41 }, { 21, 1, 2147483647 }, { 21, 2, 2147483647 }, { 19, 22, 2147483647 }, { 20, 22, 2147483647 }, }; testDirectedUndirected(edges, 21, 22, 75, 102); } /** * Network generator parameters: nodeNum = 20 arcNum = 60 supply = 20 sourceNum = 2 sinkNum = 2 * minCap = 1 maxCap = 50 seed = 1 */ @Test public void test4() { int[][] edges = { { 1, 3, 12 }, { 2, 7, 18 }, { 3, 5, 13 }, { 7, 17, 11 }, { 5, 14, 12 }, { 17, 16, 8 }, { 14, 8, 21 }, { 16, 4, 28 }, { 8, 9, 39 }, { 9, 6, 12 }, { 4, 13, 35 }, { 6, 11, 47 }, { 11, 18, 17 }, { 18, 10, 32 }, { 13, 12, 9 }, { 10, 15, 12 }, { 1, 19, 37 }, { 9, 20, 26 }, { 13, 19, 18 }, { 4, 20, 17 }, { 1, 6, 6 }, { 1, 17, 17 }, { 1, 11, 13 }, { 1, 9, 43 }, { 1, 13, 48 }, { 1, 18, 8 }, { 1, 15, 50 }, { 1, 14, 39 }, { 1, 16, 36 }, { 3, 12, 37 }, { 3, 11, 14 }, { 4, 9, 20 }, { 4, 6, 27 }, { 6, 10, 1 }, { 8, 4, 9 }, { 9, 17, 20 }, { 9, 7, 27 }, { 10, 6, 49 }, { 10, 9, 50 }, { 11, 15, 21 }, { 12, 3, 37 }, { 13, 8, 4 }, { 13, 16, 30 }, { 15, 17, 9 }, { 15, 18, 9 }, { 15, 13, 37 }, { 16, 11, 32 }, { 17, 15, 18 }, { 17, 8, 14 }, { 17, 6, 14 }, { 18, 11, 7 }, { 3, 19, 6 }, { 4, 19, 50 }, { 6, 20, 4 }, { 6, 19, 50 }, { 7, 20, 16 }, { 7, 19, 33 }, { 8, 20, 20 }, { 8, 19, 23 }, { 9, 19, 7 }, { 21, 1, 2147483647 }, { 21, 2, 2147483647 }, { 19, 22, 2147483647 }, { 20, 22, 2147483647 }, }; testDirectedUndirected(edges, 21, 22, 239, 307); } /** * Network generator parameters: nodeNum = 30 arcNum = 60 supply = 30 sourceNum = 3 sinkNum = 3 * minCap = 1 maxCap = 50 seed = 1 */ @Test public void test5() { int[][] edges = { { 1, 26, 49 }, { 2, 22, 17 }, { 3, 24, 40 }, { 26, 8, 25 }, { 22, 17, 17 }, { 24, 23, 14 }, { 8, 10, 35 }, { 17, 18, 28 }, { 23, 4, 8 }, { 10, 15, 37 }, { 18, 27, 35 }, { 4, 21, 49 }, { 15, 14, 29 }, { 27, 9, 23 }, { 14, 5, 39 }, { 5, 7, 30 }, { 7, 19, 25 }, { 9, 20, 37 }, { 19, 25, 45 }, { 25, 12, 18 }, { 12, 13, 28 }, { 20, 16, 41 }, { 13, 11, 35 }, { 21, 6, 46 }, { 15, 28, 13 }, { 9, 29, 43 }, { 20, 30, 17 }, { 17, 28, 17 }, { 4, 29, 39 }, { 21, 30, 36 }, { 2, 24, 34 }, { 3, 17, 10 }, { 3, 19, 39 }, { 3, 13, 46 }, { 3, 30, 14 }, { 4, 22, 4 }, { 5, 27, 6 }, { 5, 26, 4 }, { 6, 16, 36 }, { 6, 9, 9 }, { 7, 24, 27 }, { 7, 25, 11 }, { 10, 19, 22 }, { 11, 25, 25 }, { 12, 7, 7 }, { 12, 4, 32 }, { 13, 21, 10 }, { 14, 11, 20 }, { 14, 6, 21 }, { 15, 9, 49 }, { 17, 10, 19 }, { 18, 8, 50 }, { 18, 13, 35 }, { 19, 5, 13 }, { 19, 14, 20 }, { 21, 12, 33 }, { 5, 28, 24 }, { 7, 28, 9 }, { 10, 30, 1 }, { 12, 28, 12 }, { 31, 1, 2147483647 }, { 31, 2, 2147483647 }, { 31, 3, 2147483647 }, { 28, 32, 2147483647 }, { 29, 32, 2147483647 }, { 30, 32, 2147483647 }, }; testDirectedUndirected(edges, 31, 32, 135, 190); } /** * Network generator parameters: nodeNum = 30 arcNum = 90 supply = 30 sourceNum = 3 sinkNum = 3 * minCap = 1 maxCap = 50 seed = 1 */ @Test public void test6() { int[][] edges = { { 1, 26, 49 }, { 2, 22, 17 }, { 3, 24, 40 }, { 26, 8, 25 }, { 22, 17, 17 }, { 24, 23, 14 }, { 8, 10, 35 }, { 17, 18, 28 }, { 23, 4, 8 }, { 10, 15, 37 }, { 18, 27, 35 }, { 4, 21, 49 }, { 15, 14, 29 }, { 27, 9, 23 }, { 14, 5, 39 }, { 5, 7, 30 }, { 7, 19, 25 }, { 9, 20, 37 }, { 19, 25, 45 }, { 25, 12, 18 }, { 12, 13, 28 }, { 20, 16, 41 }, { 13, 11, 35 }, { 21, 6, 46 }, { 15, 28, 13 }, { 9, 29, 43 }, { 20, 30, 17 }, { 17, 28, 17 }, { 4, 29, 39 }, { 21, 30, 36 }, { 2, 24, 34 }, { 2, 20, 10 }, { 2, 11, 39 }, { 2, 25, 19 }, { 2, 15, 30 }, { 3, 15, 25 }, { 3, 10, 23 }, { 3, 18, 32 }, { 3, 9, 21 }, { 1, 28, 40 }, { 1, 30, 9 }, { 4, 17, 32 }, { 5, 27, 18 }, { 5, 22, 14 }, { 5, 11, 14 }, { 8, 22, 7 }, { 8, 23, 14 }, { 8, 26, 50 }, { 9, 23, 10 }, { 9, 24, 45 }, { 10, 27, 11 }, { 10, 23, 13 }, { 10, 21, 50 }, { 10, 6, 35 }, { 11, 5, 13 }, { 12, 5, 20 }, { 12, 19, 33 }, { 12, 15, 32 }, { 13, 9, 30 }, { 13, 20, 29 }, { 13, 14, 25 }, { 14, 17, 24 }, { 15, 17, 30 }, { 15, 22, 5 }, { 15, 23, 26 }, { 16, 25, 34 }, { 16, 14, 12 }, { 16, 13, 13 }, { 18, 13, 45 }, { 18, 6, 44 }, { 18, 23, 38 }, { 18, 4, 4 }, { 19, 12, 25 }, { 20, 8, 13 }, { 20, 6, 23 }, { 20, 22, 41 }, { 21, 4, 27 }, { 22, 16, 19 }, { 22, 26, 45 }, { 22, 5, 29 }, { 23, 14, 34 }, { 23, 25, 38 }, { 23, 19, 27 }, { 23, 26, 31 }, { 24, 5, 35 }, { 25, 27, 10 }, { 7, 30, 20 }, { 8, 29, 7 }, { 11, 28, 13 }, { 12, 30, 10 }, { 31, 1, 2147483647 }, { 31, 2, 2147483647 }, { 31, 3, 2147483647 }, { 28, 32, 2147483647 }, { 29, 32, 2147483647 }, { 30, 32, 2147483647 }, }; testDirectedUndirected(edges, 31, 32, 251, 264); } /** * Network generator parameters: nodeNum = 50 arcNum = 100 supply = 50 sourceNum = 5 sinkNum = 5 * minCap = 1 maxCap = 50 seed = 1 */ @Test public void test7() { int[][] edges = { { 1, 39, 14 }, { 2, 38, 47 }, { 3, 8, 50 }, { 4, 21, 38 }, { 5, 28, 14 }, { 39, 30, 26 }, { 38, 18, 38 }, { 8, 12, 5 }, { 21, 14, 12 }, { 28, 37, 14 }, { 30, 7, 34 }, { 18, 9, 16 }, { 12, 22, 9 }, { 14, 40, 12 }, { 37, 33, 14 }, { 7, 10, 10 }, { 9, 44, 21 }, { 22, 36, 37 }, { 40, 31, 26 }, { 33, 34, 14 }, { 10, 6, 34 }, { 44, 16, 13 }, { 36, 11, 27 }, { 31, 24, 35 }, { 6, 26, 46 }, { 16, 29, 39 }, { 24, 25, 30 }, { 34, 23, 24 }, { 11, 42, 8 }, { 42, 32, 34 }, { 25, 20, 28 }, { 29, 27, 21 }, { 26, 13, 36 }, { 20, 17, 12 }, { 17, 43, 12 }, { 23, 19, 47 }, { 32, 35, 33 }, { 19, 45, 37 }, { 43, 15, 25 }, { 45, 41, 41 }, { 13, 46, 40 }, { 30, 47, 40 }, { 44, 48, 27 }, { 38, 49, 49 }, { 11, 50, 16 }, { 8, 46, 16 }, { 20, 47, 21 }, { 25, 48, 12 }, { 19, 49, 14 }, { 37, 50, 14 }, { 1, 10, 34 }, { 1, 6, 24 }, { 2, 24, 32 }, { 2, 8, 10 }, { 2, 47, 21 }, { 3, 48, 49 }, { 6, 19, 45 }, { 6, 24, 15 }, { 7, 41, 29 }, { 7, 33, 24 }, { 8, 14, 48 }, { 8, 7, 21 }, { 9, 13, 45 }, { 9, 42, 18 }, { 10, 11, 37 }, { 11, 21, 31 }, { 11, 9, 41 }, { 16, 9, 26 }, { 17, 11, 14 }, { 18, 37, 19 }, { 18, 21, 6 }, { 19, 10, 26 }, { 20, 18, 42 }, { 22, 33, 16 }, { 23, 12, 17 }, { 24, 35, 28 }, { 24, 40, 41 }, { 25, 10, 27 }, { 27, 11, 34 }, { 30, 29, 46 }, { 31, 29, 32 }, { 31, 16, 29 }, { 34, 29, 26 }, { 35, 23, 17 }, { 35, 15, 45 }, { 38, 36, 2 }, { 40, 33, 47 }, { 43, 26, 25 }, { 43, 29, 37 }, { 45, 22, 34 }, { 7, 49, 23 }, { 11, 48, 40 }, { 12, 47, 21 }, { 14, 49, 1 }, { 17, 49, 32 }, { 18, 50, 34 }, { 19, 46, 21 }, { 23, 47, 40 }, { 24, 50, 46 }, { 25, 46, 44 }, { 51, 1, 2147483647 }, { 51, 2, 2147483647 }, { 51, 3, 2147483647 }, { 51, 4, 2147483647 }, { 51, 5, 2147483647 }, { 48, 52, 2147483647 }, { 49, 52, 2147483647 }, { 50, 52, 2147483647 }, { 46, 52, 2147483647 }, { 47, 52, 2147483647 }, }; testDirectedUndirected(edges, 51, 52, 290, 327); } /** * Network generator parameters: nodeNum = 50 arcNum = 150 supply = 50 sourceNum = 5 sinkNum = 5 * minCap = 1 maxCap = 50 seed = 2 */ @Test public void test8() { int[][] edges = { { 1, 12, 15 }, { 2, 34, 31 }, { 3, 39, 43 }, { 4, 35, 9 }, { 5, 45, 11 }, { 12, 23, 18 }, { 34, 29, 20 }, { 39, 10, 37 }, { 35, 27, 32 }, { 45, 8, 21 }, { 23, 28, 14 }, { 29, 22, 45 }, { 10, 14, 39 }, { 27, 42, 9 }, { 8, 19, 43 }, { 28, 33, 36 }, { 22, 13, 50 }, { 14, 25, 47 }, { 42, 43, 9 }, { 19, 31, 11 }, { 33, 17, 9 }, { 13, 15, 18 }, { 25, 21, 45 }, { 43, 11, 9 }, { 21, 18, 36 }, { 15, 16, 25 }, { 11, 44, 36 }, { 16, 38, 46 }, { 38, 26, 3 }, { 44, 9, 28 }, { 9, 41, 39 }, { 31, 24, 28 }, { 26, 6, 16 }, { 24, 36, 31 }, { 17, 40, 48 }, { 6, 30, 2 }, { 36, 20, 16 }, { 40, 7, 17 }, { 20, 32, 29 }, { 7, 37, 11 }, { 37, 46, 37 }, { 17, 47, 17 }, { 28, 48, 10 }, { 17, 49, 17 }, { 33, 50, 30 }, { 38, 46, 32 }, { 39, 47, 20 }, { 4, 48, 49 }, { 43, 49, 31 }, { 20, 50, 45 }, { 1, 10, 40 }, { 1, 15, 12 }, { 1, 41, 47 }, { 3, 8, 13 }, { 4, 33, 23 }, { 4, 8, 20 }, { 4, 44, 8 }, { 4, 40, 29 }, { 4, 12, 11 }, { 5, 40, 20 }, { 5, 16, 5 }, { 5, 39, 3 }, { 5, 11, 29 }, { 5, 44, 17 }, { 6, 45, 22 }, { 6, 33, 16 }, { 7, 41, 35 }, { 9, 8, 15 }, { 9, 35, 30 }, { 9, 33, 5 }, { 11, 22, 13 }, { 12, 20, 36 }, { 12, 10, 29 }, { 12, 15, 24 }, { 13, 17, 32 }, { 13, 18, 10 }, { 14, 27, 11 }, { 14, 37, 40 }, { 15, 38, 14 }, { 16, 20, 32 }, { 16, 18, 15 }, { 16, 17, 29 }, { 17, 22, 26 }, { 17, 42, 25 }, { 17, 41, 1 }, { 17, 33, 36 }, { 18, 10, 17 }, { 19, 28, 49 }, { 20, 45, 44 }, { 20, 36, 8 }, { 20, 8, 9 }, { 20, 14, 17 }, { 21, 9, 37 }, { 22, 44, 19 }, { 23, 11, 13 }, { 23, 10, 40 }, { 23, 27, 10 }, { 23, 39, 17 }, { 26, 24, 35 }, { 26, 9, 43 }, { 26, 11, 20 }, { 26, 43, 24 }, { 27, 23, 20 }, { 28, 6, 29 }, { 31, 37, 42 }, { 31, 32, 41 }, { 32, 6, 8 }, { 32, 25, 43 }, { 32, 18, 7 }, { 34, 26, 8 }, { 34, 17, 12 }, { 34, 15, 35 }, { 34, 44, 34 }, { 35, 36, 6 }, { 35, 19, 15 }, { 35, 11, 47 }, { 36, 44, 20 }, { 36, 14, 34 }, { 36, 29, 16 }, { 36, 23, 10 }, { 37, 30, 6 }, { 38, 28, 11 }, { 38, 41, 11 }, { 39, 22, 26 }, { 39, 38, 36 }, { 39, 6, 44 }, { 41, 31, 15 }, { 41, 30, 38 }, { 41, 28, 35 }, { 42, 17, 30 }, { 42, 22, 25 }, { 42, 35, 38 }, { 43, 26, 33 }, { 43, 37, 33 }, { 43, 17, 36 }, { 44, 38, 5 }, { 44, 22, 48 }, { 44, 30, 49 }, { 45, 37, 32 }, { 45, 29, 29 }, { 45, 11, 49 }, { 45, 34, 13 }, { 7, 47, 33 }, { 10, 50, 16 }, { 11, 46, 38 }, { 12, 47, 7 }, { 18, 48, 33 }, { 20, 49, 28 }, { 21, 49, 26 }, { 22, 49, 21 }, { 51, 1, 2147483647 }, { 51, 2, 2147483647 }, { 51, 3, 2147483647 }, { 51, 4, 2147483647 }, { 51, 5, 2147483647 }, { 48, 52, 2147483647 }, { 49, 52, 2147483647 }, { 50, 52, 2147483647 }, { 46, 52, 2147483647 }, { 47, 52, 2147483647 }, }; testDirectedUndirected(edges, 51, 52, 397, 435); } /** * Network generator parameters: nodeNum = 100 arcNum = 200 supply = 100 sourceNum = 10 sinkNum * = 10 minCap = 1 maxCap = 50 seed = 1 */ @Test public void test9() { int[][] edges = { { 1, 23, 50 }, { 2, 24, 38 }, { 3, 75, 12 }, { 4, 21, 26 }, { 5, 11, 38 }, { 6, 56, 10 }, { 7, 76, 13 }, { 8, 34, 5 }, { 9, 18, 34 }, { 10, 43, 16 }, { 23, 27, 9 }, { 24, 77, 8 }, { 75, 37, 12 }, { 21, 39, 10 }, { 11, 53, 21 }, { 56, 17, 37 }, { 76, 83, 26 }, { 34, 85, 4 }, { 18, 90, 34 }, { 43, 59, 10 }, { 27, 74, 27 }, { 77, 45, 35 }, { 37, 89, 12 }, { 39, 25, 17 }, { 53, 47, 21 }, { 17, 60, 43 }, { 83, 69, 48 }, { 85, 61, 8 }, { 90, 71, 50 }, { 59, 38, 39 }, { 74, 19, 36 }, { 45, 30, 46 }, { 89, 31, 12 }, { 25, 12, 9 }, { 47, 81, 34 }, { 60, 80, 10 }, { 69, 82, 39 }, { 61, 26, 19 }, { 71, 84, 30 }, { 38, 49, 25 }, { 19, 20, 23 }, { 30, 15, 32 }, { 31, 32, 21 }, { 12, 54, 25 }, { 81, 70, 21 }, { 80, 29, 27 }, { 82, 36, 49 }, { 26, 64, 50 }, { 36, 35, 13 }, { 35, 87, 13 }, { 54, 48, 9 }, { 87, 65, 19 }, { 29, 46, 37 }, { 70, 40, 37 }, { 48, 22, 45 }, { 64, 51, 22 }, { 46, 78, 30 }, { 65, 42, 24 }, { 84, 88, 14 }, { 49, 28, 10 }, { 28, 62, 17 }, { 78, 79, 24 }, { 42, 16, 19 }, { 22, 73, 50 }, { 62, 52, 16 }, { 32, 57, 19 }, { 52, 58, 10 }, { 79, 68, 38 }, { 73, 33, 30 }, { 51, 44, 48 }, { 40, 66, 21 }, { 66, 41, 34 }, { 15, 14, 9 }, { 44, 55, 26 }, { 14, 50, 8 }, { 33, 72, 41 }, { 57, 63, 29 }, { 68, 67, 26 }, { 88, 86, 24 }, { 58, 13, 10 }, { 23, 91, 37 }, { 20, 92, 31 }, { 74, 93, 41 }, { 23, 94, 26 }, { 50, 95, 8 }, { 2, 96, 40 }, { 45, 97, 43 }, { 50, 98, 42 }, { 2, 99, 25 }, { 2, 100, 31 }, { 75, 91, 21 }, { 75, 92, 35 }, { 21, 93, 34 }, { 73, 94, 46 }, { 53, 95, 21 }, { 60, 96, 10 }, { 35, 97, 38 }, { 55, 98, 46 }, { 9, 99, 8 }, { 58, 100, 46 }, { 1, 65, 3 }, { 1, 12, 29 }, { 1, 70, 23 }, { 2, 63, 9 }, { 2, 90, 10 }, { 5, 69, 18 }, { 5, 87, 24 }, { 5, 15, 16 }, { 5, 75, 18 }, { 6, 24, 18 }, { 6, 55, 27 }, { 6, 18, 31 }, { 6, 53, 48 }, { 7, 14, 48 }, { 9, 29, 40 }, { 9, 40, 11 }, { 10, 64, 14 }, { 10, 33, 27 }, { 3, 98, 1 }, { 13, 72, 33 }, { 14, 71, 50 }, { 14, 53, 24 }, { 15, 22, 24 }, { 17, 54, 17 }, { 18, 33, 28 }, { 18, 76, 32 }, { 19, 42, 23 }, { 20, 22, 34 }, { 21, 67, 7 }, { 21, 53, 27 }, { 22, 19, 34 }, { 22, 31, 3 }, { 23, 80, 39 }, { 24, 55, 25 }, { 25, 54, 9 }, { 25, 16, 34 }, { 26, 25, 38 }, { 27, 84, 23 }, { 27, 56, 48 }, { 28, 67, 31 }, { 31, 41, 42 }, { 31, 58, 37 }, { 34, 70, 5 }, { 37, 64, 22 }, { 37, 69, 10 }, { 38, 61, 26 }, { 39, 13, 5 }, { 40, 89, 3 }, { 40, 41, 26 }, { 41, 67, 44 }, { 43, 85, 20 }, { 44, 37, 47 }, { 44, 16, 7 }, { 46, 11, 6 }, { 46, 49, 44 }, { 47, 39, 38 }, { 48, 85, 9 }, { 51, 40, 30 }, { 53, 23, 16 }, { 54, 85, 50 }, { 55, 43, 19 }, { 55, 52, 42 }, { 56, 39, 43 }, { 56, 15, 31 }, { 58, 65, 36 }, { 58, 40, 44 }, { 59, 90, 26 }, { 61, 25, 15 }, { 61, 39, 21 }, { 64, 81, 1 }, { 65, 41, 1 }, { 67, 65, 14 }, { 67, 31, 40 }, { 68, 54, 40 }, { 69, 85, 49 }, { 72, 77, 5 }, { 72, 17, 9 }, { 73, 77, 17 }, { 75, 82, 41 }, { 75, 68, 14 }, { 76, 39, 33 }, { 77, 36, 21 }, { 77, 18, 23 }, { 78, 63, 46 }, { 78, 76, 48 }, { 84, 51, 3 }, { 87, 73, 46 }, { 88, 63, 4 }, { 88, 53, 48 }, { 89, 87, 43 }, { 89, 29, 15 }, { 90, 46, 46 }, { 90, 25, 38 }, { 12, 99, 30 }, { 18, 91, 39 }, { 23, 93, 30 }, { 24, 92, 6 }, { 30, 95, 41 }, { 32, 93, 17 }, { 34, 96, 1 }, { 101, 1, 2147483647 }, { 101, 2, 2147483647 }, { 101, 3, 2147483647 }, { 101, 4, 2147483647 }, { 101, 5, 2147483647 }, { 101, 6, 2147483647 }, { 101, 7, 2147483647 }, { 101, 8, 2147483647 }, { 101, 9, 2147483647 }, { 101, 10, 2147483647 }, { 96, 102, 2147483647 }, { 97, 102, 2147483647 }, { 98, 102, 2147483647 }, { 99, 102, 2147483647 }, { 100, 102, 2147483647 }, { 91, 102, 2147483647 }, { 92, 102, 2147483647 }, { 93, 102, 2147483647 }, { 94, 102, 2147483647 }, { 95, 102, 2147483647 }, }; testDirectedUndirected(edges, 101, 102, 536, 723); } /** * Network generator parameters: nodeNum = 100 arcNum = 300 supply = 100 sourceNum = 10 sinkNum * = 10 minCap = 1 maxCap = 50 seed = 1 */ @Test public void test10() { int[][] edges = { { 1, 23, 50 }, { 2, 24, 38 }, { 3, 75, 12 }, { 4, 21, 26 }, { 5, 11, 38 }, { 6, 56, 10 }, { 7, 76, 13 }, { 8, 34, 5 }, { 9, 18, 34 }, { 10, 43, 16 }, { 23, 27, 9 }, { 24, 77, 8 }, { 75, 37, 12 }, { 21, 39, 10 }, { 11, 53, 21 }, { 56, 17, 37 }, { 76, 83, 26 }, { 34, 85, 4 }, { 18, 90, 34 }, { 43, 59, 10 }, { 27, 74, 27 }, { 77, 45, 35 }, { 37, 89, 12 }, { 39, 25, 17 }, { 53, 47, 21 }, { 17, 60, 43 }, { 83, 69, 48 }, { 85, 61, 8 }, { 90, 71, 50 }, { 59, 38, 39 }, { 74, 19, 36 }, { 45, 30, 46 }, { 89, 31, 12 }, { 25, 12, 9 }, { 47, 81, 34 }, { 60, 80, 10 }, { 69, 82, 39 }, { 61, 26, 19 }, { 71, 84, 30 }, { 38, 49, 25 }, { 19, 20, 23 }, { 30, 15, 32 }, { 31, 32, 21 }, { 12, 54, 25 }, { 81, 70, 21 }, { 80, 29, 27 }, { 82, 36, 49 }, { 26, 64, 50 }, { 36, 35, 13 }, { 35, 87, 13 }, { 54, 48, 9 }, { 87, 65, 19 }, { 29, 46, 37 }, { 70, 40, 37 }, { 48, 22, 45 }, { 64, 51, 22 }, { 46, 78, 30 }, { 65, 42, 24 }, { 84, 88, 14 }, { 49, 28, 10 }, { 28, 62, 17 }, { 78, 79, 24 }, { 42, 16, 19 }, { 22, 73, 50 }, { 62, 52, 16 }, { 32, 57, 19 }, { 52, 58, 10 }, { 79, 68, 38 }, { 73, 33, 30 }, { 51, 44, 48 }, { 40, 66, 21 }, { 66, 41, 34 }, { 15, 14, 9 }, { 44, 55, 26 }, { 14, 50, 8 }, { 33, 72, 41 }, { 57, 63, 29 }, { 68, 67, 26 }, { 88, 86, 24 }, { 58, 13, 10 }, { 23, 91, 37 }, { 20, 92, 31 }, { 74, 93, 41 }, { 23, 94, 26 }, { 50, 95, 8 }, { 2, 96, 40 }, { 45, 97, 43 }, { 50, 98, 42 }, { 2, 99, 25 }, { 2, 100, 31 }, { 75, 91, 21 }, { 75, 92, 35 }, { 21, 93, 34 }, { 73, 94, 46 }, { 53, 95, 21 }, { 60, 96, 10 }, { 35, 97, 38 }, { 55, 98, 46 }, { 9, 99, 8 }, { 58, 100, 46 }, { 1, 65, 3 }, { 2, 56, 29 }, { 2, 57, 23 }, { 2, 71, 9 }, { 2, 40, 10 }, { 2, 33, 18 }, { 2, 34, 24 }, { 3, 23, 16 }, { 3, 24, 18 }, { 3, 74, 18 }, { 3, 22, 27 }, { 3, 78, 31 }, { 3, 50, 48 }, { 4, 14, 48 }, { 4, 88, 40 }, { 5, 58, 11 }, { 5, 36, 14 }, { 5, 90, 27 }, { 5, 70, 46 }, { 5, 29, 10 }, { 6, 18, 1 }, { 6, 77, 31 }, { 6, 83, 21 }, { 7, 23, 11 }, { 7, 50, 29 }, { 7, 11, 12 }, { 8, 23, 4 }, { 8, 39, 28 }, { 8, 81, 28 }, { 8, 14, 45 }, { 8, 77, 17 }, { 9, 61, 24 }, { 9, 41, 7 }, { 9, 57, 19 }, { 1, 92, 6 }, { 4, 94, 10 }, { 12, 25, 38 }, { 13, 84, 23 }, { 13, 56, 48 }, { 13, 61, 31 }, { 13, 18, 42 }, { 14, 89, 37 }, { 14, 37, 5 }, { 14, 62, 22 }, { 15, 16, 10 }, { 15, 89, 26 }, { 15, 71, 5 }, { 15, 66, 3 }, { 16, 80, 26 }, { 16, 71, 44 }, { 16, 17, 20 }, { 17, 37, 47 }, { 18, 29, 7 }, { 18, 78, 6 }, { 18, 87, 44 }, { 19, 39, 38 }, { 19, 72, 9 }, { 20, 40, 30 }, { 20, 45, 16 }, { 21, 85, 50 }, { 22, 43, 19 }, { 22, 52, 42 }, { 22, 75, 43 }, { 23, 38, 31 }, { 23, 48, 36 }, { 23, 75, 44 }, { 24, 90, 26 }, { 24, 18, 15 }, { 25, 33, 21 }, { 25, 11, 1 }, { 25, 35, 1 }, { 26, 65, 14 }, { 26, 31, 40 }, { 26, 36, 40 }, { 27, 85, 49 }, { 27, 14, 5 }, { 27, 89, 9 }, { 27, 15, 17 }, { 28, 82, 41 }, { 29, 52, 14 }, { 29, 89, 33 }, { 29, 90, 21 }, { 29, 58, 23 }, { 30, 63, 46 }, { 30, 76, 48 }, { 30, 51, 3 }, { 30, 79, 46 }, { 31, 63, 4 }, { 31, 53, 48 }, { 32, 87, 43 }, { 32, 29, 15 }, { 32, 34, 46 }, { 33, 90, 38 }, { 33, 80, 47 }, { 34, 62, 7 }, { 34, 68, 6 }, { 35, 40, 50 }, { 35, 45, 12 }, { 35, 30, 13 }, { 36, 44, 7 }, { 36, 48, 13 }, { 37, 30, 44 }, { 38, 61, 3 }, { 39, 46, 34 }, { 39, 83, 32 }, { 39, 20, 49 }, { 40, 56, 35 }, { 40, 13, 43 }, { 40, 80, 13 }, { 40, 14, 8 }, { 41, 59, 20 }, { 42, 40, 42 }, { 42, 90, 6 }, { 42, 45, 14 }, { 42, 73, 36 }, { 43, 71, 17 }, { 43, 19, 10 }, { 44, 18, 49 }, { 45, 89, 37 }, { 45, 53, 42 }, { 45, 86, 12 }, { 45, 62, 37 }, { 46, 21, 1 }, { 47, 70, 39 }, { 47, 22, 44 }, { 48, 58, 4 }, { 48, 66, 16 }, { 48, 19, 16 }, { 49, 66, 16 }, { 49, 44, 5 }, { 50, 39, 37 }, { 50, 29, 37 }, { 50, 38, 5 }, { 51, 80, 45 }, { 52, 22, 17 }, { 52, 12, 20 }, { 53, 85, 50 }, { 54, 67, 43 }, { 54, 69, 50 }, { 56, 61, 32 }, { 57, 80, 41 }, { 58, 39, 29 }, { 59, 50, 14 }, { 59, 78, 44 }, { 59, 88, 28 }, { 59, 56, 45 }, { 61, 74, 49 }, { 61, 67, 28 }, { 61, 90, 24 }, { 62, 66, 26 }, { 62, 53, 1 }, { 62, 68, 7 }, { 63, 33, 1 }, { 63, 21, 49 }, { 63, 70, 41 }, { 64, 89, 19 }, { 64, 28, 5 }, { 65, 58, 43 }, { 65, 43, 35 }, { 66, 58, 12 }, { 66, 63, 21 }, { 66, 39, 23 }, { 67, 59, 43 }, { 68, 19, 39 }, { 68, 54, 21 }, { 69, 87, 13 }, { 69, 77, 6 }, { 69, 59, 36 }, { 69, 23, 8 }, { 70, 54, 16 }, { 71, 90, 37 }, { 71, 79, 32 }, { 71, 23, 28 }, { 71, 68, 21 }, { 72, 85, 13 }, { 72, 43, 44 }, { 73, 57, 32 }, { 74, 78, 29 }, { 16, 99, 34 }, { 17, 93, 13 }, { 21, 92, 11 }, { 22, 97, 29 }, { 24, 99, 45 }, { 26, 96, 2 }, { 27, 93, 40 }, { 28, 95, 33 }, { 29, 98, 12 }, { 30, 93, 15 }, { 31, 99, 32 }, { 34, 100, 36 }, { 36, 99, 5 }, { 39, 98, 38 }, { 40, 99, 10 }, { 41, 91, 15 }, { 42, 91, 31 }, { 44, 91, 16 }, { 45, 99, 19 }, { 47, 92, 33 }, { 50, 99, 20 }, { 51, 93, 13 }, { 101, 1, 2147483647 }, { 101, 2, 2147483647 }, { 101, 3, 2147483647 }, { 101, 4, 2147483647 }, { 101, 5, 2147483647 }, { 101, 6, 2147483647 }, { 101, 7, 2147483647 }, { 101, 8, 2147483647 }, { 101, 9, 2147483647 }, { 101, 10, 2147483647 }, { 96, 102, 2147483647 }, { 97, 102, 2147483647 }, { 98, 102, 2147483647 }, { 99, 102, 2147483647 }, { 100, 102, 2147483647 }, { 91, 102, 2147483647 }, { 92, 102, 2147483647 }, { 93, 102, 2147483647 }, { 94, 102, 2147483647 }, { 95, 102, 2147483647 } }; testDirectedUndirected(edges, 101, 102, 906, 1081); } @Override MaximumFlowAlgorithm createSolver( Graph network) { return new BoykovKolmogorovMFImpl<>(network); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/DinicMFImplTest.java000066400000000000000000000064571402514743400314440ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Kirill Vishnyakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import static org.junit.Assert.assertEquals; public class DinicMFImplTest extends MaximumFlowAlgorithmTest { private DefaultDirectedWeightedGraph g; private MaximumFlowAlgorithm dinic; private DefaultWeightedEdge edge; private final String v1 = "v1"; private final String v2 = "v2"; private final String v3 = "v3"; @Override MaximumFlowAlgorithm createSolver( Graph network) { return new DinicMFImpl<>(network); } @Before public void init() { g = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); } @Test public void simpleTest1() { g.addVertex(v1); g.addVertex(v2); edge = g.addEdge(v1, v2); g.setEdgeWeight(edge, 100.0); dinic = new DinicMFImpl<>(g); double flow = dinic.getMaximumFlowValue(v1, v2); assertEquals(100.0, flow, 0); } @Test public void simpleTest2() { g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); edge = g.addEdge(v1, v2); g.setEdgeWeight(edge, 100.0); edge = g.addEdge(v2, v3); g.setEdgeWeight(edge, 50.0); dinic = new DinicMFImpl<>(g); double flow = dinic.getMaximumFlowValue(v1, v3); assertEquals(50.0, flow, 0); } @Test(expected = IllegalArgumentException.class) public void exceptionTest1() { g.addVertex(v1); dinic = new DinicMFImpl<>(g); double flow = dinic.getMaximumFlowValue(v1, v1); System.out.println(flow); } @Test public void disconnectedTest() { g.addVertex(v1); g.addVertex(v2); dinic = new DinicMFImpl<>(g); double flow = dinic.getMaximumFlowValue(v1, v2); assertEquals(0.0, flow, 0); } @Test public void simpleTest3() { g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); String v4 = "v4"; g.addVertex(v4); edge = g.addEdge(v1, v2); g.setEdgeWeight(edge, 2.0); edge = g.addEdge(v2, v3); g.setEdgeWeight(edge, 2.0); edge = g.addEdge(v3, v4); g.setEdgeWeight(edge, 2.0); edge = g.addEdge(v2, v4); g.setEdgeWeight(edge, 1.0); edge = g.addEdge(v1, v3); g.setEdgeWeight(edge, 1.0); dinic = new DinicMFImpl<>(g); double flow = dinic.getMaximumFlowValue(v1, v2); assertEquals(2.0, flow, 0); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/EdmondsKarpMFImplTest.java000066400000000000000000000071141402514743400326140ustar00rootroot00000000000000/* * (C) Copyright 2008-2021, by Ilya Razenshteyn and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.fail; public class EdmondsKarpMFImplTest extends MaximumFlowAlgorithmTest { @Override MaximumFlowAlgorithm createSolver( Graph network) { return new EdmondsKarpMFImpl<>(network); } // ~ Methods ---------------------------------------------------------------- @Test public void testCornerCases() { DirectedWeightedMultigraph simple = new DirectedWeightedMultigraph<>(DefaultWeightedEdge.class); simple.addVertex(0); simple.addVertex(1); DefaultWeightedEdge e = simple.addEdge(0, 1); try { new EdmondsKarpMFImpl(null); fail(); } catch (NullPointerException ex) { } try { new EdmondsKarpMFImpl<>(simple, -0.1); fail(); } catch (IllegalArgumentException ex) { } try { simple.setEdgeWeight(e, -1.0); new EdmondsKarpMFImpl<>(simple); fail(); } catch (IllegalArgumentException ex) { } try { simple.setEdgeWeight(e, 1.0); MaximumFlowAlgorithm solver = new EdmondsKarpMFImpl<>(simple); Map flow = solver.getMaximumFlow(0, 1).getFlowMap(); flow.put(e, 25.0); fail(); } catch (UnsupportedOperationException ex) { } try { MaximumFlowAlgorithm solver = new EdmondsKarpMFImpl<>(simple); solver.getMaximumFlow(2, 0); fail(); } catch (IllegalArgumentException ex) { } try { MaximumFlowAlgorithm solver = new EdmondsKarpMFImpl<>(simple); solver.getMaximumFlow(1, 2); fail(); } catch (IllegalArgumentException ex) { } try { MaximumFlowAlgorithm solver = new EdmondsKarpMFImpl<>(simple); solver.getMaximumFlow(0, 0); fail(); } catch (IllegalArgumentException ex) { } try { MaximumFlowAlgorithm solver = new EdmondsKarpMFImpl<>(simple); solver.getMaximumFlow(null, 0); fail(); } catch (IllegalArgumentException ex) { } try { MaximumFlowAlgorithm solver = new EdmondsKarpMFImpl<>(simple); solver.getMaximumFlow(0, null); fail(); } catch (IllegalArgumentException ex) { } } } EdmondsKarpMinimumSTCutTest.java000066400000000000000000000064021402514743400337460ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; /** * @author Joris Kinable */ public class EdmondsKarpMinimumSTCutTest extends MinimumSourceSinkCutTest { @Override MinimumSTCutAlgorithm createSolver( Graph network) { return new EdmondsKarpMFImpl<>(network); } @Test public void testRandomDirectedGraphs() { for (int test = 0; test < NR_RANDOM_TESTS; test++) { Graph network = generateDirectedGraph(); int source = 0; int sink = network.vertexSet().size() - 1; MinimumSTCutAlgorithm ekSolver = this.createSolver(network); MinimumSTCutAlgorithm prSolver = new PushRelabelMFImpl<>(network); double expectedCutWeight = prSolver.calculateMinCut(source, sink); double cutWeight = ekSolver.calculateMinCut(source, sink); Set sourcePartition = ekSolver.getSourcePartition(); Set sinkPartition = ekSolver.getSinkPartition(); Set cutEdges = ekSolver.getCutEdges(); this .verifyDirected( network, source, sink, expectedCutWeight, cutWeight, sourcePartition, sinkPartition, cutEdges); } } @Test public void testRandomUndirectedGraphs() { for (int test = 0; test < NR_RANDOM_TESTS; test++) { Graph network = generateUndirectedGraph(); int source = 0; int sink = network.vertexSet().size() - 1; MinimumSTCutAlgorithm ekSolver = this.createSolver(network); MinimumSTCutAlgorithm prSolver = new PushRelabelMFImpl<>(network); double expectedCutWeight = prSolver.calculateMinCut(source, sink); double cutWeight = ekSolver.calculateMinCut(source, sink); Set sourcePartition = ekSolver.getSourcePartition(); Set sinkPartition = ekSolver.getSinkPartition(); Set cutEdges = ekSolver.getCutEdges(); this .verifyUndirected( network, source, sink, expectedCutWeight, cutWeight, sourcePartition, sinkPartition, cutEdges); } } } GusfieldEquivalentFlowTreeTest.java000066400000000000000000000064141402514743400345330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.graph.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test class for the GusfieldEquivalentFlowTree implementation * * @author Joris Kinable */ public class GusfieldEquivalentFlowTreeTest extends GusfieldTreeAlgorithmsTestBase { @Override public void validateAlgorithm(SimpleWeightedGraph network) { GusfieldEquivalentFlowTree alg = new GusfieldEquivalentFlowTree<>(network); SimpleWeightedGraph equivalentFlowTree = alg.getEquivalentFlowTree(); // Verify that the Equivalent Flow tree is an actual tree assertTrue(GraphTests.isTree(equivalentFlowTree)); // Find the minimum cut in the graph StoerWagnerMinimumCut minimumCutAlg = new StoerWagnerMinimumCut<>(network); double expectedMinimumCut = minimumCutAlg.minCutWeight(); double cheapestEdge = equivalentFlowTree .edgeSet().stream().mapToDouble(equivalentFlowTree::getEdgeWeight).min().getAsDouble(); assertEquals(expectedMinimumCut, cheapestEdge, 0); MinimumSTCutAlgorithm minimumSTCutAlgorithm = new PushRelabelMFImpl<>(network); for (Integer i : network.vertexSet()) { for (Integer j : network.vertexSet()) { if (j <= i) continue; // Check cut weights double expectedCutWeight = minimumSTCutAlgorithm.calculateMinCut(i, j); assertEquals(expectedCutWeight, alg.getMaximumFlowValue(i, j), 0); assertEquals(expectedCutWeight, alg.getMaximumFlowValue(j, i), 0); // Verify the correctness of the tree // The cost of the cheapest edge in the path from i to j must equal the weight of an // i-j cut List pathEdges = DijkstraShortestPath.findPathBetween(equivalentFlowTree, i, j).getEdgeList(); DefaultWeightedEdge cheapestEdgeInPath = pathEdges .stream().min(Comparator.comparing(equivalentFlowTree::getEdgeWeight)) .orElseThrow(() -> new RuntimeException("path is empty?!")); assertEquals(expectedCutWeight, network.getEdgeWeight(cheapestEdgeInPath), 0); } } } } GusfieldGomoryHuCutTreeTest.java000066400000000000000000000115551402514743400340150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.graph.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test class for the GusfieldGomoryHuCutTree implementation * * @author Joris Kinable */ public class GusfieldGomoryHuCutTreeTest extends GusfieldTreeAlgorithmsTestBase { @Override public void validateAlgorithm(SimpleWeightedGraph network) { GusfieldGomoryHuCutTree alg = new GusfieldGomoryHuCutTree<>(network); SimpleWeightedGraph gomoryHuTree = alg.getGomoryHuTree(); // Verify that the Gomory-Hu tree is an actual tree assertTrue(GraphTests.isTree(gomoryHuTree)); // Find the minimum cut in the graph StoerWagnerMinimumCut minimumCutAlg = new StoerWagnerMinimumCut<>(network); double expectedMinimumCut = minimumCutAlg.minCutWeight(); double cheapestEdge = gomoryHuTree .edgeSet().stream().mapToDouble(gomoryHuTree::getEdgeWeight).min().getAsDouble(); assertEquals(expectedMinimumCut, cheapestEdge, 0); assertEquals(expectedMinimumCut, alg.calculateMinCut(), 0); Set partition = alg.getSourcePartition(); double cutWeight = network .edgeSet().stream() .filter( e -> partition.contains(network.getEdgeSource(e)) ^ partition.contains(network.getEdgeTarget(e))) .mapToDouble(network::getEdgeWeight).sum(); assertEquals(expectedMinimumCut, cutWeight, 0); MinimumSTCutAlgorithm minimumSTCutAlgorithm = new PushRelabelMFImpl<>(network); for (Integer i : network.vertexSet()) { for (Integer j : network.vertexSet()) { if (j <= i) continue; // Check cut weights double expectedCutWeight = minimumSTCutAlgorithm.calculateMinCut(i, j); assertEquals(expectedCutWeight, alg.getMaximumFlowValue(i, j), 0); assertEquals(expectedCutWeight, alg.getMaximumFlowValue(j, i), 0); assertEquals(expectedCutWeight, alg.calculateMinCut(j, i), 0); assertEquals(expectedCutWeight, alg.calculateMinCut(i, j), 0); assertEquals(expectedCutWeight, alg.getCutCapacity(), 0); // Check cut partitions Set sourcePartition = alg.getSourcePartition(); assertTrue(sourcePartition.contains(i)); Set sinkPartition = alg.getSinkPartition(); assertTrue(sinkPartition.contains(j)); Set intersection = new HashSet<>(sourcePartition); intersection.retainAll(sinkPartition); assertTrue(intersection.isEmpty()); cutWeight = network .edgeSet().stream() .filter( e -> sourcePartition.contains(network.getEdgeSource(e)) ^ sourcePartition.contains(network.getEdgeTarget(e))) .mapToDouble(network::getEdgeWeight).sum(); assertEquals(expectedCutWeight, cutWeight, 0); // Verify the correctness of the tree // a. the cost of the cheapest edge in the path from i to j must equal the weight of // an i-j cut SimpleWeightedGraph gomoryHuTreeCopy = alg.getGomoryHuTree(); List pathEdges = DijkstraShortestPath.findPathBetween(gomoryHuTreeCopy, i, j).getEdgeList(); DefaultWeightedEdge cheapestEdgeInPath = pathEdges .stream().min(Comparator.comparing(gomoryHuTreeCopy::getEdgeWeight)) .orElseThrow(() -> new RuntimeException("path is empty?!")); assertEquals(expectedCutWeight, network.getEdgeWeight(cheapestEdgeInPath), 0); } } } } GusfieldTreeAlgorithmsTestBase.java000066400000000000000000000133461402514743400344740ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; /** * Test base class for the GusfieldGomoryHuCutTree and GusfieldEquivalentFlow implementations * * @author Joris Kinable */ public abstract class GusfieldTreeAlgorithmsTestBase { public abstract void validateAlgorithm( SimpleWeightedGraph network); /** * Triangle graph example from the paper Very simple methods for all pairs network flow * analysis by Dan gusfield (Figure 1) */ @Test public void testTriangleGraph() { SimpleWeightedGraph network = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(0, 1, 2)); Graphs.addEdge(network, 0, 1, 3); Graphs.addEdge(network, 1, 2, 4); Graphs.addEdge(network, 0, 2, 7); validateAlgorithm(network); } /** * Square graph example from the paper Very simple methods for all pairs network flow * analysis by Dan gusfield (Figure 2) */ @Test public void testSquareGraph() { SimpleWeightedGraph network = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(1, 2, 3, 4, 5, 6)); Graphs.addEdge(network, 1, 2, 1); Graphs.addEdge(network, 3, 4, 1); Graphs.addEdge(network, 5, 6, 1); Graphs.addEdge(network, 5, 1, 1); Graphs.addEdge(network, 1, 3, 1); Graphs.addEdge(network, 6, 2, 1); Graphs.addEdge(network, 2, 4, 1); validateAlgorithm(network); } /** * Graph example from the paper Multi-Terminal Network Flows by Gomory, R. and Hu, T. */ @Test public void testGomoryHuExampleGraph() { SimpleWeightedGraph network = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(1, 2, 3, 4, 5, 6)); Graphs.addEdge(network, 1, 2, 10); Graphs.addEdge(network, 1, 6, 8); Graphs.addEdge(network, 2, 6, 3); Graphs.addEdge(network, 2, 3, 4); Graphs.addEdge(network, 2, 5, 2); Graphs.addEdge(network, 6, 3, 2); Graphs.addEdge(network, 6, 4, 2); Graphs.addEdge(network, 6, 5, 3); Graphs.addEdge(network, 5, 3, 4); Graphs.addEdge(network, 5, 4, 7); Graphs.addEdge(network, 3, 4, 5); validateAlgorithm(network); } @Test public void testGraphWithNoEdges() { SimpleWeightedGraph network = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(1, 2)); validateAlgorithm(network); } /** * Some graph taken from the wikipedia article about Gomory-Hu trees */ @Test public void testWikipediaGraph() { // Example wikipedia SimpleWeightedGraph network = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(0, 1, 2, 3, 4, 5)); Graphs.addEdge(network, 0, 1, 1); Graphs.addEdge(network, 0, 2, 7); Graphs.addEdge(network, 1, 2, 1); Graphs.addEdge(network, 1, 3, 3); Graphs.addEdge(network, 1, 4, 2); Graphs.addEdge(network, 2, 4, 4); Graphs.addEdge(network, 3, 4, 1); Graphs.addEdge(network, 3, 5, 6); Graphs.addEdge(network, 4, 5, 2); validateAlgorithm(network); } /** * Test disconnected graph */ @Test public void testDisconnectedGraph() { SimpleWeightedGraph network = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(0, 1, 2, 3, 4)); Graphs.addEdge(network, 0, 1, 3); Graphs.addEdge(network, 1, 2, 4); Graphs.addEdge(network, 0, 2, 7); Graphs.addEdge(network, 3, 4, 9); validateAlgorithm(network); } @Test public void testRandomGraphs() { Random rand = new Random(0); for (int i = 0; i < 10; i++) { SimpleWeightedGraph randomGraph = new SimpleWeightedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); int vertices = rand.nextInt((20 - 10) + 1) + 10; // 10-20 vertices double p = 0.01 * (rand.nextInt((85 - 50) + 1) + 50); // p=[0.5;0.85] GnpRandomGraphGenerator graphGen = new GnpRandomGraphGenerator<>(vertices, p); graphGen.generateGraph(randomGraph); for (DefaultWeightedEdge edge : randomGraph.edgeSet()) randomGraph.setEdgeWeight(edge, rand.nextInt(150)); validateAlgorithm(randomGraph); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MaximumFlowAlgorithmTest.java000066400000000000000000000213541402514743400334560ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Joris Kinable */ public abstract class MaximumFlowAlgorithmTest extends MaximumFlowMinimumCutAlgorithmTestBase { abstract MaximumFlowAlgorithm createSolver( Graph network); private void runTestDirected( Graph network, int[] sources, int[] sinks, double[] expectedResults) { assertTrue(sources.length == sinks.length); MaximumFlowAlgorithm solver = createSolver(network); // Calculate the max flow for each source/sink pair for (int i = 0; i < sources.length; i++) { verifyDirected( sources[i], sinks[i], expectedResults[i], network, solver.getMaximumFlow(sources[i], sinks[i])); } } static void verifyDirected( int source, int sink, double expectedResult, Graph network, MaximumFlowAlgorithm.MaximumFlow maxFlow) { Double flowValue = maxFlow.getValue(); Map flow = maxFlow.getFlowMap(); // Verify that the maximum flow value assertEquals(expectedResult, flowValue, EdmondsKarpMFImpl.DEFAULT_EPSILON); // Verify that every edge is contained in the flow map for (DefaultWeightedEdge e : network.edgeSet()) { assertTrue(flow.containsKey(e)); } // Verify that the flow on every arc is between [-DEFAULT_EPSILON, edge_capacity] for (DefaultWeightedEdge e : flow.keySet()) { assertTrue(network.containsEdge(e)); assertTrue(flow.get(e) >= -EdmondsKarpMFImpl.DEFAULT_EPSILON); assertTrue( flow.get(e) <= (network.getEdgeWeight(e) + EdmondsKarpMFImpl.DEFAULT_EPSILON)); } // Verify flow preservation: amount of incoming flow must equal amount of outgoing flow // (exception for the source/sink vertices) for (Integer v : network.vertexSet()) { double balance = 0.0; for (DefaultWeightedEdge e : network.outgoingEdgesOf(v)) { balance -= flow.get(e); } for (DefaultWeightedEdge e : network.incomingEdgesOf(v)) { balance += flow.get(e); } if (v.equals(source)) { assertEquals(-flowValue, balance, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } else if (v.equals(sink)) { assertEquals(flowValue, balance, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } else { assertEquals(0.0, balance, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } } } private void runTestUndirected( Graph graph, int source, int sink, int expectedResult) { MaximumFlowAlgorithm solver = createSolver(graph); verifyUndirected(graph, source, sink, expectedResult, solver); } static void verifyUndirected( Graph graph, int source, int sink, int expectedResult, MaximumFlowAlgorithm solver) { MaximumFlowAlgorithm.MaximumFlow maxFlow = solver.getMaximumFlow(source, sink); Double flowValue = maxFlow.getValue(); Map flow = maxFlow.getFlowMap(); assertEquals(expectedResult, flowValue.intValue()); // Verify that every edge is contained in the flow map for (DefaultWeightedEdge e : graph.edgeSet()) assertTrue(flow.containsKey(e)); // Verify that the flow on every arc is between [-DEFAULT_EPSILON, edge_capacity] for (DefaultWeightedEdge e : flow.keySet()) { assertTrue(graph.containsEdge(e)); assertTrue(flow.get(e) >= -EdmondsKarpMFImpl.DEFAULT_EPSILON); assertTrue(flow.get(e) <= (graph.getEdgeWeight(e) + EdmondsKarpMFImpl.DEFAULT_EPSILON)); } // Verify flow preservation: amount of incoming flow must equal amount of outgoing flow // (exception for the source/sink vertices) for (Integer u : graph.vertexSet()) { double balance = 0.0; for (DefaultWeightedEdge e : graph.edgesOf(u)) { Integer v = solver.getFlowDirection(e); if (u == v) // incoming flow balance += flow.get(e); else // outgoing flow balance -= flow.get(e); } if (u.equals(source)) { assertEquals(-flowValue, balance, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } else if (u.equals(sink)) { assertEquals(flowValue, balance, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } else { assertEquals(0.0, balance, MaximumFlowAlgorithmBase.DEFAULT_EPSILON); } } } @Test public void testDirectedN0() { runTestDirected(getDirectedN0(), new int[] { 1 }, new int[] { 4 }, new double[] { 5.0 }); } @Test public void testDirectedN1() { runTestDirected( getDirectedN1(), new int[] { 1 }, new int[] { 4057218 }, new double[] { 0.0 }); } @Test public void testDirectedN2() { runTestDirected(getDirectedN2(), new int[] { 3 }, new int[] { 6 }, new double[] { 2 }); } @Test public void testDirectedN3() { runTestDirected(getDirectedN3(), new int[] { 5 }, new int[] { 6 }, new double[] { 4.0 }); } @Test public void testDirectedN4() { runTestDirected( getDirectedN4(), new int[] { 1 }, new int[] { 4 }, new double[] { 2000000000.0 }); } @Test public void testDirectedN6() { runTestDirected(getDirectedN6(), new int[] { 1 }, new int[] { 50 }, new double[] { 20.0 }); } @Test public void testDirectedN7() { runTestDirected(getDirectedN7(), new int[] { 1 }, new int[] { 50 }, new double[] { 31.0 }); } @Test public void testDirectedN8() { runTestDirected(getDirectedN8(), new int[] { 0 }, new int[] { 5 }, new double[] { 23 }); } @Test public void testDirectedN9() { runTestDirected(getDirectedN9(), new int[] { 0 }, new int[] { 8 }, new double[] { 22 }); } @Test public void testDirectedN10() { runTestDirected(getDirectedN10(), new int[] { 1 }, new int[] { 99 }, new double[] { 173 }); } @Test public void testDirectedN11() { runTestDirected(getDirectedN11(), new int[] { 1 }, new int[] { 99 }, new double[] { 450 }); } @Test public void testDirectedN12() { runTestDirected(getDirectedN12(), new int[] { 1 }, new int[] { 99 }, new double[] { 203 }); } /*************** TEST CASES FOR UNDIRECTED GRAPHS ***************/ @Test public void testUndirectedN1() { runTestUndirected(getUndirectedN1(), 0, 8, 28); } @Test public void testUndirectedN2() { runTestUndirected(getUndirectedN2(), 1, 4, 93); } @Test public void testUndirectedN3() { runTestUndirected(getUndirectedN3(), 1, 49, 104); } @Test public void testUndirectedN4() { runTestUndirected(getUndirectedN4(), 1, 99, 634); } @Test public void testUndirectedN5() { runTestUndirected(getUndirectedN5(), 1, 49, 112); } @Test public void testUndirectedN6() { runTestUndirected(getUndirectedN6(), 1, 69, 194); } @Test public void testUndirectedN7() { runTestUndirected(getUndirectedN7(), 1, 69, 33); } @Test public void testUndirectedN8() { runTestUndirected(getUndirectedN8(), 1, 99, 501); } @Test public void testUndirectedN9() { runTestUndirected(getUndirectedN9(), 1, 2, 0); } } MaximumFlowMinimumCutAlgorithmTestBase.java000066400000000000000000003766541402514743400362220ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/* * (C) Copyright 2015-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; import static org.junit.Assert.assertTrue; public abstract class MaximumFlowMinimumCutAlgorithmTestBase { protected Graph constructDirectedGraph( int[] tails, int[] heads, double[] capacities, int[] sources, int[] sinks) { assertTrue(tails.length == heads.length); assertTrue(tails.length == capacities.length); DirectedWeightedMultigraph network = new DirectedWeightedMultigraph<>(DefaultWeightedEdge.class); int m = tails.length; for (int i = 0; i < m; i++) Graphs.addEdgeWithVertices(network, tails[i], heads[i], capacities[i]); for (int i = 0; i < sources.length; i++) { network.addVertex(sources[i]); network.addVertex(sinks[i]); } return network; } protected Graph constructUndirectedGraph(int[][] edges) { // Construct undirected graph SimpleWeightedGraph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); for (int[] edge : edges) // Format: {u,v,weight} Graphs.addEdgeWithVertices(graph, edge[0], edge[1], edge[2]); return graph; } /*************** TEST CASES FOR DIRECTED GRAPHS ***************/ public Graph getDirectedN0() { return constructDirectedGraph( new int[] { 1, 2, 3 }, new int[] { 2, 3, 4 }, new double[] { 10, 10, 5 }, new int[] { 1 }, new int[] { 4 }); } public Graph getDirectedN1() { return constructDirectedGraph( new int[] {}, new int[] {}, new double[] {}, new int[] { 1 }, new int[] { 4057218 }); } public Graph getDirectedN2() { return constructDirectedGraph( new int[] { 3, 1, 4, 3, 2, 8, 2, 5, 7 }, new int[] { 1, 4, 8, 2, 8, 6, 5, 7, 6 }, new double[] { 1, 1, 1, 1, 1, 1, 1, 1, 1 }, new int[] { 3 }, new int[] { 6 }); } public Graph getDirectedN3() { return constructDirectedGraph( new int[] { 5, 5, 5, 1, 1, 4, 2, 7, 8, 3 }, new int[] { 1, 4, 2, 7, 8, 3, 8, 6, 6, 6 }, new double[] { 7, 8, 573146, 31337, 1, 1, 1, 1, 2391717, 170239 }, new int[] { 5 }, new int[] { 6 }); } public Graph getDirectedN4() { return constructDirectedGraph( new int[] { 1, 1, 2, 2, 3 }, new int[] { 2, 3, 3, 4, 4 }, new double[] { 1000000000.0, 1000000000.0, 1.0, 1000000000.0, 1000000000.0 }, new int[] { 1 }, new int[] { 4 }); } public Graph getDirectedN6() { return constructDirectedGraph( new int[] { 46, 27, 44, 49, 11, 22, 17, 37, 12, 7, 12, 17, 26, 35, 4, 14, 16, 27, 2, 8, 33, 38, 2, 32, 43, 22, 32, 14, 43, 44, 43, 43, 24, 49, 16, 44, 10, 43, 10, 34, 34, 21, 24, 42, 11, 9, 23, 49, 49, 34, 34, 45, 17, 10, 43, 22, 38, 49, 3, 33, 13, 15, 2, 48, 20, 18, 29, 36, 13, 37, 5, 8, 33, 47, 47, 18, 24, 22, 42, 48, 17, 34, 42, 8, 38, 10, 39, 47, 31, 19, 6, 25, 17, 38, 33, 21, 39, 2, 32, 33, 46, 22, 37, 39, 15, 38, 4, 26, 44, 37, 17, 1, 48, 31, 31, 19, 34, 31, 40, 42, 30, 6, 14, 50, 30, 33, 44, 7, 50, 32, 43, 26, 43, 4, 28, 41, 7, 25, 1, 49, 27, 15, 38, 24, 9, 46, 45, 49, 6, 17, 18, 12, 36, 5, 31, 9, 50, 20, 30, 48, 40, 35, 15, 21, 9, 40, 47, 5, 36, 31, 10, 10, 1, 6, 39, 21, 42, 35, 41, 37, 33, 23, 42, 46, 37, 24, 12, 37, 27, 24, 36, 36, 18, 37, 50, 5, 50, 17, 7, 24, 4, 30, 41, 47, 7, 4, 44, 9, 29, 13, 48, 35, 19, 2, 46, 46, 39, 9, 13, 41, 7, 18, 48, 3, 31, 42, 32, 8, 46, 5, 4, 48, 43, 7, 49, 9, 45, 29, 6, 26, 13, 16, 25, 43, 41, 38, 45, 15, 44, 22, 21, 29, 10, 49, 35, 43, 9, 28, 5, 41, 16, 34, 27, 23, 1, 22, 29, 42, 50, 24, 23, 43, 16, 20, 33, 35, 33, 30, 9, 39, 4, 7, 20, 37, 6, 39, 46, 12, 21, 33, 34, 47, 20, 30, 22, 31, 50, 16, 13, 23, 32, 36, 41, 45, 16, 27, 4, 42, 3, 18, 38, 29, 44, 49, 32, 14, 31, 18, 6, 44, 20, 45, 48, 50, 35, 48, 48, 15, 26, 2, 36, 36, 42, 1, 5, 27, 48, 44, 41, 12, 16, 16, 43, 33, 44, 42, 35, 37, 47, 35, 50, 9, 37, 14, 29, 23, 4, 9, 28, 15, 1, 2, 35, 27, 2, 39, 46, 11, 47, 18, 25, 35, 4, 34, 37, 11, 48, 34, 26, 12, 22, 5, 1, 7, 7, 43, 32, 25, 17, 48, 48, 24, 32, 41, 28, 7, 16, 18, 16, 16, 21, 30, 15, 39, 2, 26, 31, 27, 38, 1, 3, 14, 14, 19, 42, 12, 15, 39, 14, 47, 21, 26, 8, 33, 23, 38, 34, 17, 1, 37, 4, 34, 27, 26, 40, 30, 15, 43, 27, 6, 28, 4, 13, 39, 11, 3, 11, 44, 39, 35, 47, 49, 20, 25, 4, 38, 32, 48, 13, 32, 31, 10, 8, 46, 39, 19, 35, 23, 46, 33, 22, 12, 21, 22, 36, 3, 38, 32, 14, 27, 26, 7, 49, 40, 33, 49, 36, 40, 11, 45, 38, 25, 37, 19, 19, 13, 5, 32, 13, 41, 43, 18, 50, 17, 18, 36, 38, 25, 2, 32, 2, 30, 22, 15, 15, 43, 29, 17, 38, 28, 21, 1, 12, 27, 2, 30, 8, 33, 13, 26, 27, 33, 12, 18, 14, 14, 50, 17, 9, 12, 40, 19, 46, 29, 45, 37, 24, 39, 3, 4, 46, 29, 1, 15, 25, 13, 28, 29, 1, 10, 46, 24, 25, 4, 6, 33, 35, 21, 5, 46, 23, 35, 32, 48, 23, 6, 6, 12, 35, 21, 32, 41, 44, 22, 26, 22, 19, 31, 11, 1, 16, 21, 16, 40, 6, 42, 32, 44, 24, 30, 13, 50, 6, 44, 16, 46, 47, 3, 17, 6, 22, 18, 23, 29, 39, 44, 42, 50, 8, 35, 6, 19, 32, 31, 36, 30, 45, 14, 22, 39, 44, 37, 4, 41, 13, 24, 28, 7, 13, 39, 36, 39, 47, 8, 50, 3, 20, 3, 1, 4, 24, 4, 33, 32, 34, 8, 50, 36, 28, 41, 14, 50, 42, 1, 29, 16, 38, 12, 21, 46, 8, 29, 34, 27, 48, 32, 14, 24, 47, 31, 11, 16, 3, 29, 20, 2, 30, 17, 45, 9, 28, 42, 43, 26, 42, 32, 45, 9, 20, 50, 11, 1, 25, 13, 42, 50, 23, 28, 5, 28, 17, 43, 44, 7, 38, 36, 29, 33, 14, 34, 38, 22, 32, 29, 43, 14, 32, 29, 9, 5, 36, 11, 8, 13, 40, 28, 43, 49, 37, 37, 7, 9, 16, 28, 8, 36, 14, 13, 26, 38, 3, 6, 35, 2, 13, 8, 14, 34, 34, 2, 43, 46, 29, 3, 7, 35, 42, 39, 48, 15, 31, 38, 10, 40, 13, 24, 19, 38, 4, 25, 37, 44, 22, 3, 30, 30, 46, 12, 22, 47, 50, 26, 34, 6, 26, 37, 50, 27, 10, 10, 36, 25, 14, 21, 15, 47, 25, 4, 9, 12, 40, 18, 49, 20, 44, 18, 48, 28, 46, 49, 34, 5, 19, 37, 20, 25, 50, 46, 47, 47, 7, 19, 50, 36, 26, 23, 8, 10, 7, 10, 11, 28, 23, 9, 33, 4, 25, 10, 49, 26, 15, 40, 39, 14, 5, 50, 35, 37, 42, 15, 25, 45, 40, 31, 24, 21, 14, 19, 37, 26, 44, 4, 30, 4, 30, 1, 30, 48, 16, 8, 10, 50, 47, 18, 16, 31, 27, 4, 49, 7, 49, 7, 46, 43, 28, 15, 34, 6, 33, 1, 10, 42, 38, 40, 28, 29, 13, 48, 33, 14, 16, 4, 47, 4, 4, 37, 5, 21, 18, 31, 32, 46, 11, 5, 18, 30, 34, 16, 15, 6, 38, 2, 11, 38, 4, 16, 10, 39, 2, 15, 37, 3, 21, 33, 42, 28, 17, 31, 29, 43, 36, 42, 39, 4, 24, 14, 3, 47, 29, 25, 14, 50, 2, 25, 23, 1, 3, 23, 42, 37, 41, 12, 45, 45, 38, 1, 41, 21, 36, 18, 12, 44, 5, 9, 8, 44, 40, 30, 12, 17, 45, 21, 16, 12, 18, 35, 13, 17, 45, 49 }, new int[] { 29, 5, 25, 16, 38, 2, 49, 19, 48, 47, 46, 37, 19, 44, 2, 10, 27, 37, 1, 40, 36, 36, 9, 3, 19, 29, 45, 44, 11, 18, 45, 10, 11, 28, 25, 36, 49, 30, 45, 35, 39, 20, 47, 40, 6, 12, 18, 21, 35, 10, 40, 6, 28, 50, 44, 27, 14, 9, 9, 4, 5, 38, 48, 27, 47, 11, 33, 16, 26, 7, 48, 30, 16, 11, 2, 34, 38, 16, 14, 41, 1, 44, 33, 19, 24, 26, 48, 9, 44, 42, 30, 18, 16, 33, 41, 48, 23, 4, 11, 30, 48, 30, 8, 37, 50, 28, 35, 9, 17, 45, 22, 11, 29, 20, 11, 31, 24, 8, 35, 12, 37, 39, 11, 22, 6, 9, 13, 38, 36, 1, 20, 27, 17, 44, 41, 42, 4, 28, 36, 8, 45, 14, 4, 35, 15, 21, 37, 32, 8, 27, 36, 8, 19, 2, 14, 27, 11, 4, 49, 50, 24, 24, 49, 2, 13, 38, 32, 26, 23, 48, 3, 33, 30, 49, 34, 7, 43, 15, 14, 12, 48, 25, 13, 38, 9, 27, 40, 33, 11, 13, 37, 39, 50, 2, 10, 49, 37, 43, 17, 3, 6, 41, 31, 8, 46, 39, 40, 39, 40, 42, 33, 50, 17, 14, 13, 28, 11, 6, 16, 11, 23, 25, 4, 2, 35, 48, 33, 22, 45, 1, 13, 44, 7, 19, 38, 43, 11, 35, 27, 15, 39, 33, 35, 33, 5, 8, 16, 11, 22, 36, 5, 21, 34, 18, 46, 46, 29, 31, 24, 2, 10, 1, 16, 19, 9, 39, 34, 27, 18, 17, 33, 2, 47, 39, 14, 1, 13, 9, 35, 8, 49, 50, 14, 34, 13, 15, 10, 14, 3, 25, 7, 21, 5, 32, 45, 24, 38, 23, 3, 7, 19, 28, 33, 17, 4, 41, 32, 22, 36, 16, 50, 12, 26, 27, 9, 45, 40, 9, 22, 45, 33, 3, 24, 30, 49, 22, 23, 8, 21, 34, 1, 2, 37, 21, 32, 15, 30, 4, 19, 13, 11, 3, 22, 5, 35, 23, 31, 31, 20, 41, 46, 16, 49, 50, 5, 34, 45, 30, 23, 4, 5, 29, 30, 43, 26, 29, 4, 5, 42, 20, 7, 8, 21, 30, 14, 34, 20, 11, 42, 15, 31, 28, 7, 18, 28, 41, 23, 48, 7, 14, 8, 39, 13, 40, 39, 25, 29, 8, 31, 28, 31, 39, 27, 20, 50, 36, 30, 33, 10, 33, 4, 29, 33, 29, 35, 29, 21, 49, 15, 7, 19, 22, 43, 31, 42, 6, 38, 24, 42, 11, 18, 16, 40, 46, 43, 3, 31, 48, 13, 10, 11, 22, 47, 31, 43, 17, 50, 43, 40, 2, 33, 29, 36, 11, 17, 40, 16, 39, 21, 40, 16, 30, 47, 9, 45, 36, 19, 1, 26, 3, 18, 30, 38, 7, 27, 12, 35, 35, 36, 48, 16, 26, 48, 50, 15, 14, 6, 31, 10, 49, 44, 10, 48, 34, 41, 4, 8, 4, 20, 25, 35, 47, 15, 48, 33, 48, 22, 36, 44, 47, 40, 40, 43, 9, 24, 27, 38, 18, 7, 36, 4, 49, 17, 26, 23, 47, 10, 26, 24, 10, 49, 39, 22, 29, 28, 31, 44, 26, 41, 34, 5, 24, 8, 14, 44, 18, 25, 38, 39, 16, 43, 42, 10, 29, 26, 44, 18, 45, 20, 31, 3, 29, 20, 28, 2, 12, 9, 49, 21, 7, 8, 28, 44, 21, 6, 23, 37, 20, 7, 9, 42, 18, 31, 42, 3, 44, 28, 32, 29, 47, 19, 33, 24, 36, 25, 26, 49, 5, 4, 18, 17, 43, 12, 42, 17, 49, 24, 35, 10, 48, 23, 13, 12, 17, 21, 47, 47, 4, 15, 13, 42, 32, 5, 50, 4, 26, 50, 47, 21, 46, 32, 28, 11, 10, 31, 6, 3, 10, 33, 28, 38, 16, 35, 39, 28, 19, 35, 30, 12, 5, 9, 26, 2, 36, 31, 34, 8, 5, 47, 43, 43, 16, 3, 50, 1, 49, 9, 11, 27, 32, 31, 30, 28, 6, 32, 29, 42, 26, 30, 47, 13, 44, 26, 15, 49, 18, 15, 35, 40, 34, 13, 30, 28, 40, 31, 12, 46, 49, 34, 25, 17, 46, 16, 46, 36, 39, 5, 37, 4, 25, 47, 9, 34, 21, 3, 12, 22, 1, 1, 6, 31, 13, 7, 49, 31, 7, 27, 47, 8, 37, 24, 45, 11, 30, 41, 45, 40, 41, 43, 26, 1, 5, 12, 33, 46, 21, 32, 1, 37, 25, 22, 45, 18, 10, 38, 12, 27, 5, 26, 32, 38, 12, 25, 41, 3, 42, 2, 33, 31, 6, 4, 29, 25, 26, 29, 48, 15, 37, 33, 41, 16, 29, 47, 1, 13, 5, 44, 28, 3, 14, 17, 23, 43, 26, 30, 35, 1, 2, 24, 39, 49, 40, 5, 50, 41, 12, 42, 37, 33, 19, 33, 49, 44, 28, 30, 37, 3, 8, 35, 6, 23, 11, 50, 20, 50, 21, 40, 36, 17, 38, 41, 5, 45, 40, 33, 27, 7, 8, 44, 35, 37, 35, 50, 4, 6, 47, 12, 44, 39, 8, 47, 25, 4, 23, 31, 48, 16, 39, 38, 43, 5, 23, 44, 41, 1, 37, 16, 1, 47, 35, 16, 9, 4, 3, 46, 6, 27, 47, 45, 42, 7, 12, 45, 22, 20, 10, 38, 19, 15, 37, 34, 15, 31, 8, 44, 1, 18, 31, 34, 45, 20, 17, 49, 27, 50, 16, 22, 18, 11, 6, 38, 20, 37, 42, 36, 25, 9, 39, 15, 34, 28, 11, 12, 45, 32, 29, 13, 8, 19, 21, 12, 16, 31, 5, 14, 37, 4, 27, 5, 5, 32, 44, 11, 29, 9, 1, 23, 18, 31, 36, 47, 41, 47, 43, 30, 41, 33, 5, 2, 32, 3, 9, 8, 39, 17, 44, 29, 13, 32, 50, 13, 35, 10, 14, 32, 35, 8, 14, 17, 40, 2, 33, 29, 18, 36, 46, 13, 50, 10, 11, 22, 47, 21, 1, 21, 48, 12, 50, 39, 37 }, new double[] { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }, new int[] { 1 }, new int[] { 50 }); } public Graph getDirectedN7() { return constructDirectedGraph( new int[] { 3, 42, 11, 19, 16, 45, 18, 35, 38, 7, 15, 12, 33, 21, 47, 12, 40, 25, 29, 41, 9, 40, 46, 19, 10, 6, 41, 3, 10, 48, 23, 38, 9, 50, 11, 48, 7, 2, 36, 48, 40, 7, 10, 13, 8, 50, 34, 12, 24, 15, 16, 31, 15, 29, 10, 27, 42, 46, 47, 39, 30, 22, 27, 28, 32, 44, 15, 15, 23, 6, 11, 34, 14, 24, 5, 9, 29, 34, 42, 10, 41, 50, 40, 42, 34, 36, 25, 32, 40, 29, 49, 41, 34, 30, 33, 26, 18, 28, 40, 39, 14, 7, 20, 14, 12, 44, 13, 11, 40, 14, 43, 47, 28, 1, 47, 5, 36, 35, 15, 1, 48, 5, 10, 6, 22, 41, 35, 20, 40, 41, 22, 22, 37, 37, 50, 42, 26, 6, 11, 44, 27, 16, 43, 50, 4, 13, 25, 43, 18, 10, 45, 41, 43, 49, 2, 38, 50, 39, 42, 29, 36, 3, 22, 37, 8, 16, 50, 26, 45, 5, 5, 38, 2, 6, 23, 44, 24, 43, 2, 26, 30, 2, 46, 46, 25, 12, 47, 46, 38, 35, 34, 5, 2, 13, 18, 42, 5, 25, 29, 29, 27, 5, 18, 36, 13, 13, 41, 19, 28, 20, 9, 49, 12, 41, 14, 45, 22, 2, 15, 24, 44, 50, 7, 4, 2, 23, 49, 47, 40, 16, 21, 38, 18, 19, 46, 26, 47, 42, 30, 29, 2, 37, 47, 34, 39, 12, 50, 27, 20, 36, 24, 11, 32, 35, 48, 27, 29, 44, 12, 46, 7, 20, 48, 39, 39, 9, 20, 42, 35, 33, 41, 41, 13, 45, 12, 14, 21, 36, 25, 20, 8, 31, 27, 40, 34, 23, 1, 10, 14, 19, 47, 5, 40, 46, 36, 10, 32, 37, 9, 10, 8, 26, 20, 16, 15, 11, 43, 29, 16, 1, 23, 13, 5, 9, 26, 8, 26, 26, 37, 33, 20, 17, 22, 12, 26, 23, 39, 37, 50, 2, 41, 37, 12, 35, 45, 27, 9, 14, 3, 21, 23, 35, 39, 35, 6, 24, 48, 3, 1, 46, 28, 6, 50, 12, 27, 35, 21, 9, 15, 17, 15, 44, 30, 28, 21, 17, 19, 25, 4, 49, 11, 49, 22, 4, 31, 15, 31, 29, 18, 26, 46, 13, 24, 32, 5, 9, 1, 5, 13, 24, 31, 50, 35, 11, 36, 3, 41, 38, 41, 8, 37, 25, 8, 16, 45, 23, 16, 8, 9, 40, 7, 30, 50, 26, 39, 9, 33, 1, 48, 49, 37, 49, 7, 29, 17, 27, 5, 28, 31, 40, 40, 18, 39, 3, 34, 21, 20, 34, 38, 36, 42, 24, 44, 29, 28, 38, 40, 10, 46, 29, 9, 11, 35, 10, 38, 11, 2, 43, 4, 26, 6, 32, 31, 45, 16, 46, 21, 14, 18, 43, 2, 28, 16, 30, 5, 19, 7, 41, 45, 35, 3, 48, 9, 43, 17, 37, 4, 33, 27, 45, 32, 34, 32, 12, 17, 16, 33, 4, 36, 13, 42, 36, 9, 1, 35, 10, 2, 48, 29, 21, 27, 16, 48, 38, 18, 32, 46, 15, 2, 12, 36, 31, 2, 20, 37, 50, 28, 32, 7, 9, 33, 8, 47, 22, 21, 17, 20, 29, 22, 16, 38, 22, 39, 42, 32, 36, 13, 46, 45, 47, 9, 10, 30, 24, 24, 26, 34, 41, 5, 12, 47, 15, 36, 16, 19, 9, 42, 50, 23, 26, 42, 1, 39, 16, 49, 39, 28, 36, 16, 6, 37, 7, 4, 22, 50, 41, 34, 12, 45, 24, 19, 41, 10, 11, 43, 19, 31, 45, 21, 32, 19, 40, 38, 4, 45, 30, 13, 14, 1, 14, 7, 5, 7, 17, 15, 15, 10, 12, 25, 31, 20, 10, 28, 10, 24, 50, 21, 18, 41, 24, 28, 3, 36, 17, 29, 17, 32, 43, 8, 12, 38, 26, 18, 22, 50, 16, 3, 33, 14, 49, 30, 50, 27, 47, 34, 34, 37, 30, 42, 2, 47, 18, 31, 12, 16, 26, 44, 33, 47, 14, 42, 29, 8, 20, 13, 45, 12, 1, 29, 24, 3, 29, 44, 7, 49, 40, 33, 4, 4, 37, 1, 48, 26, 2, 39, 10, 45, 23, 15, 28, 21, 39, 3, 36, 1, 21, 23, 26, 12, 24, 3, 47, 4, 46, 11, 29, 10, 2, 3, 22, 26, 30, 35, 44, 1, 34, 9, 12, 7, 44, 14, 1, 25, 36, 32, 24, 11, 16, 31, 46, 2, 33, 44, 38, 19, 18, 48, 35, 29, 47, 39, 31, 21, 12, 30, 15, 37, 18, 43, 6, 19, 49, 7, 24, 46, 38, 6, 23, 3, 30, 39, 28, 14, 41, 48, 23, 18, 36, 18, 3, 11, 34, 48, 5, 15, 49, 12, 37, 47, 24, 38, 27, 5, 27, 48, 17, 43, 39, 2, 23, 17, 20, 11, 1, 25, 35, 44, 10, 28, 47, 40, 13, 6, 16, 5, 46, 47, 10, 39, 42, 3, 42, 26, 6, 1, 2, 48, 16, 14, 6, 6, 33, 27, 42, 32, 43, 13, 32, 10, 30, 32, 25, 4, 24, 15, 43, 33, 2, 14, 43, 2, 46, 4, 48, 33, 8, 47, 44, 43, 46, 24, 23, 17, 32, 38, 20, 9, 15, 47, 42, 43, 50, 34, 4, 2, 43, 17, 40, 27, 3, 43, 33, 38, 2, 22, 33, 27, 9, 37, 45, 9, 13, 22, 10, 16, 25, 9, 19, 35, 43, 47, 10, 4, 15, 13, 30, 34, 43, 28, 34, 48, 28, 11, 33, 28, 19, 31, 5, 2, 32, 48, 49, 36, 42, 50, 41, 6, 39, 18, 12, 21, 9, 50, 15, 38, 24, 33, 22, 20, 12, 30, 20, 13, 16, 29, 3, 25, 21, 49, 22, 30, 44, 41, 21, 39, 17, 39, 35, 43, 1, 27, 20, 30, 29, 24, 15, 41, 42, 37, 22, 15, 18, 39, 49, 18, 45, 43, 14, 25, 30, 25, 33, 47, 7, 46, 24, 40, 9, 33, 7, 45, 33, 23, 21, 44, 21, 14, 4, 22, 43, 49, 28, 20, 17, 6, 47, 46, 34, 4, 3, 46, 28, 36, 5, 34, 38, 5, 23, 10, 36, 30, 19, 32, 13, 35, 5, 19, 13, 43, 8, 28, 17, 11, 45, 13, 1, 3, 47, 20, 12, 46, 30, 15, 17, 2, 42, 48, 37, 33, 7, 35, 13, 32, 36, 18, 32, 32, 21, 49, 38, 20, 21, 43, 25, 23, 15, 48, 14, 3, 8, 45, 12, 24, 32, 34, 26, 49, 46, 36, 11, 8, 44, 50, 18, 30, 19, 26, 42, 14, 22, 16, 39, 43, 2, 40, 12, 34, 4, 22, 9, 44, 23, 46, 37, 31, 9, 6, 6, 24, 27, 45, 9, 44, 18, 32, 35, 7, 12, 45, 6, 18, 7, 29, 22, 23, 1, 34, 26, 38, 15, 25, 12, 36, 46, 21, 49, 30, 19, 24, 34, 41, 20, 12, 19, 13, 25, 18, 19, 1, 18, 9, 4, 39, 16, 41, 36, 33, 32, 12, 20, 47, 26, 17, 30, 8, 50, 43, 31, 16, 25, 12, 34, 29, 37, 35, 33, 45, 47, 7, 40, 19, 15, 16, 34, 45, 28, 33, 22, 10, 49, 31, 30, 29, 21, 29, 26, 44, 17, 42, 28, 20, 41, 29, 26, 28, 9, 15, 18, 1, 40, 27, 15, 1, 50, 1, 4, 11, 28, 42, 6, 3, 39, 29, 44, 4, 44, 10, 35, 23, 49, 9, 17, 34, 19, 33, 23, 5, 48, 24, 10, 8, 41, 47, 11, 7, 35, 28, 23, 37, 25, 46, 8, 38, 32, 35, 42, 28, 18, 1, 13, 21, 26, 46, 25, 37, 36, 19, 8, 2, 32, 43, 19, 50, 34, 50, 30, 19, 24, 5, 44, 39, 9, 10, 39, 47, 7, 4, 40, 40, 13, 38, 11, 33, 24, 23, 20, 40, 34, 25, 22, 24, 31, 43, 14, 10, 18, 36, 27, 29, 20, 48, 44, 3, 46, 22, 27, 15, 19, 26, 18, 50, 14, 37, 26, 14, 22, 4, 37, 21, 23, 38, 20, 9, 46, 32, 48, 37, 12, 47, 30, 39, 28, 9, 28, 6, 39, 32, 5, 50, 27, 50, 42, 32, 36, 35, 30, 45, 24, 16, 11, 11, 36, 18, 21, 4, 23, 29, 19, 1, 21, 31, 27, 44, 40, 17, 26, 13, 13, 34, 26, 27, 7, 20, 26, 12, 14, 18, 45, 29, 43, 44, 14, 12, 1, 7, 40, 36, 32, 13, 43, 33, 25, 7, 24, 39, 35, 7, 47, 25, 14, 27, 41, 50, 45, 25, 21, 18, 43, 3, 11, 11, 46, 13, 37, 49, 1, 37, 38, 8, 7, 40, 14, 39, 8, 1, 40, 14, 5, 43, 24, 37, 45, 47, 34, 37, 16, 37, 21, 38, 21, 20, 13, 22, 50, 21, 29, 19, 11, 27, 18, 28, 35, 1, 17, 48, 33, 40, 38, 43, 49, 10, 18, 50, 37, 15, 1, 11, 40, 7, 13, 7, 4, 26, 28, 4, 35, 27, 40, 30, 21, 3, 8, 38, 45, 5, 35, 41, 31, 37, 15 }, new int[] { 30, 47, 2, 22, 17, 34, 33, 23, 11, 14, 34, 13, 9, 48, 23, 10, 1, 22, 40, 28, 42, 45, 48, 9, 18, 27, 22, 6, 22, 1, 11, 17, 49, 22, 42, 43, 32, 50, 30, 15, 32, 18, 19, 26, 48, 36, 6, 24, 32, 40, 47, 44, 43, 21, 13, 16, 46, 11, 35, 15, 20, 13, 36, 26, 4, 13, 49, 47, 6, 21, 5, 19, 9, 3, 4, 33, 17, 12, 41, 48, 15, 8, 10, 19, 8, 9, 11, 9, 11, 45, 9, 29, 3, 11, 35, 15, 42, 32, 23, 7, 46, 23, 26, 1, 8, 19, 35, 40, 16, 13, 6, 18, 30, 25, 20, 8, 42, 46, 3, 49, 26, 15, 1, 12, 23, 33, 11, 25, 4, 49, 2, 10, 38, 36, 3, 28, 38, 8, 49, 45, 49, 14, 12, 11, 5, 19, 32, 17, 5, 14, 24, 18, 47, 28, 10, 34, 40, 2, 34, 13, 20, 27, 26, 40, 46, 31, 48, 36, 46, 32, 13, 4, 24, 7, 12, 43, 22, 7, 40, 41, 33, 3, 27, 10, 10, 26, 40, 35, 7, 48, 45, 44, 29, 20, 48, 25, 7, 4, 37, 47, 32, 1, 21, 15, 28, 44, 19, 2, 35, 4, 2, 21, 11, 35, 3, 8, 25, 36, 35, 19, 24, 38, 11, 45, 49, 50, 16, 34, 22, 20, 5, 15, 22, 4, 3, 30, 22, 21, 35, 14, 16, 32, 24, 23, 5, 4, 33, 40, 44, 33, 34, 15, 1, 18, 32, 5, 35, 7, 38, 4, 24, 16, 9, 48, 1, 27, 47, 3, 17, 40, 27, 1, 24, 48, 43, 32, 20, 8, 43, 23, 50, 29, 20, 19, 30, 30, 18, 11, 47, 36, 5, 6, 30, 29, 22, 35, 50, 27, 11, 38, 49, 48, 43, 48, 2, 12, 48, 2, 50, 12, 17, 47, 27, 7, 6, 40, 19, 27, 29, 32, 14, 3, 6, 2, 49, 4, 10, 41, 18, 48, 14, 4, 50, 36, 9, 19, 37, 45, 14, 43, 21, 12, 6, 29, 25, 45, 20, 44, 38, 41, 29, 37, 21, 1, 47, 28, 28, 17, 9, 49, 45, 47, 4, 1, 41, 19, 28, 20, 44, 20, 8, 32, 20, 28, 5, 22, 22, 12, 31, 11, 45, 9, 39, 24, 10, 12, 4, 46, 30, 31, 15, 14, 7, 7, 21, 50, 32, 6, 13, 42, 22, 8, 7, 19, 30, 39, 22, 17, 41, 20, 5, 15, 30, 17, 41, 50, 48, 28, 21, 3, 2, 48, 10, 50, 14, 28, 50, 16, 25, 36, 44, 49, 49, 32, 35, 36, 37, 25, 22, 29, 14, 49, 29, 26, 25, 3, 38, 47, 32, 8, 30, 37, 20, 27, 16, 24, 43, 26, 29, 13, 34, 41, 24, 22, 29, 23, 3, 16, 38, 30, 30, 33, 34, 13, 24, 42, 8, 8, 28, 39, 15, 13, 4, 33, 23, 10, 6, 45, 24, 36, 16, 38, 3, 46, 5, 9, 1, 27, 3, 34, 31, 18, 44, 24, 30, 25, 15, 40, 27, 24, 45, 6, 17, 35, 30, 23, 37, 24, 7, 7, 39, 10, 9, 7, 24, 27, 21, 43, 26, 39, 46, 33, 37, 45, 17, 34, 24, 32, 11, 26, 2, 47, 11, 17, 19, 47, 11, 6, 19, 10, 38, 34, 34, 37, 28, 9, 46, 42, 34, 36, 46, 1, 12, 3, 34, 29, 16, 42, 32, 20, 15, 15, 27, 25, 13, 42, 43, 24, 30, 31, 42, 20, 14, 21, 19, 45, 27, 48, 17, 14, 33, 25, 39, 34, 46, 39, 3, 4, 29, 2, 11, 41, 24, 16, 50, 48, 16, 8, 29, 12, 48, 45, 12, 46, 17, 13, 29, 27, 16, 19, 38, 37, 39, 26, 11, 13, 39, 17, 11, 36, 6, 34, 46, 9, 10, 50, 35, 16, 44, 18, 9, 22, 45, 39, 49, 8, 8, 43, 29, 6, 28, 44, 26, 31, 2, 47, 18, 16, 4, 19, 38, 32, 11, 21, 33, 2, 36, 21, 48, 42, 10, 49, 43, 1, 50, 49, 41, 32, 48, 10, 48, 28, 46, 29, 38, 21, 5, 50, 13, 7, 45, 7, 39, 26, 9, 12, 7, 25, 16, 11, 23, 33, 26, 27, 3, 7, 31, 28, 32, 46, 20, 32, 2, 36, 39, 34, 50, 1, 9, 37, 7, 31, 1, 16, 37, 44, 36, 49, 49, 26, 26, 7, 47, 25, 27, 27, 47, 24, 41, 47, 35, 12, 2, 30, 23, 11, 28, 44, 5, 25, 22, 26, 11, 44, 12, 8, 20, 43, 4, 28, 16, 50, 36, 1, 44, 48, 46, 13, 43, 8, 17, 4, 36, 3, 42, 14, 29, 50, 20, 40, 1, 17, 14, 48, 6, 29, 20, 26, 42, 7, 41, 50, 20, 30, 8, 29, 9, 18, 21, 10, 42, 31, 29, 6, 46, 6, 34, 9, 40, 12, 42, 14, 15, 1, 3, 17, 41, 4, 46, 5, 11, 23, 50, 29, 50, 46, 33, 42, 22, 50, 23, 38, 33, 35, 5, 11, 15, 18, 40, 49, 20, 34, 11, 17, 7, 13, 14, 31, 41, 1, 5, 9, 15, 16, 2, 6, 2, 40, 3, 1, 33, 37, 12, 34, 39, 45, 40, 50, 30, 12, 10, 6, 24, 1, 47, 20, 27, 25, 3, 28, 15, 4, 31, 11, 41, 29, 17, 21, 15, 29, 16, 21, 33, 16, 5, 12, 26, 43, 14, 40, 29, 24, 40, 26, 10, 3, 42, 41, 50, 4, 29, 2, 18, 15, 37, 32, 33, 36, 34, 30, 31, 42, 17, 1, 31, 31, 26, 14, 9, 14, 37, 13, 41, 37, 42, 2, 3, 32, 13, 28, 50, 30, 39, 39, 20, 36, 6, 44, 24, 3, 46, 39, 10, 32, 50, 24, 16, 4, 21, 26, 44, 32, 5, 41, 12, 46, 24, 37, 50, 17, 18, 46, 19, 30, 35, 6, 38, 25, 26, 23, 1, 39, 39, 19, 33, 23, 3, 6, 41, 50, 20, 11, 42, 4, 8, 15, 15, 12, 34, 29, 41, 45, 35, 10, 48, 43, 28, 5, 43, 1, 30, 5, 28, 29, 36, 10, 25, 16, 15, 38, 36, 29, 14, 23, 42, 11, 37, 46, 37, 10, 24, 33, 27, 32, 31, 43, 24, 11, 48, 17, 24, 14, 48, 38, 17, 40, 11, 12, 24, 46, 19, 25, 21, 43, 10, 15, 37, 6, 17, 23, 18, 26, 19, 28, 24, 12, 6, 10, 46, 29, 39, 42, 4, 11, 43, 31, 43, 5, 1, 6, 3, 18, 37, 3, 6, 26, 40, 8, 39, 49, 49, 38, 42, 42, 27, 9, 11, 18, 15, 20, 5, 15, 50, 19, 21, 28, 47, 4, 30, 22, 16, 46, 30, 12, 12, 40, 44, 16, 1, 3, 50, 9, 4, 14, 27, 31, 31, 37, 47, 28, 30, 23, 44, 20, 46, 39, 17, 45, 40, 13, 47, 39, 25, 14, 1, 38, 2, 25, 35, 37, 14, 33, 40, 13, 17, 48, 13, 25, 37, 17, 19, 10, 43, 10, 13, 10, 22, 50, 15, 23, 5, 41, 11, 46, 26, 47, 13, 26, 13, 2, 8, 14, 41, 48, 5, 20, 7, 9, 3, 31, 45, 25, 38, 47, 6, 33, 10, 26, 36, 13, 33, 4, 3, 24, 23, 5, 29, 6, 46, 17, 2, 39, 17, 15, 13, 42, 26, 18, 6, 9, 42, 21, 36, 3, 38, 31, 5, 2, 7, 27, 22, 48, 21, 46, 8, 34, 37, 19, 41, 43, 16, 20, 16, 13, 15, 25, 12, 22, 39, 19, 49, 5, 5, 47, 8, 32, 44, 28, 44, 29, 33, 42, 12, 13, 8, 23, 21, 9, 38, 45, 14, 38, 5, 32, 17, 18, 17, 20, 40, 42, 28, 19, 6, 12, 3, 35, 39, 39, 15, 49, 1, 9, 22, 38, 36, 42, 18, 29, 24, 38, 43, 12, 36, 43, 42, 47, 2, 35, 16, 11, 18, 35, 5, 7, 36, 4, 42, 40, 12, 23, 39, 4, 34, 34, 6, 40, 48, 13, 30, 35, 44, 22, 48, 2, 31, 34, 14, 47, 9, 22, 44, 49, 24, 3, 38, 3, 39, 22, 20, 41, 1, 50, 29, 26, 41, 7, 26, 42, 28, 22, 3, 35, 19, 1, 32, 29, 7, 27, 34, 35, 1, 13, 11, 49, 18, 23, 36, 48, 49, 43, 9, 47, 21, 5, 6, 21, 15, 38, 39, 44, 17, 28, 30, 36, 30, 8, 41, 46, 5, 50, 49, 27, 45, 48, 32, 34, 34, 45, 37, 49, 37, 16, 24, 44, 9, 8, 27, 24, 38, 28, 14, 25, 18, 6, 37, 33, 3, 12, 36, 16, 24, 17, 4, 41, 48, 37, 11, 38, 45, 26, 35, 10, 36, 33, 5, 37, 25, 13, 32, 50, 19, 22, 5, 34, 18, 44, 6, 38, 12, 26, 34, 13, 11, 47, 38, 31, 31, 33, 31, 2, 31, 44, 9, 48, 4, 14, 32, 12, 40, 8, 27, 20, 25, 36, 10, 37, 11, 26, 24, 40, 4, 32, 25, 23, 49, 43, 38, 39, 9, 5 }, new double[] { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }, new int[] { 1 }, new int[] { 50 }); } public Graph getDirectedN8() { return constructDirectedGraph( new int[] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4 }, new int[] { 1, 2, 2, 3, 1, 4, 2, 5, 3, 5 }, new double[] { 16, 13, 10, 12, 4, 14, 9, 20, 7, 4 }, new int[] { 0 }, new int[] { 5 }); } public Graph getDirectedN9() { return constructDirectedGraph( new int[] { 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5, 5, 6, 6, 7, 7 }, new int[] { 1, 2, 3, 5, 6, 2, 6, 4, 3, 2, 4, 7, 7, 6, 8, 4, 8, 6, 8 }, new double[] { 12, 8, 11, 7, 6, 5, 9, 4, 2, 13, 6, 12, 7, 4, 3, 15, 9, 8, 10 }, new int[] { 0 }, new int[] { 8 }); } public Graph getDirectedN10() { return constructDirectedGraph( new int[] { 82, 75, 26, 4, 43, 89, 54, 42, 48, 26, 42, 66, 76, 80, 93, 88, 13, 17, 37, 83, 90, 54, 81, 29, 8, 29, 4, 90, 6, 66, 32, 49, 29, 49, 10, 74, 46, 35, 6, 43, 20, 63, 77, 10, 29, 22, 15, 39, 91, 76, 70, 38, 72, 45, 81, 6, 66, 11, 97, 27, 72, 17, 70, 76, 31, 94, 1, 41, 17, 59, 8, 26, 97, 76, 92, 5, 67, 83, 82, 64, 92, 15, 42, 72, 100, 74, 31, 26, 95, 58, 72, 73, 20, 3, 82, 4, 71, 70, 42, 35, 12, 4, 95, 76, 33, 100, 3, 37, 57, 42, 43, 45, 94, 11, 4, 32, 19, 69, 1, 34, 25, 92, 24, 81, 82, 94, 12, 44, 35, 30, 76, 37, 34, 7, 91, 90, 5, 63, 95, 71, 88, 76, 33, 29, 34, 35, 43, 15, 5, 33, 92, 14, 93, 50, 47, 82, 4, 35, 25, 54, 3, 81, 38, 60, 72, 70, 99, 71, 49, 83, 85, 94, 15, 68, 40, 1, 59, 20, 34, 100, 6, 40, 25, 6, 88, 21, 19, 88, 6, 66, 18, 91, 56, 55, 63, 44, 75, 5, 6, 77, 50, 39, 74, 24, 82, 92, 38, 58, 66, 18, 48, 85, 75, 75, 84, 22, 53, 91, 46, 94, 43, 48, 43, 29, 99, 52, 96, 26, 47, 30, 75, 36, 57, 25, 83, 63, 27, 99, 3, 31, 95, 83, 31, 23, 7, 37, 43, 81, 74, 57, 67, 72, 93, 98, 69, 81, 10, 93, 70, 75, 60, 60, 4, 30, 85, 73, 91, 39, 14, 98, 2, 72, 51, 12, 54, 90, 87, 27, 16, 28, 29, 93, 36, 91, 73, 27, 98, 91, 100, 16, 86, 47, 51, 70, 80, 28, 71, 78, 82, 31, 37, 94, 76, 63, 90, 39, 24, 11, 27, 95, 66, 35, 25, 69, 63, 70, 61, 72, 16, 47, 28, 63, 71, 95, 49, 64, 6, 13, 70, 3, 10, 26, 96, 83, 85, 34, 38, 35, 36, 56, 89, 54, 53, 61, 79, 11, 61, 73, 78, 73, 86, 44, 58, 36, 66, 94, 72, 69, 62, 63, 2, 69, 40, 75, 93, 58, 20, 58, 24, 86, 5, 72, 30, 26, 66, 37, 90, 86, 20, 36, 87, 27, 60, 28, 10, 3, 44, 67, 87, 12, 84, 15, 45, 83, 39, 87, 31, 38, 46, 82, 91, 98, 65, 39, 47, 17, 27, 59, 66, 32, 49, 30, 54, 85, 83, 34, 25, 62, 14, 58, 11, 48, 24, 23, 85, 85, 82, 25, 53, 13, 80, 3, 83, 48, 7, 10, 65, 66, 30, 87, 53, 46, 28, 7, 98, 77, 16, 9, 87, 13, 64, 46, 63, 19, 99, 4, 52, 28, 40, 71, 86, 51, 12, 64, 37, 21, 1, 13, 52, 53, 100, 6, 59, 28, 93, 67, 43, 38, 47, 45, 16, 65, 38, 13, 12, 41, 2, 94, 40, 91, 35, 96, 14, 99, 18, 93, 80, 62, 70, 51, 24, 8, 72, 57, 11, 94, 59, 45, 45, 64, 36, 35, 100, 13, 78, 56, 97, 46, 42, 32, 53, 25, 55, 100, 33, 63, 71, 16, 62, 10, 87, 53, 52, 67, 82, 23, 95, 53, 55, 61, 48, 63, 96, 65, 82, 69, 6, 38, 55, 33, 22, 37, 93, 18, 40, 84, 71, 51, 72, 18, 41, 24, 6, 47, 78, 46, 63, 45, 75, 74, 21, 52, 58, 30, 16, 93, 93, 43, 63, 37, 98, 6, 31, 96, 87, 64, 71, 14, 32, 100, 77, 29, 93, 52, 62, 99, 21, 55, 86, 92, 49, 23, 83, 75, 42, 88, 23, 46, 31, 33, 1, 54, 71, 45, 61, 89, 10, 52, 21, 59, 36, 90, 41, 13, 86, 51, 70, 78, 35, 48, 22, 43, 7, 3, 99, 13, 86, 36, 100, 34, 75, 83, 23, 65, 30, 5, 57, 53, 98, 23, 97, 58, 49, 56, 91, 48, 42, 76, 33, 47, 78, 94, 55, 74, 64, 31, 18, 14, 49, 94, 47, 27, 38, 87, 69, 39, 27, 86, 90, 12, 36, 68, 13, 2, 72, 27, 23, 79, 71, 46, 69, 28, 73, 65, 35, 19, 51, 30, 91, 40, 38, 36, 2, 2, 28, 17, 17, 79, 18, 67, 2, 22, 71, 20, 82, 59, 69, 55, 66, 97, 9, 37, 82, 75, 33, 74, 10, 4, 60, 71, 14, 41, 32, 30, 60, 36, 12, 64, 37, 68, 27, 22, 80, 78, 83, 20, 93, 42, 12, 94, 94, 16, 55, 93, 32, 86, 66, 92, 41, 92, 95, 51, 48, 16, 91, 68, 81, 9, 96, 7, 70, 45, 35, 61, 6, 16, 98, 74, 43, 4, 14, 92, 24, 75, 15, 29, 81, 94, 97, 84, 19, 38, 29, 59, 87, 16, 56, 26, 76, 71, 30, 28, 71, 82, 3, 46, 81, 59, 82, 39, 51, 94, 6, 5, 77, 81, 18, 10, 20, 83, 90, 74, 31, 58, 88, 75, 98, 91, 80, 28, 77, 19, 12, 94, 24, 61, 78, 13, 91, 27, 98, 92, 5, 97, 63, 26, 12, 9, 19, 79, 61, 39, 43, 62, 62, 53, 83, 19, 71, 100, 18, 44, 37, 42, 34, 88, 39, 83, 11, 22, 32, 94, 81, 19, 75, 75, 66, 33, 76, 20, 54, 54, 27, 27, 94, 67, 44, 72, 51, 19, 54, 91, 76, 29, 64, 63, 60, 84, 77, 87, 22, 96, 9, 43, 36, 93, 15, 2, 90, 91, 95, 18, 39, 77, 60, 7, 90, 68, 71, 100, 69, 16, 35, 88, 4, 76, 79, 26, 45, 30, 26, 36, 30, 47, 44, 15, 29, 82, 41, 38, 70, 36, 70, 13, 87, 17, 15, 53, 24, 49, 89, 61, 23, 91, 46, 66, 46, 10, 37, 11, 79, 82, 87, 9, 25, 12, 91, 8, 34, 8, 72, 74, 52, 92, 71, 76, 33, 88, 68, 95, 83, 94, 28, 57, 18, 21, 68, 79, 20, 55, 48, 14, 19, 38, 94, 27, 8, 80, 2, 50, 99 }, new int[] { 18, 94, 91, 37, 92, 18, 98, 61, 84, 15, 72, 22, 100, 11, 37, 69, 2, 43, 92, 15, 65, 40, 79, 57, 79, 7, 100, 7, 33, 73, 64, 85, 11, 77, 74, 77, 10, 80, 36, 9, 78, 74, 64, 53, 77, 89, 60, 95, 56, 37, 86, 78, 82, 25, 12, 56, 50, 65, 52, 64, 3, 90, 74, 55, 35, 96, 35, 92, 6, 69, 42, 50, 2, 16, 10, 6, 58, 34, 41, 51, 81, 84, 32, 95, 21, 4, 53, 85, 2, 51, 7, 27, 94, 35, 87, 72, 92, 30, 23, 19, 87, 23, 66, 6, 67, 7, 75, 87, 53, 64, 70, 38, 97, 73, 84, 57, 53, 14, 29, 82, 24, 50, 10, 32, 25, 62, 82, 93, 32, 6, 81, 83, 38, 82, 35, 100, 3, 43, 41, 1, 86, 73, 40, 56, 16, 4, 41, 48, 36, 3, 77, 98, 100, 14, 67, 7, 93, 92, 16, 100, 40, 68, 61, 20, 78, 44, 27, 33, 23, 8, 27, 79, 88, 10, 64, 50, 76, 9, 90, 13, 5, 5, 98, 94, 79, 28, 59, 11, 62, 89, 20, 94, 15, 72, 69, 90, 66, 79, 37, 83, 79, 75, 18, 95, 13, 53, 10, 100, 95, 63, 6, 12, 35, 18, 72, 71, 73, 51, 93, 98, 58, 52, 85, 3, 82, 9, 32, 89, 16, 21, 5, 10, 75, 92, 92, 85, 73, 5, 1, 11, 49, 79, 16, 6, 93, 93, 60, 55, 7, 51, 23, 42, 96, 58, 64, 95, 60, 10, 100, 87, 21, 59, 17, 56, 61, 38, 93, 78, 39, 34, 32, 4, 74, 30, 93, 1, 56, 97, 26, 57, 39, 57, 22, 38, 89, 57, 36, 44, 63, 14, 18, 76, 80, 80, 48, 3, 30, 64, 91, 92, 35, 71, 53, 76, 43, 70, 17, 5, 32, 45, 78, 76, 93, 98, 49, 61, 86, 2, 76, 96, 29, 99, 14, 98, 26, 66, 100, 56, 40, 46, 50, 55, 60, 56, 74, 86, 54, 6, 49, 53, 1, 18, 15, 10, 66, 62, 13, 15, 17, 4, 29, 49, 1, 21, 48, 21, 6, 86, 81, 94, 38, 88, 56, 55, 30, 29, 95, 23, 70, 69, 22, 76, 90, 23, 68, 4, 25, 44, 18, 34, 40, 71, 98, 71, 89, 61, 40, 83, 93, 1, 13, 85, 28, 89, 28, 10, 49, 52, 49, 34, 27, 28, 63, 64, 80, 51, 19, 61, 71, 100, 66, 77, 59, 72, 65, 7, 8, 46, 19, 78, 23, 53, 40, 42, 10, 86, 22, 55, 71, 89, 100, 31, 88, 31, 89, 90, 13, 6, 79, 39, 2, 82, 53, 97, 12, 27, 88, 10, 63, 51, 99, 76, 24, 65, 16, 19, 85, 54, 20, 45, 55, 41, 67, 89, 11, 73, 26, 83, 42, 38, 1, 3, 95, 33, 24, 95, 27, 94, 75, 68, 77, 86, 47, 25, 29, 96, 39, 16, 36, 37, 47, 61, 45, 80, 79, 27, 67, 28, 32, 11, 31, 39, 48, 19, 16, 56, 87, 19, 17, 87, 82, 73, 12, 26, 2, 7, 55, 87, 35, 81, 78, 40, 67, 38, 93, 57, 42, 61, 36, 67, 86, 50, 23, 52, 26, 88, 10, 79, 20, 55, 43, 75, 92, 72, 32, 19, 96, 56, 13, 100, 85, 29, 72, 3, 54, 100, 41, 28, 98, 78, 14, 92, 63, 86, 82, 69, 73, 23, 43, 98, 23, 58, 70, 36, 92, 70, 35, 18, 34, 46, 78, 25, 8, 12, 88, 10, 79, 3, 11, 42, 35, 93, 13, 83, 26, 62, 8, 26, 47, 85, 90, 55, 50, 92, 94, 18, 21, 9, 96, 50, 40, 34, 31, 43, 80, 43, 80, 59, 35, 35, 93, 35, 62, 93, 80, 35, 94, 41, 61, 32, 21, 55, 15, 52, 39, 49, 75, 20, 72, 68, 84, 13, 46, 25, 75, 49, 85, 51, 68, 45, 26, 97, 1, 90, 85, 51, 1, 63, 53, 74, 89, 81, 35, 22, 37, 64, 55, 61, 86, 84, 83, 28, 96, 77, 96, 10, 7, 6, 21, 49, 88, 15, 59, 12, 61, 31, 37, 42, 78, 50, 66, 27, 62, 11, 96, 10, 12, 50, 20, 82, 57, 92, 14, 98, 99, 37, 42, 83, 22, 32, 44, 80, 98, 21, 17, 13, 38, 79, 63, 86, 63, 31, 23, 100, 85, 49, 63, 42, 38, 8, 41, 79, 54, 84, 65, 53, 65, 2, 48, 79, 42, 60, 37, 72, 11, 43, 99, 69, 42, 32, 23, 55, 34, 31, 94, 87, 70, 6, 23, 30, 81, 29, 50, 89, 12, 48, 80, 8, 77, 61, 59, 37, 14, 72, 54, 18, 37, 37, 16, 16, 76, 28, 11, 98, 52, 69, 67, 55, 12, 77, 91, 30, 43, 15, 30, 36, 63, 38, 92, 74, 11, 2, 52, 83, 77, 92, 30, 57, 86, 38, 21, 67, 93, 64, 2, 7, 6, 7, 4, 6, 51, 68, 65, 81, 76, 50, 80, 60, 93, 43, 68, 39, 14, 14, 48, 25, 56, 69, 99, 55, 30, 84, 74, 56, 61, 83, 83, 49, 57, 3, 50, 81, 12, 76, 99, 42, 28, 2, 53, 36, 76, 48, 66, 13, 43, 80, 84, 5, 30, 30, 26, 49, 77, 33, 69, 24, 83, 34, 33, 75, 11, 6, 33, 70, 74, 38, 59, 60, 2, 93, 38, 88, 88, 32, 63, 100, 68, 62, 23, 72, 17, 93, 32, 29, 47, 8, 79, 9, 40, 31, 61, 43, 42, 72, 99, 6, 47, 22, 5, 15, 29, 40, 25, 33, 14, 36, 47, 93, 62, 49, 75, 44, 81, 18, 63, 6, 36, 55, 87, 82, 96, 62, 18, 77, 95, 96, 31, 64, 61, 47, 34, 95, 53, 52, 79, 12, 89, 85, 38, 26, 73, 58, 17, 26, 74, 8, 58, 36, 76, 43, 36, 3, 79, 83, 82, 50, 17, 68, 31, 62, 69, 66, 20, 63, 38, 64, 8, 75, 26, 16, 27, 52, 7, 13, 10, 26, 67, 43, 70, 77, 1 }, new double[] { 20, 58, 39, 57, 25, 13, 51, 21, 29, 14, 37, 37, 33, 22, 55, 54, 44, 35, 51, 30, 53, 48, 34, 57, 12, 20, 13, 42, 52, 33, 55, 55, 47, 47, 12, 22, 35, 13, 48, 45, 20, 35, 15, 40, 58, 51, 34, 46, 22, 22, 29, 37, 26, 56, 17, 26, 18, 47, 53, 57, 47, 48, 15, 18, 18, 27, 11, 28, 18, 28, 48, 32, 17, 11, 36, 40, 57, 22, 45, 30, 27, 33, 27, 29, 15, 12, 49, 53, 11, 51, 33, 40, 28, 24, 26, 33, 17, 32, 15, 31, 46, 25, 29, 13, 27, 27, 41, 57, 48, 23, 40, 35, 58, 31, 57, 17, 46, 51, 50, 11, 31, 44, 40, 25, 46, 14, 44, 22, 52, 10, 38, 13, 59, 55, 51, 20, 35, 22, 54, 19, 27, 21, 31, 33, 37, 38, 26, 31, 45, 51, 46, 53, 23, 17, 18, 38, 26, 18, 50, 35, 15, 55, 18, 39, 25, 35, 43, 50, 51, 16, 58, 42, 38, 51, 27, 39, 49, 51, 12, 41, 37, 41, 38, 11, 44, 23, 49, 22, 20, 33, 32, 25, 23, 51, 13, 12, 40, 44, 44, 38, 25, 42, 29, 58, 48, 50, 20, 26, 33, 22, 14, 54, 22, 38, 49, 32, 19, 34, 35, 50, 53, 20, 27, 10, 42, 32, 30, 53, 24, 12, 10, 36, 12, 55, 15, 23, 53, 58, 37, 30, 56, 12, 45, 36, 29, 53, 32, 17, 10, 45, 54, 26, 54, 58, 29, 48, 38, 45, 56, 36, 57, 34, 34, 49, 53, 13, 33, 52, 46, 34, 15, 54, 55, 56, 31, 17, 44, 47, 37, 44, 39, 42, 10, 18, 15, 12, 21, 40, 46, 48, 29, 11, 36, 52, 45, 24, 27, 26, 33, 22, 25, 22, 18, 51, 11, 46, 22, 13, 13, 28, 30, 48, 59, 28, 21, 20, 39, 17, 46, 25, 27, 35, 55, 46, 46, 34, 31, 45, 20, 11, 50, 34, 37, 30, 23, 37, 27, 36, 12, 44, 28, 39, 33, 28, 31, 51, 40, 46, 14, 53, 32, 16, 58, 27, 17, 44, 59, 22, 22, 37, 53, 48, 22, 19, 30, 36, 40, 53, 14, 29, 58, 57, 42, 36, 58, 59, 16, 36, 24, 26, 30, 33, 56, 38, 31, 15, 21, 22, 16, 58, 21, 25, 46, 22, 39, 47, 48, 21, 50, 10, 13, 32, 57, 49, 37, 15, 27, 37, 59, 38, 28, 59, 59, 50, 32, 31, 37, 21, 11, 41, 11, 43, 21, 37, 32, 51, 14, 18, 13, 49, 38, 16, 17, 35, 33, 45, 12, 16, 13, 30, 42, 41, 39, 20, 49, 26, 14, 25, 59, 34, 40, 27, 52, 23, 22, 32, 49, 39, 32, 56, 17, 20, 38, 44, 55, 39, 30, 27, 50, 41, 34, 56, 17, 17, 12, 43, 24, 17, 17, 35, 18, 46, 23, 30, 24, 11, 16, 27, 36, 40, 17, 11, 40, 35, 14, 39, 54, 32, 12, 15, 49, 29, 13, 30, 31, 35, 29, 40, 24, 24, 48, 56, 43, 47, 52, 16, 29, 20, 30, 17, 16, 35, 33, 37, 38, 49, 27, 11, 15, 49, 54, 53, 56, 18, 43, 14, 17, 55, 41, 47, 41, 21, 14, 41, 39, 50, 35, 20, 24, 46, 55, 14, 28, 50, 57, 32, 53, 14, 54, 55, 34, 24, 25, 48, 48, 55, 16, 54, 22, 32, 18, 19, 15, 11, 27, 47, 20, 27, 43, 10, 43, 26, 44, 43, 41, 20, 29, 13, 35, 16, 25, 28, 22, 40, 37, 53, 13, 29, 51, 25, 18, 24, 18, 38, 38, 31, 53, 16, 20, 37, 43, 16, 10, 56, 23, 35, 22, 46, 14, 46, 46, 11, 49, 43, 13, 30, 25, 19, 41, 27, 46, 46, 28, 29, 56, 26, 18, 23, 47, 59, 23, 45, 39, 33, 43, 12, 42, 28, 32, 43, 16, 25, 30, 10, 45, 10, 42, 44, 29, 28, 12, 40, 33, 16, 52, 41, 48, 28, 45, 26, 18, 55, 11, 39, 21, 27, 44, 41, 30, 27, 36, 41, 41, 25, 49, 26, 39, 10, 55, 34, 31, 54, 26, 58, 38, 13, 12, 55, 57, 30, 27, 42, 17, 18, 19, 17, 14, 16, 35, 26, 36, 21, 25, 25, 46, 39, 46, 19, 43, 18, 13, 37, 44, 47, 55, 14, 55, 10, 19, 40, 18, 16, 17, 23, 24, 49, 46, 26, 20, 56, 19, 37, 21, 50, 14, 23, 16, 16, 21, 31, 16, 36, 23, 53, 16, 54, 35, 47, 36, 26, 45, 56, 48, 54, 13, 38, 17, 53, 37, 36, 31, 58, 50, 11, 13, 23, 58, 31, 15, 42, 43, 41, 37, 47, 26, 30, 28, 51, 57, 43, 24, 19, 42, 41, 56, 21, 56, 56, 43, 40, 16, 55, 22, 42, 21, 57, 28, 17, 17, 49, 53, 32, 12, 23, 32, 27, 27, 52, 24, 26, 56, 31, 16, 12, 17, 51, 27, 11, 50, 51, 19, 41, 38, 21, 59, 48, 14, 38, 19, 42, 16, 41, 39, 29, 55, 21, 14, 59, 35, 19, 56, 18, 32, 43, 15, 46, 31, 50, 52, 12, 16, 36, 29, 55, 45, 52, 18, 13, 17, 45, 50, 49, 51, 54, 59, 59, 40, 33, 40, 32, 33, 41, 33, 39, 26, 10, 59, 28, 59, 13, 40, 49, 32, 14, 11, 16, 46, 10, 35, 57, 47, 35, 32, 21, 29, 30, 20, 15, 53, 48, 24, 27, 22, 12, 32, 39, 32, 48, 48, 18, 18, 44, 26, 18, 56, 56, 42, 39, 41, 32, 18, 57, 48, 50, 44, 31, 48, 58, 32, 12, 12, 58, 10, 31, 33, 17, 53, 23, 56, 35, 40, 19, 32, 35, 59, 52, 24, 47, 45, 37, 15, 26, 25, 39, 49, 37, 10, 43, 37, 23, 45, 51, 34, 12, 25, 56, 27, 54, 52, 15, 11, 34, 46, 53, 55, 52, 31, 21, 50, 14, 12, 14, 46, 14, 21, 53, 37, 57, 23, 36, 2147483647 }, new int[] { 1 }, new int[] { 99 }); } public Graph getDirectedN11() { return constructDirectedGraph( new int[] { 99, 38, 98, 90, 54, 21, 86, 38, 65, 95, 83, 61, 54, 26, 55, 78, 3, 18, 65, 24, 86, 31, 16, 48, 57, 35, 20, 48, 62, 57, 80, 64, 63, 16, 24, 91, 60, 31, 16, 79, 6, 66, 98, 85, 24, 33, 63, 84, 81, 79, 33, 16, 11, 39, 42, 42, 48, 95, 36, 58, 100, 71, 6, 60, 32, 59, 1, 75, 51, 72, 67, 68, 25, 15, 41, 27, 95, 9, 30, 99, 20, 35, 33, 90, 62, 23, 27, 51, 41, 80, 26, 14, 10, 26, 6, 87, 32, 95, 7, 78, 19, 39, 81, 36, 36, 80, 86, 92, 57, 66, 55, 30, 38, 8, 63, 35, 42, 88, 29, 42, 33, 18, 69, 17, 45, 64, 9, 9, 61, 45, 43, 62, 69, 31, 82, 87, 25, 8, 56, 68, 6, 84, 73, 94, 63, 63, 98, 86, 22, 77, 95, 66, 36, 42, 48, 14, 5, 23, 2, 79, 47, 62, 31, 47, 59, 1, 78, 43, 47, 100, 43, 67, 68, 45, 36, 37, 81, 2, 22, 22, 45, 27, 60, 14, 93, 23, 87, 19, 11, 11, 36, 20, 63, 55, 85, 43, 70, 21, 11, 38, 26, 50, 31, 3, 1, 68, 5, 29, 1, 58, 82, 62, 48, 53, 59, 99, 15, 42, 90, 52, 31, 77, 7, 27, 75, 91, 96, 81, 15, 36, 61, 72, 4, 53, 45, 49, 88, 91, 34, 67, 98, 40, 86, 24, 14, 52, 11, 69, 86, 27, 98, 2, 13, 4, 35, 84, 73, 14, 68, 8, 24, 72, 64, 51, 75, 95, 32, 35, 39, 42, 47, 87, 100, 41, 15, 13, 15, 76, 52, 84, 80, 4, 56, 11, 98, 43, 5, 91, 85, 34, 72, 32, 75, 23, 28, 66, 100, 10, 82, 30, 58, 54, 85, 46, 90, 80, 78, 7, 1, 39, 61, 26, 64, 81, 97, 20, 48, 40, 51, 87, 70, 14, 99, 43, 47, 79, 41, 42, 33, 75, 71, 32, 91, 51, 79, 23, 91, 21, 44, 9, 75, 33, 33, 46, 68, 60, 83, 24, 63, 73, 16, 17, 7, 53, 65, 61, 26, 34, 73, 63, 71, 58, 40, 15, 13, 27, 72, 31, 39, 1, 47, 84, 93, 24, 62, 93, 89, 9, 47, 8, 71, 84, 20, 15, 83, 76, 57, 43, 6, 85, 6, 16, 56, 29, 53, 43, 7, 55, 72, 33, 58, 75, 36, 87, 83, 76, 6, 94, 94, 91, 99, 1, 82, 51, 82, 20, 88, 9, 94, 45, 77, 56, 85, 5, 41, 26, 93, 17, 65, 55, 46, 61, 52, 40, 82, 4, 56, 69, 15, 13, 11, 50, 51, 47, 2, 38, 64, 71, 30, 18, 70, 63, 22, 39, 12, 68, 4, 43, 21, 60, 58, 93, 69, 25, 49, 72, 98, 84, 90, 74, 23, 83, 65, 94, 16, 7, 63, 43, 17, 29, 94, 91, 17, 40, 9, 26, 83, 66, 73, 99, 18, 85, 21, 86, 60, 75, 94, 87, 72, 12, 44, 46, 22, 31, 51, 92, 8, 94, 19, 16, 30, 3, 89, 69, 34, 25, 13, 59, 48, 95, 60, 56, 21, 60, 23, 85, 86, 61, 83, 83, 88, 47, 65, 72, 59, 52, 79, 9, 52, 73, 94, 40, 62, 26, 83, 88, 39, 37, 76, 70, 15, 31, 75, 82, 44, 49, 30, 39, 88, 45, 29, 90, 1, 37, 78, 84, 56, 78, 80, 58, 80, 6, 39, 92, 3, 12, 1, 58, 94, 60, 96, 52, 88, 84, 37, 47, 66, 75, 62, 65, 65, 29, 28, 12, 26, 57, 74, 95, 32, 21, 11, 83, 9, 89, 43, 37, 87, 52, 5, 96, 14, 97, 16, 41, 89, 35, 65, 99, 97, 33, 55, 63, 66, 85, 19, 2, 23, 41, 21, 69, 60, 19, 82, 53, 9, 99, 96, 77, 57, 100, 41, 95, 28, 20, 92, 83, 8, 38, 57, 97, 74, 86, 74, 48, 75, 6, 2, 98, 58, 60, 50, 15, 10, 77, 14, 9, 1, 62, 40, 27, 48, 49, 39, 10, 91, 44, 29, 52, 73, 87, 19, 34, 76, 29, 20, 39, 87, 96, 84, 1, 72, 51, 94, 48, 21, 27, 22, 50, 77, 7, 76, 79, 11, 43, 80, 50, 17, 81, 55, 84, 99, 2, 59, 12, 41, 22, 59, 11, 17, 37, 71, 94, 41, 58, 21, 26, 16, 86, 96, 92, 90, 27, 25, 46, 33, 86, 97, 12, 9, 85, 1, 52, 7, 98, 76, 40, 17, 40, 15, 100, 29, 47, 45, 13, 75, 2, 33, 14, 80, 39, 18, 45, 20, 75, 30, 62, 37, 33, 47, 54, 7, 1, 5, 57, 94, 66, 46, 74, 19, 23, 45, 96, 24, 10, 99, 9, 3, 75, 54, 82, 18, 66, 68, 89, 93, 14, 39, 57, 91, 16, 32, 16, 29, 88, 59, 8, 12, 26, 92, 73, 6, 49, 48, 2, 69, 41, 59, 2, 73, 66, 100, 52, 26, 10, 53, 21, 9, 59, 72, 13, 96, 85, 22, 97, 36, 89, 54, 71, 17, 96, 29, 25, 89, 29, 26, 85, 68, 34, 5, 82, 40, 9, 85, 29, 23, 75, 67, 2, 75, 59, 9, 27, 95, 19, 76, 56, 57, 21, 18, 84, 66, 61, 63, 66, 63, 28, 13, 79, 55, 33, 99, 73, 54, 64, 45, 74, 88, 5, 78, 26, 94, 57, 99, 44, 83, 97, 95, 77, 51, 20, 5, 7, 37, 86, 27, 26, 97, 5, 22, 50, 14, 90, 38, 93, 60, 35, 54, 85, 59, 35, 38, 22, 35, 94, 44, 13, 35, 96, 14, 31, 1, 23, 83, 14, 48, 23, 80, 5, 22, 90, 56, 4, 66, 84, 2, 88, 90, 53, 64, 77, 95, 19, 40, 74, 26, 81, 37, 66, 57, 76, 66, 14, 84, 11, 94, 62, 30, 10, 46, 99, 25, 98, 84, 21, 68, 48, 75, 85, 98, 61, 64, 31, 40, 21, 39, 76, 100, 46, 87, 6, 17, 42, 70, 60, 51, 88, 38, 56, 96, 100, 62, 99, 92, 65, 71, 58, 39, 47, 14, 11, 22, 91, 5, 66, 19, 94, 95, 27, 5, 30, 71, 59, 12, 28, 92, 91, 100, 64, 81, 42, 81, 38, 20, 48, 57, 20, 11, 82, 50, 21, 93, 89, 61, 54, 71, 50, 67, 52, 7, 54, 82, 18, 64, 99, 8, 96, 77, 35, 39, 56, 80, 85, 64, 2, 57, 18, 31, 36, 39, 32, 64, 38, 87, 34, 52, 15, 38, 10, 64, 74, 79, 56, 52, 77, 14, 41, 69, 65, 82, 27, 7, 47, 16, 7, 60, 8, 62, 38, 78, 38, 99 }, new int[] { 84, 13, 58, 15, 4, 41, 68, 47, 16, 42, 8, 39, 39, 39, 28, 34, 84, 19, 85, 17, 25, 13, 69, 81, 74, 17, 79, 1, 64, 99, 35, 44, 93, 39, 65, 67, 26, 22, 74, 33, 75, 34, 41, 57, 58, 11, 46, 28, 39, 100, 41, 99, 53, 83, 48, 43, 39, 33, 73, 96, 65, 65, 98, 44, 6, 28, 35, 55, 21, 63, 49, 95, 90, 86, 66, 11, 96, 92, 21, 61, 29, 62, 52, 1, 75, 3, 22, 41, 7, 67, 28, 24, 5, 22, 30, 73, 65, 71, 93, 20, 70, 43, 47, 53, 54, 8, 43, 89, 90, 73, 48, 18, 21, 10, 40, 36, 50, 54, 96, 79, 60, 3, 81, 63, 5, 36, 27, 45, 28, 1, 100, 29, 60, 48, 24, 38, 44, 92, 7, 15, 7, 44, 2, 5, 66, 91, 27, 75, 57, 27, 55, 77, 94, 27, 78, 67, 23, 5, 44, 61, 62, 18, 23, 58, 38, 3, 56, 65, 49, 56, 45, 25, 81, 75, 89, 55, 13, 15, 5, 55, 78, 94, 87, 58, 24, 13, 27, 72, 73, 28, 51, 18, 73, 89, 21, 91, 72, 17, 96, 37, 57, 61, 29, 31, 26, 2, 91, 27, 60, 77, 94, 43, 59, 49, 66, 6, 16, 25, 63, 2, 4, 41, 89, 14, 28, 3, 27, 63, 52, 8, 33, 89, 14, 98, 88, 68, 58, 12, 81, 40, 75, 4, 99, 12, 25, 28, 71, 2, 53, 24, 16, 20, 53, 98, 68, 80, 59, 22, 16, 77, 96, 24, 45, 97, 83, 92, 63, 46, 23, 34, 59, 15, 46, 54, 89, 87, 10, 73, 25, 81, 44, 41, 43, 10, 55, 31, 64, 54, 67, 83, 82, 85, 25, 38, 96, 86, 43, 91, 57, 23, 70, 55, 40, 13, 61, 66, 48, 3, 21, 97, 27, 43, 77, 64, 40, 58, 9, 91, 30, 78, 16, 54, 33, 75, 43, 54, 74, 93, 4, 26, 47, 70, 59, 45, 74, 53, 88, 55, 9, 77, 19, 9, 66, 86, 91, 81, 70, 1, 67, 28, 80, 36, 79, 33, 78, 76, 5, 39, 71, 16, 46, 63, 33, 30, 76, 92, 99, 99, 86, 56, 93, 79, 32, 43, 76, 84, 90, 17, 8, 13, 18, 2, 26, 85, 61, 40, 86, 59, 68, 99, 3, 36, 20, 86, 68, 95, 59, 68, 94, 93, 91, 23, 92, 31, 12, 70, 67, 58, 39, 46, 83, 75, 11, 33, 4, 67, 17, 63, 40, 81, 79, 69, 91, 15, 6, 87, 17, 98, 34, 9, 64, 32, 91, 35, 84, 60, 27, 70, 29, 35, 89, 75, 49, 57, 59, 40, 100, 99, 81, 66, 2, 75, 48, 46, 27, 29, 80, 11, 32, 68, 40, 1, 66, 23, 19, 52, 9, 49, 34, 6, 93, 57, 9, 11, 28, 30, 8, 93, 94, 80, 61, 75, 69, 69, 19, 96, 10, 79, 89, 85, 12, 4, 96, 55, 28, 32, 10, 63, 87, 71, 42, 17, 6, 24, 23, 50, 64, 69, 22, 44, 15, 4, 20, 18, 70, 94, 55, 92, 26, 88, 21, 66, 29, 92, 68, 23, 6, 92, 64, 14, 90, 16, 50, 31, 96, 99, 82, 95, 80, 85, 25, 19, 57, 97, 54, 1, 63, 89, 83, 47, 92, 58, 16, 25, 28, 38, 32, 33, 51, 47, 9, 58, 46, 78, 49, 83, 71, 30, 51, 62, 76, 89, 10, 100, 97, 99, 15, 25, 52, 8, 77, 6, 50, 93, 53, 60, 9, 3, 95, 39, 93, 72, 57, 22, 1, 35, 61, 19, 20, 16, 16, 66, 2, 39, 64, 10, 52, 37, 75, 6, 34, 84, 98, 30, 86, 96, 83, 42, 86, 78, 45, 56, 32, 7, 17, 58, 27, 93, 7, 61, 41, 46, 97, 24, 74, 13, 72, 70, 7, 36, 55, 2, 48, 38, 86, 40, 56, 89, 66, 67, 15, 88, 16, 42, 56, 54, 46, 83, 69, 61, 58, 6, 36, 100, 60, 34, 82, 89, 60, 19, 30, 99, 60, 62, 21, 30, 49, 73, 97, 14, 10, 32, 15, 73, 88, 69, 68, 39, 76, 69, 66, 100, 98, 91, 40, 83, 35, 31, 57, 50, 22, 46, 94, 89, 97, 9, 87, 3, 36, 57, 26, 92, 48, 63, 64, 61, 95, 52, 30, 35, 30, 26, 94, 80, 26, 46, 48, 22, 33, 9, 86, 87, 58, 41, 7, 39, 5, 81, 75, 92, 53, 47, 52, 96, 91, 58, 95, 53, 41, 12, 4, 18, 8, 25, 52, 4, 29, 36, 20, 79, 71, 73, 16, 61, 73, 37, 50, 24, 24, 73, 46, 59, 79, 56, 18, 14, 44, 100, 98, 54, 12, 37, 36, 87, 57, 78, 34, 79, 21, 63, 40, 54, 39, 84, 3, 33, 19, 81, 85, 65, 51, 49, 25, 59, 32, 57, 65, 83, 8, 80, 31, 23, 89, 63, 26, 40, 62, 89, 75, 37, 25, 33, 79, 88, 70, 62, 54, 89, 7, 21, 56, 43, 51, 34, 12, 95, 69, 26, 57, 84, 20, 11, 76, 21, 41, 52, 18, 16, 54, 21, 49, 41, 88, 16, 56, 95, 27, 53, 73, 56, 14, 40, 16, 59, 6, 17, 15, 30, 6, 5, 67, 17, 98, 42, 11, 31, 58, 12, 41, 40, 89, 12, 94, 82, 20, 9, 39, 49, 41, 53, 12, 26, 55, 45, 50, 81, 38, 22, 86, 72, 45, 81, 80, 46, 12, 67, 69, 73, 71, 18, 42, 3, 82, 53, 34, 70, 99, 56, 87, 40, 42, 44, 60, 33, 99, 54, 55, 55, 32, 21, 29, 73, 73, 74, 33, 84, 77, 34, 99, 55, 54, 33, 56, 3, 28, 6, 40, 82, 47, 49, 7, 55, 47, 30, 75, 32, 42, 43, 79, 72, 45, 52, 34, 29, 41, 26, 57, 18, 100, 3, 35, 30, 27, 6, 53, 84, 22, 60, 99, 35, 9, 72, 48, 99, 22, 39, 86, 8, 26, 83, 49, 8, 30, 5, 81, 64, 51, 99, 29, 77, 78, 80, 19, 27, 25, 78, 3, 74, 83, 61, 19, 60, 18, 72, 1, 3, 62, 65, 85, 11, 83, 55, 41, 1, 62, 29, 71, 68, 50, 85, 85, 95, 71, 29, 83, 91, 64, 2, 16, 35, 44, 88, 50, 24, 8, 33, 90, 46, 1, 11, 7, 47, 16, 98, 61, 31, 86, 15, 88, 73, 59, 19, 43, 98, 10, 10, 82, 48, 49, 9, 51, 44, 51, 17, 87, 75, 1, 75, 19, 42, 16, 80, 56, 55, 15, 12, 33, 61, 67, 6, 64, 20, 58, 13, 11, 95, 25, 55, 67, 7, 1 }, new double[] { 20, 58, 39, 57, 25, 13, 51, 21, 29, 14, 37, 37, 33, 22, 55, 54, 44, 35, 51, 30, 53, 48, 34, 57, 12, 20, 13, 42, 52, 33, 55, 55, 47, 47, 12, 22, 35, 13, 48, 45, 20, 35, 15, 40, 58, 51, 34, 46, 22, 22, 29, 37, 26, 56, 17, 26, 18, 47, 53, 57, 47, 48, 15, 18, 18, 27, 11, 28, 18, 28, 48, 32, 17, 11, 36, 40, 57, 22, 45, 30, 27, 33, 27, 29, 15, 12, 49, 53, 11, 51, 33, 40, 28, 24, 26, 33, 17, 32, 15, 31, 46, 25, 29, 13, 27, 27, 41, 57, 48, 23, 40, 35, 58, 31, 57, 17, 46, 51, 50, 11, 31, 44, 40, 25, 46, 14, 44, 22, 52, 10, 38, 13, 59, 55, 51, 20, 35, 22, 54, 19, 27, 21, 31, 33, 37, 38, 26, 31, 45, 51, 46, 53, 23, 17, 18, 38, 26, 18, 50, 35, 15, 55, 18, 39, 25, 35, 43, 50, 51, 16, 58, 42, 38, 51, 27, 39, 49, 51, 12, 41, 37, 41, 38, 11, 44, 23, 49, 22, 20, 33, 32, 25, 23, 51, 13, 12, 40, 44, 44, 38, 25, 42, 29, 58, 48, 50, 20, 26, 33, 22, 14, 54, 22, 38, 49, 32, 19, 34, 35, 50, 53, 20, 27, 10, 42, 32, 30, 53, 24, 12, 10, 36, 12, 55, 15, 23, 53, 58, 37, 30, 56, 12, 45, 36, 29, 53, 32, 17, 10, 45, 54, 26, 54, 58, 29, 48, 38, 45, 56, 36, 57, 34, 34, 49, 53, 13, 33, 52, 46, 34, 15, 54, 55, 56, 31, 17, 44, 47, 37, 44, 39, 42, 10, 18, 15, 12, 21, 40, 46, 48, 29, 11, 36, 52, 45, 24, 27, 26, 33, 22, 25, 22, 18, 51, 11, 46, 22, 13, 13, 28, 30, 48, 59, 28, 21, 20, 39, 17, 46, 25, 27, 35, 55, 46, 46, 34, 31, 45, 20, 11, 50, 34, 37, 30, 23, 37, 27, 36, 12, 44, 28, 39, 33, 28, 31, 51, 40, 46, 14, 53, 32, 16, 58, 27, 17, 44, 59, 22, 22, 37, 53, 48, 22, 19, 30, 36, 40, 53, 14, 29, 58, 57, 42, 36, 58, 59, 16, 36, 24, 26, 30, 33, 56, 38, 31, 15, 21, 22, 16, 58, 21, 25, 46, 22, 39, 47, 48, 21, 50, 10, 13, 32, 57, 49, 37, 15, 27, 37, 59, 38, 28, 59, 59, 50, 32, 31, 37, 21, 11, 41, 11, 43, 21, 37, 32, 51, 14, 18, 13, 49, 38, 16, 17, 35, 33, 45, 12, 16, 13, 30, 42, 41, 39, 20, 49, 26, 14, 25, 59, 34, 40, 27, 52, 23, 22, 32, 49, 39, 32, 56, 17, 20, 38, 44, 55, 39, 30, 27, 50, 41, 34, 56, 17, 17, 12, 43, 24, 17, 17, 35, 18, 46, 23, 30, 24, 11, 16, 27, 36, 40, 17, 11, 40, 35, 14, 39, 54, 32, 12, 15, 49, 29, 13, 30, 31, 35, 29, 40, 24, 24, 48, 56, 43, 47, 52, 16, 29, 20, 30, 17, 16, 35, 33, 37, 38, 49, 27, 11, 15, 49, 54, 53, 56, 18, 43, 14, 17, 55, 41, 47, 41, 21, 14, 41, 39, 50, 35, 20, 24, 46, 55, 14, 28, 50, 57, 32, 53, 14, 54, 55, 34, 24, 25, 48, 48, 55, 16, 54, 22, 32, 18, 19, 15, 11, 27, 47, 20, 27, 43, 10, 43, 26, 44, 43, 41, 20, 29, 13, 35, 16, 25, 28, 22, 40, 37, 53, 13, 29, 51, 25, 18, 24, 18, 38, 38, 31, 53, 16, 20, 37, 43, 16, 10, 56, 23, 35, 22, 46, 14, 46, 46, 11, 49, 43, 13, 30, 25, 19, 41, 27, 46, 46, 28, 29, 56, 26, 18, 23, 47, 59, 23, 45, 39, 33, 43, 12, 42, 28, 32, 43, 16, 25, 30, 10, 45, 10, 42, 44, 29, 28, 12, 40, 33, 16, 52, 41, 48, 28, 45, 26, 18, 55, 11, 39, 21, 27, 44, 41, 30, 27, 36, 41, 41, 25, 49, 26, 39, 10, 55, 34, 31, 54, 26, 58, 38, 13, 12, 55, 57, 30, 27, 42, 17, 18, 19, 17, 14, 16, 35, 26, 36, 21, 25, 25, 46, 39, 46, 19, 43, 18, 13, 37, 44, 47, 55, 14, 55, 10, 19, 40, 18, 16, 17, 23, 24, 49, 46, 26, 20, 56, 19, 37, 21, 50, 14, 23, 16, 16, 21, 31, 16, 36, 23, 53, 16, 54, 35, 47, 36, 26, 45, 56, 48, 54, 13, 38, 17, 53, 37, 36, 31, 58, 50, 11, 13, 23, 58, 31, 15, 42, 43, 41, 37, 47, 26, 30, 28, 51, 57, 43, 24, 19, 42, 41, 56, 21, 56, 56, 43, 40, 16, 55, 22, 42, 21, 57, 28, 17, 17, 49, 53, 32, 12, 23, 32, 27, 27, 52, 24, 26, 56, 31, 16, 12, 17, 51, 27, 11, 50, 51, 19, 41, 38, 21, 59, 48, 14, 38, 19, 42, 16, 41, 39, 29, 55, 21, 14, 59, 35, 19, 56, 18, 32, 43, 15, 46, 31, 50, 52, 12, 16, 36, 29, 55, 45, 52, 18, 13, 17, 45, 50, 49, 51, 54, 59, 59, 40, 33, 40, 32, 33, 41, 33, 39, 26, 10, 59, 28, 59, 13, 40, 49, 32, 14, 11, 16, 46, 10, 35, 57, 47, 35, 32, 21, 29, 30, 20, 15, 53, 48, 24, 27, 22, 12, 32, 39, 32, 48, 48, 18, 18, 44, 26, 18, 56, 56, 42, 39, 41, 32, 18, 57, 48, 50, 44, 31, 48, 58, 32, 12, 12, 58, 10, 31, 33, 17, 53, 23, 56, 35, 40, 19, 32, 35, 59, 52, 24, 47, 45, 37, 15, 26, 25, 39, 49, 37, 10, 43, 37, 23, 45, 51, 34, 12, 25, 56, 27, 54, 52, 15, 11, 34, 46, 53, 55, 52, 31, 21, 50, 14, 12, 14, 46, 14, 21, 53, 37, 57, 23, 36, 19, 52, 42, 20, 36, 49, 58, 21, 39, 33, 50, 43, 31, 53, 41, 31, 20, 47, 40, 11, 36, 11, 40, 29, 24, 13, 44, 40, 34, 13, 49, 20, 21, 51, 29, 15, 40, 24, 35, 49, 39, 22, 37, 52, 25, 49, 38, 16, 57, 19, 53, 50, 32, 49, 38, 41, 18, 14, 46, 21, 59, 26, 13, 10, 44, 40, 53, 11, 22, 11, 40, 24, 19, 23, 36, 42, 41, 16, 12, 34, 52, 48, 32, 50, 18, 27, 46, 22, 21, 21, 50, 20, 37, 20, 43, 15, 43, 32, 27, 44, 2147483647 }, new int[] { 1 }, new int[] { 99 }); } public Graph getDirectedN12() { return constructDirectedGraph( new int[] { 11, 68, 97, 73, 17, 65, 76, 72, 25, 50, 26, 9, 54, 29, 93, 73, 4, 62, 84, 40, 78, 77, 59, 75, 82, 30, 15, 51, 9, 92, 20, 51, 95, 59, 24, 50, 74, 65, 50, 30, 27, 76, 88, 82, 39, 45, 87, 40, 71, 90, 50, 63, 16, 72, 79, 85, 76, 13, 64, 73, 82, 79, 99, 75, 97, 91, 2, 54, 53, 63, 15, 24, 92, 87, 89, 98, 65, 75, 36, 95, 28, 7, 64, 93, 32, 85, 34, 36, 50, 14, 77, 28, 37, 29, 86, 82, 40, 77, 64, 3, 9, 47, 44, 7, 69, 57, 1, 31, 12, 44, 12, 55, 81, 78, 13, 68, 76, 12, 76, 98, 78, 33, 25, 43, 72, 92, 64, 29, 4, 98, 16, 96, 70, 41, 19, 37, 15, 66, 52, 72, 4, 82, 4, 58, 60, 56, 5, 59, 49, 77, 21, 79, 38, 83, 38, 29, 78, 92, 9, 25, 65, 48, 1, 28, 33, 42, 8, 82, 8, 10, 45, 70, 47, 50, 82, 80, 92, 76, 70, 92, 29, 69, 59, 25, 74, 25, 41, 59, 6, 88, 21, 91, 98, 62, 10, 99, 67, 24, 83, 23, 92, 100, 94, 34, 8, 88, 82, 34, 45, 4, 34, 97, 78, 48, 67, 26, 32, 78, 92, 16, 97, 75, 13, 52, 55, 47, 52, 13, 10, 73, 11, 60, 75, 48, 11, 4, 46, 58, 54, 36, 96, 65, 89, 39, 13, 80, 3, 42, 91, 2, 32, 74, 33, 55, 82, 25, 84, 64, 67, 53, 37, 80, 15, 72, 4, 65, 14, 36, 50, 14, 36, 68, 35, 87, 95, 13, 96, 86, 16, 98, 10, 91, 2, 23, 5, 89, 37, 6, 11, 99, 48, 84, 99, 66, 11, 94, 6, 51, 91, 54, 86, 20, 48, 67, 87, 82, 64, 64, 32, 76, 70, 54, 74, 4, 21, 96, 14, 96, 87, 39, 82, 90, 43, 61, 87, 41, 15, 9, 27, 81, 58, 19, 43, 18, 2, 46, 85, 37, 15, 28, 34, 89, 73, 61, 41, 71, 2, 20, 70, 78, 77, 43, 51, 100, 31, 35, 26, 34, 5, 13, 20, 82, 3, 58, 80, 78, 51, 5, 4, 62, 65, 38, 42, 53, 34, 1, 98, 70, 21, 55, 52, 23, 13, 58, 76, 58, 66, 47, 54, 15, 3, 28, 91, 19, 25, 2, 65, 94, 26, 79, 91, 66, 43, 78, 73, 80, 68, 32, 3, 39, 19, 64, 24, 16, 3, 72, 54, 78, 62, 41, 45, 78, 42, 84, 95, 89, 58, 29, 47, 16, 14, 54, 65, 17, 46, 77, 81, 22, 37, 100, 26, 61, 71, 55, 10, 76, 48, 76, 71, 64, 42, 45, 37, 64, 5, 46, 98, 94, 88, 70, 34, 63, 67, 65, 46, 8, 89, 28, 55, 5, 83, 89, 66, 92, 61, 38, 75, 99, 33, 89, 53, 76, 26, 55, 22, 19, 22, 20, 18, 8, 82, 43, 76, 60, 97, 80, 33, 93, 33, 32, 35, 65, 67, 96, 100, 94, 86, 79, 29, 26, 75, 23, 100, 3, 83, 83, 26, 32, 79, 71, 1, 22, 18, 57, 46, 20, 58, 86, 12, 27, 11, 3, 31, 6, 97, 56, 70, 90, 18, 79, 88, 34, 2, 85, 26, 79, 36, 83, 81, 71, 94, 100, 17, 83, 28, 30, 37, 1, 8, 16, 87, 5, 30, 4, 49, 1, 41, 2, 70, 46, 17, 8, 81, 10, 42, 49, 22, 38, 93, 100, 100, 13, 13, 38, 93, 46, 62, 67, 14, 41, 17, 81, 79, 22, 37, 63, 42, 83, 23, 13, 86, 27, 43, 42, 89, 5, 39, 52, 4, 48, 15, 54, 29, 95, 75, 71, 92, 56, 35, 30, 93, 3, 45, 100, 76, 81, 61, 95, 9, 91, 29, 100, 40, 26, 81, 81, 94, 48, 66, 55, 54, 53, 54, 57, 32, 95, 98, 50, 100, 17, 98, 94, 56, 40, 57, 81, 89, 88, 29, 2, 90, 80, 86, 77, 61, 40, 88, 22, 2, 92, 81, 15, 78, 27, 54, 34, 52, 95, 88, 82, 76, 86, 53, 52, 14, 35, 23, 82, 6, 61, 73, 7, 64, 72, 56, 61, 16, 11, 33, 59, 99 }, new int[] { 10, 81, 41, 77, 26, 75, 75, 49, 74, 41, 85, 49, 59, 19, 98, 56, 41, 11, 4, 77, 66, 94, 91, 23, 28, 28, 93, 54, 37, 67, 53, 9, 91, 88, 89, 79, 83, 3, 90, 92, 47, 21, 92, 3, 82, 42, 29, 24, 40, 66, 52, 20, 29, 77, 20, 53, 60, 80, 52, 53, 50, 67, 2, 39, 42, 27, 50, 15, 7, 39, 53, 65, 27, 77, 16, 3, 8, 92, 8, 93, 19, 61, 97, 9, 62, 11, 13, 61, 23, 28, 96, 89, 89, 75, 11, 47, 27, 78, 51, 1, 18, 83, 4, 40, 65, 89, 69, 16, 84, 25, 59, 56, 67, 27, 23, 64, 38, 56, 3, 22, 29, 55, 77, 7, 6, 83, 45, 57, 11, 44, 53, 53, 95, 11, 89, 20, 66, 68, 58, 31, 43, 75, 84, 88, 11, 61, 57, 36, 1, 66, 37, 9, 74, 58, 83, 2, 14, 12, 88, 2, 46, 100, 14, 47, 90, 58, 49, 17, 11, 23, 80, 73, 52, 93, 74, 21, 99, 88, 90, 66, 59, 41, 29, 80, 69, 40, 21, 65, 89, 35, 53, 8, 35, 27, 57, 85, 80, 55, 40, 88, 89, 86, 82, 4, 51, 50, 64, 64, 22, 22, 91, 53, 91, 25, 99, 91, 28, 67, 63, 27, 51, 96, 16, 83, 77, 59, 91, 5, 21, 32, 23, 9, 90, 92, 97, 33, 43, 93, 65, 40, 3, 4, 91, 77, 19, 29, 29, 86, 40, 67, 93, 20, 44, 78, 33, 55, 91, 54, 97, 20, 88, 16, 97, 64, 51, 97, 24, 48, 74, 31, 13, 89, 26, 69, 63, 90, 52, 80, 46, 62, 70, 94, 91, 25, 53, 14, 24, 87, 9, 22, 55, 64, 38, 62, 36, 30, 1, 84, 59, 2, 16, 10, 86, 34, 46, 12, 87, 29, 75, 39, 58, 67, 56, 87, 41, 16, 86, 18, 48, 34, 79, 55, 38, 86, 19, 1, 95, 12, 15, 44, 44, 99, 77, 92, 77, 7, 45, 94, 100, 25, 68, 96, 92, 3, 65, 21, 14, 98, 1, 32, 91, 50, 32, 24, 49, 42, 55, 99, 73, 17, 30, 80, 80, 33, 11, 68, 64, 8, 9, 6, 44, 32, 14, 35, 18, 25, 36, 80, 80, 64, 15, 56, 40, 5, 49, 42, 39, 95, 53, 1, 17, 48, 38, 26, 30, 12, 94, 78, 16, 82, 69, 10, 45, 84, 20, 51, 38, 44, 36, 15, 87, 11, 70, 13, 73, 95, 37, 64, 71, 64, 88, 38, 40, 65, 67, 30, 57, 34, 32, 45, 91, 33, 19, 58, 3, 81, 25, 90, 98, 19, 10, 68, 100, 58, 53, 35, 83, 43, 6, 85, 32, 70, 93, 15, 74, 45, 63, 2, 46, 18, 83, 91, 91, 2, 69, 97, 42, 74, 83, 12, 64, 47, 46, 48, 77, 51, 69, 10, 91, 23, 81, 93, 75, 47, 15, 51, 91, 82, 7, 75, 71, 84, 30, 15, 76, 67, 92, 87, 51, 3, 74, 77, 86, 15, 95, 17, 90, 49, 40, 59, 25, 47, 54, 30, 63, 42, 56, 77, 19, 45, 44, 33, 29, 80, 89, 16, 75, 42, 7, 81, 49, 25, 47, 99, 19, 95, 61, 9, 91, 4, 51, 89, 97, 64, 58, 26, 4, 6, 75, 61, 88, 44, 98, 23, 30, 12, 57, 93, 32, 86, 98, 32, 78, 61, 17, 12, 50, 66, 54, 29, 15, 63, 80, 74, 11, 68, 28, 14, 78, 80, 17, 28, 31, 98, 7, 88, 100, 90, 42, 7, 16, 84, 10, 37, 97, 17, 29, 5, 10, 22, 2, 17, 98, 90, 66, 45, 29, 7, 14, 41, 27, 100, 79, 86, 19, 62, 54, 82, 45, 100, 60, 15, 12, 61, 5, 38, 81, 15, 22, 4, 97, 7, 56, 87, 31, 77, 20, 15, 8, 35, 86, 31, 27, 31, 80, 42, 89, 11, 26, 24, 12, 5, 41, 52, 21, 97, 10, 78, 39, 60, 97, 32, 22, 88, 42, 75, 72, 47, 99, 16, 19, 39, 75, 62, 70, 65, 89, 51, 58, 72, 42, 27, 64, 40, 43, 62, 15, 1, 52, 5, 86, 80, 24, 98, 57, 59, 69, 55, 86, 5, 1 }, new double[] { 20, 58, 39, 57, 25, 13, 51, 21, 29, 14, 37, 37, 33, 22, 55, 54, 44, 35, 51, 30, 53, 48, 34, 57, 12, 20, 13, 42, 52, 33, 55, 55, 47, 47, 12, 22, 35, 13, 48, 45, 20, 35, 15, 40, 58, 51, 34, 46, 22, 22, 29, 37, 26, 56, 17, 26, 18, 47, 53, 57, 47, 48, 15, 18, 18, 27, 11, 28, 18, 28, 48, 32, 17, 11, 36, 40, 57, 22, 45, 30, 27, 33, 27, 29, 15, 12, 49, 53, 11, 51, 33, 40, 28, 24, 26, 33, 17, 32, 15, 31, 46, 25, 29, 13, 27, 27, 41, 57, 48, 23, 40, 35, 58, 31, 57, 17, 46, 51, 50, 11, 31, 44, 40, 25, 46, 14, 44, 22, 52, 10, 38, 13, 59, 55, 51, 20, 35, 22, 54, 19, 27, 21, 31, 33, 37, 38, 26, 31, 45, 51, 46, 53, 23, 17, 18, 38, 26, 18, 50, 35, 15, 55, 18, 39, 25, 35, 43, 50, 51, 16, 58, 42, 38, 51, 27, 39, 49, 51, 12, 41, 37, 41, 38, 11, 44, 23, 49, 22, 20, 33, 32, 25, 23, 51, 13, 12, 40, 44, 44, 38, 25, 42, 29, 58, 48, 50, 20, 26, 33, 22, 14, 54, 22, 38, 49, 32, 19, 34, 35, 50, 53, 20, 27, 10, 42, 32, 30, 53, 24, 12, 10, 36, 12, 55, 15, 23, 53, 58, 37, 30, 56, 12, 45, 36, 29, 53, 32, 17, 10, 45, 54, 26, 54, 58, 29, 48, 38, 45, 56, 36, 57, 34, 34, 49, 53, 13, 33, 52, 46, 34, 15, 54, 55, 56, 31, 17, 44, 47, 37, 44, 39, 42, 10, 18, 15, 12, 21, 40, 46, 48, 29, 11, 36, 52, 45, 24, 27, 26, 33, 22, 25, 22, 18, 51, 11, 46, 22, 13, 13, 28, 30, 48, 59, 28, 21, 20, 39, 17, 46, 25, 27, 35, 55, 46, 46, 34, 31, 45, 20, 11, 50, 34, 37, 30, 23, 37, 27, 36, 12, 44, 28, 39, 33, 28, 31, 51, 40, 46, 14, 53, 32, 16, 58, 27, 17, 44, 59, 22, 22, 37, 53, 48, 22, 19, 30, 36, 40, 53, 14, 29, 58, 57, 42, 36, 58, 59, 16, 36, 24, 26, 30, 33, 56, 38, 31, 15, 21, 22, 16, 58, 21, 25, 46, 22, 39, 47, 48, 21, 50, 10, 13, 32, 57, 49, 37, 15, 27, 37, 59, 38, 28, 59, 59, 50, 32, 31, 37, 21, 11, 41, 11, 43, 21, 37, 32, 51, 14, 18, 13, 49, 38, 16, 17, 35, 33, 45, 12, 16, 13, 30, 42, 41, 39, 20, 49, 26, 14, 25, 59, 34, 40, 27, 52, 23, 22, 32, 49, 39, 32, 56, 17, 20, 38, 44, 55, 39, 30, 27, 50, 41, 34, 56, 17, 17, 12, 43, 24, 17, 17, 35, 18, 46, 23, 30, 24, 11, 16, 27, 36, 40, 17, 11, 40, 35, 14, 39, 54, 32, 12, 15, 49, 29, 13, 30, 31, 35, 29, 40, 24, 24, 48, 56, 43, 47, 52, 16, 29, 20, 30, 17, 16, 35, 33, 37, 38, 49, 27, 11, 15, 49, 54, 53, 56, 18, 43, 14, 17, 55, 41, 47, 41, 21, 14, 41, 39, 50, 35, 20, 24, 46, 55, 14, 28, 50, 57, 32, 53, 14, 54, 55, 34, 24, 25, 48, 48, 55, 16, 54, 22, 32, 18, 19, 15, 11, 27, 47, 20, 27, 43, 10, 43, 26, 44, 43, 41, 20, 29, 13, 35, 16, 25, 28, 22, 40, 37, 53, 13, 29, 51, 25, 18, 24, 18, 38, 38, 31, 53, 16, 20, 37, 43, 16, 10, 56, 23, 35, 22, 46, 14, 46, 46, 11, 49, 43, 13, 30, 25, 19, 41, 27, 46, 46, 28, 29, 56, 26, 18, 23, 47, 59, 23, 45, 39, 33, 43, 12, 42, 28, 32, 43, 16, 25, 30, 10, 45, 10, 42, 44, 29, 28, 12, 40, 33, 16, 52, 41, 48, 28, 45, 26, 18, 55, 11, 39, 21, 27, 44, 41, 30, 27, 36, 41, 41, 25, 49, 26, 39, 10, 55, 34, 31, 54, 26, 58, 38, 13, 12, 55, 57, 30, 2147483647 }, new int[] { 1 }, new int[] { 99 }); } public Graph generateDirectedGraph() { GraphGenerator randomGraphGenerator = new GnmRandomGraphGenerator<>(100, 500); Random rand = new Random(); SimpleDirectedWeightedGraph directedGraph = new SimpleDirectedWeightedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); randomGraphGenerator.generateGraph(directedGraph); directedGraph .edgeSet().stream().forEach(e -> directedGraph.setEdgeWeight(e, rand.nextInt(100))); return directedGraph; } /*************** TEST CASES FOR UNDIRECTED GRAPHS ***************/ public Graph getUndirectedN1() { int[][] edges = { { 0, 1, 12 }, { 0, 2, 15 }, { 0, 3, 20 }, { 1, 5, 5 }, { 1, 6, 2 }, { 1, 2, 5 }, { 2, 6, 6 }, { 2, 4, 3 }, { 2, 3, 11 }, { 3, 4, 4 }, { 3, 7, 8 }, { 4, 6, 6 }, { 4, 7, 1 }, { 5, 6, 9 }, { 5, 8, 18 }, { 6, 7, 7 }, { 6, 8, 13 }, { 7, 8, 10 } }; return constructUndirectedGraph(edges); } public Graph getUndirectedN2() { int[][] edges = { { 2, 1, 20 }, { 3, 4, 58 }, { 1, 3, 39 }, { 3, 2, 57 }, { 4, 2, 25 }, { 5, 3, 13 }, { 5, 1, 51 }, { 5, 4, 21 } }; return constructUndirectedGraph(edges); } public Graph getUndirectedN3() { int[][] edges = { { 8, 15, 20 }, { 24, 30, 58 }, { 26, 33, 39 }, { 2, 22, 57 }, { 26, 5, 25 }, { 39, 22, 13 }, { 37, 10, 51 }, { 15, 6, 21 }, { 28, 19, 29 }, { 24, 35, 14 }, { 6, 20, 37 }, { 31, 17, 37 }, { 29, 9, 33 }, { 19, 40, 22 }, { 12, 32, 55 }, { 15, 27, 54 }, { 47, 25, 44 }, { 43, 1, 35 }, { 31, 11, 51 }, { 17, 42, 30 }, { 44, 36, 53 }, { 17, 25, 48 }, { 19, 46, 34 }, { 2, 44, 57 }, { 5, 32, 12 }, { 50, 2, 20 }, { 14, 10, 13 }, { 5, 24, 42 }, { 15, 25, 52 }, { 44, 25, 33 }, { 1, 28, 55 }, { 45, 10, 55 }, { 31, 20, 47 }, { 22, 34, 47 }, { 16, 5, 12 }, { 42, 40, 22 }, { 39, 45, 35 }, { 37, 8, 13 }, { 47, 32, 48 }, { 7, 10, 45 }, { 34, 8, 20 }, { 44, 41, 35 }, { 43, 30, 15 }, { 27, 43, 40 }, { 3, 7, 58 }, { 19, 32, 51 }, { 10, 5, 34 }, { 15, 10, 46 }, { 31, 50, 22 }, { 24, 40, 22 }, { 30, 13, 29 }, { 6, 10, 37 }, { 48, 29, 26 }, { 28, 26, 56 }, { 22, 48, 17 }, { 4, 39, 26 }, { 21, 24, 18 }, { 2, 30, 47 }, { 32, 48, 53 }, { 48, 6, 57 }, { 20, 22, 47 }, { 31, 18, 48 }, { 12, 15, 15 }, { 21, 14, 18 }, { 25, 49, 18 }, { 2, 46, 27 }, { 45, 47, 11 }, { 37, 6, 28 }, { 28, 21, 18 }, { 42, 18, 28 }, { 10, 1, 48 }, { 31, 30, 32 }, { 32, 16, 17 }, { 32, 28, 11 }, { 38, 35, 36 }, { 20, 28, 40 }, { 29, 43, 57 }, { 7, 23, 22 }, { 28, 27, 45 }, { 23, 16, 30 }, { 11, 44, 27 }, { 14, 49, 33 }, { 11, 7, 27 }, { 46, 39, 29 }, { 8, 26, 15 }, { 14, 7, 12 }, { 47, 33, 49 }, { 49, 8, 53 }, { 29, 5, 11 }, { 36, 41, 51 }, { 18, 14, 33 }, { 25, 39, 40 }, { 21, 39, 28 }, { 42, 15, 24 }, { 39, 11, 26 }, { 16, 22, 33 }, { 10, 11, 17 }, { 38, 13, 32 }, { 34, 32, 15 }, { 31, 33, 31 }, { 45, 7, 46 }, { 34, 3, 25 }, { 7, 29, 29 }, { 22, 13, 13 }, { 35, 25, 27 }, { 16, 35, 27 }, { 33, 45, 41 }, { 6, 18, 57 }, { 35, 22, 48 }, { 8, 43, 23 }, { 44, 45, 40 }, { 5, 20, 35 }, { 34, 14, 58 }, { 48, 45, 31 }, { 27, 24, 57 }, { 3, 18, 17 }, { 6, 29, 46 }, { 6, 30, 51 }, { 28, 15, 50 }, { 13, 32, 11 } }; return constructUndirectedGraph(edges); } public Graph getUndirectedN4() { int[][] edges = { { 100, 23, 20 }, { 96, 70, 58 }, { 79, 97, 39 }, { 24, 22, 57 }, { 11, 64, 25 }, { 64, 96, 13 }, { 87, 62, 51 }, { 23, 15, 21 }, { 12, 37, 29 }, { 68, 81, 14 }, { 54, 59, 37 }, { 75, 29, 37 }, { 3, 24, 33 }, { 92, 43, 22 }, { 37, 23, 55 }, { 31, 43, 54 }, { 37, 53, 44 }, { 3, 79, 35 }, { 50, 63, 51 }, { 1, 95, 30 }, { 11, 12, 53 }, { 8, 100, 48 }, { 9, 92, 34 }, { 10, 89, 57 }, { 45, 47, 12 }, { 85, 39, 20 }, { 54, 41, 13 }, { 89, 87, 42 }, { 44, 46, 52 }, { 67, 99, 33 }, { 40, 87, 55 }, { 19, 50, 55 }, { 40, 73, 47 }, { 4, 92, 47 }, { 99, 46, 12 }, { 87, 17, 22 }, { 90, 82, 35 }, { 66, 97, 13 }, { 78, 75, 48 }, { 43, 90, 45 }, { 10, 25, 20 }, { 74, 24, 35 }, { 6, 77, 15 }, { 13, 39, 40 }, { 6, 13, 58 }, { 70, 34, 51 }, { 76, 42, 34 }, { 99, 50, 46 }, { 53, 38, 22 }, { 21, 13, 22 }, { 75, 33, 29 }, { 63, 74, 37 }, { 56, 51, 26 }, { 51, 99, 56 }, { 23, 66, 17 }, { 99, 87, 26 }, { 16, 99, 18 }, { 74, 85, 47 }, { 82, 15, 53 }, { 90, 94, 57 }, { 85, 65, 47 }, { 26, 55, 48 }, { 27, 3, 15 }, { 52, 19, 18 }, { 84, 89, 18 }, { 16, 57, 27 }, { 29, 32, 11 }, { 29, 37, 28 }, { 93, 86, 18 }, { 94, 45, 28 }, { 60, 31, 48 }, { 74, 98, 32 }, { 9, 3, 17 }, { 31, 32, 11 }, { 22, 25, 36 }, { 17, 60, 40 }, { 5, 99, 57 }, { 98, 8, 22 }, { 73, 33, 45 }, { 69, 33, 30 }, { 42, 70, 27 }, { 85, 70, 33 }, { 5, 63, 27 }, { 4, 8, 29 }, { 44, 45, 15 }, { 12, 5, 12 }, { 83, 87, 49 }, { 4, 2, 53 }, { 17, 89, 11 }, { 83, 37, 51 }, { 82, 64, 33 }, { 41, 50, 40 }, { 84, 16, 28 }, { 9, 10, 24 }, { 12, 6, 26 }, { 14, 100, 33 }, { 25, 8, 17 }, { 79, 85, 32 }, { 52, 39, 15 }, { 63, 8, 31 }, { 55, 43, 46 }, { 21, 79, 25 }, { 80, 91, 29 }, { 89, 53, 13 }, { 23, 9, 27 }, { 95, 57, 27 }, { 4, 29, 41 }, { 93, 39, 57 }, { 62, 54, 48 }, { 85, 86, 23 }, { 85, 1, 40 }, { 17, 55, 35 }, { 35, 43, 58 }, { 79, 93, 31 }, { 97, 80, 57 }, { 90, 87, 17 }, { 68, 88, 46 }, { 22, 30, 51 }, { 42, 82, 50 }, { 49, 51, 11 }, { 74, 44, 31 }, { 87, 14, 44 }, { 86, 71, 40 }, { 23, 55, 25 }, { 85, 60, 46 }, { 28, 37, 14 }, { 97, 54, 44 }, { 7, 53, 22 }, { 87, 58, 52 }, { 44, 62, 10 }, { 57, 11, 38 }, { 99, 28, 13 }, { 7, 64, 59 }, { 31, 84, 55 }, { 36, 91, 51 }, { 22, 88, 20 }, { 84, 39, 35 }, { 23, 70, 22 }, { 85, 76, 54 }, { 18, 84, 19 }, { 41, 7, 27 }, { 99, 73, 21 }, { 66, 79, 31 }, { 22, 31, 33 }, { 44, 5, 37 }, { 95, 14, 38 }, { 92, 95, 26 }, { 37, 14, 31 }, { 74, 68, 45 }, { 4, 24, 51 }, { 45, 81, 46 }, { 100, 47, 53 }, { 100, 59, 23 }, { 8, 52, 17 }, { 24, 94, 18 }, { 70, 71, 38 }, { 83, 85, 26 }, { 59, 83, 18 }, { 83, 14, 50 }, { 76, 72, 35 }, { 65, 82, 15 }, { 99, 9, 55 }, { 30, 55, 18 }, { 73, 21, 39 }, { 96, 100, 25 }, { 88, 85, 35 }, { 11, 35, 43 }, { 45, 3, 50 }, { 93, 77, 51 }, { 98, 42, 16 }, { 25, 34, 58 }, { 14, 73, 42 }, { 71, 59, 38 }, { 33, 94, 51 }, { 51, 87, 27 }, { 42, 85, 39 }, { 18, 3, 49 }, { 44, 28, 51 }, { 11, 84, 12 }, { 81, 93, 41 }, { 7, 29, 37 }, { 18, 42, 41 }, { 19, 98, 38 }, { 35, 26, 11 }, { 6, 96, 44 }, { 96, 40, 23 }, { 67, 50, 49 }, { 30, 69, 22 }, { 14, 97, 20 }, { 43, 48, 33 }, { 40, 79, 32 }, { 86, 89, 25 }, { 31, 44, 23 }, { 94, 8, 51 }, { 89, 1, 13 }, { 50, 57, 12 }, { 38, 94, 40 }, { 7, 97, 44 }, { 33, 96, 44 }, { 59, 98, 38 }, { 62, 100, 25 }, { 77, 15, 42 }, { 79, 7, 29 }, { 28, 35, 58 }, { 95, 25, 48 }, { 76, 91, 50 }, { 99, 76, 20 }, { 28, 51, 26 }, { 84, 98, 33 }, { 4, 15, 22 }, { 88, 44, 14 }, { 83, 79, 54 }, { 49, 45, 22 }, { 3, 93, 38 }, { 81, 43, 49 }, { 77, 48, 32 }, { 38, 68, 19 }, { 11, 56, 34 }, { 6, 28, 35 }, { 34, 72, 50 }, { 94, 16, 53 }, { 62, 55, 20 }, { 5, 50, 27 }, { 42, 92, 10 }, { 2, 1, 42 }, { 46, 96, 32 }, { 15, 12, 30 }, { 57, 6, 53 }, { 84, 79, 24 }, { 49, 75, 12 }, { 90, 9, 10 }, { 15, 84, 36 }, { 32, 58, 12 }, { 57, 94, 55 }, { 26, 77, 15 }, { 6, 35, 23 }, { 48, 37, 53 }, { 15, 56, 58 }, { 45, 74, 37 }, { 10, 8, 30 }, { 88, 15, 56 }, { 97, 67, 12 }, { 83, 98, 45 }, { 93, 45, 36 }, { 25, 81, 29 }, { 73, 51, 53 }, { 24, 43, 32 }, { 26, 53, 17 }, { 84, 59, 10 }, { 97, 18, 45 }, { 32, 26, 54 }, { 61, 30, 26 }, { 24, 45, 54 }, { 88, 25, 58 }, { 50, 54, 29 }, { 62, 40, 48 }, { 95, 23, 38 }, { 95, 63, 45 }, { 63, 59, 56 }, { 99, 10, 36 }, { 25, 84, 57 }, { 92, 29, 34 }, { 58, 14, 34 }, { 79, 12, 49 }, { 1, 70, 53 }, { 35, 79, 13 }, { 91, 83, 33 }, { 45, 26, 52 }, { 72, 78, 46 }, { 52, 92, 34 }, { 28, 25, 15 }, { 22, 46, 54 }, { 47, 8, 55 }, { 26, 52, 56 }, { 50, 34, 31 }, { 41, 99, 17 }, { 2, 52, 44 }, { 84, 10, 47 }, { 77, 20, 37 }, { 22, 78, 44 }, { 81, 67, 39 }, { 67, 56, 42 }, { 34, 95, 10 }, { 14, 39, 18 }, { 63, 86, 15 }, { 88, 35, 12 }, { 3, 7, 21 }, { 14, 55, 40 }, { 25, 18, 46 }, { 81, 19, 48 }, { 45, 98, 29 }, { 18, 38, 11 }, { 59, 55, 36 }, { 75, 85, 52 }, { 86, 95, 45 }, { 50, 53, 24 }, { 82, 7, 27 }, { 30, 87, 26 }, { 2, 51, 33 }, { 11, 40, 22 }, { 5, 64, 25 }, { 72, 15, 22 }, { 94, 25, 18 }, { 61, 10, 51 }, { 39, 2, 11 }, { 86, 51, 46 }, { 56, 6, 22 }, { 15, 16, 13 }, { 11, 27, 13 }, { 58, 54, 28 }, { 86, 62, 30 }, { 88, 52, 48 }, { 48, 63, 59 }, { 89, 96, 28 }, { 50, 33, 21 }, { 36, 23, 20 }, { 83, 77, 39 }, { 70, 68, 17 }, { 60, 10, 46 }, { 20, 68, 25 }, { 30, 81, 27 }, { 10, 23, 35 }, { 9, 40, 55 }, { 44, 23, 46 }, { 92, 17, 46 }, { 98, 91, 34 }, { 64, 72, 31 }, { 47, 94, 45 }, { 56, 43, 20 }, { 39, 54, 11 }, { 10, 26, 50 }, { 62, 80, 34 }, { 7, 19, 37 }, { 98, 46, 30 }, { 20, 23, 23 }, { 1, 97, 37 }, { 12, 95, 27 }, { 40, 60, 36 }, { 12, 29, 12 }, { 67, 48, 44 }, { 67, 31, 28 }, { 11, 85, 39 }, { 95, 66, 33 }, { 97, 100, 28 }, { 18, 12, 31 }, { 84, 95, 51 }, { 70, 88, 40 }, { 17, 84, 46 }, { 97, 81, 14 }, { 91, 73, 53 }, { 61, 8, 32 }, { 46, 56, 16 }, { 81, 71, 58 }, { 15, 39, 27 }, { 24, 96, 17 }, { 82, 16, 44 }, { 20, 45, 59 }, { 96, 45, 22 }, { 11, 93, 22 }, { 13, 64, 37 }, { 88, 38, 53 }, { 3, 51, 48 }, { 81, 16, 22 }, { 7, 40, 19 }, { 33, 13, 30 }, { 18, 28, 36 }, { 86, 65, 40 }, { 52, 14, 53 }, { 21, 67, 14 }, { 99, 96, 29 }, { 78, 80, 58 }, { 2, 99, 57 }, { 77, 78, 42 }, { 12, 65, 36 }, { 51, 98, 58 }, { 48, 73, 59 }, { 36, 28, 16 }, { 57, 67, 36 }, { 21, 9, 24 }, { 15, 73, 26 }, { 26, 54, 30 }, { 90, 65, 33 }, { 29, 100, 56 }, { 11, 79, 38 }, { 95, 61, 31 }, { 64, 19, 15 }, { 66, 64, 21 }, { 92, 19, 22 }, { 70, 5, 16 }, { 36, 33, 58 }, { 71, 48, 21 }, { 46, 89, 25 }, { 79, 48, 46 }, { 70, 86, 22 }, { 89, 77, 39 }, { 42, 78, 47 }, { 99, 15, 48 }, { 88, 36, 21 }, { 88, 93, 50 }, { 49, 77, 10 }, { 76, 62, 13 }, { 15, 67, 32 }, { 52, 3, 57 }, { 10, 6, 49 }, { 99, 81, 37 }, { 22, 27, 15 }, { 61, 74, 27 }, { 45, 86, 37 }, { 57, 10, 59 }, { 91, 8, 38 }, { 80, 48, 28 }, { 100, 99, 59 }, { 27, 83, 59 }, { 3, 54, 50 }, { 67, 66, 32 }, { 47, 9, 31 }, { 39, 68, 37 }, { 63, 75, 21 }, { 46, 35, 11 }, { 25, 2, 41 }, { 72, 80, 11 }, { 20, 48, 43 }, { 39, 96, 21 }, { 50, 76, 37 }, { 35, 17, 32 }, { 10, 77, 51 }, { 50, 22, 14 }, { 8, 99, 18 }, { 93, 50, 13 }, { 23, 12, 49 }, { 84, 42, 38 }, { 69, 1, 16 }, { 17, 90, 17 }, { 27, 62, 35 }, { 94, 72, 33 }, { 33, 67, 45 }, { 15, 43, 12 }, { 99, 31, 16 }, { 7, 67, 13 }, { 91, 9, 30 }, { 16, 24, 42 }, { 88, 37, 41 }, { 5, 29, 39 }, { 86, 49, 20 }, { 36, 37, 49 }, { 93, 37, 26 }, { 89, 94, 14 }, { 53, 28, 25 }, { 34, 30, 59 }, { 7, 42, 34 }, { 59, 27, 40 }, { 61, 64, 27 }, { 49, 66, 52 }, { 35, 60, 23 }, { 47, 85, 22 }, { 34, 42, 32 }, { 4, 10, 49 }, { 51, 66, 39 }, { 36, 61, 32 }, { 9, 11, 56 }, { 91, 86, 17 }, { 53, 45, 20 }, { 71, 53, 38 }, { 75, 61, 44 }, { 85, 82, 55 }, { 97, 96, 39 }, { 52, 44, 30 }, { 6, 14, 27 }, { 31, 89, 50 }, { 93, 25, 41 }, { 52, 95, 34 }, { 60, 27, 56 }, { 34, 82, 17 }, { 10, 20, 17 }, { 17, 6, 12 }, { 54, 5, 43 }, { 74, 88, 24 }, { 91, 96, 17 }, { 10, 58, 17 }, { 62, 95, 35 }, { 91, 58, 18 }, { 30, 67, 46 }, { 53, 91, 23 }, { 62, 47, 30 }, { 30, 39, 24 }, { 39, 65, 11 }, { 86, 11, 16 }, { 41, 72, 27 }, { 78, 49, 36 }, { 14, 92, 40 }, { 65, 99, 17 }, { 15, 59, 11 }, { 44, 98, 40 }, { 50, 18, 35 }, { 46, 21, 14 }, { 11, 7, 39 }, { 50, 44, 54 }, { 1, 46, 32 }, { 45, 4, 12 }, { 21, 82, 15 }, { 5, 4, 49 }, { 31, 74, 29 }, { 87, 24, 13 }, { 72, 68, 30 }, { 45, 33, 31 }, { 25, 91, 35 }, { 12, 66, 29 }, { 2, 68, 40 }, { 3, 19, 24 }, { 5, 22, 24 }, { 24, 10, 48 }, { 72, 63, 56 }, { 99, 13, 43 }, { 67, 9, 47 }, { 64, 100, 52 }, { 57, 60, 16 }, { 71, 84, 29 }, { 82, 97, 20 }, { 62, 15, 30 }, { 73, 41, 17 }, { 2, 17, 16 }, { 99, 85, 35 }, { 5, 42, 33 }, { 3, 58, 37 }, { 70, 8, 38 }, { 71, 24, 49 }, { 10, 51, 27 }, { 85, 55, 11 }, { 68, 100, 15 }, { 41, 3, 49 }, { 30, 92, 54 }, { 39, 81, 53 }, { 86, 83, 56 }, { 18, 78, 18 }, { 27, 71, 43 }, { 10, 12, 14 }, { 7, 90, 17 }, { 8, 45, 55 }, { 10, 54, 41 }, { 32, 76, 47 }, { 7, 32, 41 }, { 3, 16, 21 }, { 5, 52, 14 }, { 4, 93, 41 }, { 60, 37, 39 }, { 75, 80, 50 }, { 88, 57, 35 }, { 80, 17, 20 }, { 67, 8, 24 }, { 18, 92, 46 }, { 66, 75, 55 }, { 56, 17, 14 }, { 36, 19, 28 }, { 38, 61, 50 }, { 19, 84, 57 }, { 17, 51, 32 }, { 81, 84, 53 }, { 80, 56, 14 }, { 42, 58, 54 }, { 26, 99, 55 }, { 9, 29, 34 }, { 58, 2, 24 }, { 24, 8, 25 }, { 21, 52, 48 }, { 93, 83, 48 }, { 16, 59, 55 }, { 67, 88, 16 }, { 1, 12, 54 }, { 90, 80, 22 }, { 57, 78, 32 }, { 58, 71, 18 }, { 81, 91, 19 }, { 2, 11, 15 }, { 73, 96, 11 }, { 70, 2, 27 }, { 1, 52, 47 }, { 37, 70, 20 }, { 76, 80, 27 }, { 50, 1, 43 }, { 14, 71, 10 }, { 7, 80, 43 }, { 97, 12, 26 }, { 58, 26, 44 }, { 8, 75, 43 }, { 65, 33, 41 }, { 77, 3, 20 }, { 86, 17, 29 }, { 99, 55, 13 }, { 92, 32, 35 }, { 12, 91, 16 }, { 82, 55, 25 }, { 41, 6, 28 }, { 62, 84, 22 }, { 6, 70, 40 }, { 79, 72, 37 }, { 90, 12, 53 }, { 76, 92, 13 }, { 94, 99, 29 }, { 48, 7, 51 }, { 90, 68, 25 }, { 46, 91, 18 }, { 20, 32, 24 }, { 13, 98, 18 }, { 10, 86, 38 }, { 78, 64, 38 }, { 24, 47, 31 }, { 55, 56, 53 }, { 87, 88, 16 }, { 88, 27, 20 }, { 89, 3, 37 }, { 72, 67, 43 }, { 34, 15, 16 }, { 6, 2, 10 }, { 92, 57, 56 }, { 55, 72, 23 }, { 9, 70, 35 }, { 83, 74, 22 }, { 67, 98, 46 }, { 20, 37, 14 }, { 46, 76, 46 }, { 93, 10, 46 }, { 7, 49, 11 }, { 25, 11, 49 }, { 18, 62, 43 }, { 5, 88, 13 }, { 88, 58, 30 }, { 62, 6, 25 }, { 29, 93, 19 }, { 88, 43, 41 }, { 35, 97, 27 }, { 2, 74, 46 }, { 28, 98, 46 }, { 7, 15, 28 }, { 74, 67, 29 }, { 38, 10, 56 }, { 11, 18, 26 }, { 16, 34, 18 }, { 8, 19, 23 }, { 43, 83, 47 }, { 86, 72, 59 }, { 19, 73, 23 }, { 42, 80, 45 }, { 41, 5, 39 }, { 71, 8, 33 }, { 87, 65, 43 }, { 72, 24, 12 }, { 2, 57, 42 }, { 9, 83, 28 }, { 84, 29, 32 }, { 83, 48, 43 }, { 60, 14, 16 }, { 79, 38, 25 }, { 74, 77, 30 }, { 80, 40, 10 }, { 78, 51, 45 }, { 86, 3, 10 }, { 73, 27, 42 }, { 89, 43, 44 }, { 97, 49, 29 }, { 82, 67, 28 }, { 14, 34, 12 }, { 60, 94, 40 }, { 48, 97, 33 }, { 50, 21, 16 }, { 87, 50, 52 }, { 2, 14, 41 }, { 38, 17, 48 }, { 27, 82, 28 }, { 58, 41, 45 }, { 45, 55, 26 }, { 5, 26, 18 }, { 71, 94, 55 }, { 1, 17, 11 }, { 54, 91, 39 }, { 43, 70, 21 }, { 34, 52, 27 }, { 18, 47, 44 }, { 30, 89, 41 }, { 100, 15, 30 }, { 70, 79, 27 }, { 100, 48, 36 }, { 86, 87, 41 }, { 75, 77, 41 }, { 33, 32, 25 }, { 64, 31, 49 }, { 95, 83, 26 }, { 50, 82, 39 }, { 55, 33, 10 }, { 24, 15, 55 }, { 25, 69, 34 }, { 68, 3, 31 }, { 53, 41, 54 }, { 70, 48, 26 }, { 62, 46, 58 }, { 63, 32, 38 }, { 12, 49, 13 }, { 55, 91, 12 }, { 9, 50, 55 }, { 78, 55, 57 }, { 46, 23, 30 }, { 52, 75, 27 }, { 34, 22, 42 }, { 11, 4, 17 }, { 20, 57, 18 }, { 64, 95, 19 }, { 100, 4, 17 }, { 56, 16, 14 }, { 44, 94, 16 }, { 94, 86, 35 }, { 7, 8, 26 }, { 80, 2, 36 }, { 88, 49, 21 }, { 37, 32, 25 }, { 33, 20, 25 }, { 85, 90, 46 }, { 55, 36, 39 }, { 35, 53, 46 }, { 60, 49, 19 }, { 7, 66, 43 }, { 56, 76, 18 }, { 47, 46, 13 }, { 54, 64, 37 }, { 74, 6, 44 }, { 76, 67, 47 }, { 64, 56, 55 }, { 35, 16, 14 }, { 19, 6, 55 }, { 52, 57, 10 }, { 30, 90, 19 }, { 73, 75, 40 }, { 17, 43, 18 }, { 58, 56, 16 }, { 73, 80, 17 }, { 95, 26, 23 }, { 79, 2, 24 }, { 42, 13, 49 }, { 30, 43, 46 }, { 59, 30, 26 }, { 67, 78, 20 }, { 55, 65, 56 }, { 99, 82, 19 }, { 50, 47, 37 }, { 14, 75, 21 }, { 21, 27, 50 }, { 80, 98, 14 }, { 26, 47, 23 }, { 44, 99, 16 }, { 47, 6, 16 }, { 89, 6, 21 }, { 38, 72, 31 }, { 70, 95, 16 }, { 8, 84, 36 }, { 33, 30, 23 }, { 18, 9, 53 }, { 61, 19, 16 }, { 21, 35, 54 }, { 16, 7, 35 }, { 64, 12, 47 }, { 57, 56, 36 }, { 80, 69, 26 }, { 14, 26, 45 }, { 68, 65, 56 }, { 97, 37, 48 }, { 16, 47, 54 }, { 61, 72, 13 }, { 94, 63, 38 }, { 92, 2, 17 }, { 76, 48, 53 }, { 97, 11, 37 }, { 37, 73, 36 }, { 87, 60, 31 }, { 44, 48, 58 }, { 97, 28, 50 }, { 87, 11, 11 }, { 6, 94, 13 }, { 3, 1, 23 }, { 47, 83, 58 }, { 52, 18, 31 }, { 18, 88, 15 }, { 62, 45, 42 }, { 12, 75, 43 }, { 96, 56, 41 }, { 88, 40, 37 }, { 30, 76, 47 }, { 62, 51, 26 }, { 94, 58, 30 }, { 96, 71, 28 }, { 25, 85, 51 }, { 8, 48, 57 }, { 16, 100, 43 }, { 83, 1, 24 }, { 87, 82, 19 }, { 51, 92, 42 }, { 57, 34, 41 }, { 25, 35, 56 }, { 25, 66, 21 }, { 45, 97, 56 }, { 88, 79, 56 }, { 70, 30, 43 }, { 90, 15, 40 }, { 57, 93, 16 }, { 42, 90, 55 }, { 64, 68, 22 }, { 10, 96, 42 }, { 46, 24, 21 }, { 54, 1, 57 }, { 35, 85, 28 }, { 8, 53, 17 }, { 6, 90, 17 }, { 52, 48, 49 }, { 96, 90, 53 }, { 11, 43, 32 }, { 54, 98, 12 }, { 31, 10, 23 }, { 29, 98, 32 }, { 64, 22, 27 }, { 32, 79, 27 }, { 29, 94, 52 }, { 10, 27, 24 }, { 30, 45, 26 }, { 39, 69, 56 }, { 63, 15, 31 }, { 21, 83, 16 }, { 86, 77, 12 }, { 85, 66, 17 }, { 42, 37, 51 }, { 83, 24, 27 }, { 91, 31, 11 }, { 1, 29, 50 }, { 46, 3, 51 }, { 68, 56, 19 }, { 88, 100, 41 }, { 11, 80, 38 }, { 2, 44, 21 }, { 42, 53, 59 }, { 37, 64, 48 }, { 70, 76, 14 }, { 82, 11, 38 }, { 79, 23, 19 }, { 34, 73, 42 }, { 57, 70, 16 }, { 57, 13, 41 }, { 48, 18, 39 }, { 50, 25, 29 }, { 88, 6, 55 }, { 83, 25, 21 }, { 75, 55, 14 }, { 12, 93, 59 }, { 40, 65, 35 }, { 89, 63, 19 }, { 27, 78, 56 }, { 63, 88, 18 }, { 69, 10, 32 }, { 79, 29, 43 }, { 17, 24, 15 }, { 44, 16, 46 }, { 2, 8, 31 }, { 73, 26, 50 }, { 25, 37, 52 }, { 54, 87, 12 }, { 33, 6, 16 }, { 67, 41, 36 }, { 72, 6, 29 }, { 67, 95, 55 }, { 35, 98, 45 }, { 49, 34, 52 }, { 63, 18, 18 }, { 70, 4, 13 }, { 17, 8, 17 }, { 53, 36, 45 }, { 37, 100, 50 }, { 74, 81, 49 }, { 38, 39, 51 }, { 28, 76, 54 }, { 93, 75, 59 }, { 18, 81, 59 }, { 3, 53, 40 }, { 23, 87, 33 }, { 75, 27, 40 }, { 7, 99, 32 }, { 48, 69, 33 }, { 65, 25, 41 }, { 31, 69, 33 }, { 96, 50, 39 }, { 40, 70, 26 }, { 93, 22, 10 }, { 20, 2, 59 }, { 67, 19, 28 }, { 84, 73, 59 }, { 33, 2, 13 }, { 71, 62, 40 }, { 70, 11, 49 }, { 50, 27, 32 }, { 98, 56, 14 }, { 45, 57, 11 }, { 45, 90, 16 }, { 9, 65, 46 }, { 64, 39, 10 }, { 41, 55, 35 }, { 8, 33, 57 }, { 18, 2, 47 }, { 14, 85, 35 }, { 77, 41, 32 }, { 20, 27, 21 }, { 65, 13, 29 }, { 84, 23, 30 }, { 5, 20, 20 }, { 73, 90, 15 }, { 10, 92, 53 }, { 46, 13, 48 }, { 91, 3, 24 }, { 98, 30, 27 }, { 89, 49, 22 }, { 74, 3, 12 }, { 30, 42, 32 }, { 5, 23, 39 }, { 9, 96, 32 }, { 77, 90, 48 }, { 21, 57, 48 }, { 34, 24, 18 }, { 73, 36, 18 }, { 68, 57, 44 }, { 13, 72, 26 }, { 6, 80, 18 }, { 22, 84, 56 }, { 49, 30, 56 }, { 79, 37, 42 }, { 61, 62, 39 }, { 58, 36, 41 }, { 79, 30, 32 }, { 46, 32, 18 }, { 26, 81, 57 }, { 28, 19, 48 }, { 20, 76, 50 }, { 18, 20, 44 }, { 41, 20, 31 }, { 64, 53, 48 }, { 85, 97, 58 }, { 83, 60, 32 }, { 13, 32, 12 }, { 84, 47, 12 }, { 2, 24, 58 }, { 80, 4, 10 }, { 73, 47, 31 }, { 47, 51, 33 }, { 77, 85, 17 }, { 37, 43, 53 }, { 96, 77, 23 }, { 84, 3, 56 }, { 53, 98, 35 }, { 28, 66, 40 }, { 16, 11, 19 }, { 6, 7, 32 }, { 84, 33, 35 }, { 91, 43, 59 }, { 91, 42, 52 }, { 3, 82, 24 }, { 12, 55, 47 }, { 75, 65, 45 }, { 55, 92, 37 }, { 96, 78, 15 }, { 62, 3, 26 }, { 81, 5, 25 }, { 70, 75, 39 }, { 17, 13, 49 }, { 17, 91, 37 }, { 28, 91, 10 }, { 89, 23, 43 }, { 28, 75, 37 }, { 35, 19, 23 }, { 2, 35, 45 }, { 48, 82, 51 }, { 19, 53, 34 }, { 56, 75, 12 }, { 55, 1, 25 }, { 15, 64, 56 }, { 53, 4, 27 }, { 43, 82, 54 }, { 76, 45, 52 }, { 37, 55, 15 }, { 59, 5, 11 }, { 48, 25, 34 }, { 62, 72, 46 }, { 61, 5, 53 }, { 4, 91, 55 }, { 46, 45, 52 }, { 40, 100, 31 }, { 98, 16, 21 }, { 31, 34, 50 }, { 42, 12, 14 }, { 86, 81, 12 }, { 14, 13, 14 }, { 24, 13, 46 }, { 43, 47, 14 }, { 32, 4, 21 }, { 20, 87, 53 }, { 1, 44, 37 }, { 25, 97, 57 }, { 94, 66, 23 }, { 35, 99, 36 } }; return constructUndirectedGraph(edges); } public Graph getUndirectedN5() { int[][] edges = { { 18, 3, 20 }, { 31, 30, 58 }, { 19, 8, 39 }, { 38, 22, 57 }, { 21, 34, 25 }, { 47, 21, 13 }, { 49, 40, 51 }, { 29, 10, 21 }, { 20, 23, 29 }, { 46, 42, 14 }, { 20, 32, 37 }, { 1, 28, 37 }, { 24, 44, 33 }, { 34, 39, 22 }, { 31, 27, 55 }, { 17, 23, 54 }, { 15, 44, 44 }, { 40, 6, 35 }, { 49, 4, 51 }, { 14, 35, 30 }, { 25, 35, 53 }, { 34, 4, 48 }, { 17, 29, 34 }, { 24, 21, 57 }, { 41, 33, 12 }, { 8, 18, 20 }, { 10, 8, 13 }, { 27, 6, 42 }, { 3, 45, 52 }, { 8, 40, 33 }, { 31, 45, 55 }, { 4, 28, 55 }, { 34, 20, 47 }, { 47, 33, 47 }, { 30, 27, 12 }, { 34, 40, 22 }, { 5, 36, 35 }, { 25, 9, 13 }, { 10, 1, 48 }, { 1, 27, 45 }, { 19, 40, 20 }, { 30, 41, 35 }, { 34, 36, 15 }, { 42, 47, 40 }, { 29, 36, 58 }, { 5, 39, 51 }, { 47, 29, 34 }, { 23, 44, 46 }, { 3, 48, 22 }, { 13, 1, 22 }, { 19, 23, 29 }, { 27, 19, 37 }, { 41, 2, 26 }, { 46, 50, 56 }, { 41, 12, 17 }, { 4, 14, 26 }, { 34, 33, 18 }, { 11, 2, 47 }, { 21, 22, 53 }, { 25, 32, 57 }, { 39, 31, 47 }, { 30, 20, 48 }, { 34, 47, 15 }, { 29, 16, 18 }, { 4, 27, 18 }, { 28, 48, 27 }, { 30, 2, 11 }, { 35, 12, 28 }, { 45, 28, 18 }, { 35, 32, 28 }, { 29, 22, 48 }, { 2, 39, 32 }, { 38, 13, 17 }, { 46, 4, 11 }, { 3, 39, 36 }, { 35, 16, 40 }, { 8, 43, 57 }, { 38, 40, 22 }, { 39, 43, 45 }, { 41, 44, 30 }, { 50, 43, 27 }, { 4, 47, 33 }, { 46, 40, 27 }, { 18, 32, 29 }, { 37, 3, 15 }, { 34, 16, 12 }, { 41, 45, 49 }, { 33, 11, 53 }, { 16, 4, 11 }, { 25, 3, 51 }, { 26, 21, 33 }, { 19, 32, 40 }, { 38, 9, 28 }, { 26, 30, 24 }, { 41, 17, 26 }, { 29, 21, 33 }, { 39, 23, 17 }, { 32, 46, 32 }, { 40, 9, 15 }, { 17, 16, 31 }, { 6, 47, 46 }, { 8, 22, 25 }, { 41, 36, 29 }, { 33, 3, 13 }, { 47, 35, 27 }, { 4, 21, 27 }, { 45, 46, 41 }, { 10, 19, 57 }, { 48, 14, 48 }, { 50, 16, 23 }, { 25, 23, 40 }, { 6, 48, 35 }, { 44, 26, 58 }, { 33, 7, 31 }, { 39, 33, 57 }, { 37, 44, 17 }, { 26, 50, 46 }, { 7, 41, 51 }, { 19, 38, 50 }, { 38, 30, 11 }, { 2, 25, 31 }, { 22, 32, 44 }, { 32, 15, 40 }, { 12, 42, 25 }, { 48, 36, 46 }, { 40, 43, 14 }, { 19, 5, 44 }, { 37, 23, 22 }, { 10, 18, 52 }, { 11, 49, 10 }, { 33, 29, 38 }, { 4, 8, 13 }, { 44, 31, 59 }, { 48, 30, 55 }, { 22, 28, 51 }, { 47, 1, 20 }, { 14, 30, 35 }, { 24, 46, 22 }, { 33, 46, 54 }, { 13, 14, 19 }, { 39, 15, 27 }, { 27, 14, 21 }, { 24, 47, 31 }, { 33, 17, 33 }, { 9, 12, 37 }, { 9, 39, 38 }, { 10, 24, 26 }, { 50, 36, 31 }, { 11, 48, 45 }, { 10, 36, 51 }, { 47, 11, 46 }, { 7, 22, 53 }, { 13, 39, 23 }, { 28, 32, 17 }, { 50, 30, 18 }, { 21, 41, 38 }, { 44, 46, 26 }, { 1, 2, 18 }, { 43, 19, 50 }, { 27, 42, 35 }, { 32, 48, 15 }, { 23, 48, 55 }, { 28, 50, 18 }, { 24, 3, 39 }, { 36, 35, 25 }, { 15, 5, 35 }, { 30, 3, 43 }, { 29, 27, 50 }, { 17, 35, 51 }, { 14, 45, 16 }, { 18, 17, 58 }, { 1, 30, 42 }, { 17, 22, 38 }, { 18, 12, 51 }, { 12, 8, 27 }, { 15, 22, 39 }, { 11, 50, 49 }, { 19, 29, 51 }, { 25, 22, 12 }, { 45, 5, 41 }, { 6, 36, 37 }, { 2, 4, 41 }, { 46, 7, 38 }, { 3, 31, 11 }, { 3, 10, 44 }, { 31, 26, 23 }, { 44, 21, 49 }, { 37, 36, 22 }, { 20, 39, 20 }, { 22, 3, 33 }, { 39, 19, 32 }, { 28, 10, 25 }, { 44, 4, 23 }, { 10, 46, 51 }, { 27, 50, 13 }, { 12, 3, 12 }, { 3, 29, 40 }, { 48, 4, 44 }, { 32, 43, 44 }, { 48, 12, 38 } }; return constructUndirectedGraph(edges); } public Graph getUndirectedN6() { int[][] edges = { { 20, 51, 20 }, { 43, 61, 58 }, { 27, 6, 39 }, { 47, 13, 57 }, { 63, 4, 25 }, { 37, 68, 13 }, { 31, 60, 51 }, { 67, 57, 21 }, { 46, 44, 29 }, { 47, 60, 14 }, { 7, 51, 37 }, { 61, 58, 37 }, { 21, 28, 33 }, { 62, 36, 22 }, { 18, 47, 55 }, { 43, 57, 54 }, { 52, 28, 44 }, { 9, 47, 35 }, { 48, 19, 51 }, { 56, 50, 30 }, { 26, 15, 53 }, { 8, 61, 48 }, { 37, 9, 34 }, { 9, 20, 57 }, { 2, 58, 12 }, { 57, 19, 20 }, { 19, 6, 13 }, { 27, 54, 42 }, { 28, 6, 52 }, { 50, 67, 33 }, { 37, 64, 55 }, { 21, 55, 55 }, { 22, 39, 47 }, { 24, 16, 47 }, { 19, 20, 12 }, { 65, 69, 22 }, { 6, 12, 35 }, { 6, 25, 13 }, { 2, 32, 48 }, { 65, 12, 45 }, { 63, 59, 20 }, { 41, 7, 35 }, { 53, 41, 15 }, { 44, 39, 40 }, { 31, 44, 58 }, { 24, 58, 51 }, { 49, 4, 34 }, { 26, 67, 46 }, { 60, 8, 22 }, { 5, 7, 22 }, { 7, 64, 29 }, { 33, 1, 37 }, { 38, 29, 26 }, { 5, 52, 56 }, { 62, 37, 17 }, { 69, 46, 26 }, { 54, 22, 18 }, { 32, 55, 47 }, { 47, 29, 53 }, { 59, 36, 57 }, { 40, 66, 47 }, { 35, 53, 48 }, { 57, 49, 15 }, { 2, 7, 18 }, { 70, 42, 18 }, { 12, 54, 27 }, { 49, 1, 11 }, { 31, 34, 28 }, { 27, 64, 18 }, { 35, 52, 28 }, { 51, 28, 48 }, { 48, 60, 32 }, { 57, 17, 17 }, { 57, 1, 11 }, { 9, 33, 36 }, { 13, 46, 40 }, { 10, 59, 57 }, { 67, 53, 22 }, { 26, 48, 45 }, { 69, 53, 30 }, { 59, 64, 27 }, { 48, 1, 33 }, { 13, 33, 27 }, { 60, 64, 29 }, { 51, 50, 15 }, { 4, 43, 12 }, { 25, 10, 49 }, { 65, 11, 53 }, { 9, 2, 11 }, { 16, 23, 51 }, { 1, 59, 33 }, { 68, 5, 40 }, { 1, 2, 28 }, { 43, 37, 24 }, { 39, 27, 26 }, { 21, 19, 33 }, { 33, 61, 17 }, { 60, 55, 32 }, { 42, 55, 15 }, { 64, 16, 31 }, { 17, 13, 46 }, { 23, 52, 25 }, { 23, 29, 29 }, { 52, 7, 13 }, { 6, 50, 27 }, { 13, 36, 27 }, { 64, 67, 41 }, { 3, 65, 57 }, { 17, 69, 48 }, { 3, 22, 23 }, { 31, 27, 40 }, { 62, 42, 35 }, { 15, 11, 58 }, { 61, 45, 31 }, { 43, 65, 57 }, { 14, 40, 17 }, { 37, 6, 46 }, { 55, 43, 51 }, { 54, 29, 50 }, { 33, 4, 11 }, { 39, 68, 31 }, { 62, 47, 44 }, { 65, 63, 40 }, { 6, 38, 25 }, { 32, 69, 46 }, { 5, 8, 14 }, { 68, 55, 44 }, { 32, 7, 22 }, { 33, 53, 52 }, { 15, 2, 10 }, { 50, 61, 38 }, { 22, 57, 13 }, { 7, 26, 59 }, { 63, 25, 55 }, { 67, 32, 51 }, { 26, 63, 20 }, { 29, 2, 35 }, { 21, 43, 22 }, { 55, 35, 54 }, { 15, 45, 19 }, { 13, 2, 27 }, { 47, 16, 21 }, { 66, 50, 31 }, { 13, 49, 33 }, { 38, 63, 37 }, { 26, 12, 38 }, { 5, 36, 26 }, { 49, 62, 31 }, { 2, 56, 45 }, { 64, 63, 51 }, { 49, 18, 46 }, { 18, 5, 53 }, { 28, 29, 23 }, { 24, 12, 17 }, { 26, 55, 18 }, { 21, 18, 38 }, { 14, 33, 26 }, { 26, 1, 18 }, { 68, 64, 50 }, { 30, 53, 35 }, { 58, 5, 15 }, { 22, 55, 55 }, { 38, 14, 18 }, { 8, 30, 39 }, { 57, 4, 25 }, { 44, 27, 35 }, { 10, 62, 43 }, { 5, 57, 50 }, { 63, 28, 51 }, { 33, 63, 16 }, { 36, 3, 58 }, { 57, 44, 42 }, { 28, 61, 38 }, { 45, 57, 51 }, { 20, 15, 27 }, { 40, 34, 39 }, { 9, 26, 49 }, { 62, 18, 51 }, { 34, 24, 12 }, { 66, 36, 41 }, { 45, 56, 37 }, { 40, 32, 41 }, { 57, 51, 38 }, { 47, 45, 11 }, { 6, 20, 44 }, { 1, 8, 23 }, { 30, 47, 49 }, { 62, 69, 22 }, { 53, 58, 20 }, { 19, 1, 33 }, { 41, 12, 32 }, { 7, 3, 25 }, { 41, 42, 23 }, { 5, 10, 51 }, { 55, 58, 13 }, { 8, 40, 12 }, { 5, 30, 40 }, { 22, 15, 44 }, { 38, 68, 44 }, { 62, 14, 38 } }; return constructUndirectedGraph(edges); } public Graph getUndirectedN7() { int[][] edges = { { 5, 40, 20 }, { 5, 38, 58 }, { 17, 40, 39 }, { 61, 2, 57 }, { 25, 38, 25 }, { 23, 47, 13 }, { 40, 26, 51 }, { 7, 57, 21 }, { 34, 40, 29 }, { 1, 33, 14 }, { 32, 37, 37 }, { 57, 40, 37 }, { 42, 9, 33 }, { 11, 70, 22 }, { 8, 28, 55 }, { 5, 18, 54 }, { 41, 36, 44 }, { 2, 19, 35 }, { 3, 5, 51 }, { 11, 25, 30 }, { 34, 18, 53 }, { 6, 59, 48 }, { 48, 58, 34 }, { 39, 21, 57 }, { 42, 2, 12 }, { 27, 7, 20 }, { 62, 11, 13 }, { 43, 48, 42 }, { 56, 40, 52 }, { 21, 25, 33 }, { 67, 59, 55 }, { 67, 32, 55 }, { 23, 65, 47 }, { 12, 8, 47 }, { 58, 10, 12 }, { 59, 34, 22 }, { 9, 69, 35 }, { 22, 54, 13 }, { 55, 33, 48 }, { 39, 16, 45 }, { 1, 14, 20 }, { 52, 65, 35 }, { 65, 42, 15 }, { 23, 42, 40 }, { 63, 8, 58 }, { 34, 46, 51 }, { 66, 33, 34 }, { 30, 53, 46 }, { 39, 63, 22 }, { 2, 53, 22 }, { 24, 27, 29 }, { 49, 44, 37 }, { 27, 1, 26 }, { 3, 60, 56 }, { 53, 22, 17 }, { 58, 15, 26 }, { 45, 20, 18 }, { 20, 14, 47 }, { 30, 61, 53 }, { 46, 65, 57 }, { 33, 44, 47 }, { 13, 45, 48 }, { 63, 24, 15 }, { 39, 44, 18 }, { 18, 44, 18 }, { 19, 61, 27 }, { 4, 43, 11 }, { 26, 20, 28 }, { 32, 66, 18 }, { 30, 47, 28 }, { 53, 35, 48 }, { 50, 10, 32 }, { 27, 35, 17 }, { 67, 65, 11 }, { 46, 36, 36 }, { 24, 35, 40 }, { 55, 68, 57 }, { 47, 32, 22 }, { 55, 36, 45 }, { 19, 20, 30 } }; return constructUndirectedGraph(edges); } public Graph getUndirectedN8() { int[][] edges = { { 22, 65, 20 }, { 59, 32, 58 }, { 45, 13, 39 }, { 29, 75, 57 }, { 100, 54, 25 }, { 96, 59, 13 }, { 83, 43, 51 }, { 76, 84, 21 }, { 77, 88, 29 }, { 95, 8, 14 }, { 79, 70, 37 }, { 99, 78, 37 }, { 7, 59, 33 }, { 89, 39, 22 }, { 53, 64, 55 }, { 38, 95, 54 }, { 17, 69, 44 }, { 83, 28, 35 }, { 72, 16, 51 }, { 77, 13, 30 }, { 6, 70, 53 }, { 97, 67, 48 }, { 48, 88, 34 }, { 87, 73, 57 }, { 83, 38, 12 }, { 45, 80, 20 }, { 3, 7, 13 }, { 24, 74, 42 }, { 96, 98, 52 }, { 47, 26, 33 }, { 1, 72, 55 }, { 31, 51, 55 }, { 21, 56, 47 }, { 67, 71, 47 }, { 49, 86, 12 }, { 71, 29, 22 }, { 47, 11, 35 }, { 31, 13, 13 }, { 80, 8, 48 }, { 46, 30, 45 }, { 64, 74, 20 }, { 6, 54, 35 }, { 11, 57, 15 }, { 59, 26, 40 }, { 92, 9, 58 }, { 71, 44, 51 }, { 63, 55, 34 }, { 84, 60, 46 }, { 29, 65, 22 }, { 94, 35, 22 }, { 31, 2, 29 }, { 13, 87, 37 }, { 92, 21, 26 }, { 86, 80, 56 }, { 99, 32, 17 }, { 54, 63, 26 }, { 81, 12, 18 }, { 36, 78, 47 }, { 64, 28, 53 }, { 100, 75, 57 }, { 18, 80, 47 }, { 66, 47, 48 }, { 85, 57, 15 }, { 64, 15, 18 }, { 55, 98, 18 }, { 60, 93, 27 }, { 80, 10, 11 }, { 78, 33, 28 }, { 95, 29, 18 }, { 6, 82, 28 }, { 66, 20, 48 }, { 10, 50, 32 }, { 95, 97, 17 }, { 58, 57, 11 }, { 79, 62, 36 }, { 56, 6, 40 }, { 31, 25, 57 }, { 74, 85, 22 }, { 96, 32, 45 }, { 88, 5, 30 }, { 70, 59, 27 }, { 28, 92, 33 }, { 60, 62, 27 }, { 6, 38, 29 }, { 78, 53, 15 }, { 85, 97, 12 }, { 26, 43, 49 }, { 23, 6, 53 }, { 4, 39, 11 }, { 25, 34, 51 }, { 29, 87, 33 }, { 20, 100, 40 }, { 99, 87, 28 }, { 21, 55, 24 }, { 46, 92, 26 }, { 39, 67, 33 }, { 47, 6, 17 }, { 74, 82, 32 }, { 74, 63, 15 }, { 28, 41, 31 }, { 23, 43, 46 }, { 64, 21, 25 }, { 13, 46, 29 }, { 13, 8, 13 }, { 34, 72, 27 }, { 64, 69, 27 }, { 76, 69, 41 }, { 94, 21, 57 }, { 54, 64, 48 }, { 51, 86, 23 }, { 16, 83, 40 }, { 83, 50, 35 }, { 63, 10, 58 }, { 31, 55, 31 }, { 2, 34, 57 }, { 100, 94, 17 }, { 47, 94, 46 }, { 53, 36, 51 }, { 82, 34, 50 }, { 54, 8, 11 }, { 52, 37, 31 }, { 41, 32, 44 }, { 16, 62, 40 }, { 39, 46, 25 }, { 63, 13, 46 }, { 52, 51, 14 }, { 56, 23, 44 }, { 100, 79, 22 }, { 92, 66, 52 }, { 9, 70, 10 }, { 38, 65, 38 }, { 70, 51, 13 }, { 97, 43, 59 }, { 11, 44, 55 }, { 13, 34, 51 }, { 92, 47, 20 }, { 61, 18, 35 }, { 60, 68, 22 }, { 20, 91, 54 }, { 54, 84, 19 }, { 23, 69, 27 }, { 55, 46, 21 }, { 50, 72, 31 }, { 59, 48, 33 }, { 42, 21, 37 }, { 35, 25, 38 }, { 70, 41, 26 }, { 56, 98, 31 }, { 8, 4, 45 }, { 37, 56, 51 }, { 83, 60, 46 }, { 51, 93, 53 }, { 11, 24, 23 }, { 53, 82, 17 }, { 97, 100, 18 }, { 8, 61, 38 }, { 44, 92, 26 }, { 29, 43, 18 }, { 90, 29, 50 }, { 11, 15, 35 }, { 12, 80, 15 }, { 1, 71, 55 }, { 58, 62, 18 }, { 3, 83, 39 }, { 14, 17, 25 }, { 37, 48, 35 }, { 58, 18, 43 }, { 52, 30, 50 }, { 78, 75, 51 }, { 69, 18, 16 }, { 44, 52, 58 }, { 49, 35, 42 }, { 24, 9, 38 }, { 95, 35, 51 }, { 44, 59, 27 }, { 98, 75, 39 }, { 1, 53, 49 }, { 50, 21, 51 }, { 92, 14, 12 }, { 14, 26, 41 }, { 15, 82, 37 }, { 37, 32, 41 }, { 1, 66, 38 }, { 66, 69, 11 }, { 81, 48, 44 }, { 50, 59, 23 }, { 68, 7, 49 }, { 84, 100, 22 }, { 65, 19, 20 }, { 3, 54, 33 }, { 17, 18, 32 }, { 60, 99, 25 }, { 100, 77, 23 }, { 56, 10, 51 }, { 98, 35, 13 }, { 70, 47, 12 }, { 29, 15, 40 }, { 10, 37, 44 }, { 88, 20, 44 }, { 25, 4, 38 }, { 77, 37, 25 }, { 1, 5, 42 }, { 69, 19, 29 }, { 74, 22, 58 }, { 12, 84, 48 }, { 87, 26, 50 }, { 76, 34, 20 }, { 90, 85, 26 }, { 58, 97, 33 }, { 58, 70, 22 }, { 35, 50, 14 }, { 63, 98, 54 }, { 30, 25, 22 }, { 53, 83, 38 }, { 12, 73, 49 }, { 90, 73, 32 }, { 86, 40, 19 }, { 67, 44, 34 }, { 55, 14, 35 }, { 18, 31, 50 }, { 41, 96, 53 }, { 17, 81, 20 }, { 65, 6, 27 }, { 19, 35, 10 }, { 23, 20, 42 }, { 28, 30, 32 }, { 24, 18, 30 }, { 84, 55, 53 }, { 75, 43, 24 }, { 41, 25, 12 }, { 64, 25, 10 }, { 69, 49, 36 }, { 24, 32, 12 }, { 94, 77, 55 }, { 2, 65, 15 }, { 29, 4, 23 }, { 43, 9, 53 }, { 95, 79, 58 }, { 100, 3, 37 }, { 32, 58, 30 }, { 70, 62, 56 }, { 34, 1, 12 }, { 71, 8, 45 }, { 13, 57, 36 }, { 36, 5, 29 }, { 5, 67, 53 }, { 20, 8, 32 }, { 13, 32, 17 }, { 96, 56, 10 }, { 27, 79, 45 }, { 74, 41, 54 }, { 23, 60, 26 }, { 56, 71, 54 }, { 82, 88, 58 }, { 49, 54, 29 }, { 57, 71, 48 }, { 52, 76, 38 }, { 62, 32, 45 }, { 75, 70, 56 }, { 29, 7, 36 }, { 71, 53, 57 }, { 72, 21, 34 }, { 15, 6, 34 }, { 34, 59, 49 }, { 67, 60, 53 }, { 85, 73, 13 }, { 14, 90, 33 }, { 21, 34, 52 }, { 43, 13, 46 }, { 94, 50, 34 }, { 29, 19, 15 }, { 14, 11, 54 }, { 22, 2, 55 }, { 57, 49, 56 }, { 30, 36, 31 }, { 25, 5, 17 }, { 41, 76, 44 }, { 77, 97, 47 }, { 14, 71, 37 }, { 10, 34, 44 }, { 93, 41, 39 }, { 81, 31, 42 }, { 40, 67, 10 }, { 69, 82, 18 }, { 62, 50, 15 }, { 60, 29, 12 }, { 74, 47, 21 }, { 76, 47, 40 }, { 46, 100, 46 }, { 37, 4, 48 }, { 26, 35, 29 }, { 41, 8, 11 }, { 26, 92, 36 }, { 71, 63, 52 }, { 78, 71, 45 }, { 48, 68, 24 }, { 26, 44, 27 }, { 93, 11, 26 }, { 67, 20, 33 }, { 99, 37, 22 }, { 29, 72, 25 }, { 64, 100, 22 }, { 56, 77, 18 }, { 98, 16, 51 }, { 71, 89, 11 }, { 64, 90, 46 }, { 38, 20, 22 }, { 79, 45, 13 }, { 54, 26, 13 }, { 12, 49, 28 }, { 70, 68, 30 }, { 27, 65, 48 }, { 84, 4, 59 }, { 34, 48, 28 }, { 100, 60, 21 }, { 18, 35, 20 }, { 92, 4, 39 }, { 3, 95, 17 }, { 56, 79, 46 }, { 13, 65, 25 }, { 43, 40, 27 }, { 61, 69, 35 }, { 22, 100, 55 }, { 54, 52, 46 }, { 97, 41, 46 }, { 58, 5, 34 }, { 31, 98, 31 }, { 80, 78, 45 }, { 33, 73, 20 }, { 62, 24, 11 }, { 6, 36, 50 }, { 75, 55, 34 }, { 57, 93, 37 }, { 37, 27, 30 }, { 42, 84, 23 }, { 75, 63, 37 }, { 43, 24, 27 }, { 57, 20, 36 }, { 63, 48, 12 }, { 51, 82, 44 }, { 38, 9, 28 }, { 75, 13, 39 }, { 69, 78, 33 }, { 81, 16, 28 }, { 12, 7, 31 }, { 77, 65, 51 }, { 78, 56, 40 }, { 4, 86, 46 }, { 66, 2, 14 }, { 41, 44, 53 }, { 50, 52, 32 }, { 61, 57, 16 }, { 71, 40, 58 }, { 25, 26, 27 }, { 77, 41, 17 }, { 87, 77, 44 }, { 9, 49, 59 }, { 3, 60, 22 }, { 83, 92, 22 }, { 39, 47, 37 }, { 67, 58, 53 }, { 19, 76, 48 }, { 44, 90, 22 }, { 72, 98, 19 }, { 79, 20, 30 }, { 70, 74, 36 }, { 66, 63, 40 }, { 36, 70, 53 }, { 35, 30, 14 }, { 46, 42, 29 }, { 85, 93, 58 }, { 31, 63, 57 }, { 36, 18, 42 }, { 45, 99, 36 }, { 88, 53, 58 }, { 66, 78, 59 }, { 86, 13, 16 }, { 33, 49, 36 }, { 86, 55, 24 }, { 68, 76, 26 }, { 48, 23, 30 }, { 100, 5, 33 }, { 65, 5, 56 }, { 23, 46, 38 }, { 10, 81, 31 }, { 77, 98, 15 }, { 25, 78, 21 }, { 28, 4, 22 }, { 35, 55, 16 }, { 71, 65, 58 }, { 81, 56, 21 }, { 89, 80, 25 }, { 97, 64, 46 }, { 36, 79, 22 }, { 40, 20, 39 }, { 66, 67, 47 }, { 70, 21, 48 }, { 57, 30, 21 }, { 60, 38, 50 }, { 28, 34, 10 }, { 90, 30, 13 }, { 100, 85, 32 }, { 49, 52, 57 }, { 45, 69, 49 }, { 29, 68, 37 }, { 88, 63, 15 }, { 64, 96, 27 }, { 3, 98, 37 }, { 69, 91, 59 }, { 69, 89, 38 }, { 89, 38, 28 }, { 43, 54, 59 }, { 4, 73, 59 }, { 94, 49, 50 }, { 80, 27, 32 }, { 86, 92, 31 }, { 24, 54, 37 }, { 100, 82, 21 }, { 16, 33, 11 }, { 42, 8, 41 }, { 68, 89, 11 }, { 51, 74, 43 }, { 16, 1, 21 }, { 41, 88, 37 }, { 16, 23, 32 }, { 25, 49, 51 }, { 58, 50, 14 }, { 94, 46, 18 }, { 43, 93, 13 }, { 18, 12, 49 }, { 46, 82, 38 }, { 90, 99, 16 }, { 54, 13, 17 }, { 98, 57, 35 }, { 9, 72, 33 }, { 32, 64, 45 }, { 2, 4, 12 }, { 75, 15, 16 }, { 76, 18, 13 }, { 49, 72, 30 }, { 56, 75, 42 }, { 11, 18, 41 }, { 76, 50, 39 }, { 49, 30, 20 }, { 23, 76, 49 }, { 25, 6, 26 }, { 81, 66, 14 }, { 100, 53, 25 }, { 32, 39, 59 }, { 54, 93, 34 }, { 30, 44, 40 }, { 29, 24, 27 }, { 67, 31, 52 }, { 94, 38, 23 }, { 67, 69, 22 }, { 61, 9, 32 }, { 58, 88, 49 }, { 89, 61, 39 }, { 58, 84, 32 }, { 66, 50, 56 }, { 95, 73, 17 }, { 63, 51, 20 }, { 10, 59, 38 }, { 87, 58, 44 }, { 25, 57, 55 }, { 43, 88, 39 }, { 99, 24, 30 }, { 28, 5, 27 }, { 9, 44, 50 }, { 17, 16, 41 }, { 69, 73, 34 }, { 98, 29, 56 }, { 44, 97, 17 }, { 9, 23, 17 }, { 75, 28, 12 }, { 68, 41, 43 }, { 86, 67, 24 }, { 65, 44, 17 }, { 17, 82, 17 }, { 5, 41, 35 }, { 35, 73, 18 }, { 64, 26, 46 }, { 25, 66, 23 }, { 88, 55, 30 }, { 99, 22, 24 }, { 83, 26, 11 }, { 78, 88, 16 }, { 50, 25, 27 }, { 1, 96, 36 }, { 45, 48, 40 }, { 21, 3, 17 }, { 84, 31, 11 }, { 84, 65, 40 }, { 24, 100, 35 }, { 94, 73, 14 }, { 29, 70, 39 }, { 57, 6, 54 }, { 80, 99, 32 }, { 52, 96, 12 }, { 19, 16, 15 }, { 10, 47, 49 }, { 82, 56, 29 }, { 5, 86, 13 }, { 21, 13, 30 }, { 26, 39, 31 }, { 94, 97, 35 }, { 10, 62, 29 }, { 7, 28, 40 }, { 83, 6, 24 }, { 72, 92, 24 }, { 95, 93, 48 }, { 48, 94, 56 }, { 76, 94, 43 }, { 49, 28, 47 }, { 74, 87, 52 }, { 53, 3, 16 }, { 89, 32, 29 }, { 41, 33, 20 }, { 12, 94, 30 }, { 34, 91, 17 }, { 86, 12, 16 }, { 96, 67, 35 }, { 44, 70, 33 }, { 60, 50, 37 }, { 25, 21, 38 }, { 34, 89, 49 }, { 17, 9, 27 }, { 27, 99, 11 }, { 62, 38, 15 }, { 35, 12, 49 }, { 3, 58, 54 }, { 66, 7, 53 }, { 17, 57, 56 }, { 90, 53, 18 }, { 47, 1, 43 }, { 11, 5, 14 }, { 78, 45, 17 }, { 68, 52, 55 }, { 61, 100, 41 }, { 46, 57, 47 }, { 100, 66, 41 }, { 40, 73, 21 }, { 95, 84, 14 }, { 29, 46, 41 }, { 86, 57, 39 }, { 99, 55, 50 }, { 64, 70, 35 }, { 9, 6, 20 }, { 32, 47, 24 }, { 84, 57, 46 }, { 74, 60, 55 }, { 87, 78, 14 }, { 7, 43, 28 }, { 16, 74, 50 }, { 11, 54, 57 }, { 68, 92, 32 }, { 1, 41, 53 }, { 57, 48, 14 }, { 37, 96, 54 }, { 96, 25, 55 }, { 43, 53, 34 }, { 60, 8, 24 }, { 37, 2, 25 }, { 87, 16, 48 }, { 92, 56, 48 }, { 69, 2, 55 }, { 39, 38, 16 }, { 2, 29, 54 }, { 18, 37, 22 }, { 90, 97, 32 }, { 10, 33, 18 }, { 22, 20, 19 }, { 34, 63, 15 }, { 64, 87, 11 }, { 31, 9, 27 }, { 71, 81, 47 }, { 1, 18, 20 }, { 71, 96, 27 }, { 34, 44, 43 }, { 60, 40, 10 }, { 67, 50, 43 }, { 85, 68, 26 }, { 33, 99, 44 }, { 87, 25, 43 }, { 82, 24, 41 }, { 72, 87, 20 }, { 77, 66, 29 }, { 82, 12, 13 }, { 46, 21, 35 }, { 65, 70, 16 }, { 23, 83, 25 }, { 54, 37, 28 }, { 42, 97, 22 }, { 21, 14, 40 }, { 16, 14, 37 }, { 84, 33, 53 }, { 92, 82, 13 }, { 48, 91, 29 }, { 30, 24, 51 }, { 44, 45, 25 }, { 28, 44, 18 }, { 76, 93, 24 }, { 67, 49, 18 }, { 37, 35, 38 }, { 55, 22, 38 }, { 77, 63, 31 }, { 92, 77, 53 }, { 99, 21, 16 }, { 2, 33, 20 }, { 91, 5, 37 }, { 28, 79, 43 }, { 95, 54, 16 }, { 15, 28, 10 }, { 14, 19, 56 }, { 60, 98, 23 }, { 93, 59, 35 }, { 12, 8, 22 }, { 49, 20, 46 }, { 39, 62, 14 }, { 52, 80, 46 }, { 57, 68, 46 }, { 35, 58, 11 }, { 99, 77, 49 }, { 99, 95, 43 }, { 74, 9, 13 }, { 55, 65, 30 }, { 6, 71, 25 }, { 10, 15, 19 }, { 83, 37, 41 }, { 43, 44, 27 }, { 87, 54, 46 }, { 72, 56, 46 }, { 41, 43, 28 }, { 85, 91, 29 }, { 90, 33, 56 }, { 38, 44, 26 }, { 16, 53, 18 }, { 82, 55, 23 }, { 62, 59, 47 }, { 94, 32, 59 }, { 98, 18, 23 }, { 87, 92, 45 }, { 40, 7, 39 }, { 99, 9, 33 }, { 10, 97, 43 }, { 92, 37, 12 }, { 9, 58, 42 }, { 29, 18, 28 }, { 89, 12, 32 }, { 75, 99, 43 }, { 42, 15, 16 }, { 20, 25, 25 }, { 50, 79, 30 }, { 30, 93, 10 }, { 16, 64, 45 }, { 13, 47, 10 }, { 47, 34, 42 }, { 76, 79, 44 }, { 7, 97, 29 }, { 15, 85, 28 }, { 98, 66, 12 }, { 17, 49, 40 }, { 84, 69, 33 }, { 80, 44, 16 }, { 90, 68, 52 }, { 15, 25, 41 }, { 75, 36, 48 }, { 94, 3, 28 }, { 74, 42, 45 }, { 58, 80, 26 }, { 86, 91, 18 }, { 77, 86, 55 }, { 15, 1, 11 }, { 78, 35, 39 }, { 15, 39, 21 }, { 95, 17, 27 }, { 99, 34, 44 }, { 96, 91, 41 }, { 1, 88, 30 }, { 49, 10, 27 }, { 59, 1, 36 }, { 33, 63, 41 }, { 24, 79, 41 }, { 44, 62, 25 }, { 84, 40, 49 }, { 16, 60, 26 }, { 99, 47, 39 }, { 69, 55, 10 }, { 26, 11, 55 }, { 40, 97, 34 }, { 6, 11, 31 }, { 14, 54, 54 }, { 23, 81, 26 }, { 18, 53, 58 }, { 80, 69, 38 }, { 52, 32, 13 }, { 74, 48, 12 }, { 12, 17, 55 }, { 57, 38, 57 }, { 96, 82, 30 }, { 9, 13, 27 }, { 21, 37, 42 }, { 81, 36, 17 }, { 77, 31, 18 }, { 97, 38, 19 }, { 61, 16, 17 }, { 80, 40, 14 }, { 49, 44, 16 }, { 10, 38, 35 }, { 63, 84, 26 }, { 87, 37, 36 }, { 18, 92, 21 }, { 33, 62, 25 }, { 76, 8, 25 }, { 49, 56, 46 }, { 16, 54, 39 }, { 16, 7, 46 }, { 16, 55, 19 }, { 19, 43, 43 }, { 71, 79, 18 }, { 70, 15, 13 }, { 52, 99, 37 }, { 22, 45, 44 }, { 2, 88, 47 }, { 28, 97, 55 }, { 8, 90, 14 }, { 17, 7, 55 }, { 14, 38, 10 }, { 91, 32, 19 }, { 31, 78, 40 }, { 14, 95, 18 }, { 67, 74, 16 }, { 58, 47, 17 }, { 60, 28, 23 }, { 41, 13, 24 }, { 71, 13, 49 }, { 10, 99, 46 }, { 10, 3, 26 }, { 34, 24, 20 }, { 61, 30, 56 }, { 79, 47, 19 }, { 32, 30, 37 }, { 51, 65, 21 }, { 63, 68, 50 }, { 98, 4, 14 }, { 72, 59, 23 }, { 100, 56, 16 }, { 10, 74, 16 }, { 3, 8, 21 }, { 54, 36, 31 } }; return constructUndirectedGraph(edges); } public Graph getUndirectedN9() { int[][] edges = { { 1, 2, 0 }, { 2, 3, 1 }, { 3, 4, 0 }, { 4, 1, 1 }, { 1, 5, 1 }, { 4, 5, 1 }, { 6, 2, 1 }, { 3, 6, 1 } }; return constructUndirectedGraph(edges); } public Graph generateUndirectedGraph() { GraphGenerator randomGraphGenerator = new GnmRandomGraphGenerator<>(100, 500); Random rand = new Random(); SimpleWeightedGraph undirectedGraph = new SimpleWeightedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); randomGraphGenerator.generateGraph(undirectedGraph); undirectedGraph .edgeSet().stream().forEach(e -> undirectedGraph.setEdgeWeight(e, rand.nextInt(100))); return undirectedGraph; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/MinimumSourceSinkCutTest.java000066400000000000000000000174401402514743400334400ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Joris Kinable */ public abstract class MinimumSourceSinkCutTest extends MaximumFlowMinimumCutAlgorithmTestBase { public static final int NR_RANDOM_TESTS = 500; abstract MinimumSTCutAlgorithm createSolver( Graph network); private void runTestDirected( Graph network, int source, int sink, double expectedCutWeight) { network.addVertex(source); network.addVertex(sink); MinimumSTCutAlgorithm mc = createSolver(network); double cutWeight = mc.calculateMinCut(source, sink); Set sourcePartition = mc.getSourcePartition(); Set sinkPartition = mc.getSinkPartition(); Set cutEdges = mc.getCutEdges(); this .verifyDirected( network, source, sink, expectedCutWeight, cutWeight, sourcePartition, sinkPartition, cutEdges); } void verifyDirected( Graph network, int source, int sink, double expectedCutWeight, double cutWeight, Set sourcePartition, Set sinkPartition, Set cutEdges) { assertEquals(expectedCutWeight, cutWeight, 0); assertTrue(sourcePartition.contains(source)); assertTrue(sinkPartition.contains(sink)); assertTrue(Collections.disjoint(sourcePartition, sinkPartition)); Set unionSet = new HashSet<>(sourcePartition); unionSet.addAll(sinkPartition); unionSet.removeAll(network.vertexSet()); assertTrue(unionSet.isEmpty()); assertEquals( network .edgeSet().stream() .filter( e -> sourcePartition.contains(network.getEdgeSource(e)) && sinkPartition.contains(network.getEdgeTarget(e))) .collect(Collectors.toSet()), cutEdges); assertEquals(cutWeight, cutEdges.stream().mapToDouble(network::getEdgeWeight).sum(), 0); } private void runTestUndirected( Graph network, int source, int sink, double expectedCutWeight) { MinimumSTCutAlgorithm mc = createSolver(network); double cutWeight = mc.calculateMinCut(source, sink); Set sourcePartition = mc.getSourcePartition(); Set sinkPartition = mc.getSinkPartition(); Set cutEdges = mc.getCutEdges(); this .verifyUndirected( network, source, sink, expectedCutWeight, cutWeight, sourcePartition, sinkPartition, cutEdges); } void verifyUndirected( Graph network, int source, int sink, double expectedCutWeight, double cutWeight, Set sourcePartition, Set sinkPartition, Set cutEdges) { assertEquals(expectedCutWeight, cutWeight, 0); assertTrue(sourcePartition.contains(source)); assertTrue(sinkPartition.contains(sink)); assertTrue(Collections.disjoint(sourcePartition, sinkPartition)); Set unionSet = new HashSet<>(sourcePartition); unionSet.addAll(sinkPartition); unionSet.removeAll(network.vertexSet()); assertTrue(unionSet.isEmpty()); assertEquals( network .edgeSet().stream() .filter( e -> sourcePartition.contains(network.getEdgeSource(e)) ^ sourcePartition.contains(network.getEdgeTarget(e))) .collect(Collectors.toSet()), cutEdges); assertEquals(cutWeight, cutEdges.stream().mapToDouble(network::getEdgeWeight).sum(), 0); } @Test public void testProblematicCase() { Graph network = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(network, 1, 2, 0); Graphs.addEdgeWithVertices(network, 1, 4, 1); Graphs.addEdgeWithVertices(network, 1, 5, 1); Graphs.addEdgeWithVertices(network, 4, 5, 1); Graphs.addEdgeWithVertices(network, 2, 3, 1); Graphs.addEdgeWithVertices(network, 2, 6, 1); Graphs.addEdgeWithVertices(network, 3, 6, 1); Graphs.addEdgeWithVertices(network, 3, 4, 0); runTestUndirected(network, 1, 6, 0); } @Test public void testDirectedN0() { runTestDirected(getDirectedN0(), 1, 4, 5.0); } @Test public void testDirectedN1() { runTestDirected(getDirectedN1(), 1, 4057218, 0.0); } @Test public void testDirectedN2() { runTestDirected(getDirectedN2(), 3, 6, 2.0); } @Test public void testDirectedN3() { runTestDirected(getDirectedN3(), 5, 6, 4.0); } @Test public void testDirectedN4() { runTestDirected(getDirectedN4(), 1, 4, 2000000000.0); } @Test public void testDirectedN6() { runTestDirected(getDirectedN6(), 1, 50, 20.0); } @Test public void testDirectedN7() { runTestDirected(getDirectedN7(), 1, 50, 31.0); } @Test public void testDirectedN8() { runTestDirected(getDirectedN8(), 0, 5, 23.0); } @Test public void testDirectedN9() { runTestDirected(getDirectedN9(), 0, 8, 22.0); } @Test public void testDirectedN10() { runTestDirected(getDirectedN10(), 1, 99, 173.0); } @Test public void testDirectedN11() { runTestDirected(getDirectedN11(), 1, 99, 450.0); } @Test public void testDirectedN12() { runTestDirected(getDirectedN12(), 1, 99, 203.0); } /*************** TEST CASES FOR UNDIRECTED GRAPHS ***************/ @Test public void testUndirectedN1() { runTestUndirected(getUndirectedN1(), 0, 8, 28); } @Test public void testUndirectedN2() { runTestUndirected(getUndirectedN2(), 1, 4, 93); } @Test public void testUndirectedN3() { runTestUndirected(getUndirectedN3(), 1, 49, 104); } @Test public void testUndirectedN4() { runTestUndirected(getUndirectedN4(), 1, 99, 634); } @Test public void testUndirectedN5() { runTestUndirected(getUndirectedN5(), 1, 49, 112); } @Test public void testUndirectedN6() { runTestUndirected(getUndirectedN6(), 1, 69, 194); } @Test public void testUndirectedN7() { runTestUndirected(getUndirectedN7(), 1, 69, 33); } @Test public void testUndirectedN8() { runTestUndirected(getUndirectedN8(), 1, 99, 501); } @Test public void testUndirectedN9() { runTestUndirected(getUndirectedN9(), 1, 2, 0); } } PadbergRaoOddMinimumCutsetTest.java000066400000000000000000000221551402514743400344440ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.*; /** * Test class for the PadbergRaoOddMinimumCutset implementation * * @author Joris Kinable */ public class PadbergRaoOddMinimumCutsetTest { private void runTest( SimpleWeightedGraph network, Set oddVertices, boolean useTreeCompression) { PadbergRaoOddMinimumCutset padbergRaoOddMinimumCutset = new PadbergRaoOddMinimumCutset<>(network); double cutValue = padbergRaoOddMinimumCutset.calculateMinCut(oddVertices, useTreeCompression); Set sourcePartition = padbergRaoOddMinimumCutset.getSourcePartition(); Set sinkPartition = padbergRaoOddMinimumCutset.getSinkPartition(); Set cutEdges = padbergRaoOddMinimumCutset.getCutEdges(); Set intersection = new HashSet<>(sourcePartition); intersection.retainAll(sinkPartition); assertTrue(intersection.isEmpty()); Set union = new HashSet<>(sourcePartition); union.addAll(sinkPartition); assertEquals(network.vertexSet(), union); assertTrue(PadbergRaoOddMinimumCutset.isOddVertexSet(sourcePartition, oddVertices)); assertTrue(PadbergRaoOddMinimumCutset.isOddVertexSet(sinkPartition, oddVertices)); Set expectedCutEdges = network .edgeSet().stream() .filter( e -> sourcePartition.contains(network.getEdgeSource(e)) ^ sourcePartition.contains(network.getEdgeTarget(e))) .collect(Collectors.toSet()); assertEquals(expectedCutEdges, cutEdges); double expectedWeight = cutEdges.stream().mapToDouble(network::getEdgeWeight).sum(); assertEquals(expectedWeight, cutValue, 0); // Verify whether the returned odd cut-set is indeed of minimum weight. To verify this, we // exhaustively iterate over all possible cutsets. GusfieldGomoryHuCutTree gusfieldGomoryHuCutTreeAlgorithm = new GusfieldGomoryHuCutTree<>(network); SimpleWeightedGraph gomoryHuCutTree = gusfieldGomoryHuCutTreeAlgorithm.getGomoryHuTree(); Set edges = new LinkedHashSet<>(gomoryHuCutTree.edgeSet()); boolean foundBest = false; // Just to make sure that our brute-force approach is exhaustive for (DefaultWeightedEdge edge : edges) { Integer source = gomoryHuCutTree.getEdgeSource(edge); Integer target = gomoryHuCutTree.getEdgeTarget(edge); double edgeWeight = gomoryHuCutTree.getEdgeWeight(edge); gomoryHuCutTree.removeEdge(edge); // Temporarily remove edge Set partition = new ConnectivityInspector<>(gomoryHuCutTree).connectedSetOf(source); if (PadbergRaoOddMinimumCutset.isOddVertexSet(partition, oddVertices)) { // If the // source // partition forms an // odd cutset, check // whether the cut // isn't better than // the one we already // found. assertTrue(cutValue <= edgeWeight); foundBest |= cutValue == edgeWeight; } gomoryHuCutTree.addEdge(source, target, edge); // Place edge back } assertTrue(foundBest); } @Test public void testIsOddSetMethod() { Set vertices = Set.of(1, 2, 3, 4, 5, 6); Set oddVertices1 = Set.of(1, 2, 3, 7); Set oddVertices2 = Set.of(1, 2, 3, 4); assertTrue(PadbergRaoOddMinimumCutset.isOddVertexSet(vertices, oddVertices1)); assertFalse(PadbergRaoOddMinimumCutset.isOddVertexSet(vertices, oddVertices2)); } /** * Test the example graph from the paper Odd Minimum Cut-Sets and b-Matchings by Padberg and Rao */ @Test public void testExampleGraph() { SimpleWeightedGraph network = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(1, 2, 3, 4, 5, 6)); Graphs.addEdge(network, 1, 2, 10); Graphs.addEdge(network, 1, 6, 8); Graphs.addEdge(network, 2, 6, 3); Graphs.addEdge(network, 2, 3, 4); Graphs.addEdge(network, 2, 5, 2); Graphs.addEdge(network, 6, 3, 2); Graphs.addEdge(network, 6, 4, 2); Graphs.addEdge(network, 6, 5, 3); Graphs.addEdge(network, 5, 3, 4); Graphs.addEdge(network, 5, 4, 7); Graphs.addEdge(network, 3, 4, 5); Set oddVertices = Set.of(2, 3, 5, 6); this.runTest(network, oddVertices, true); this.runTest(network, oddVertices, false); } /** * Test disconnected graph */ @Test public void testDisconnectedGraph() { SimpleWeightedGraph network = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(0, 1, 2, 3, 4)); Graphs.addEdge(network, 0, 1, 3); Graphs.addEdge(network, 1, 2, 4); Graphs.addEdge(network, 0, 2, 7); Graphs.addEdge(network, 3, 4, 9); Set oddVertices = Set.of(0, 1, 2, 4); this.runTest(network, oddVertices, true); this.runTest(network, oddVertices, false); } /** * Another graph to test */ @Test public void testGraph() { SimpleWeightedGraph network = new SimpleWeightedGraph(DefaultWeightedEdge.class); network.addVertex(7); network.addVertex(10); network.addVertex(12); network.addVertex(3); network.addVertex(1); network.addVertex(5); network.addVertex(6); Graphs.addEdge(network, 1, 12, 1.0); Graphs.addEdge(network, 3, 5, 1.0); Graphs.addEdge(network, 5, 6, 1.0); Graphs.addEdge(network, 6, 12, 4.0); Set oddVertices = new LinkedHashSet(Arrays.asList(7, 10, 12, 3)); this.runTest(network, oddVertices, true); this.runTest(network, oddVertices, false); } /** * Test random graphs */ @Test public void testRandomGraphs() { Random rand = new Random(0); for (int i = 0; i < 8; i++) { SimpleWeightedGraph randomGraph = new SimpleWeightedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); int vertices = rand.nextInt((30 - 10) + 1) + 10; // 10-30 vertices double p = 0.01 * (rand.nextInt((85 - 50) + 1) + 50); // p=[0.5;0.85] GnpRandomGraphGenerator graphGen = new GnpRandomGraphGenerator<>(vertices, p); graphGen.generateGraph(randomGraph); for (DefaultWeightedEdge edge : randomGraph.edgeSet()) randomGraph.setEdgeWeight(edge, rand.nextInt(150)); for (int j = 0; j < 8; j++) { // Select a random subset of vertices of even cardinality. These will be the 'odd' // vertices. int max = vertices - 1; int min = 2; if (max % 2 == 1) --max; int nrOfOddVertices = min + 2 * (int) (rand.nextDouble() * ((max - min) / 2 + 1)); // even // number // between // 2 // and // |V|-1 Set oddVertices = CollectionUtil.newLinkedHashSetWithExpectedSize(nrOfOddVertices); List allVertices = new ArrayList<>(randomGraph.vertexSet()); for (int k = 0; k < nrOfOddVertices; k++) { oddVertices.add(allVertices.remove(rand.nextInt(allVertices.size()))); } this.runTest(randomGraph, oddVertices, true); this.runTest(randomGraph, oddVertices, false); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/PushRelabelMFImplTest.java000066400000000000000000000055521402514743400326170ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import static org.junit.Assert.assertEquals; public class PushRelabelMFImplTest extends MaximumFlowAlgorithmTest { @Override MaximumFlowAlgorithm createSolver( Graph network) { return new PushRelabelMFImpl<>(network); } @Test public void testSimpleDirectedWeightedGraph() { SimpleDirectedWeightedGraph graph = new SimpleDirectedWeightedGraph<>(DefaultEdge.class); graph.addVertex(-1); graph.addVertex(-2); graph.addVertex(0); graph.addVertex(1); graph.addEdge(-1, 0); graph.setEdgeWeight(graph.getEdge(-1, 0), 1.0); graph.addEdge(0, -2); graph.setEdgeWeight(graph.getEdge(0, -2), 0.9999999999999999); graph.addEdge(-1, 1); graph.setEdgeWeight(graph.getEdge(-1, 1), 1.0); graph.addEdge(1, -2); graph.setEdgeWeight(graph.getEdge(1, -2), 1.66498); graph.addEdge(0, 1); graph.setEdgeWeight(graph.getEdge(0, 1), 0.66498); graph.addEdge(1, 0); graph.setEdgeWeight(graph.getEdge(1, 0), 0.66498); PushRelabelMFImpl mf = new PushRelabelMFImpl<>(graph); Assert.assertEquals(2.0, mf.calculateMinCut(-1, -2), 1e-9); } @Test public void testPushRelabelWithNonIdenticalNode() { SimpleDirectedGraph g1 = new SimpleDirectedGraph(DefaultEdge.class); g1.addVertex("v0"); g1.addVertex("v1"); g1.addVertex("v2"); g1.addVertex("v3"); g1.addVertex("v4"); g1.addEdge("v0", "v2"); g1.addEdge("v3", "v4"); g1.addEdge("v1", "v0"); g1.addEdge("v0", "v4"); g1.addEdge("v0", "v1"); g1.addEdge("v2", "v1"); MaximumFlowAlgorithm mf1 = new PushRelabelMFImpl<>(g1); String sourceFlow = "v3"; String sinkFlow = "v0"; double flow = mf1.getMaximumFlowValue(sourceFlow, sinkFlow); assertEquals(0.0, flow, 0); } } PushRelabelMinimumSTCutTest.java000066400000000000000000000110751402514743400337470ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * @author Joris Kinable */ public class PushRelabelMinimumSTCutTest extends MinimumSourceSinkCutTest { @Override MinimumSTCutAlgorithm createSolver( Graph network) { return new PushRelabelMFImpl<>(network); } @Test public void testDisconnected1() { SimpleDirectedWeightedGraph network = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(0, 1, 2, 3, 4, 5)); network.addEdge(2, 4); network.addEdge(3, 4); network.addEdge(1, 4); network.addEdge(0, 1); network.addEdge(2, 0); network.addEdge(1, 0); network.addEdge(4, 0); network.addEdge(4, 1); network.addEdge(1, 3); network.addEdge(4, 3); MinimumSTCutAlgorithm prSolver = this.createSolver(network); double cutWeight = prSolver.calculateMinCut(0, 5); assertEquals(0d, cutWeight, 0); } @Test public void testDisconnected2() { SimpleDirectedWeightedGraph network = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(network, Arrays.asList(0, 1, 2)); network.addEdge(0, 1); MinimumSTCutAlgorithm prSolver = this.createSolver(network); double cutWeight = prSolver.calculateMinCut(0, 2); assertEquals(0d, cutWeight, 0); } @Test public void testRandomDirectedGraphs() { for (int test = 0; test < NR_RANDOM_TESTS; test++) { Graph network = generateDirectedGraph(); int source = 0; int sink = network.vertexSet().size() - 1; MinimumSTCutAlgorithm prSolver = this.createSolver(network); MinimumSTCutAlgorithm ekSolver = new EdmondsKarpMFImpl<>(network); double expectedCutWeight = ekSolver.calculateMinCut(source, sink); double cutWeight = prSolver.calculateMinCut(source, sink); Set sourcePartition = prSolver.getSourcePartition(); Set sinkPartition = prSolver.getSinkPartition(); Set cutEdges = prSolver.getCutEdges(); this .verifyDirected( network, source, sink, expectedCutWeight, cutWeight, sourcePartition, sinkPartition, cutEdges); } } @Test public void testRandomUndirectedGraphs() { for (int test = 0; test < NR_RANDOM_TESTS; test++) { Graph network = generateUndirectedGraph(); int source = 0; int sink = network.vertexSet().size() - 1; MinimumSTCutAlgorithm prSolver = this.createSolver(network); MinimumSTCutAlgorithm ekSolver = new EdmondsKarpMFImpl<>(network); double expectedCutWeight = ekSolver.calculateMinCut(source, sink); double cutWeight = prSolver.calculateMinCut(source, sink); Set sourcePartition = prSolver.getSourcePartition(); Set sinkPartition = prSolver.getSinkPartition(); Set cutEdges = prSolver.getCutEdges(); this .verifyUndirected( network, source, sink, expectedCutWeight, cutWeight, sourcePartition, sinkPartition, cutEdges); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/mincost/000077500000000000000000000000001402514743400273065ustar00rootroot00000000000000CapacityScalingMinimumCostFlowTest.java000066400000000000000000003355711402514743400370230ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/flow/mincost/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.flow.mincost; import org.jgrapht.*; import org.jgrapht.alg.interfaces.MinimumCostFlowAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import org.junit.runner.*; import org.junit.runners.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit tests for {@link CapacityScalingMinimumCostFlow} * * @author Timofey Chudakov */ @RunWith(Parameterized.class) public class CapacityScalingMinimumCostFlowTest { private static final double EPS = 1e-9; private int scalingFactor; public CapacityScalingMinimumCostFlowTest(int scalingFactor) { this.scalingFactor = scalingFactor; } @Parameterized.Parameters public static Object[] params() { return new Integer[] { 1, 2, 3, 4, 5 }; } @Test public void testGetMinimumCostFlow1() { int[][] testCase = new int[][] { { 1, 3 }, { 2, -3 }, { 1, 2, 0, 4, 5 } }; test(testCase, 15); } @Test public void testGetMinimumCostFlow2() { int[][] testCase = new int[][] { { 1, 4 }, { 4, -4 }, { 1, 2, 0, 4, 2 }, { 1, 3, 0, 1, 3 }, { 2, 3, 0, 1, 1 }, { 2, 4, 0, 5, 6 }, { 3, 4, 0, 4, 2 } }; test(testCase, 26); } @Test public void testGetMinimumCostFlow3() { int[][] testCase = new int[][] { { 1, 2 }, { 2, 5 }, { 6, -7 }, { 1, 5, 0, 3, 6 }, { 3, 6, 0, 3, 9 }, { 3, 1, 0, 3, 6 }, { 5, 3, 0, 3, 4 }, { 5, 6, 0, 7, 4 }, { 2, 4, 0, 5, 10 }, { 2, 3, 0, 1, 3 }, { 4, 6, 0, 5, 10 }, { 4, 1, 0, 5, 3 }, { 4, 3, 0, 1, 8 }, }; test(testCase, 112); } /** * Test case generated with NETGEN generator params: vertices = 6, edges = 12, sources = 2, * sinks = 2, supply = 10, min. capacity = 1, max. capacity = 10, min. cost = 1, max. cost = 10, * capacitated = 50%, seed = 1 */ @Test public void testGetMinimumCostFlow4() { int testCase[][] = new int[][] { { 1, 2731 }, { 2, 414 }, { 3, -1264 }, { 4, 216 }, { 5, -1785 }, { 6, -312 }, { 1, 4, 910, 2147483647, 10 }, { 1, 5, 957, 2147483647, 1 }, { 1, 3, 863, 2147483647, 3 }, { 3, 5, 1, 1, -5 }, { 3, 6, 1, 1, 10 }, { 3, 4, 1, 9, 2 }, { 4, 3, 1, 1, 8 }, { 4, 5, 820, 2147483647, 4 }, { 4, 6, 306, 2147483647, 7 }, { 2, 5, 1, 9, 7 }, { 2, 6, 1, 9, 10 }, { 2, 3, 403, 2147483647, 6 }, }; test(testCase, 20594); } /** * Test case generated with NETGEN generator params: vertices = 8, edges = 16, sources = 2, * sinks = 2, supply = 15, min. capacity = 1, max. capacity = 10, min. cost = 1, max. cost = 10, * capacitated = 50%, seed = 1 */ @Test public void testGetMinimumCostFlow5() { int testCase[][] = new int[][] { { 1, 635 }, { 2, 980 }, { 3, 1658 }, { 4, -462 }, { 5, -821 }, { 6, -1123 }, { 7, -13 }, { 8, -854 }, { 1, 5, 625, 2147483647, 10 }, { 1, 7, 1, 8, 2 }, { 3, 4, 805, 2147483647, 10 }, { 3, 6, 855, 2147483647, 3 }, { 4, 8, 847, 2147483647, 5 }, { 4, 7, 1, 9, 9 }, { 4, 5, 197, 2147483647, 4 }, { 5, 3, 1, 9, 1 }, { 5, 7, 1, 4, 3 }, { 2, 6, 973, 2147483647, 10 }, { 2, 4, 1, 4, 10 }, { 6, 7, 1, 6, 10 }, { 6, 8, 1, 6, 8 }, { 6, 3, 1, 6, 2 }, { 6, 5, 1, 10, 5 }, { 6, 4, 701, 2147483647, 2 }, }; test(testCase, 33206); } /** * Test case generated with NETGEN generator params: vertices = 6, edges = 12, sources = 2, * sinks = 2, supply = 10, min. capacity = 1, max. capacity = 10, min. cost = 1, max. cost = 10, * capacitated = 50%, seed = 1 */ @Test public void testGetMinimumCostFlow6() { int testCase[][] = new int[][] { { 1, 10 }, { 2, 6 }, { 3, -211 }, { 4, 506 }, { 5, -10 }, { 6, -301 }, { 1, 3, 1, 7, 10 }, { 1, 5, 1, 2, 4 }, { 1, 4, 1, 1, 6 }, { 3, 4, 1, 7, 3 }, { 3, 6, 295, 2147483647, 10 }, { 3, 5, 1, 8, 1 }, { 4, 5, 1, 7, 10 }, { 4, 3, 506, 2147483647, 8 }, { 4, 6, 1, 1, -10 }, { 2, 5, 1, 3, 2 }, { 2, 6, 1, 3, 10 }, { 2, 3, 1, 10, 10 }, }; test(testCase, 7154); } /** * Test case generated with NETGEN generator params: vertices = 6, edges = 12, sources = 2, * sinks = 2, supply = 10, min. capacity = 1, max. capacity = 10, min. cost = 1, max. cost = 10, * capacitated = 100%, seed = 1 */ @Test public void testGetMinimumCostFlow7() { scalingFactor = 2; int testCase[][] = new int[][] { { 1, 7 }, { 2, 1430 }, { 3, -1350 }, { 4, 840 }, { 5, -499 }, { 6, -428 }, { 1, 3, 1, 4, 10 }, { 1, 4, 1, 10, 10 }, { 1, 5, 1, 10, 9 }, { 3, 4, 1, 4, 10 }, { 3, 6, 1, 1, -3 }, { 3, 5, 489, 2147483647, 6 }, { 4, 6, 1, 4, 10 }, { 4, 5, 1, 4, 1 }, { 4, 3, 840, 2147483647, 10 }, { 2, 6, 423, 2147483647, 10 }, { 2, 5, 1, 6, 10 }, { 2, 3, 1000, 2147483647, 9 }, }; test(testCase, 24717); } /** * Test case generated with NETGEN generator params: vertices = 6, edges = 12, sources = 2, * sinks = 2, supply = 10, min. capacity = 1, max. capacity = 10, min. cost = 1, max. cost = 10, * capacitated = 100%, seed = 1 */ @Test public void testGetMinimumCostFlow8() { int testCase[][] = new int[][] { { 1, 7 }, { 2, 1294 }, { 3, -332 }, { 4, 265 }, { 5, -460 }, { 6, -774 }, { 1, 3, 1, 4, 10 }, { 1, 4, 1, 10, 10 }, { 1, 5, 1, 10, 9 }, { 3, 4, 1, 4, 10 }, { 3, 6, 1, 8, 3 }, { 3, 5, 450, 2147483647, 6 }, { 4, 6, 1, 4, -10 }, { 4, 5, 1, 4, 1 }, { 4, 3, 265, 2147483647, 10 }, { 2, 6, 769, 2147483647, 10 }, { 2, 5, 1, 6, 10 }, { 2, 3, 518, 2147483647, 9 }, }; test(testCase, 17819); } /** * Test case generated with NETGEN generator params: vertices = 6, edges = 12, sources = 2, * sinks = 2, supply = 10, min. capacity = 1, max. capacity = 10, min. cost = 1, max. cost = 10, * capacitated = 50%, seed = 1 */ @Test public void testGetMinimumCostFlow9() { int testCase[][] = new int[][] { { 1, 7 }, { 2, 763 }, { 3, -637 }, { 4, 164 }, { 5, -138 }, { 6, -159 }, { 1, 3, 1, 4, 10 }, { 1, 4, 1, 2, -10 }, { 1, 5, 1, 10, 9 }, { 3, 4, 1, 4, 10 }, { 3, 6, 1, 8, 3 }, { 3, 5, 128, 2147483647, 6 }, { 4, 6, 1, 4, 10 }, { 4, 5, 1, 4, 1 }, { 4, 3, 164, 2147483647, 10 }, { 2, 6, 154, 2147483647, 10 }, { 2, 5, 1, 6, 10 }, { 2, 3, 602, 2147483647, 9 }, }; test(testCase, 9487); } /** * Test case generated with NETGEN generator params: vertices = 10, edges = 30, sources = 3, * sinks = 3, supply = 30, min. capacity = 1, max. capacity = 50, min. cost = 1, max. cost = 50, * capacitated = 50%, seed = 268101079 */ @Test public void testGetMinimumCostFlow10() { int testCase[][] = new int[][] { { 1, 8 }, { 2, 16 }, { 3, 6 }, { 8, -16 }, { 9, -6 }, { 10, -8 }, { 1, 5, 0, 2147483647, 50 }, { 1, 7, 0, 5, 32 }, { 1, 9, 0, 18, 33 }, { 1, 8, 0, 2147483647, 31 }, { 1, 4, 0, 45, 35 }, { 5, 6, 0, 8, 50 }, { 5, 10, 0, 2147483647, 2 }, { 5, 8, 0, 11, 18 }, { 5, 7, 0, 21, 44 }, { 5, 9, 0, 2147483647, 22 }, { 5, 4, 0, 35, 48 }, { 6, 8, 0, 8, 19 }, { 6, 9, 0, 2147483647, 20 }, { 6, 4, 0, 2147483647, 47 }, { 6, 5, 0, 26, 22 }, { 6, 10, 0, 9, 5 }, { 6, 7, 0, 47, 46 }, { 2, 4, 0, 2147483647, 50 }, { 2, 7, 0, 2147483647, 17 }, { 2, 9, 0, 25, 28 }, { 2, 6, 0, 8, 3 }, { 4, 7, 0, 2147483647, 18 }, { 4, 10, 0, 16, 33 }, { 4, 8, 0, 2147483647, 6 }, { 4, 9, 0, 12, 13 }, { 7, 9, 0, 16, 50 }, { 7, 8, 0, 2147483647, 50 }, { 3, 10, 0, 6, 21 }, { 3, 8, 0, 6, 50 }, { 3, 7, 0, 33, 37 }, }; test(testCase, 802); } /** * Test case generated with NETGEN generator params: vertices = 10, edges = 30, sources = 3, * sinks = 3, supply = 30, min. capacity = 1, max. capacity = 50, min. cost = 1, max. cost = * 100, capacitated = 50%, seed = 651272247 */ @Test public void testGetMinimumCostFlow11() { int testCase[][] = new int[][] { { 1, 1 }, { 2, 23 }, { 3, 6 }, { 8, -17 }, { 9, -9 }, { 10, -4 }, { 1, 7, 0, 1, 100 }, { 1, 6, 0, 10, 15 }, { 1, 4, 0, 37, 72 }, { 5, 10, 0, 2147483647, 81 }, { 5, 9, 0, 2147483647, 10 }, { 5, 8, 0, 1, 100 }, { 5, 6, 0, 27, 85 }, { 5, 4, 0, 2147483647, 87 }, { 7, 5, 0, 1, 100 }, { 7, 6, 0, 2147483647, 25 }, { 2, 6, 0, 23, 29 }, { 2, 7, 0, 2147483647, 34 }, { 6, 9, 0, 2147483647, 100 }, { 6, 8, 0, 2147483647, 56 }, { 6, 5, 0, 15, 32 }, { 6, 10, 0, 2147483647, 76 }, { 6, 7, 0, 10, 36 }, { 6, 4, 0, 2147483647, 66 }, { 3, 4, 0, 2147483647, 26 }, { 3, 5, 0, 32, 27 }, { 3, 8, 0, 41, 75 }, { 3, 7, 0, 2147483647, 80 }, { 3, 9, 0, 2147483647, 63 }, { 3, 6, 0, 30, 64 }, { 4, 9, 0, 2147483647, 13 }, { 4, 10, 0, 2147483647, 100 }, { 4, 5, 0, 25, 20 }, { 4, 8, 0, 2147483647, 100 }, { 4, 7, 0, 2147483647, 1 }, { 4, 6, 0, 2147483647, 59 }, }; test(testCase, 2286); } /** * Test case generated with NETGEN generator params: vertices = 30, edges = 100, sources = 5, * sinks = 5, supply = 50, min. capacity = 1, max. capacity = 100, min. cost = 1, max. cost = * 100, capacitated = 100%, seed = 1685408561 */ @Test public void testGetMinimumCostFlow12() { int testCase[][] = new int[][] { { 1, 8 }, { 2, 10 }, { 3, 9 }, { 4, 18 }, { 5, 5 }, { 26, -9 }, { 27, -15 }, { 28, -4 }, { 29, -6 }, { 30, -16 }, { 1, 11, 0, 2147483647, 85 }, { 1, 18, 0, 12, 24 }, { 1, 24, 0, 2147483647, 92 }, { 1, 29, 0, 15, 48 }, { 1, 14, 0, 49, 80 }, { 11, 16, 0, 8, 100 }, { 11, 21, 0, 94, 18 }, { 11, 26, 0, 2147483647, 36 }, { 11, 12, 0, 2147483647, 21 }, { 11, 23, 0, 12, 18 }, { 14, 26, 0, 8, 51 }, { 14, 20, 0, 32, 49 }, { 14, 10, 0, 2147483647, 91 }, { 14, 11, 0, 9, 59 }, { 16, 14, 0, 8, 42 }, { 16, 30, 0, 8, 100 }, { 16, 27, 0, 86, 3 }, { 2, 20, 0, 2147483647, 100 }, { 2, 7, 0, 2147483647, 58 }, { 8, 24, 0, 2147483647, 100 }, { 8, 12, 0, 18, 13 }, { 8, 6, 0, 23, 51 }, { 8, 7, 0, 2147483647, 17 }, { 8, 26, 0, 88, 37 }, { 13, 8, 0, 10, 100 }, { 13, 17, 0, 58, 69 }, { 20, 13, 0, 2147483647, 100 }, { 20, 18, 0, 2147483647, 10 }, { 24, 28, 0, 10, 100 }, { 24, 30, 0, 2147483647, 100 }, { 24, 27, 0, 2147483647, 48 }, { 24, 22, 0, 2147483647, 22 }, { 24, 12, 0, 71, 81 }, { 24, 11, 0, 68, 20 }, { 3, 9, 0, 9, 2 }, { 3, 16, 0, 2147483647, 17 }, { 3, 14, 0, 67, 84 }, { 3, 17, 0, 19, 69 }, { 3, 11, 0, 87, 54 }, { 3, 23, 0, 2147483647, 23 }, { 3, 15, 0, 2, 41 }, { 6, 17, 0, 2147483647, 21 }, { 6, 13, 0, 2147483647, 4 }, { 6, 26, 0, 90, 65 }, { 7, 18, 0, 9, 100 }, { 7, 9, 0, 24, 64 }, { 9, 6, 0, 2147483647, 83 }, { 9, 7, 0, 2147483647, 93 }, { 9, 19, 0, 99, 51 }, { 9, 18, 0, 2147483647, 26 }, { 9, 14, 0, 2147483647, 61 }, { 17, 7, 0, 2147483647, 66 }, { 17, 11, 0, 92, 90 }, { 17, 19, 0, 2147483647, 36 }, { 18, 30, 0, 2147483647, 96 }, { 18, 27, 0, 9, 42 }, { 18, 21, 0, 58, 46 }, { 18, 14, 0, 2147483647, 77 }, { 18, 12, 0, 96, 69 }, { 4, 19, 0, 2147483647, 50 }, { 4, 27, 0, 18, 54 }, { 4, 14, 0, 2147483647, 71 }, { 4, 20, 0, 2147483647, 47 }, { 4, 23, 0, 67, 85 }, { 4, 29, 0, 62, 47 }, { 4, 24, 0, 2147483647, 31 }, { 10, 29, 0, 2147483647, 99 }, { 10, 23, 0, 2147483647, 12 }, { 10, 19, 0, 51, 13 }, { 10, 24, 0, 2147483647, 21 }, { 10, 22, 0, 62, 5 }, { 12, 27, 0, 2147483647, 28 }, { 12, 23, 0, 2147483647, 75 }, { 12, 14, 0, 26, 37 }, { 12, 25, 0, 2147483647, 97 }, { 12, 17, 0, 2147483647, 46 }, { 15, 10, 0, 18, 100 }, { 15, 29, 0, 2147483647, 67 }, { 15, 12, 0, 2147483647, 73 }, { 15, 17, 0, 28, 97 }, { 19, 12, 0, 18, 100 }, { 19, 15, 0, 2147483647, 22 }, { 19, 25, 0, 92, 71 }, { 19, 13, 0, 28, 41 }, { 19, 23, 0, 2147483647, 98 }, { 21, 15, 0, 2147483647, 100 }, { 21, 26, 0, 2147483647, 76 }, { 21, 30, 0, 91, 1 }, { 21, 13, 0, 33, 6 }, { 23, 21, 0, 2147483647, 91 }, { 23, 19, 0, 36, 76 }, { 23, 11, 0, 25, 54 }, { 5, 25, 0, 5, 89 }, { 5, 20, 0, 16, 6 }, { 22, 29, 0, 2147483647, 66 }, { 22, 28, 0, 5, 100 }, { 22, 13, 0, 2147483647, 1 }, { 22, 11, 0, 2147483647, 97 }, { 25, 22, 0, 5, 100 }, { 25, 14, 0, 2147483647, 73 }, }; test(testCase, 4067); } /** * Test case generated with NETGEN generator params: vertices = 30, edges = 100, sources = 5, * sinks = 5, supply = 50, min. capacity = 1, max. capacity = 100, min. cost = 1, max. cost = * 100, capacitated = 50%, seed = 843930509 */ @Test public void testGetMinimumCostFlow13() { int testCase[][] = new int[][] { { 1, 1391 }, { 2, 790 }, { 3, 1671 }, { 4, 815 }, { 5, 342 }, { 6, -148 }, { 7, 1254 }, { 8, 848 }, { 9, 169 }, { 10, -282 }, { 11, 978 }, { 12, 956 }, { 13, 127 }, { 14, 493 }, { 15, -1432 }, { 16, 1224 }, { 17, 725 }, { 18, 286 }, { 19, 1092 }, { 20, -1069 }, { 21, -3223 }, { 22, 49 }, { 23, -510 }, { 24, 1927 }, { 25, -757 }, { 26, -495 }, { 27, -179 }, { 28, -1988 }, { 29, -2288 }, { 30, -2766 }, { 1, 10, 494, 2147483647, 61 }, { 1, 15, 1, 29, 3 }, { 1, 22, 1, 31, 34 }, { 1, 19, 884, 2147483647, 13 }, { 10, 21, 1, 11, 80 }, { 10, 13, 455, 2147483647, 97 }, { 10, 16, 19, 2147483647, 80 }, { 10, 18, 417, 2147483647, 23 }, { 10, 23, 673, 2147483647, 83 }, { 11, 16, 1, 11, 9 }, { 11, 29, 502, 2147483647, 100 }, { 11, 20, 1, 29, 14 }, { 11, 21, 364, 2147483647, 70 }, { 11, 27, 112, 2147483647, 37 }, { 16, 30, 348, 2147483647, 21 }, { 16, 23, 1, 37, 74 }, { 16, 15, 912, 2147483647, 9 }, { 16, 22, 31, 2147483647, 43 }, { 16, 7, 599, 2147483647, 65 }, { 21, 11, 1, 11, 100 }, { 21, 27, 1, 15, 5 }, { 21, 16, 1, 72, 33 }, { 21, 15, 1, 55, 14 }, { 2, 14, 779, 2147483647, 100 }, { 2, 24, 1, 36, 69 }, { 6, 13, 816, 2147483647, 71 }, { 6, 16, 541, 2147483647, 27 }, { 6, 10, 1, 64, 48 }, { 6, 26, 1, 2, 82 }, { 13, 23, 418, 2147483647, 100 }, { 13, 15, 978, 2147483647, 45 }, { 13, 7, 1, 27, 29 }, { 13, 11, 1, 98, 75 }, { 13, 6, 1, 7, 75 }, { 14, 19, 364, 2147483647, 100 }, { 14, 26, 1, 47, 62 }, { 14, 24, 1, 39, 77 }, { 14, 22, 907, 2147483647, 49 }, { 19, 6, 999, 2147483647, 40 }, { 19, 27, 1, 10, 100 }, { 19, 7, 1, 33, 76 }, { 19, 25, 966, 2147483647, 30 }, { 19, 20, 375, 2147483647, 91 }, { 23, 29, 77, 2147483647, 100 }, { 23, 12, 1, 40, 93 }, { 23, 6, 505, 2147483647, 95 }, { 3, 24, 1, 10, 100 }, { 3, 27, 46, 2147483647, 51 }, { 3, 28, 758, 2147483647, 38 }, { 3, 9, 856, 2147483647, 36 }, { 8, 30, 967, 2147483647, 29 }, { 8, 17, 1, 10, 40 }, { 8, 22, 1, 42, 7 }, { 8, 9, 606, 2147483647, 19 }, { 17, 18, 1, 10, 31 }, { 17, 20, 725, 2147483647, 4 }, { 18, 20, 629, 2147483647, 100 }, { 18, 28, 884, 2147483647, 31 }, { 18, 10, 1, 43, 49 }, { 18, 27, 1, 16, 4 }, { 20, 27, 1, 10, 100 }, { 20, 30, 661, 2147483647, 55 }, { 22, 8, 726, 2147483647, 100 }, { 22, 10, 429, 2147483647, 66 }, { 22, 26, 2, 100, 92 }, { 22, 29, 1, 80, 19 }, { 22, 20, 1, 43, 9 }, { 22, 12, 790, 2147483647, 75 }, { 22, 19, 1, 82, 86 }, { 24, 22, 960, 2147483647, 100 }, { 24, 16, 26, 2147483647, 35 }, { 24, 21, 943, 2147483647, 7 }, { 24, 13, 1, 42, 4 }, { 4, 9, 807, 2147483647, 100 }, { 4, 22, 1, 52, 13 }, { 4, 12, 1, 77, 66 }, { 4, 8, 1, 52, 5 }, { 7, 27, 1, 5, 100 }, { 7, 29, 562, 2147483647, 100 }, { 7, 6, 1, 46, 56 }, { 7, 15, 370, 2147483647, 65 }, { 7, 10, 922, 2147483647, 73 }, { 9, 25, 682, 2147483647, 33 }, { 9, 30, 781, 2147483647, 98 }, { 9, 23, 1, 77, 77 }, { 9, 21, 974, 2147483647, 58 }, { 25, 7, 1, 5, 45 }, { 25, 18, 811, 2147483647, 54 }, { 25, 16, 79, 2147483647, 64 }, { 5, 12, 325, 2147483647, 100 }, { 5, 6, 1, 79, 7 }, { 5, 14, 1, 45, 83 }, { 5, 21, 1, 91, 21 }, { 12, 15, 737, 2147483647, 100 }, { 12, 29, 391, 2147483647, 86 }, { 12, 19, 1, 51, 39 }, { 12, 21, 944, 2147483647, 29 }, { 15, 28, 340, 2147483647, 100 }, { 15, 29, 743, 2147483647, 48 }, { 15, 26, 484, 2147483647, 100 }, }; test(testCase, 1982153); } /** * Test case generated with NETGEN generator params: vertices = 50, edges = 154, sources = 20, * sinks = 20, supply = 200, min. capacity = 1, max. capacity = 1000, min. cost = 1, max. cost = * 100, capacitated = 10%, seed = 1342893451 */ @Test public void testGetMinimumCostFlow14() { int testCase[][] = new int[][] { { 1, 11 }, { 2, 25 }, { 3, 9 }, { 4, 10 }, { 5, 10 }, { 6, 5 }, { 7, 6 }, { 8, 10 }, { 9, 4 }, { 10, 18 }, { 11, 7 }, { 12, 9 }, { 13, 8 }, { 14, 19 }, { 15, 10 }, { 16, 13 }, { 17, 1 }, { 18, 7 }, { 19, 13 }, { 20, 5 }, { 31, -11 }, { 32, -5 }, { 33, -9 }, { 34, -2 }, { 35, -8 }, { 36, -2 }, { 37, -13 }, { 38, -7 }, { 39, -11 }, { 40, -10 }, { 42, -28 }, { 43, -13 }, { 44, -15 }, { 45, -22 }, { 46, -10 }, { 47, -8 }, { 49, -15 }, { 50, -11 }, { 1, 25, 0, 2147483647, 38 }, { 1, 31, 0, 2147483647, 93 }, { 1, 45, 0, 2147483647, 47 }, { 1, 21, 0, 2147483647, 18 }, { 25, 45, 0, 11, 14 }, { 25, 43, 0, 11, 100 }, { 25, 39, 0, 2147483647, 69 }, { 25, 42, 0, 2147483647, 38 }, { 25, 29, 0, 216, 31 }, { 2, 21, 0, 25, 46 }, { 2, 33, 0, 973, 72 }, { 2, 37, 0, 150, 10 }, { 2, 39, 0, 2147483647, 67 }, { 2, 32, 0, 994, 9 }, { 2, 29, 0, 11, 24 }, { 21, 47, 0, 25, 100 }, { 21, 44, 0, 25, 100 }, { 21, 31, 0, 25, 34 }, { 21, 46, 0, 25, 100 }, { 21, 29, 0, 879, 15 }, { 21, 35, 0, 2147483647, 23 }, { 21, 42, 0, 321, 64 }, { 21, 38, 0, 2147483647, 27 }, { 21, 27, 0, 2147483647, 13 }, { 21, 45, 0, 160, 60 }, { 3, 23, 0, 2147483647, 100 }, { 3, 48, 0, 104, 88 }, { 3, 25, 0, 967, 18 }, { 3, 46, 0, 72, 53 }, { 23, 33, 0, 2147483647, 15 }, { 23, 46, 0, 9, 30 }, { 23, 44, 0, 2147483647, 100 }, { 23, 31, 0, 9, 98 }, { 23, 24, 0, 556, 25 }, { 23, 47, 0, 2147483647, 19 }, { 23, 35, 0, 509, 19 }, { 23, 50, 0, 2147483647, 14 }, { 23, 40, 0, 82, 18 }, { 23, 42, 0, 21, 74 }, { 4, 30, 0, 2147483647, 100 }, { 4, 41, 0, 389, 4 }, { 4, 24, 0, 2147483647, 22 }, { 30, 40, 0, 2147483647, 57 }, { 30, 37, 0, 10, 7 }, { 30, 36, 0, 2147483647, 100 }, { 30, 34, 0, 10, 59 }, { 30, 49, 0, 523, 19 }, { 30, 47, 0, 2147483647, 25 }, { 30, 45, 0, 2147483647, 26 }, { 5, 24, 0, 10, 94 }, { 5, 23, 0, 649, 62 }, { 5, 28, 0, 2147483647, 71 }, { 5, 34, 0, 880, 4 }, { 5, 36, 0, 2147483647, 73 }, { 5, 46, 0, 835, 65 }, { 24, 32, 0, 10, 100 }, { 24, 31, 0, 2147483647, 100 }, { 24, 50, 0, 2147483647, 100 }, { 24, 46, 0, 2147483647, 44 }, { 24, 26, 0, 2147483647, 50 }, { 24, 27, 0, 2147483647, 17 }, { 24, 22, 0, 2147483647, 51 }, { 24, 33, 0, 175, 96 }, { 24, 44, 0, 2147483647, 21 }, { 6, 26, 0, 2147483647, 100 }, { 6, 29, 0, 836, 13 }, { 26, 29, 0, 5, 34 }, { 26, 42, 0, 5, 93 }, { 26, 33, 0, 2147483647, 19 }, { 26, 48, 0, 5, 28 }, { 26, 45, 0, 5, 100 }, { 26, 31, 0, 2147483647, 51 }, { 26, 21, 0, 2147483647, 53 }, { 26, 46, 0, 2147483647, 54 }, { 29, 49, 0, 2147483647, 100 }, { 29, 35, 0, 2147483647, 100 }, { 29, 50, 0, 5, 91 }, { 29, 46, 0, 5, 100 }, { 29, 45, 0, 2147483647, 79 }, { 29, 25, 0, 328, 76 }, { 29, 34, 0, 2147483647, 10 }, { 29, 22, 0, 842, 24 }, { 29, 47, 0, 2147483647, 81 }, { 29, 33, 0, 622, 11 }, { 7, 42, 0, 2147483647, 100 }, { 7, 38, 0, 2147483647, 100 }, { 7, 27, 0, 783, 70 }, { 7, 41, 0, 2147483647, 58 }, { 7, 49, 0, 2147483647, 78 }, { 7, 26, 0, 322, 16 }, { 8, 37, 0, 10, 39 }, { 8, 31, 0, 10, 100 }, { 8, 35, 0, 690, 64 }, { 8, 45, 0, 701, 74 }, { 8, 39, 0, 76, 81 }, { 8, 46, 0, 850, 38 }, { 9, 44, 0, 2147483647, 100 }, { 9, 43, 0, 2147483647, 88 }, { 9, 46, 0, 2147483647, 7 }, { 9, 42, 0, 420, 35 }, { 9, 21, 0, 2147483647, 73 }, { 9, 39, 0, 470, 89 }, { 10, 49, 0, 18, 100 }, { 10, 42, 0, 18, 100 }, { 10, 21, 0, 658, 24 }, { 11, 50, 0, 7, 16 }, { 11, 43, 0, 2147483647, 7 }, { 11, 31, 0, 720, 76 }, { 11, 32, 0, 2147483647, 93 }, { 12, 43, 0, 2147483647, 46 }, { 12, 39, 0, 9, 100 }, { 12, 44, 0, 947, 6 }, { 12, 47, 0, 2147483647, 16 }, { 12, 27, 0, 2147483647, 13 }, { 12, 41, 0, 2147483647, 43 }, { 13, 22, 0, 8, 100 }, { 13, 38, 0, 2147483647, 13 }, { 13, 48, 0, 2147483647, 47 }, { 13, 34, 0, 2147483647, 38 }, { 13, 41, 0, 126, 11 }, { 22, 40, 0, 8, 70 }, { 22, 45, 0, 8, 100 }, { 22, 44, 0, 2147483647, 74 }, { 22, 37, 0, 8, 100 }, { 22, 27, 0, 2147483647, 92 }, { 14, 45, 0, 19, 22 }, { 14, 42, 0, 19, 83 }, { 14, 36, 0, 962, 37 }, { 15, 44, 0, 10, 100 }, { 15, 38, 0, 2147483647, 8 }, { 15, 22, 0, 2147483647, 42 }, { 15, 29, 0, 2147483647, 37 }, { 16, 33, 0, 2147483647, 100 }, { 16, 42, 0, 2147483647, 58 }, { 16, 31, 0, 2147483647, 81 }, { 16, 29, 0, 378, 78 }, { 17, 45, 0, 2147483647, 31 }, { 17, 43, 0, 1, 40 }, { 17, 25, 0, 2147483647, 68 }, { 18, 50, 0, 7, 100 }, { 18, 45, 0, 2147483647, 100 }, { 19, 28, 0, 13, 100 }, { 19, 45, 0, 387, 9 }, { 28, 35, 0, 13, 18 }, { 28, 42, 0, 2147483647, 47 }, { 28, 34, 0, 2147483647, 100 }, { 28, 40, 0, 13, 100 }, { 20, 27, 0, 5, 100 }, { 27, 39, 0, 2147483647, 60 }, { 27, 38, 0, 2147483647, 100 }, { 27, 47, 0, 5, 24 }, { 27, 32, 0, 2147483647, 6 }, { 27, 41, 0, 2147483647, 100 }, { 27, 48, 0, 5, 100 }, }; test(testCase, 9919); } /** * Test case generated with NETGEN generator params: vertices = 50, edges = 150, sources = 20, * sinks = 20, supply = 200, min. capacity = 1, max. capacity = 1000, min. cost = 1, max. cost = * 100, capacitated = 70%, seed = 684190206 */ @Test public void testGetMinimumCostFlow15() { int testCase[][] = new int[][] { { 1, 2795 }, { 2, 756 }, { 3, 1215 }, { 4, 16 }, { 5, 1955 }, { 6, 334 }, { 7, 716 }, { 8, 1414 }, { 9, 482 }, { 10, 320 }, { 11, 369 }, { 12, 54 }, { 13, 1485 }, { 14, 563 }, { 15, 1871 }, { 16, 5 }, { 17, 554 }, { 18, 17 }, { 19, 134 }, { 20, 1582 }, { 21, 2107 }, { 22, 617 }, { 23, 676 }, { 24, 1009 }, { 25, 1051 }, { 26, 1107 }, { 27, 204 }, { 28, 3026 }, { 29, -122 }, { 30, 1573 }, { 31, -2153 }, { 32, -2656 }, { 33, -1422 }, { 34, -1605 }, { 35, -1583 }, { 36, -1066 }, { 37, -1335 }, { 38, -992 }, { 39, -888 }, { 40, -1455 }, { 41, -745 }, { 42, -1464 }, { 43, -603 }, { 44, -719 }, { 45, -494 }, { 46, -2326 }, { 47, -3254 }, { 48, -771 }, { 49, -1471 }, { 50, -883 }, { 1, 29, 1, 5, 58 }, { 1, 32, 764, 2147483647, 98 }, { 1, 22, 455, 2147483647, 100 }, { 1, 27, 795, 2147483647, 37 }, { 1, 31, 772, 2147483647, 32 }, { 1, 34, 3, 332, 38 }, { 29, 46, 621, 2147483647, 96 }, { 29, 44, 1, 5, 100 }, { 29, 42, 1, 5, 100 }, { 29, 35, 1, 5, 100 }, { 29, 26, 1, 275, 1 }, { 2, 30, 740, 2147483647, 28 }, { 2, 37, 7, 602, 34 }, { 2, 45, 6, 707, 65 }, { 2, 46, 1, 750, 22 }, { 30, 38, 519, 2147483647, 4 }, { 30, 39, 63, 2147483647, 100 }, { 30, 48, 1, 2, 100 }, { 30, 45, 484, 2147483647, 81 }, { 30, 29, 746, 2147483647, 46 }, { 30, 35, 298, 2147483647, 35 }, { 30, 43, 466, 2147483647, 82 }, { 3, 24, 139, 2147483647, 100 }, { 3, 47, 547, 2147483647, 34 }, { 3, 37, 4, 618, 96 }, { 3, 42, 506, 2147483647, 77 }, { 3, 44, 2, 306, 71 }, { 3, 48, 5, 550, 76 }, { 21, 49, 520, 2147483647, 94 }, { 21, 47, 474, 2147483647, 100 }, { 21, 43, 1, 12, 70 }, { 21, 33, 883, 2147483647, 100 }, { 21, 50, 3, 589, 91 }, { 21, 44, 267, 2147483647, 74 }, { 21, 30, 264, 2147483647, 54 }, { 21, 24, 1, 607, 58 }, { 21, 35, 467, 2147483647, 95 }, { 24, 21, 1, 12, 85 }, { 24, 37, 134, 2147483647, 100 }, { 24, 31, 256, 2147483647, 100 }, { 24, 34, 508, 2147483647, 100 }, { 24, 50, 841, 2147483647, 13 }, { 24, 46, 1, 248, 70 }, { 24, 26, 1, 128, 44 }, { 24, 36, 1, 141, 5 }, { 4, 27, 1, 10, 100 }, { 4, 21, 5, 715, 27 }, { 27, 50, 1, 10, 100 }, { 27, 47, 901, 2147483647, 100 }, { 27, 35, 1, 10, 73 }, { 27, 33, 513, 2147483647, 100 }, { 27, 40, 1, 309, 92 }, { 5, 22, 678, 2147483647, 100 }, { 5, 32, 260, 2147483647, 16 }, { 5, 48, 5, 476, 88 }, { 5, 46, 500, 2147483647, 96 }, { 5, 31, 498, 2147483647, 46 }, { 5, 49, 3, 495, 83 }, { 22, 34, 967, 2147483647, 36 }, { 22, 31, 1, 11, 100 }, { 22, 42, 1, 11, 39 }, { 22, 32, 1, 11, 12 }, { 22, 45, 2, 129, 10 }, { 22, 40, 654, 2147483647, 87 }, { 22, 26, 7, 856, 7 }, { 22, 35, 149, 2147483647, 14 }, { 6, 28, 310, 2147483647, 100 }, { 6, 38, 3, 416, 21 }, { 6, 47, 1, 132, 34 }, { 6, 33, 1, 348, 93 }, { 28, 40, 1, 19, 100 }, { 28, 47, 561, 2147483647, 59 }, { 28, 36, 514, 2147483647, 100 }, { 28, 37, 859, 2147483647, 100 }, { 28, 46, 458, 2147483647, 7 }, { 28, 42, 946, 2147483647, 32 }, { 7, 32, 688, 2147483647, 91 }, { 7, 47, 1, 15, 90 }, { 7, 46, 7, 737, 51 }, { 7, 33, 3, 746, 74 }, { 7, 25, 2, 977, 18 }, { 8, 38, 1, 4, 80 }, { 8, 31, 617, 2147483647, 100 }, { 8, 40, 792, 2147483647, 9 }, { 9, 25, 466, 2147483647, 13 }, { 9, 32, 8, 859, 53 }, { 25, 26, 600, 2147483647, 100 }, { 25, 43, 1, 8, 100 }, { 25, 32, 1, 8, 100 }, { 25, 39, 823, 2147483647, 59 }, { 25, 34, 64, 2147483647, 100 }, { 25, 46, 207, 2147483647, 73 }, { 25, 38, 19, 2147483647, 97 }, { 25, 48, 557, 2147483647, 32 }, { 25, 50, 33, 2147483647, 66 }, { 26, 48, 63, 2147483647, 83 }, { 26, 37, 1, 8, 100 }, { 26, 40, 1, 8, 32 }, { 26, 45, 1, 8, 8 }, { 26, 41, 393, 2147483647, 85 }, { 26, 24, 594, 2147483647, 40 }, { 26, 49, 664, 2147483647, 70 }, { 10, 48, 131, 2147483647, 100 }, { 10, 44, 178, 2147483647, 36 }, { 10, 42, 1, 469, 29 }, { 11, 34, 1, 12, 100 }, { 11, 41, 345, 2147483647, 13 }, { 11, 37, 1, 3, 67 }, { 11, 36, 3, 472, 90 }, { 11, 33, 6, 603, 90 }, { 11, 26, 1, 41, 10 }, { 12, 33, 1, 13, 7 }, { 12, 34, 36, 2147483647, 100 }, { 12, 42, 4, 847, 10 }, { 13, 37, 84, 2147483647, 100 }, { 13, 46, 195, 2147483647, 100 }, { 13, 35, 2, 627, 70 }, { 13, 27, 417, 2147483647, 9 }, { 13, 47, 753, 2147483647, 12 }, { 14, 23, 1, 17, 64 }, { 14, 49, 276, 2147483647, 35 }, { 14, 37, 1, 171, 36 }, { 14, 44, 265, 2147483647, 91 }, { 14, 28, 3, 302, 29 }, { 23, 37, 207, 2147483647, 100 }, { 23, 46, 303, 2147483647, 100 }, { 23, 41, 1, 17, 100 }, { 23, 47, 1, 17, 100 }, { 23, 31, 3, 750, 69 }, { 23, 22, 32, 2147483647, 40 }, { 23, 35, 1, 146, 68 }, { 23, 32, 129, 2147483647, 7 }, { 15, 42, 1, 2, 36 }, { 15, 38, 440, 2147483647, 67 }, { 15, 21, 767, 2147483647, 59 }, { 15, 35, 661, 2147483647, 90 }, { 16, 34, 1, 1, 95 }, { 16, 37, 1, 1, 100 }, { 16, 31, 2, 173, 2 }, { 17, 48, 1, 4, 40 }, { 17, 49, 1, 4, 25 }, { 17, 46, 6, 563, 92 }, { 17, 36, 542, 2147483647, 20 }, { 18, 33, 1, 15, 100 }, { 18, 46, 1, 15, 61 }, { 19, 43, 132, 2147483647, 100 }, { 19, 39, 1, 1, 65 }, { 20, 38, 1, 5, 21 }, { 20, 32, 790, 2147483647, 27 }, { 20, 25, 786, 2147483647, 47 }, }; test(testCase, 2223579); } /** * Test case generated with NETGEN generator params: vertices = 100, edges = 704, sources = 25, * sinks = 25, supply = 200, min. capacity = 1, max. capacity = 1000, min. cost = 1, max. cost = * 20, capacitated = 60%, seed = 1787733798 */ @Test public void testGetMinimumCostFlow16() { int testCase[][] = new int[][] { { 1, 4633 }, { 2, 2319 }, { 3, 4198 }, { 4, 5 }, { 5, 632 }, { 6, 712 }, { 7, 1391 }, { 8, 352 }, { 9, 451 }, { 10, 6187 }, { 11, 387 }, { 12, 2936 }, { 13, 1989 }, { 14, 498 }, { 15, 903 }, { 16, 1656 }, { 17, 6166 }, { 18, 3393 }, { 19, 2559 }, { 20, 1753 }, { 21, 493 }, { 22, 1992 }, { 23, 916 }, { 24, 893 }, { 25, 4075 }, { 26, 1654 }, { 27, 229 }, { 28, -640 }, { 29, -638 }, { 30, 3403 }, { 31, 65 }, { 32, -842 }, { 33, 2515 }, { 34, -969 }, { 35, 1923 }, { 36, -914 }, { 37, 542 }, { 38, -162 }, { 39, 865 }, { 40, 2454 }, { 41, 5550 }, { 42, 1262 }, { 43, -514 }, { 44, -1999 }, { 45, 986 }, { 46, 747 }, { 47, 711 }, { 48, -1441 }, { 49, -1026 }, { 50, 2577 }, { 51, 61 }, { 52, -1619 }, { 53, -77 }, { 54, -2799 }, { 55, -701 }, { 56, -7 }, { 57, -587 }, { 58, 664 }, { 59, 561 }, { 60, -2044 }, { 61, -2047 }, { 62, -544 }, { 63, 3828 }, { 64, 2185 }, { 65, 3753 }, { 66, -2554 }, { 67, -688 }, { 68, -2502 }, { 69, 2299 }, { 70, -148 }, { 71, -571 }, { 72, -2946 }, { 73, 4628 }, { 74, 650 }, { 75, -3672 }, { 76, -1979 }, { 77, -3311 }, { 78, -3750 }, { 79, -1998 }, { 80, -4140 }, { 81, -3882 }, { 82, -2634 }, { 83, -1567 }, { 84, -2260 }, { 85, -3949 }, { 86, -1264 }, { 87, -2951 }, { 88, -3001 }, { 89, -1908 }, { 90, -2301 }, { 91, -1186 }, { 92, -3067 }, { 93, -1904 }, { 94, -4066 }, { 95, -2201 }, { 96, -1848 }, { 97, -1689 }, { 98, -2234 }, { 99, -1255 }, { 100, -2605 }, { 1, 36, 1, 2, 20 }, { 1, 93, 522, 2147483647, 13 }, { 1, 88, 1, 239, 16 }, { 1, 81, 789, 2147483647, 10 }, { 1, 68, 2, 199, 4 }, { 1, 94, 44, 2147483647, 9 }, { 1, 49, 418, 2147483647, 5 }, { 1, 26, 547, 2147483647, 2 }, { 1, 78, 995, 2147483647, 1 }, { 1, 38, 4, 712, 11 }, { 1, 32, 660, 2147483647, 18 }, { 1, 43, 1, 367, 9 }, { 1, 75, 353, 2147483647, 8 }, { 1, 85, 286, 2147483647, 9 }, { 1, 60, 2, 365, 13 }, { 1, 53, 6, 506, 9 }, { 36, 70, 1, 2, 20 }, { 36, 79, 5, 652, 2 }, { 36, 65, 822, 2147483647, 20 }, { 36, 74, 5, 496, 8 }, { 36, 83, 5, 553, 9 }, { 36, 50, 8, 806, 16 }, { 36, 27, 2, 193, 3 }, { 36, 67, 468, 2147483647, 10 }, { 36, 72, 138, 2147483647, 1 }, { 36, 73, 1, 749, 20 }, { 36, 77, 948, 2147483647, 20 }, { 36, 97, 4, 993, 19 }, { 70, 98, 177, 2147483647, 19 }, { 70, 88, 1, 2, 13 }, { 70, 34, 459, 2147483647, 18 }, { 70, 69, 406, 2147483647, 7 }, { 70, 50, 1, 7, 2 }, { 70, 64, 804, 2147483647, 17 }, { 70, 89, 1, 58, 18 }, { 70, 73, 123, 2147483647, 11 }, { 70, 45, 4, 493, 16 }, { 70, 38, 6, 711, 17 }, { 2, 44, 1, 3, 17 }, { 2, 74, 400, 2147483647, 10 }, { 2, 94, 244, 2147483647, 13 }, { 2, 50, 132, 2147483647, 10 }, { 2, 80, 722, 2147483647, 13 }, { 2, 43, 2, 482, 19 }, { 2, 27, 7, 937, 8 }, { 2, 77, 780, 2147483647, 1 }, { 2, 90, 3, 283, 14 }, { 2, 59, 5, 444, 12 }, { 2, 86, 7, 678, 14 }, { 2, 42, 5, 590, 11 }, { 2, 31, 6, 518, 7 }, { 2, 34, 2, 218, 3 }, { 40, 50, 1, 3, 20 }, { 40, 77, 4, 391, 9 }, { 40, 87, 737, 2147483647, 3 }, { 40, 65, 563, 2147483647, 18 }, { 40, 64, 762, 2147483647, 19 }, { 40, 100, 2, 812, 11 }, { 40, 97, 263, 2147483647, 16 }, { 40, 96, 242, 2147483647, 17 }, { 40, 61, 1, 323, 10 }, { 40, 36, 731, 2147483647, 8 }, { 44, 40, 1, 3, 5 }, { 44, 28, 626, 2147483647, 11 }, { 50, 88, 58, 2147483647, 3 }, { 50, 93, 501, 2147483647, 20 }, { 50, 96, 225, 2147483647, 20 }, { 50, 85, 1, 498, 6 }, { 50, 73, 735, 2147483647, 10 }, { 50, 30, 108, 2147483647, 2 }, { 50, 56, 8, 718, 2 }, { 50, 68, 512, 2147483647, 16 }, { 50, 71, 5, 742, 17 }, { 50, 51, 714, 2147483647, 7 }, { 50, 100, 607, 2147483647, 13 }, { 50, 83, 789, 2147483647, 14 }, { 50, 26, 3, 980, 10 }, { 50, 99, 1, 91, 15 }, { 50, 53, 6, 652, 11 }, { 50, 91, 186, 2147483647, 18 }, { 3, 35, 1, 2, 16 }, { 3, 44, 188, 2147483647, 8 }, { 3, 81, 996, 2147483647, 13 }, { 3, 61, 3, 700, 9 }, { 3, 67, 1, 19, 1 }, { 3, 60, 3, 583, 9 }, { 3, 82, 2, 627, 5 }, { 3, 66, 962, 2147483647, 12 }, { 3, 79, 4, 668, 10 }, { 3, 72, 426, 2147483647, 20 }, { 3, 76, 937, 2147483647, 2 }, { 3, 97, 4, 417, 14 }, { 3, 47, 5, 490, 19 }, { 3, 34, 663, 2147483647, 17 }, { 3, 36, 1, 88, 13 }, { 35, 47, 1, 2, 20 }, { 35, 91, 529, 2147483647, 20 }, { 35, 76, 348, 2147483647, 18 }, { 35, 99, 5, 691, 18 }, { 35, 100, 614, 2147483647, 10 }, { 35, 65, 246, 2147483647, 5 }, { 35, 26, 626, 2147483647, 16 }, { 35, 81, 7, 660, 1 }, { 35, 39, 3, 530, 13 }, { 35, 97, 411, 2147483647, 9 }, { 35, 50, 545, 2147483647, 7 }, { 35, 49, 248, 2147483647, 13 }, { 35, 98, 663, 2147483647, 4 }, { 47, 66, 883, 2147483647, 20 }, { 47, 91, 1, 34, 16 }, { 47, 68, 969, 2147483647, 10 }, { 47, 59, 964, 2147483647, 16 }, { 47, 95, 481, 2147483647, 6 }, { 47, 62, 211, 2147483647, 9 }, { 66, 88, 424, 2147483647, 5 }, { 66, 93, 1, 2, 6 }, { 66, 26, 7, 696, 2 }, { 66, 76, 2, 473, 14 }, { 66, 39, 4, 691, 3 }, { 4, 26, 1, 3, 19 }, { 4, 47, 1, 533, 5 }, { 26, 57, 980, 2147483647, 7 }, { 26, 70, 1, 61, 17 }, { 26, 76, 1, 163, 12 }, { 26, 28, 185, 2147483647, 1 }, { 26, 31, 11, 2147483647, 11 }, { 26, 41, 3, 272, 18 }, { 26, 43, 5, 945, 11 }, { 26, 35, 1, 55, 9 }, { 26, 90, 659, 2147483647, 9 }, { 26, 93, 1, 97, 16 }, { 26, 55, 319, 2147483647, 7 }, { 26, 61, 961, 2147483647, 1 }, { 26, 51, 115, 2147483647, 16 }, { 26, 84, 4, 388, 2 }, { 57, 94, 291, 2147483647, 17 }, { 57, 97, 117, 2147483647, 20 }, { 57, 42, 52, 2147483647, 10 }, { 57, 83, 136, 2147483647, 5 }, { 57, 65, 6, 539, 13 }, { 57, 95, 2, 143, 3 }, { 57, 68, 905, 2147483647, 13 }, { 57, 64, 726, 2147483647, 8 }, { 57, 34, 688, 2147483647, 5 }, { 57, 59, 1, 232, 13 }, { 57, 70, 615, 2147483647, 20 }, { 5, 34, 1, 11, 17 }, { 5, 91, 1, 418, 20 }, { 5, 86, 1, 6, 20 }, { 5, 80, 1, 82, 11 }, { 5, 87, 3, 515, 11 }, { 5, 43, 2, 279, 18 }, { 5, 81, 370, 2147483647, 8 }, { 5, 30, 5, 810, 15 }, { 5, 48, 2, 140, 18 }, { 5, 64, 1, 199, 18 }, { 5, 65, 6, 757, 11 }, { 5, 94, 228, 2147483647, 17 }, { 34, 55, 121, 2147483647, 10 }, { 34, 81, 1, 11, 18 }, { 34, 35, 961, 2147483647, 7 }, { 34, 41, 211, 2147483647, 13 }, { 34, 82, 4, 973, 3 }, { 34, 26, 2, 901, 16 }, { 34, 44, 179, 2147483647, 3 }, { 55, 78, 85, 2147483647, 20 }, { 55, 81, 5, 883, 8 }, { 55, 34, 3, 390, 6 }, { 55, 33, 1, 311, 20 }, { 55, 85, 1, 186, 15 }, { 55, 93, 6, 574, 1 }, { 55, 46, 978, 2147483647, 14 }, { 55, 72, 231, 2147483647, 6 }, { 6, 72, 143, 2147483647, 12 }, { 6, 46, 5, 886, 14 }, { 6, 32, 7, 664, 9 }, { 6, 36, 278, 2147483647, 5 }, { 6, 47, 277, 2147483647, 1 }, { 33, 80, 692, 2147483647, 20 }, { 33, 81, 51, 2147483647, 20 }, { 33, 88, 1, 92, 10 }, { 33, 93, 466, 2147483647, 13 }, { 33, 60, 877, 2147483647, 4 }, { 33, 41, 54, 2147483647, 5 }, { 33, 67, 235, 2147483647, 5 }, { 33, 38, 72, 2147483647, 9 }, { 33, 94, 2, 137, 1 }, { 33, 34, 8, 722, 2 }, { 33, 65, 1, 131, 16 }, { 33, 27, 1, 348, 17 }, { 33, 57, 480, 2147483647, 4 }, { 33, 100, 810, 2147483647, 2 }, { 33, 62, 485, 2147483647, 5 }, { 33, 55, 8, 937, 11 }, { 33, 90, 1, 612, 5 }, { 72, 33, 1, 2, 20 }, { 72, 62, 2, 253, 5 }, { 72, 52, 2, 107, 3 }, { 72, 71, 88, 2147483647, 5 }, { 7, 65, 1, 8, 8 }, { 7, 85, 439, 2147483647, 11 }, { 7, 47, 9, 950, 1 }, { 7, 63, 204, 2147483647, 15 }, { 7, 78, 729, 2147483647, 18 }, { 7, 37, 1, 927, 6 }, { 30, 94, 597, 2147483647, 20 }, { 30, 59, 5, 538, 17 }, { 30, 55, 808, 2147483647, 1 }, { 30, 43, 3, 677, 2 }, { 30, 83, 442, 2147483647, 15 }, { 30, 26, 46, 2147483647, 3 }, { 30, 68, 718, 2147483647, 11 }, { 30, 92, 954, 2147483647, 3 }, { 30, 62, 1, 131, 15 }, { 30, 78, 406, 2147483647, 10 }, { 30, 44, 4, 703, 1 }, { 30, 75, 246, 2147483647, 2 }, { 30, 38, 829, 2147483647, 6 }, { 30, 85, 1, 323, 2 }, { 30, 98, 399, 2147483647, 4 }, { 52, 30, 1, 8, 16 }, { 52, 88, 1, 8, 20 }, { 52, 73, 1, 22, 11 }, { 52, 46, 2, 187, 10 }, { 52, 78, 219, 2147483647, 4 }, { 52, 48, 4, 565, 20 }, { 65, 68, 272, 2147483647, 20 }, { 65, 100, 437, 2147483647, 9 }, { 65, 98, 689, 2147483647, 6 }, { 65, 97, 2, 243, 6 }, { 65, 95, 843, 2147483647, 5 }, { 65, 69, 10, 981, 10 }, { 65, 49, 393, 2147483647, 10 }, { 65, 81, 168, 2147483647, 14 }, { 65, 29, 1, 451, 7 }, { 65, 57, 999, 2147483647, 10 }, { 65, 71, 1, 68, 13 }, { 65, 51, 1, 126, 2 }, { 65, 82, 592, 2147483647, 2 }, { 65, 90, 484, 2147483647, 1 }, { 65, 28, 1, 85, 16 }, { 65, 77, 926, 2147483647, 5 }, { 65, 56, 378, 2147483647, 8 }, { 68, 52, 1, 8, 20 }, { 68, 56, 4, 962, 1 }, { 68, 54, 580, 2147483647, 17 }, { 68, 32, 1, 192, 15 }, { 68, 67, 658, 2147483647, 17 }, { 68, 44, 945, 2147483647, 2 }, { 68, 87, 1, 88, 3 }, { 68, 38, 473, 2147483647, 6 }, { 8, 67, 273, 2147483647, 3 }, { 8, 29, 7, 946, 12 }, { 8, 88, 57, 2147483647, 20 }, { 8, 80, 3, 284, 5 }, { 8, 59, 4, 714, 2 }, { 54, 88, 495, 2147483647, 7 }, { 54, 60, 2, 999, 4 }, { 54, 58, 822, 2147483647, 17 }, { 54, 70, 544, 2147483647, 18 }, { 67, 54, 951, 2147483647, 20 }, { 67, 79, 531, 2147483647, 20 }, { 67, 39, 3, 926, 16 }, { 67, 99, 1, 251, 17 }, { 67, 81, 4, 884, 12 }, { 67, 84, 5, 746, 4 }, { 67, 41, 4, 473, 17 }, { 9, 73, 1, 10, 17 }, { 9, 50, 1, 26, 1 }, { 9, 36, 2, 101, 9 }, { 9, 62, 181, 2147483647, 14 }, { 9, 35, 1, 855, 1 }, { 9, 78, 2, 734, 5 }, { 9, 40, 247, 2147483647, 11 }, { 9, 27, 1, 3, 18 }, { 9, 86, 5, 408, 14 }, { 71, 97, 686, 2147483647, 6 }, { 71, 100, 2, 734, 13 }, { 71, 61, 736, 2147483647, 4 }, { 71, 82, 1, 189, 20 }, { 71, 44, 5, 484, 3 }, { 71, 34, 1, 117, 12 }, { 73, 71, 815, 2147483647, 20 }, { 73, 85, 738, 2147483647, 12 }, { 73, 72, 4, 353, 10 }, { 73, 92, 3, 508, 13 }, { 73, 74, 175, 2147483647, 19 }, { 73, 79, 1, 888, 1 }, { 73, 48, 366, 2147483647, 13 }, { 73, 31, 6, 958, 10 }, { 73, 61, 1, 427, 11 }, { 73, 58, 781, 2147483647, 12 }, { 73, 36, 456, 2147483647, 16 }, { 73, 49, 650, 2147483647, 19 }, { 73, 54, 140, 2147483647, 10 }, { 73, 55, 130, 2147483647, 14 }, { 73, 38, 10, 988, 8 }, { 73, 47, 774, 2147483647, 12 }, { 73, 26, 5, 530, 12 }, { 73, 86, 807, 2147483647, 16 }, { 10, 60, 1, 12, 20 }, { 10, 85, 389, 2147483647, 1 }, { 10, 34, 177, 2147483647, 11 }, { 10, 69, 576, 2147483647, 14 }, { 10, 49, 5, 751, 2 }, { 10, 28, 7, 811, 1 }, { 10, 33, 479, 2147483647, 20 }, { 10, 51, 2, 449, 8 }, { 10, 89, 638, 2147483647, 5 }, { 10, 42, 831, 2147483647, 14 }, { 10, 87, 564, 2147483647, 19 }, { 10, 75, 918, 2147483647, 1 }, { 10, 29, 1, 336, 6 }, { 10, 48, 894, 2147483647, 10 }, { 10, 70, 3, 308, 3 }, { 10, 81, 690, 2147483647, 15 }, { 37, 96, 236, 2147483647, 20 }, { 37, 84, 224, 2147483647, 20 }, { 37, 60, 349, 2147483647, 19 }, { 37, 100, 127, 2147483647, 18 }, { 37, 68, 7, 897, 18 }, { 37, 54, 3, 391, 9 }, { 60, 37, 1, 12, 6 }, { 60, 55, 1, 108, 1 }, { 60, 95, 3, 970, 17 }, { 60, 56, 1, 85, 9 }, { 60, 27, 856, 2147483647, 8 }, { 60, 43, 3, 553, 2 }, { 60, 62, 1, 641, 12 }, { 60, 28, 2, 319, 8 }, { 60, 30, 2, 937, 13 }, { 60, 59, 1, 89, 14 }, { 11, 49, 1, 10, 20 }, { 11, 99, 2, 835, 1 }, { 11, 32, 9, 956, 9 }, { 11, 33, 365, 2147483647, 1 }, { 32, 92, 1, 10, 20 }, { 32, 84, 151, 2147483647, 20 }, { 32, 40, 591, 2147483647, 6 }, { 32, 90, 1, 193, 3 }, { 32, 99, 1, 202, 3 }, { 32, 60, 835, 2147483647, 11 }, { 32, 34, 4, 458, 16 }, { 49, 32, 1, 10, 15 }, { 49, 47, 192, 2147483647, 18 }, { 49, 54, 604, 2147483647, 11 }, { 12, 51, 49, 2147483647, 14 }, { 12, 71, 292, 2147483647, 11 }, { 12, 61, 423, 2147483647, 1 }, { 12, 86, 331, 2147483647, 11 }, { 12, 68, 851, 2147483647, 6 }, { 12, 83, 3, 308, 16 }, { 12, 80, 967, 2147483647, 18 }, { 12, 31, 1, 323, 17 }, { 12, 62, 3, 933, 14 }, { 12, 97, 3, 236, 13 }, { 12, 37, 3, 861, 3 }, { 51, 92, 1, 10, 5 }, { 51, 94, 905, 2147483647, 20 }, { 51, 86, 5, 564, 5 }, { 51, 77, 297, 2147483647, 4 }, { 51, 60, 1, 104, 11 }, { 51, 30, 1, 24, 12 }, { 51, 52, 739, 2147483647, 4 }, { 51, 45, 4, 707, 20 }, { 51, 85, 246, 2147483647, 14 }, { 51, 54, 479, 2147483647, 5 }, { 51, 29, 162, 2147483647, 2 }, { 51, 47, 742, 2147483647, 5 }, { 51, 35, 7, 667, 7 }, { 13, 43, 954, 2147483647, 5 }, { 13, 59, 1, 994, 17 }, { 13, 93, 7, 985, 10 }, { 13, 95, 128, 2147483647, 4 }, { 13, 87, 893, 2147483647, 8 }, { 43, 89, 712, 2147483647, 20 }, { 43, 94, 462, 2147483647, 4 }, { 43, 72, 10, 973, 19 }, { 43, 30, 200, 2147483647, 10 }, { 43, 44, 1, 532, 14 }, { 43, 73, 1, 727, 16 }, { 43, 65, 5, 811, 12 }, { 14, 58, 1, 7, 20 }, { 14, 84, 487, 2147483647, 15 }, { 14, 90, 3, 267, 18 }, { 41, 81, 60, 2147483647, 11 }, { 41, 62, 479, 2147483647, 4 }, { 41, 27, 2, 111, 3 }, { 41, 63, 750, 2147483647, 6 }, { 41, 46, 5, 604, 6 }, { 41, 57, 610, 2147483647, 16 }, { 41, 69, 863, 2147483647, 14 }, { 41, 34, 3, 398, 16 }, { 41, 70, 962, 2147483647, 6 }, { 41, 71, 801, 2147483647, 15 }, { 41, 75, 715, 2147483647, 18 }, { 41, 55, 615, 2147483647, 9 }, { 41, 42, 3, 330, 10 }, { 58, 41, 46, 2147483647, 20 }, { 58, 98, 85, 2147483647, 20 }, { 58, 26, 337, 2147483647, 19 }, { 58, 38, 701, 2147483647, 2 }, { 58, 54, 943, 2147483647, 18 }, { 58, 60, 3, 217, 15 }, { 58, 78, 71, 2147483647, 20 }, { 58, 48, 211, 2147483647, 6 }, { 58, 32, 359, 2147483647, 10 }, { 15, 28, 890, 2147483647, 20 }, { 15, 38, 6, 769, 20 }, { 28, 53, 548, 2147483647, 16 }, { 28, 76, 1, 7, 20 }, { 28, 46, 4, 399, 14 }, { 28, 32, 1, 485, 16 }, { 28, 60, 2, 495, 20 }, { 28, 92, 693, 2147483647, 1 }, { 28, 94, 1, 90, 17 }, { 53, 88, 639, 2147483647, 4 }, { 53, 73, 146, 2147483647, 5 }, { 53, 47, 1, 44, 3 }, { 53, 49, 7, 892, 16 }, { 16, 56, 1, 9, 20 }, { 16, 55, 2, 294, 20 }, { 16, 74, 1, 189, 12 }, { 16, 26, 8, 771, 1 }, { 16, 76, 4, 506, 9 }, { 16, 48, 337, 2147483647, 20 }, { 16, 51, 2, 223, 9 }, { 16, 59, 919, 2147483647, 10 }, { 16, 46, 372, 2147483647, 7 }, { 16, 82, 1, 233, 5 }, { 56, 83, 1, 9, 15 }, { 56, 80, 823, 2147483647, 20 }, { 56, 55, 7, 697, 3 }, { 56, 84, 412, 2147483647, 7 }, { 17, 27, 841, 2147483647, 20 }, { 17, 64, 994, 2147483647, 7 }, { 17, 85, 989, 2147483647, 12 }, { 17, 46, 7, 640, 12 }, { 17, 44, 810, 2147483647, 2 }, { 17, 29, 963, 2147483647, 13 }, { 17, 30, 2, 221, 16 }, { 17, 74, 43, 2147483647, 7 }, { 17, 83, 173, 2147483647, 14 }, { 17, 42, 339, 2147483647, 1 }, { 17, 93, 200, 2147483647, 12 }, { 17, 45, 1, 94, 5 }, { 17, 47, 792, 2147483647, 8 }, { 27, 46, 57, 2147483647, 17 }, { 27, 83, 7, 671, 10 }, { 27, 56, 849, 2147483647, 11 }, { 27, 53, 2, 234, 10 }, { 27, 69, 357, 2147483647, 5 }, { 27, 87, 2, 166, 4 }, { 27, 88, 20, 2147483647, 1 }, { 27, 66, 1, 319, 7 }, { 27, 34, 4, 568, 1 }, { 27, 72, 966, 2147483647, 3 }, { 27, 42, 241, 2147483647, 8 }, { 27, 94, 1, 608, 12 }, { 27, 26, 4, 489, 10 }, { 46, 77, 1, 12, 20 }, { 46, 96, 1, 12, 20 }, { 46, 34, 1, 174, 3 }, { 46, 31, 681, 2147483647, 17 }, { 46, 92, 540, 2147483647, 15 }, { 46, 85, 808, 2147483647, 14 }, { 46, 32, 6, 505, 10 }, { 46, 38, 242, 2147483647, 9 }, { 46, 66, 332, 2147483647, 7 }, { 18, 63, 1, 14, 20 }, { 18, 75, 704, 2147483647, 1 }, { 18, 96, 1, 22, 6 }, { 18, 66, 1, 349, 1 }, { 18, 43, 925, 2147483647, 19 }, { 18, 64, 537, 2147483647, 10 }, { 18, 62, 875, 2147483647, 16 }, { 18, 45, 328, 2147483647, 5 }, { 18, 48, 6, 647, 17 }, { 18, 30, 1, 315, 2 }, { 38, 82, 1, 14, 19 }, { 38, 32, 667, 2147483647, 5 }, { 38, 72, 978, 2147483647, 15 }, { 38, 57, 1, 378, 16 }, { 38, 80, 4, 482, 10 }, { 38, 44, 8, 800, 1 }, { 38, 46, 2, 105, 4 }, { 38, 58, 487, 2147483647, 13 }, { 38, 74, 4, 392, 3 }, { 38, 98, 4, 763, 20 }, { 38, 31, 630, 2147483647, 1 }, { 63, 74, 682, 2147483647, 20 }, { 63, 89, 410, 2147483647, 18 }, { 63, 31, 1, 99, 1 }, { 63, 37, 1, 149, 20 }, { 63, 29, 742, 2147483647, 13 }, { 63, 64, 827, 2147483647, 16 }, { 63, 43, 1, 377, 12 }, { 63, 90, 824, 2147483647, 1 }, { 63, 35, 900, 2147483647, 12 }, { 63, 61, 645, 2147483647, 10 }, { 63, 46, 1, 36, 1 }, { 63, 53, 308, 2147483647, 1 }, { 74, 38, 1, 14, 20 }, { 74, 84, 1, 14, 12 }, { 74, 85, 1, 14, 20 }, { 74, 62, 3, 795, 6 }, { 74, 37, 397, 2147483647, 8 }, { 74, 98, 191, 2147483647, 8 }, { 74, 28, 179, 2147483647, 15 }, { 74, 94, 343, 2147483647, 20 }, { 74, 92, 838, 2147483647, 1 }, { 74, 93, 5, 442, 3 }, { 74, 40, 5, 522, 12 }, { 19, 29, 1, 11, 20 }, { 19, 69, 646, 2147483647, 3 }, { 19, 57, 233, 2147483647, 15 }, { 19, 34, 431, 2147483647, 3 }, { 19, 51, 680, 2147483647, 19 }, { 19, 59, 1, 133, 7 }, { 19, 62, 3, 356, 10 }, { 19, 52, 553, 2147483647, 12 }, { 29, 39, 1, 11, 10 }, { 29, 87, 1, 11, 20 }, { 29, 64, 3, 451, 20 }, { 29, 92, 28, 2147483647, 13 }, { 29, 60, 397, 2147483647, 6 }, { 29, 30, 3, 701, 13 }, { 29, 40, 8, 812, 13 }, { 29, 52, 211, 2147483647, 19 }, { 29, 26, 3, 380, 3 }, { 29, 98, 19, 2147483647, 7 }, { 29, 100, 3, 366, 7 }, { 29, 49, 4, 643, 1 }, { 29, 85, 1, 180, 4 }, { 29, 51, 917, 2147483647, 2 }, { 29, 75, 2, 959, 4 }, { 29, 82, 1, 20, 8 }, { 29, 44, 483, 2147483647, 2 }, { 29, 72, 1, 290, 7 }, { 39, 82, 856, 2147483647, 19 }, { 39, 38, 6, 987, 9 }, { 39, 56, 3, 564, 11 }, { 39, 98, 1, 233, 14 }, { 39, 76, 6, 2147483647, 20 }, { 39, 88, 5, 542, 13 }, { 20, 75, 805, 2147483647, 6 }, { 20, 89, 138, 2147483647, 15 }, { 20, 65, 785, 2147483647, 2 }, { 20, 70, 1, 88, 15 }, { 20, 59, 6, 512, 17 }, { 20, 34, 1, 120, 11 }, { 20, 90, 8, 809, 6 }, { 20, 50, 1, 345, 12 }, { 48, 88, 178, 2147483647, 20 }, { 48, 97, 195, 2147483647, 15 }, { 48, 45, 4, 931, 17 }, { 48, 96, 3, 237, 20 }, { 69, 48, 1, 8, 20 }, { 69, 82, 2, 580, 15 }, { 69, 34, 2, 444, 15 }, { 69, 88, 719, 2147483647, 16 }, { 69, 36, 521, 2147483647, 15 }, { 69, 75, 475, 2147483647, 13 }, { 69, 50, 821, 2147483647, 3 }, { 69, 73, 1, 285, 14 }, { 69, 58, 1, 4, 19 }, { 69, 93, 191, 2147483647, 11 }, { 69, 78, 561, 2147483647, 7 }, { 69, 32, 715, 2147483647, 17 }, { 69, 27, 568, 2147483647, 15 }, { 69, 56, 2, 353, 13 }, { 69, 30, 798, 2147483647, 6 }, { 75, 69, 46, 2147483647, 8 }, { 75, 99, 367, 2147483647, 20 }, { 75, 93, 1, 8, 5 }, { 75, 96, 895, 2147483647, 13 }, { 75, 77, 4, 617, 16 }, { 75, 27, 1, 302, 11 }, { 75, 50, 1, 990, 10 }, { 75, 73, 99, 2147483647, 15 }, { 75, 51, 336, 2147483647, 10 }, { 75, 62, 7, 967, 9 }, { 75, 90, 1, 980, 12 }, { 75, 42, 1, 69, 5 }, { 75, 52, 1, 102, 11 }, { 75, 35, 9, 2147483647, 4 }, { 75, 60, 443, 2147483647, 7 }, { 21, 31, 129, 2147483647, 20 }, { 21, 69, 1, 768, 7 }, { 21, 96, 4, 359, 9 }, { 21, 75, 351, 2147483647, 6 }, { 31, 45, 1, 8, 1 }, { 31, 83, 1, 8, 5 }, { 31, 29, 844, 2147483647, 18 }, { 31, 52, 5, 794, 14 }, { 31, 89, 1, 845, 8 }, { 31, 33, 1, 403, 7 }, { 31, 70, 3, 993, 4 }, { 31, 76, 674, 2147483647, 6 }, { 45, 99, 494, 2147483647, 20 }, { 45, 47, 2, 523, 11 }, { 45, 57, 823, 2147483647, 4 }, { 45, 80, 6, 571, 2 }, { 45, 73, 6, 903, 20 }, { 22, 62, 754, 2147483647, 19 }, { 22, 43, 3, 559, 6 }, { 22, 51, 3, 349, 7 }, { 22, 86, 7, 654, 14 }, { 22, 54, 9, 842, 14 }, { 22, 56, 4, 777, 20 }, { 22, 78, 507, 2147483647, 17 }, { 22, 79, 697, 2147483647, 19 }, { 22, 27, 3, 222, 4 }, { 62, 99, 363, 2147483647, 4 }, { 62, 89, 1, 5, 20 }, { 62, 39, 1, 118, 18 }, { 62, 69, 174, 2147483647, 14 }, { 62, 66, 14, 2147483647, 2 }, { 62, 78, 149, 2147483647, 18 }, { 62, 75, 647, 2147483647, 13 }, { 62, 95, 4, 332, 11 }, { 62, 74, 2, 434, 15 }, { 62, 52, 330, 2147483647, 6 }, { 62, 33, 717, 2147483647, 7 }, { 62, 45, 3, 414, 1 }, { 62, 29, 1, 34, 9 }, { 62, 50, 370, 2147483647, 15 }, { 62, 65, 7, 778, 3 }, { 62, 88, 6, 796, 4 }, { 62, 36, 849, 2147483647, 10 }, { 62, 86, 98, 2147483647, 5 }, { 62, 90, 2, 990, 17 }, { 23, 42, 471, 2147483647, 5 }, { 23, 46, 432, 2147483647, 16 }, { 42, 64, 609, 2147483647, 20 }, { 42, 50, 1, 738, 4 }, { 42, 76, 3, 259, 16 }, { 42, 80, 917, 2147483647, 4 }, { 42, 78, 8, 981, 7 }, { 42, 81, 733, 2147483647, 20 }, { 42, 35, 443, 2147483647, 2 }, { 42, 88, 5, 770, 17 }, { 42, 44, 2, 817, 3 }, { 42, 100, 1, 173, 20 }, { 42, 52, 5, 733, 17 }, { 42, 73, 120, 2147483647, 17 }, { 42, 62, 358, 2147483647, 15 }, { 64, 96, 221, 2147483647, 6 }, { 64, 85, 32, 2147483647, 18 }, { 64, 82, 667, 2147483647, 18 }, { 64, 54, 953, 2147483647, 7 }, { 64, 43, 3, 389, 4 }, { 64, 79, 756, 2147483647, 12 }, { 64, 62, 808, 2147483647, 7 }, { 64, 87, 742, 2147483647, 18 }, { 64, 63, 553, 2147483647, 20 }, { 64, 61, 1, 28, 13 }, { 64, 77, 345, 2147483647, 17 }, { 64, 67, 552, 2147483647, 7 }, { 64, 30, 935, 2147483647, 19 }, { 64, 78, 5, 693, 1 }, { 64, 94, 108, 2147483647, 7 }, { 64, 75, 668, 2147483647, 17 }, { 64, 49, 97, 2147483647, 16 }, { 64, 74, 2, 955, 11 }, { 24, 59, 881, 2147483647, 20 }, { 24, 80, 2, 343, 20 }, { 24, 38, 1, 263, 15 }, { 24, 43, 1, 139, 1 }, { 59, 94, 823, 2147483647, 20 }, { 59, 78, 1, 8, 13 }, { 59, 95, 738, 2147483647, 18 }, { 59, 84, 957, 2147483647, 10 }, { 59, 90, 313, 2147483647, 18 }, { 59, 76, 1, 16, 20 }, { 59, 65, 2, 123, 20 }, { 59, 72, 142, 2147483647, 19 }, { 59, 43, 2, 284, 18 }, { 59, 88, 372, 2147483647, 6 }, { 25, 61, 245, 2147483647, 20 }, { 25, 94, 1, 143, 8 }, { 25, 47, 2, 179, 2 }, { 25, 66, 799, 2147483647, 7 }, { 25, 99, 4, 745, 14 }, { 25, 36, 482, 2147483647, 19 }, { 25, 59, 2, 609, 15 }, { 25, 37, 1, 981, 1 }, { 25, 29, 2, 947, 20 }, { 25, 98, 1, 534, 5 }, { 25, 33, 165, 2147483647, 17 }, { 25, 38, 597, 2147483647, 14 }, { 25, 84, 4, 903, 10 }, { 25, 26, 3, 425, 8 }, { 25, 63, 6, 854, 9 }, { 25, 86, 1, 40, 14 }, { 25, 62, 111, 2147483647, 4 }, { 25, 51, 709, 2147483647, 8 }, { 25, 68, 929, 2147483647, 15 }, { 61, 99, 1, 11, 20 }, { 61, 82, 497, 2147483647, 20 }, { 61, 86, 1, 11, 19 }, { 61, 90, 1, 11, 17 }, { 61, 91, 468, 2147483647, 20 }, { 61, 95, 1, 11, 20 }, }; test(testCase, 2000171); } /** * Test case generated with NETGEN generator params: vertices = 200, edges = 1501, sources = 50, * sinks = 50, supply = 1000, min. capacity = 1, max. capacity = 10000, min. cost = 1, max. cost * = 20, capacitated = 60%, seed = 346867034 */ @Test public void testGetMinimumCostFlow17() { int testCase[][] = new int[][] { { 1, 2474 }, { 2, 2676 }, { 3, 4366 }, { 4, 7126 }, { 5, 1006 }, { 6, 3683 }, { 7, 3081 }, { 8, 2395 }, { 9, 3372 }, { 10, 2421 }, { 11, 3762 }, { 12, 2197 }, { 13, 4288 }, { 14, 1488 }, { 15, 1118 }, { 16, 1746 }, { 17, 3126 }, { 18, 274 }, { 19, 5439 }, { 20, 2759 }, { 21, 3886 }, { 22, 4610 }, { 23, 4486 }, { 24, 4541 }, { 25, 258 }, { 26, 3060 }, { 27, 1463 }, { 28, 1104 }, { 29, 4864 }, { 30, 4064 }, { 31, 4473 }, { 32, 660 }, { 33, 4348 }, { 34, 2836 }, { 35, 278 }, { 36, 1378 }, { 37, 3872 }, { 38, 3898 }, { 39, 4266 }, { 40, 5751 }, { 41, 1487 }, { 42, 2000 }, { 43, 668 }, { 44, 1823 }, { 45, 1497 }, { 46, 3331 }, { 47, 1151 }, { 48, 1156 }, { 49, 352 }, { 50, 1182 }, { 51, 2178 }, { 52, 2579 }, { 53, -1866 }, { 54, 1631 }, { 55, -6605 }, { 56, -3179 }, { 57, 2881 }, { 58, 578 }, { 59, -202 }, { 60, 1498 }, { 61, -2284 }, { 62, 1422 }, { 63, -2874 }, { 64, -324 }, { 65, -3346 }, { 66, -1163 }, { 67, 3478 }, { 68, 1706 }, { 69, 1498 }, { 70, -1594 }, { 71, 1661 }, { 72, 622 }, { 73, 1964 }, { 74, 4598 }, { 75, -1219 }, { 76, -392 }, { 77, 2143 }, { 78, -136 }, { 79, -1163 }, { 80, -258 }, { 81, 280 }, { 82, -593 }, { 83, 374 }, { 84, 4827 }, { 85, 1525 }, { 86, 2228 }, { 87, -1658 }, { 88, 665 }, { 89, -747 }, { 90, 319 }, { 91, 1756 }, { 92, -1102 }, { 93, -2325 }, { 94, 1019 }, { 95, -615 }, { 96, -1498 }, { 97, -1243 }, { 98, -7595 }, { 99, -2666 }, { 100, 4033 }, { 101, 1187 }, { 102, -455 }, { 103, 311 }, { 104, 2524 }, { 105, 1715 }, { 106, -1564 }, { 107, 5167 }, { 108, 2981 }, { 109, -417 }, { 110, -1970 }, { 111, -1433 }, { 112, -1451 }, { 113, -2119 }, { 114, -1487 }, { 115, -1875 }, { 116, 3505 }, { 117, -1 }, { 118, 5867 }, { 119, 3397 }, { 120, 2343 }, { 121, 879 }, { 122, -646 }, { 123, -1691 }, { 124, -932 }, { 125, 2598 }, { 126, -2181 }, { 127, -1849 }, { 128, 436 }, { 129, 1910 }, { 130, 2348 }, { 131, 52 }, { 132, 364 }, { 133, -1614 }, { 134, -1052 }, { 135, 246 }, { 136, -2268 }, { 137, 1424 }, { 138, 4244 }, { 139, -7281 }, { 140, 1049 }, { 141, 5552 }, { 142, -1973 }, { 143, -149 }, { 144, 2718 }, { 145, -2921 }, { 146, -266 }, { 147, 1923 }, { 148, -1842 }, { 149, 2166 }, { 150, 3504 }, { 151, -1797 }, { 152, -1843 }, { 153, -6399 }, { 154, -1446 }, { 155, -4597 }, { 156, -3660 }, { 157, -3563 }, { 158, -3779 }, { 159, -4092 }, { 160, -2963 }, { 161, -3012 }, { 162, -3504 }, { 163, -2791 }, { 164, -1642 }, { 165, -2578 }, { 166, -1710 }, { 167, -1384 }, { 168, -2428 }, { 169, -2210 }, { 170, -2063 }, { 171, -3516 }, { 172, -2489 }, { 173, -839 }, { 174, -1605 }, { 175, -3353 }, { 176, -2262 }, { 177, -2566 }, { 178, -2817 }, { 179, -5543 }, { 180, -3416 }, { 181, -3615 }, { 182, -2276 }, { 183, -5069 }, { 184, -862 }, { 185, -4166 }, { 186, -1949 }, { 187, -5121 }, { 188, -3488 }, { 189, -4123 }, { 190, -5798 }, { 191, -6077 }, { 192, -4953 }, { 193, -1826 }, { 194, -2776 }, { 195, -2257 }, { 196, -3360 }, { 197, -7396 }, { 198, -4588 }, { 199, -1752 }, { 200, -2010 }, { 1, 126, 107, 2147483647, 20 }, { 1, 89, 4, 623, 4 }, { 1, 113, 6, 2147483647, 8 }, { 1, 73, 701, 2147483647, 13 }, { 1, 122, 49, 5469, 12 }, { 1, 192, 3, 1878, 20 }, { 1, 171, 132, 2147483647, 2 }, { 1, 160, 43, 8240, 4 }, { 1, 177, 568, 2147483647, 5 }, { 1, 120, 73, 8854, 10 }, { 1, 133, 364, 2147483647, 10 }, { 1, 96, 43, 7566, 7 }, { 1, 188, 96, 9886, 3 }, { 1, 150, 284, 2147483647, 19 }, { 121, 174, 1, 1, 5 }, { 121, 101, 962, 2147483647, 19 }, { 121, 191, 20, 4354, 16 }, { 121, 142, 646, 2147483647, 9 }, { 121, 169, 60, 2147483647, 20 }, { 121, 165, 10, 3249, 16 }, { 121, 160, 331, 2147483647, 10 }, { 121, 106, 408, 2147483647, 14 }, { 121, 91, 1, 4032, 12 }, { 126, 135, 833, 2147483647, 20 }, { 126, 181, 717, 2147483647, 16 }, { 126, 110, 797, 2147483647, 2 }, { 126, 144, 104, 2147483647, 13 }, { 135, 121, 1, 1, 7 }, { 135, 176, 589, 2147483647, 20 }, { 135, 60, 80, 2147483647, 16 }, { 135, 132, 44, 5550, 19 }, { 135, 192, 27, 3266, 10 }, { 135, 105, 344, 2147483647, 1 }, { 135, 158, 32, 4414, 1 }, { 135, 71, 56, 9298, 13 }, { 135, 181, 610, 2147483647, 12 }, { 135, 152, 23, 3271, 17 }, { 135, 157, 25, 4051, 20 }, { 2, 95, 70, 2147483647, 7 }, { 2, 104, 13, 2369, 2 }, { 2, 192, 698, 2147483647, 11 }, { 2, 73, 20, 3848, 15 }, { 2, 114, 19, 4942, 19 }, { 2, 59, 848, 2147483647, 14 }, { 2, 133, 1, 266, 1 }, { 2, 178, 304, 2147483647, 16 }, { 2, 171, 692, 2147483647, 12 }, { 95, 140, 619, 2147483647, 16 }, { 95, 184, 47, 9906, 9 }, { 95, 59, 10, 3373, 16 }, { 105, 185, 356, 2147483647, 19 }, { 105, 170, 479, 2147483647, 14 }, { 105, 110, 547, 2147483647, 18 }, { 105, 107, 15, 3775, 12 }, { 105, 60, 8, 1810, 12 }, { 105, 80, 558, 2147483647, 20 }, { 105, 71, 91, 9311, 11 }, { 105, 98, 41, 6393, 3 }, { 105, 109, 906, 2147483647, 3 }, { 105, 111, 252, 2147483647, 6 }, { 105, 198, 960, 2147483647, 15 }, { 110, 183, 789, 2147483647, 16 }, { 110, 105, 1, 11, 19 }, { 110, 177, 21, 3137, 11 }, { 140, 110, 1, 11, 20 }, { 140, 179, 340, 2147483647, 6 }, { 140, 196, 23, 2810, 14 }, { 140, 123, 909, 2147483647, 11 }, { 140, 67, 19, 2458, 20 }, { 140, 64, 38, 2147483647, 1 }, { 140, 148, 8, 2301, 1 }, { 140, 144, 58, 8002, 10 }, { 140, 121, 146, 2147483647, 12 }, { 140, 56, 430, 2147483647, 4 }, { 140, 52, 881, 2147483647, 10 }, { 140, 191, 1, 87, 1 }, { 140, 84, 41, 4306, 1 }, { 140, 70, 74, 8268, 2 }, { 140, 163, 20, 4702, 3 }, { 140, 90, 333, 2147483647, 9 }, { 3, 70, 921, 2147483647, 20 }, { 3, 102, 400, 2147483647, 14 }, { 3, 83, 55, 5714, 1 }, { 3, 146, 19, 2253, 14 }, { 3, 176, 41, 2147483647, 18 }, { 3, 68, 16, 3327, 19 }, { 3, 123, 400, 2147483647, 2 }, { 3, 171, 511, 2147483647, 13 }, { 3, 181, 299, 2147483647, 7 }, { 3, 55, 21, 3199, 13 }, { 3, 141, 4, 1057, 16 }, { 3, 164, 7, 651, 20 }, { 3, 98, 582, 2147483647, 14 }, { 3, 99, 91, 9175, 15 }, { 3, 161, 971, 2147483647, 9 }, { 70, 104, 1, 28, 20 }, { 70, 186, 603, 2147483647, 10 }, { 70, 112, 965, 2147483647, 8 }, { 70, 91, 16, 5511, 16 }, { 70, 140, 10, 2147483647, 19 }, { 70, 126, 382, 2147483647, 19 }, { 70, 184, 17, 4655, 15 }, { 70, 177, 63, 6216, 1 }, { 70, 129, 5, 5454, 12 }, { 70, 196, 652, 2147483647, 10 }, { 70, 144, 109, 2147483647, 11 }, { 70, 122, 4, 9646, 1 }, { 70, 200, 972, 2147483647, 18 }, { 70, 194, 570, 2147483647, 2 }, { 70, 108, 20, 2069, 2 }, { 104, 164, 493, 2147483647, 3 }, { 104, 177, 860, 2147483647, 14 }, { 104, 90, 211, 2147483647, 7 }, { 104, 99, 399, 2147483647, 17 }, { 104, 77, 37, 9638, 19 }, { 104, 130, 4, 967, 8 }, { 104, 197, 7, 2147483647, 18 }, { 104, 166, 365, 2147483647, 3 }, { 104, 111, 6, 7151, 17 }, { 104, 120, 123, 2147483647, 1 }, { 104, 62, 34, 2147483647, 13 }, { 104, 158, 523, 2147483647, 14 }, { 104, 79, 32, 3591, 15 }, { 104, 113, 896, 2147483647, 8 }, { 104, 89, 418, 2147483647, 19 }, { 104, 92, 753, 2147483647, 15 }, { 104, 87, 34, 4378, 14 }, { 4, 56, 616, 2147483647, 20 }, { 4, 123, 4, 9220, 11 }, { 4, 155, 969, 2147483647, 5 }, { 4, 55, 966, 2147483647, 7 }, { 4, 158, 177, 2147483647, 18 }, { 4, 85, 898, 2147483647, 4 }, { 4, 150, 1, 123, 4 }, { 4, 178, 462, 2147483647, 9 }, { 4, 151, 305, 2147483647, 20 }, { 4, 187, 905, 2147483647, 2 }, { 4, 193, 52, 2147483647, 2 }, { 4, 129, 24, 2787, 17 }, { 4, 195, 824, 2147483647, 15 }, { 4, 191, 589, 2147483647, 2 }, { 4, 83, 289, 2147483647, 18 }, { 56, 129, 1, 45, 20 }, { 56, 152, 24, 3036, 7 }, { 56, 189, 327, 2147483647, 9 }, { 56, 140, 482, 2147483647, 3 }, { 56, 195, 150, 2147483647, 3 }, { 68, 177, 1, 45, 20 }, { 68, 199, 1, 45, 3 }, { 68, 153, 956, 2147483647, 1 }, { 68, 196, 21, 3695, 8 }, { 68, 70, 72, 9915, 10 }, { 68, 64, 534, 2147483647, 14 }, { 68, 146, 33, 4958, 16 }, { 68, 115, 21, 4617, 13 }, { 68, 84, 255, 2147483647, 1 }, { 68, 134, 632, 2147483647, 3 }, { 68, 139, 478, 2147483647, 15 }, { 68, 61, 530, 2147483647, 13 }, { 71, 164, 1, 45, 5 }, { 71, 68, 1, 45, 20 }, { 71, 114, 705, 2147483647, 19 }, { 71, 180, 668, 2147483647, 4 }, { 71, 73, 34, 2147483647, 17 }, { 71, 159, 968, 2147483647, 18 }, { 71, 145, 431, 2147483647, 16 }, { 129, 71, 809, 2147483647, 20 }, { 129, 185, 152, 2147483647, 17 }, { 129, 102, 26, 6025, 1 }, { 129, 60, 311, 2147483647, 1 }, { 129, 68, 5, 443, 20 }, { 129, 183, 175, 2147483647, 12 }, { 129, 55, 836, 2147483647, 15 }, { 129, 113, 28, 3584, 3 }, { 129, 166, 6, 3474, 12 }, { 129, 130, 579, 2147483647, 19 }, { 129, 196, 744, 2147483647, 9 }, { 129, 62, 24, 3169, 5 }, { 129, 123, 6, 8114, 20 }, { 129, 81, 29, 5719, 2 }, { 129, 170, 1, 93, 3 }, { 129, 153, 2, 417, 6 }, { 5, 114, 20, 2147483647, 20 }, { 5, 126, 31, 3414, 15 }, { 5, 98, 858, 2147483647, 17 }, { 5, 177, 56, 5993, 8 }, { 5, 63, 4, 8335, 11 }, { 114, 139, 605, 2147483647, 20 }, { 114, 182, 587, 2147483647, 17 }, { 114, 104, 9, 7610, 1 }, { 114, 78, 310, 2147483647, 16 }, { 114, 135, 26, 4029, 11 }, { 114, 70, 60, 2147483647, 8 }, { 114, 161, 13, 3850, 1 }, { 139, 179, 870, 2147483647, 20 }, { 139, 98, 607, 2147483647, 19 }, { 139, 65, 488, 2147483647, 19 }, { 6, 73, 131, 2147483647, 20 }, { 6, 172, 546, 2147483647, 17 }, { 6, 197, 846, 2147483647, 20 }, { 6, 164, 32, 7052, 6 }, { 6, 166, 152, 2147483647, 20 }, { 6, 156, 9, 2780, 3 }, { 6, 174, 485, 2147483647, 6 }, { 6, 56, 24, 2994, 9 }, { 6, 150, 436, 2147483647, 14 }, { 6, 55, 49, 2147483647, 17 }, { 6, 157, 574, 2147483647, 16 }, { 6, 186, 261, 2147483647, 11 }, { 6, 93, 42, 4154, 18 }, { 6, 76, 64, 2147483647, 4 }, { 73, 92, 947, 2147483647, 20 }, { 73, 181, 1, 32, 19 }, { 73, 127, 49, 7060, 12 }, { 73, 160, 24, 2422, 12 }, { 73, 82, 534, 2147483647, 2 }, { 73, 98, 861, 2147483647, 17 }, { 73, 89, 196, 2147483647, 3 }, { 73, 150, 23, 2415, 10 }, { 73, 188, 30, 4484, 6 }, { 73, 123, 126, 2147483647, 19 }, { 73, 53, 223, 2147483647, 8 }, { 73, 125, 534, 2147483647, 10 }, { 73, 153, 4, 1504, 4 }, { 73, 130, 721, 2147483647, 3 }, { 73, 104, 3, 3185, 10 }, { 73, 63, 320, 2147483647, 10 }, { 73, 185, 459, 2147483647, 11 }, { 92, 97, 1, 32, 20 }, { 92, 190, 974, 2147483647, 3 }, { 92, 121, 369, 2147483647, 18 }, { 92, 55, 83, 9894, 7 }, { 92, 118, 68, 7213, 5 }, { 92, 84, 644, 2147483647, 8 }, { 92, 65, 9, 5822, 11 }, { 92, 139, 932, 2147483647, 19 }, { 92, 104, 21, 4487, 20 }, { 92, 58, 9, 2147483647, 12 }, { 92, 149, 44, 2147483647, 11 }, { 92, 134, 15, 1427, 1 }, { 92, 169, 11, 1951, 11 }, { 92, 142, 240, 2147483647, 5 }, { 97, 122, 1, 32, 14 }, { 97, 175, 12, 2198, 14 }, { 97, 192, 775, 2147483647, 4 }, { 97, 176, 56, 2147483647, 10 }, { 97, 74, 39, 3886, 19 }, { 122, 184, 1, 32, 17 }, { 122, 187, 968, 2147483647, 20 }, { 122, 174, 1, 32, 18 }, { 122, 193, 17, 2094, 14 }, { 122, 55, 22, 2333, 7 }, { 122, 179, 82, 9508, 3 }, { 122, 77, 641, 2147483647, 2 }, { 122, 125, 17, 8602, 5 }, { 122, 199, 12, 6645, 2 }, { 122, 106, 12, 2028, 10 }, { 122, 89, 4, 493, 6 }, { 122, 85, 12, 9556, 18 }, { 7, 150, 14, 2147483647, 20 }, { 7, 131, 48, 2147483647, 13 }, { 7, 161, 2, 4923, 16 }, { 7, 136, 829, 2147483647, 9 }, { 7, 185, 422, 2147483647, 1 }, { 7, 64, 16, 6699, 2 }, { 7, 183, 44, 7760, 12 }, { 7, 160, 570, 2147483647, 13 }, { 7, 86, 112, 2147483647, 18 }, { 7, 140, 44, 2147483647, 7 }, { 7, 159, 935, 2147483647, 8 }, { 53, 134, 1, 45, 9 }, { 53, 175, 1, 45, 6 }, { 53, 76, 27, 8048, 9 }, { 134, 180, 1, 45, 20 }, { 134, 197, 667, 2147483647, 13 }, { 134, 190, 36, 4296, 5 }, { 134, 194, 10, 1779, 12 }, { 134, 107, 633, 2147483647, 12 }, { 150, 53, 472, 2147483647, 20 }, { 150, 151, 1, 45, 1 }, { 150, 174, 965, 2147483647, 8 }, { 150, 167, 26, 4263, 7 }, { 150, 59, 21, 2766, 10 }, { 150, 139, 919, 2147483647, 4 }, { 150, 69, 18, 2602, 13 }, { 150, 56, 73, 2147483647, 15 }, { 150, 118, 5, 7982, 1 }, { 150, 185, 744, 2147483647, 12 }, { 150, 191, 31, 8397, 3 }, { 150, 79, 903, 2147483647, 20 }, { 150, 134, 2, 3564, 15 }, { 150, 197, 528, 2147483647, 4 }, { 150, 125, 579, 2147483647, 12 }, { 150, 178, 41, 5497, 13 }, { 8, 127, 1, 29, 20 }, { 8, 71, 11, 4672, 19 }, { 8, 67, 5, 1413, 18 }, { 8, 193, 214, 2147483647, 19 }, { 8, 123, 393, 2147483647, 9 }, { 8, 60, 796, 2147483647, 8 }, { 8, 99, 413, 2147483647, 17 }, { 8, 151, 16, 1616, 7 }, { 8, 166, 60, 2147483647, 12 }, { 8, 59, 350, 2147483647, 1 }, { 8, 159, 75, 2147483647, 2 }, { 8, 140, 14, 9486, 20 }, { 8, 61, 18, 4251, 10 }, { 52, 143, 33, 2147483647, 20 }, { 52, 146, 763, 2147483647, 7 }, { 52, 59, 182, 2147483647, 17 }, { 52, 198, 519, 2147483647, 12 }, { 52, 88, 27, 2725, 6 }, { 52, 183, 199, 2147483647, 4 }, { 52, 197, 937, 2147483647, 3 }, { 52, 96, 7, 1080, 13 }, { 52, 161, 233, 2147483647, 3 }, { 52, 157, 606, 2147483647, 14 }, { 52, 144, 23, 6011, 6 }, { 52, 179, 17, 4134, 3 }, { 52, 134, 8, 2878, 14 }, { 52, 184, 45, 2147483647, 9 }, { 52, 139, 809, 2147483647, 6 }, { 52, 61, 4, 409, 12 }, { 52, 132, 451, 2147483647, 18 }, { 76, 52, 563, 2147483647, 20 }, { 76, 181, 596, 2147483647, 20 }, { 76, 187, 73, 2147483647, 14 }, { 76, 121, 2, 457, 17 }, { 127, 148, 689, 2147483647, 20 }, { 127, 182, 1, 29, 18 }, { 127, 123, 200, 2147483647, 9 }, { 127, 76, 405, 2147483647, 8 }, { 127, 74, 355, 2147483647, 7 }, { 143, 161, 193, 2147483647, 18 }, { 143, 159, 665, 2147483647, 17 }, { 143, 117, 33, 6845, 7 }, { 143, 194, 34, 6106, 5 }, { 143, 75, 152, 2147483647, 6 }, { 143, 138, 108, 2147483647, 8 }, { 143, 178, 340, 2147483647, 17 }, { 143, 140, 44, 7188, 11 }, { 143, 191, 913, 2147483647, 19 }, { 143, 164, 24, 2147483647, 9 }, { 143, 84, 70, 8979, 4 }, { 143, 97, 69, 7649, 16 }, { 148, 76, 1, 29, 20 }, { 148, 170, 1, 29, 20 }, { 148, 93, 195, 2147483647, 16 }, { 148, 58, 139, 2147483647, 14 }, { 148, 63, 6, 713, 10 }, { 148, 155, 84, 8846, 5 }, { 148, 87, 21, 2147483647, 20 }, { 148, 75, 629, 2147483647, 16 }, { 148, 95, 12, 1734, 1 }, { 148, 124, 80, 7970, 18 }, { 148, 143, 483, 2147483647, 7 }, { 9, 142, 40, 2147483647, 20 }, { 9, 145, 15, 3013, 10 }, { 9, 176, 8, 1690, 19 }, { 9, 63, 325, 2147483647, 4 }, { 9, 89, 295, 2147483647, 20 }, { 9, 110, 345, 2147483647, 11 }, { 9, 128, 19, 6145, 2 }, { 9, 182, 77, 9997, 4 }, { 9, 175, 50, 5835, 9 }, { 9, 126, 998, 2147483647, 16 }, { 9, 156, 587, 2147483647, 17 }, { 9, 115, 4, 3233, 4 }, { 9, 82, 7, 2147483647, 20 }, { 9, 84, 457, 2147483647, 15 }, { 9, 109, 67, 7956, 18 }, { 9, 60, 56, 5784, 4 }, { 61, 179, 648, 2147483647, 18 }, { 61, 55, 21, 3057, 6 }, { 61, 199, 110, 2147483647, 13 }, { 61, 127, 6, 4717, 13 }, { 61, 160, 7, 2283, 16 }, { 142, 61, 772, 2147483647, 15 }, { 142, 158, 1, 22, 2 }, { 142, 188, 3, 4691, 20 }, { 142, 64, 1, 6914, 2 }, { 142, 196, 125, 2147483647, 18 }, { 142, 123, 15, 2147483647, 20 }, { 142, 70, 726, 2147483647, 11 }, { 142, 96, 162, 2147483647, 11 }, { 142, 134, 127, 2147483647, 17 }, { 142, 165, 371, 2147483647, 11 }, { 142, 162, 528, 2147483647, 14 }, { 142, 185, 3, 438, 12 }, { 142, 179, 25, 6905, 13 }, { 142, 161, 524, 2147483647, 18 }, { 142, 163, 598, 2147483647, 6 }, { 142, 55, 39, 9411, 20 }, { 10, 54, 1, 18, 13 }, { 10, 147, 572, 2147483647, 17 }, { 10, 170, 895, 2147483647, 17 }, { 10, 135, 77, 2147483647, 7 }, { 10, 156, 6, 736, 16 }, { 10, 159, 555, 2147483647, 18 }, { 10, 197, 297, 2147483647, 9 }, { 54, 147, 1, 18, 20 }, { 54, 158, 452, 2147483647, 20 }, { 54, 171, 383, 2147483647, 9 }, { 54, 126, 989, 2147483647, 3 }, { 54, 197, 20, 6703, 20 }, { 54, 117, 2, 206, 4 }, { 54, 104, 432, 2147483647, 3 }, { 54, 51, 666, 2147483647, 3 }, { 54, 156, 650, 2147483647, 13 }, { 147, 161, 1, 18, 20 }, { 147, 197, 48, 2147483647, 20 }, { 147, 109, 2, 723, 18 }, { 147, 58, 47, 7454, 5 }, { 147, 52, 820, 2147483647, 16 }, { 147, 141, 83, 9109, 10 }, { 147, 164, 17, 2963, 14 }, { 147, 156, 57, 7713, 3 }, { 147, 193, 21, 2047, 1 }, { 147, 136, 627, 2147483647, 14 }, { 147, 157, 629, 2147483647, 16 }, { 147, 179, 50, 6956, 13 }, { 147, 189, 859, 2147483647, 6 }, { 147, 155, 15, 4362, 11 }, { 147, 120, 69, 9967, 7 }, { 147, 129, 651, 2147483647, 10 }, { 147, 51, 55, 5702, 6 }, { 11, 106, 880, 2147483647, 20 }, { 11, 92, 887, 2147483647, 20 }, { 11, 80, 58, 8221, 18 }, { 11, 194, 66, 6916, 6 }, { 11, 136, 34, 7654, 2 }, { 11, 142, 522, 2147483647, 5 }, { 11, 63, 57, 7709, 7 }, { 11, 113, 469, 2147483647, 19 }, { 11, 75, 16, 1860, 8 }, { 11, 115, 717, 2147483647, 3 }, { 11, 127, 39, 8917, 15 }, { 11, 61, 1, 56, 16 }, { 69, 196, 149, 2147483647, 20 }, { 69, 142, 868, 2147483647, 14 }, { 69, 140, 4, 2147483647, 5 }, { 69, 175, 936, 2147483647, 4 }, { 69, 74, 16, 1877, 4 }, { 69, 130, 753, 2147483647, 9 }, { 69, 157, 57, 2147483647, 11 }, { 69, 164, 12, 7900, 6 }, { 106, 115, 848, 2147483647, 20 }, { 106, 114, 15, 5272, 11 }, { 106, 194, 63, 8813, 6 }, { 106, 129, 69, 8620, 14 }, { 106, 162, 251, 2147483647, 11 }, { 106, 176, 322, 2147483647, 12 }, { 106, 65, 842, 2147483647, 4 }, { 106, 198, 12, 8777, 6 }, { 106, 78, 2, 985, 15 }, { 106, 64, 107, 2147483647, 18 }, { 106, 199, 9, 2147483647, 8 }, { 106, 93, 762, 2147483647, 9 }, { 106, 195, 84, 2147483647, 6 }, { 115, 69, 1, 16, 20 }, { 115, 156, 1, 16, 14 }, { 115, 152, 662, 2147483647, 5 }, { 115, 75, 803, 2147483647, 20 }, { 12, 107, 766, 2147483647, 11 }, { 12, 78, 339, 2147483647, 13 }, { 12, 66, 25, 3939, 19 }, { 12, 98, 439, 2147483647, 16 }, { 12, 136, 456, 2147483647, 14 }, { 12, 56, 16, 2735, 10 }, { 12, 55, 70, 7604, 20 }, { 12, 125, 48, 8941, 4 }, { 107, 200, 392, 2147483647, 20 }, { 107, 185, 969, 2147483647, 9 }, { 107, 176, 7, 5726, 8 }, { 107, 187, 37, 4489, 19 }, { 107, 99, 23, 4674, 15 }, { 107, 65, 786, 2147483647, 4 }, { 107, 183, 312, 2147483647, 4 }, { 107, 93, 13, 7708, 2 }, { 107, 113, 805, 2147483647, 17 }, { 107, 82, 183, 2147483647, 8 }, { 107, 190, 357, 2147483647, 17 }, { 107, 85, 8, 836, 2 }, { 107, 179, 563, 2147483647, 1 }, { 107, 184, 16, 8367, 2 }, { 107, 142, 992, 2147483647, 10 }, { 107, 143, 76, 2147483647, 3 }, { 107, 100, 698, 2147483647, 10 }, { 107, 172, 387, 2147483647, 7 }, { 13, 81, 363, 2147483647, 14 }, { 13, 57, 1, 2920, 5 }, { 13, 113, 950, 2147483647, 7 }, { 13, 152, 321, 2147483647, 13 }, { 13, 93, 656, 2147483647, 11 }, { 13, 99, 82, 2147483647, 2 }, { 13, 53, 13, 1214, 19 }, { 13, 179, 76, 9141, 16 }, { 13, 198, 564, 2147483647, 1 }, { 13, 130, 81, 8935, 19 }, { 13, 158, 49, 7805, 2 }, { 13, 184, 58, 6452, 16 }, { 13, 133, 104, 2147483647, 11 }, { 13, 76, 25, 3216, 7 }, { 13, 193, 935, 2147483647, 18 }, { 77, 182, 642, 2147483647, 20 }, { 77, 188, 1, 10, 19 }, { 77, 154, 1, 10, 8 }, { 77, 86, 632, 2147483647, 14 }, { 77, 172, 642, 2147483647, 3 }, { 77, 144, 103, 2147483647, 19 }, { 77, 132, 25, 9038, 4 }, { 77, 99, 370, 2147483647, 9 }, { 77, 63, 142, 2147483647, 8 }, { 77, 79, 598, 2147483647, 13 }, { 77, 164, 55, 9888, 7 }, { 81, 89, 835, 2147483647, 8 }, { 81, 194, 598, 2147483647, 20 }, { 81, 129, 9, 1386, 18 }, { 81, 83, 32, 9254, 10 }, { 89, 133, 367, 2147483647, 5 }, { 89, 125, 24, 2147483647, 8 }, { 89, 122, 27, 3786, 2 }, { 89, 119, 9, 1360, 6 }, { 89, 156, 247, 2147483647, 15 }, { 89, 162, 519, 2147483647, 4 }, { 89, 127, 8, 2450, 4 }, { 89, 153, 868, 2147483647, 7 }, { 133, 77, 1, 10, 20 }, { 133, 150, 5, 9673, 6 }, { 133, 123, 37, 4167, 18 }, { 133, 161, 717, 2147483647, 8 }, { 133, 132, 186, 2147483647, 8 }, { 133, 92, 708, 2147483647, 20 }, { 133, 90, 12, 2875, 1 }, { 14, 51, 336, 2147483647, 13 }, { 14, 63, 7, 687, 8 }, { 14, 185, 38, 7679, 3 }, { 14, 54, 69, 9563, 13 }, { 14, 189, 19, 4370, 10 }, { 14, 139, 18, 7376, 7 }, { 14, 105, 562, 2147483647, 15 }, { 14, 123, 12, 6895, 19 }, { 14, 103, 44, 7112, 13 }, { 14, 62, 44, 5822, 6 }, { 14, 114, 285, 2147483647, 17 }, { 14, 61, 43, 2147483647, 4 }, { 51, 192, 104, 2147483647, 20 }, { 51, 160, 27, 2147483647, 7 }, { 51, 198, 421, 2147483647, 18 }, { 51, 71, 156, 2147483647, 11 }, { 51, 81, 8, 3892, 1 }, { 51, 66, 16, 4923, 7 }, { 51, 171, 52, 7001, 10 }, { 51, 95, 42, 7526, 15 }, { 51, 188, 517, 2147483647, 19 }, { 51, 142, 49, 7044, 1 }, { 51, 135, 22, 2147483647, 14 }, { 51, 175, 858, 2147483647, 2 }, { 51, 152, 367, 2147483647, 18 }, { 51, 143, 774, 2147483647, 9 }, { 51, 62, 734, 2147483647, 18 }, { 51, 128, 3, 7982, 1 }, { 51, 163, 836, 2147483647, 13 }, { 51, 200, 325, 2147483647, 8 }, { 15, 108, 1, 4, 20 }, { 15, 170, 358, 2147483647, 13 }, { 15, 176, 129, 2147483647, 10 }, { 15, 187, 64, 7865, 8 }, { 15, 59, 4, 715, 9 }, { 15, 169, 558, 2147483647, 18 }, { 108, 183, 985, 2147483647, 20 }, { 108, 198, 1, 4, 6 }, { 108, 181, 432, 2147483647, 5 }, { 108, 192, 644, 2147483647, 3 }, { 108, 86, 4, 4052, 19 }, { 108, 72, 594, 2147483647, 16 }, { 108, 111, 83, 8665, 9 }, { 108, 160, 62, 6810, 16 }, { 108, 83, 863, 2147483647, 5 }, { 16, 65, 917, 2147483647, 20 }, { 16, 70, 5, 6264, 16 }, { 16, 91, 25, 2559, 18 }, { 16, 169, 335, 2147483647, 13 }, { 16, 60, 1, 4474, 17 }, { 16, 72, 116, 2147483647, 5 }, { 16, 192, 73, 8487, 19 }, { 16, 121, 185, 2147483647, 6 }, { 16, 51, 72, 2147483647, 8 }, { 65, 191, 632, 2147483647, 20 }, { 65, 197, 1, 17, 2 }, { 65, 180, 464, 2147483647, 4 }, { 65, 123, 35, 4017, 6 }, { 65, 67, 3, 4848, 16 }, { 17, 94, 718, 2147483647, 18 }, { 17, 106, 6, 4391, 1 }, { 17, 122, 555, 2147483647, 20 }, { 17, 195, 67, 7506, 15 }, { 17, 191, 890, 2147483647, 1 }, { 17, 87, 2, 209, 12 }, { 17, 68, 11, 5814, 2 }, { 17, 156, 857, 2147483647, 7 }, { 94, 179, 1, 20, 20 }, { 94, 187, 228, 2147483647, 11 }, { 94, 197, 372, 2147483647, 2 }, { 94, 188, 93, 2147483647, 15 }, { 94, 153, 327, 2147483647, 6 }, { 94, 155, 454, 2147483647, 2 }, { 94, 76, 20, 6658, 6 }, { 94, 53, 262, 2147483647, 18 }, { 94, 55, 677, 2147483647, 2 }, { 94, 105, 382, 2147483647, 1 }, { 94, 91, 186, 2147483647, 18 }, { 18, 86, 1, 29, 15 }, { 18, 122, 56, 5828, 15 }, { 18, 83, 188, 2147483647, 3 }, { 86, 160, 997, 2147483647, 20 }, { 86, 200, 172, 2147483647, 20 }, { 86, 106, 2, 4665, 17 }, { 86, 188, 3, 5339, 1 }, { 86, 153, 520, 2147483647, 14 }, { 86, 169, 584, 2147483647, 11 }, { 86, 115, 549, 2147483647, 2 }, { 86, 98, 783, 2147483647, 6 }, { 86, 174, 75, 9764, 3 }, { 86, 112, 53, 5703, 18 }, { 86, 100, 10, 7830, 2 }, { 86, 116, 161, 2147483647, 14 }, { 86, 172, 38, 4758, 11 }, { 86, 72, 22, 6458, 8 }, { 19, 85, 747, 2147483647, 20 }, { 19, 116, 4, 3562, 13 }, { 19, 197, 781, 2147483647, 15 }, { 19, 94, 314, 2147483647, 6 }, { 19, 124, 26, 6311, 1 }, { 19, 171, 17, 2972, 6 }, { 19, 177, 16, 5605, 5 }, { 19, 97, 317, 2147483647, 10 }, { 19, 111, 65, 7488, 7 }, { 19, 164, 903, 2147483647, 14 }, { 19, 119, 207, 2147483647, 19 }, { 19, 140, 659, 2147483647, 12 }, { 19, 148, 761, 2147483647, 17 }, { 19, 55, 41, 6213, 7 }, { 19, 131, 556, 2147483647, 7 }, { 85, 171, 1, 25, 13 }, { 85, 156, 1, 25, 9 }, { 85, 104, 16, 7673, 17 }, { 85, 70, 36, 7801, 10 }, { 85, 155, 800, 2147483647, 5 }, { 85, 153, 19, 4949, 13 }, { 85, 119, 18, 3870, 16 }, { 85, 200, 12, 2147483647, 6 }, { 85, 142, 561, 2147483647, 1 }, { 85, 114, 676, 2147483647, 17 }, { 85, 139, 805, 2147483647, 2 }, { 85, 72, 182, 2147483647, 2 }, { 85, 53, 122, 2147483647, 17 }, { 85, 51, 559, 2147483647, 13 }, { 85, 175, 11, 7428, 9 }, { 85, 131, 7, 1757, 20 }, { 20, 57, 1, 1, 9 }, { 20, 58, 604, 2147483647, 18 }, { 20, 194, 50, 5128, 13 }, { 20, 55, 950, 2147483647, 7 }, { 20, 96, 20, 2147483647, 2 }, { 20, 111, 25, 2651, 17 }, { 20, 64, 12, 3459, 1 }, { 20, 167, 21, 3527, 12 }, { 20, 76, 21, 3425, 17 }, { 20, 92, 6, 710, 4 }, { 20, 69, 8, 8772, 1 }, { 20, 122, 482, 2147483647, 9 }, { 20, 192, 486, 2147483647, 13 }, { 20, 156, 53, 9987, 15 }, { 20, 63, 19, 6658, 10 }, { 57, 155, 691, 2147483647, 20 }, { 57, 156, 1, 1, 20 }, { 57, 198, 96, 2147483647, 7 }, { 57, 130, 10, 5415, 3 }, { 57, 133, 373, 2147483647, 3 }, { 57, 107, 19, 3182, 5 }, { 57, 154, 515, 2147483647, 9 }, { 57, 151, 8, 1312, 10 }, { 57, 82, 191, 2147483647, 6 }, { 57, 147, 509, 2147483647, 14 }, { 57, 186, 931, 2147483647, 17 }, { 57, 66, 21, 3703, 9 }, { 57, 140, 290, 2147483647, 11 }, { 21, 87, 1, 16, 2 }, { 21, 145, 19, 2041, 10 }, { 21, 183, 654, 2147483647, 10 }, { 21, 106, 857, 2147483647, 14 }, { 21, 54, 902, 2147483647, 2 }, { 21, 197, 3, 7597, 6 }, { 21, 113, 10, 1033, 17 }, { 21, 177, 818, 2147483647, 17 }, { 21, 156, 606, 2147483647, 16 }, { 72, 154, 1, 16, 13 }, { 72, 187, 1, 16, 20 }, { 72, 120, 801, 2147483647, 12 }, { 72, 96, 474, 2147483647, 9 }, { 72, 73, 24, 2923, 8 }, { 72, 101, 23, 2758, 4 }, { 72, 153, 384, 2147483647, 14 }, { 72, 99, 636, 2147483647, 18 }, { 72, 66, 39, 8346, 16 }, { 72, 168, 9, 1076, 13 }, { 72, 95, 40, 6499, 2 }, { 72, 111, 62, 7388, 5 }, { 87, 72, 1, 16, 6 }, { 87, 86, 26, 5286, 19 }, { 87, 55, 17, 3009, 2 }, { 87, 156, 17, 1985, 13 }, { 22, 117, 1, 20, 9 }, { 22, 146, 104, 2147483647, 11 }, { 22, 169, 7, 1730, 8 }, { 22, 92, 10, 1950, 2 }, { 22, 143, 977, 2147483647, 9 }, { 22, 121, 199, 2147483647, 5 }, { 22, 51, 6, 2309, 4 }, { 22, 103, 614, 2147483647, 1 }, { 22, 119, 437, 2147483647, 2 }, { 22, 91, 59, 6037, 3 }, { 22, 197, 961, 2147483647, 6 }, { 22, 81, 22, 3105, 13 }, { 22, 97, 474, 2147483647, 5 }, { 22, 90, 719, 2147483647, 1 }, { 60, 176, 1, 20, 10 }, { 60, 132, 33, 5575, 18 }, { 60, 79, 560, 2147483647, 16 }, { 60, 199, 366, 2147483647, 7 }, { 60, 55, 945, 2147483647, 7 }, { 60, 77, 24, 7999, 18 }, { 60, 122, 867, 2147483647, 12 }, { 60, 196, 26, 3133, 20 }, { 117, 119, 1, 20, 20 }, { 117, 153, 84, 2147483647, 4 }, { 117, 135, 13, 1816, 7 }, { 119, 60, 1, 20, 19 }, { 119, 154, 711, 2147483647, 20 }, { 119, 146, 7, 709, 18 }, { 119, 172, 6, 8551, 8 }, { 119, 163, 271, 2147483647, 10 }, { 119, 180, 935, 2147483647, 1 }, { 119, 126, 329, 2147483647, 6 }, { 119, 52, 14, 2503, 1 }, { 119, 102, 30, 2147483647, 17 }, { 119, 67, 34, 9928, 16 }, { 119, 111, 191, 2147483647, 5 }, { 119, 179, 855, 2147483647, 4 }, { 119, 62, 2, 204, 5 }, { 119, 148, 719, 2147483647, 17 }, { 119, 88, 978, 2147483647, 16 }, { 119, 84, 10, 1028, 11 }, { 119, 87, 16, 1679, 14 }, { 23, 101, 781, 2147483647, 20 }, { 23, 153, 290, 2147483647, 2 }, { 23, 171, 705, 2147483647, 20 }, { 23, 52, 3, 4064, 14 }, { 23, 131, 10, 2216, 2 }, { 23, 78, 852, 2147483647, 3 }, { 23, 118, 749, 2147483647, 8 }, { 23, 64, 37, 4544, 11 }, { 23, 179, 4, 1885, 15 }, { 23, 119, 980, 2147483647, 13 }, { 23, 148, 47, 5911, 11 }, { 23, 99, 11, 9285, 19 }, { 63, 187, 190, 2147483647, 12 }, { 63, 165, 256, 2147483647, 17 }, { 63, 90, 8, 8477, 19 }, { 63, 173, 55, 2147483647, 4 }, { 101, 63, 891, 2147483647, 14 }, { 101, 193, 572, 2147483647, 20 }, { 101, 77, 67, 2147483647, 11 }, { 101, 81, 74, 7782, 20 }, { 101, 199, 450, 2147483647, 6 }, { 101, 171, 10, 8658, 8 }, { 101, 185, 83, 9397, 20 }, { 101, 131, 97, 2147483647, 13 }, { 101, 76, 12, 1186, 12 }, { 101, 149, 69, 8115, 10 }, { 101, 139, 750, 2147483647, 1 }, { 24, 82, 1, 6, 20 }, { 24, 65, 551, 2147483647, 3 }, { 24, 153, 622, 2147483647, 18 }, { 24, 53, 790, 2147483647, 16 }, { 24, 155, 64, 8629, 20 }, { 24, 198, 629, 2147483647, 11 }, { 24, 92, 176, 2147483647, 18 }, { 24, 148, 9, 8907, 5 }, { 24, 133, 600, 2147483647, 12 }, { 24, 56, 79, 9965, 19 }, { 24, 163, 993, 2147483647, 15 }, { 24, 99, 21, 3655, 6 }, { 82, 154, 1, 6, 6 }, { 82, 155, 1, 6, 1 }, { 82, 100, 152, 2147483647, 16 }, { 82, 112, 66, 7308, 20 }, { 82, 144, 506, 2147483647, 5 }, { 82, 106, 921, 2147483647, 6 }, { 82, 117, 12, 6736, 14 }, { 82, 190, 8, 3922, 5 }, { 82, 160, 5, 732, 14 }, { 82, 70, 34, 9037, 17 }, { 82, 90, 647, 2147483647, 14 }, { 25, 84, 62, 2147483647, 1 }, { 25, 180, 169, 2147483647, 17 }, { 84, 128, 1, 27, 20 }, { 84, 180, 1, 27, 20 }, { 84, 121, 622, 2147483647, 18 }, { 84, 165, 236, 2147483647, 4 }, { 84, 135, 602, 2147483647, 8 }, { 84, 125, 769, 2147483647, 14 }, { 84, 171, 29, 6883, 20 }, { 84, 150, 621, 2147483647, 20 }, { 84, 149, 985, 2147483647, 14 }, { 84, 70, 55, 6447, 17 }, { 84, 56, 922, 2147483647, 1 }, { 84, 126, 211, 2147483647, 19 }, { 84, 66, 663, 2147483647, 9 }, { 84, 105, 81, 2147483647, 20 }, { 84, 151, 42, 4525, 7 }, { 84, 61, 641, 2147483647, 15 }, { 128, 157, 1, 27, 18 }, { 128, 158, 334, 2147483647, 2 }, { 128, 181, 679, 2147483647, 11 }, { 128, 188, 859, 2147483647, 8 }, { 128, 108, 42, 4376, 9 }, { 128, 152, 8, 2752, 18 }, { 128, 176, 5, 3009, 16 }, { 128, 89, 56, 5842, 1 }, { 128, 114, 505, 2147483647, 18 }, { 128, 199, 3, 680, 2 }, { 128, 155, 26, 7080, 11 }, { 128, 127, 17, 3249, 6 }, { 26, 93, 780, 2147483647, 2 }, { 26, 169, 372, 2147483647, 5 }, { 26, 179, 85, 2147483647, 17 }, { 26, 145, 896, 2147483647, 8 }, { 26, 55, 869, 2147483647, 13 }, { 26, 103, 32, 8555, 9 }, { 26, 63, 17, 5690, 17 }, { 26, 68, 1, 3027, 17 }, { 26, 151, 1, 3565, 6 }, { 58, 157, 1, 7, 14 }, { 58, 165, 239, 2147483647, 9 }, { 58, 169, 85, 8479, 20 }, { 58, 121, 36, 2147483647, 10 }, { 58, 118, 221, 2147483647, 20 }, { 58, 74, 171, 2147483647, 8 }, { 58, 166, 26, 3207, 7 }, { 58, 114, 736, 2147483647, 17 }, { 58, 196, 197, 2147483647, 14 }, { 58, 167, 973, 2147483647, 2 }, { 58, 189, 143, 2147483647, 2 }, { 58, 68, 768, 2147483647, 17 }, { 58, 186, 9, 1917, 5 }, { 58, 142, 739, 2147483647, 17 }, { 62, 162, 1, 7, 20 }, { 62, 58, 454, 2147483647, 8 }, { 62, 194, 118, 2147483647, 16 }, { 62, 189, 1, 2902, 13 }, { 62, 69, 19, 2850, 18 }, { 62, 145, 56, 2147483647, 15 }, { 62, 147, 724, 2147483647, 19 }, { 62, 134, 19, 2147483647, 9 }, { 62, 138, 69, 2147483647, 15 }, { 62, 79, 32, 6519, 8 }, { 62, 59, 12, 9780, 20 }, { 62, 176, 930, 2147483647, 19 }, { 62, 125, 55, 8245, 8 }, { 93, 62, 1, 7, 17 }, { 93, 142, 1, 8087, 12 }, { 93, 123, 238, 2147483647, 4 }, { 93, 128, 743, 2147483647, 11 }, { 27, 98, 638, 2147483647, 20 }, { 27, 76, 69, 7914, 18 }, { 27, 69, 105, 2147483647, 11 }, { 27, 131, 642, 2147483647, 6 }, { 79, 167, 270, 2147483647, 20 }, { 79, 199, 1, 9, 11 }, { 79, 100, 60, 7456, 12 }, { 79, 93, 45, 5238, 20 }, { 79, 97, 8, 8866, 9 }, { 79, 133, 135, 2147483647, 10 }, { 79, 123, 3, 830, 17 }, { 79, 146, 24, 5010, 13 }, { 79, 112, 448, 2147483647, 17 }, { 79, 152, 46, 6506, 3 }, { 79, 168, 30, 3972, 8 }, { 79, 56, 602, 2147483647, 4 }, { 98, 79, 1, 9, 2 }, { 98, 177, 9, 9973, 16 }, { 98, 200, 43, 5602, 16 }, { 98, 151, 39, 4679, 18 }, { 98, 187, 32, 3485, 6 }, { 98, 130, 49, 9028, 3 }, { 28, 136, 1, 30, 13 }, { 28, 113, 705, 2147483647, 6 }, { 28, 189, 28, 7992, 16 }, { 28, 79, 340, 2147483647, 1 }, { 66, 185, 1, 30, 5 }, { 66, 63, 674, 2147483647, 1 }, { 66, 110, 1, 261, 10 }, { 136, 66, 303, 2147483647, 20 }, { 136, 198, 1, 30, 18 }, { 136, 162, 4, 1343, 13 }, { 136, 192, 7, 1283, 17 }, { 136, 166, 2, 1135, 14 }, { 136, 185, 73, 7249, 2 }, { 29, 123, 1, 17, 14 }, { 29, 104, 12, 1154, 1 }, { 29, 61, 758, 2147483647, 16 }, { 29, 58, 71, 2147483647, 9 }, { 29, 119, 26, 3590, 4 }, { 29, 111, 990, 2147483647, 10 }, { 29, 134, 685, 2147483647, 18 }, { 29, 93, 14, 1381, 11 }, { 29, 106, 999, 2147483647, 15 }, { 29, 91, 570, 2147483647, 1 }, { 29, 98, 87, 9113, 13 }, { 29, 188, 32, 8365, 5 }, { 29, 77, 129, 2147483647, 13 }, { 29, 192, 473, 2147483647, 14 }, { 123, 130, 387, 2147483647, 20 }, { 123, 191, 775, 2147483647, 18 }, { 123, 70, 757, 2147483647, 6 }, { 130, 167, 41, 2147483647, 20 }, { 130, 178, 232, 2147483647, 18 }, { 130, 106, 779, 2147483647, 5 }, { 130, 145, 358, 2147483647, 1 }, { 130, 53, 7, 3906, 5 }, { 130, 96, 776, 2147483647, 20 }, { 130, 108, 58, 6416, 4 }, { 130, 111, 818, 2147483647, 14 }, { 130, 52, 3, 266, 14 }, { 130, 199, 46, 7409, 9 }, { 130, 160, 816, 2147483647, 3 }, { 130, 69, 492, 2147483647, 18 }, { 130, 183, 765, 2147483647, 3 }, { 130, 196, 18, 4578, 1 }, { 130, 139, 50, 2147483647, 14 }, { 130, 77, 34, 7871, 5 }, { 130, 142, 38, 4024, 12 }, { 130, 97, 521, 2147483647, 4 }, { 30, 138, 616, 2147483647, 15 }, { 30, 87, 59, 9068, 2 }, { 30, 142, 676, 2147483647, 10 }, { 30, 177, 4, 3187, 8 }, { 30, 98, 188, 2147483647, 12 }, { 30, 133, 32, 6501, 5 }, { 30, 173, 36, 7252, 19 }, { 30, 153, 676, 2147483647, 18 }, { 30, 128, 67, 9903, 11 }, { 30, 175, 750, 2147483647, 6 }, { 30, 56, 958, 2147483647, 3 }, { 138, 172, 412, 2147483647, 20 }, { 138, 151, 288, 2147483647, 20 }, { 138, 145, 62, 6392, 12 }, { 138, 165, 443, 2147483647, 3 }, { 138, 70, 599, 2147483647, 16 }, { 138, 62, 177, 2147483647, 13 }, { 138, 116, 5, 1756, 17 }, { 138, 102, 19, 5047, 19 }, { 138, 147, 50, 6978, 5 }, { 138, 198, 5, 4165, 6 }, { 138, 188, 698, 2147483647, 3 }, { 138, 132, 222, 2147483647, 14 }, { 138, 95, 902, 2147483647, 17 }, { 138, 190, 22, 4589, 4 }, { 138, 195, 766, 2147483647, 13 }, { 138, 168, 390, 2147483647, 6 }, { 31, 90, 1, 11, 16 }, { 31, 170, 290, 2147483647, 8 }, { 31, 94, 108, 2147483647, 12 }, { 31, 132, 25, 2147483647, 7 }, { 31, 120, 274, 2147483647, 18 }, { 31, 127, 880, 2147483647, 16 }, { 31, 57, 660, 2147483647, 17 }, { 31, 68, 311, 2147483647, 8 }, { 31, 172, 49, 4949, 3 }, { 31, 134, 907, 2147483647, 16 }, { 31, 77, 1, 435, 12 }, { 31, 65, 793, 2147483647, 17 }, { 31, 143, 132, 2147483647, 1 }, { 31, 98, 31, 6956, 11 }, { 75, 157, 953, 2147483647, 10 }, { 75, 162, 552, 2147483647, 7 }, { 75, 102, 33, 8542, 12 }, { 75, 117, 22, 3753, 1 }, { 75, 183, 19, 3768, 4 }, { 75, 73, 34, 5038, 5 }, { 75, 58, 17, 9920, 8 }, { 90, 166, 433, 2147483647, 20 }, { 90, 96, 936, 2147483647, 6 }, { 90, 168, 987, 2147483647, 4 }, { 90, 110, 71, 9957, 14 }, { 90, 190, 7, 3781, 12 }, { 90, 54, 10, 4785, 3 }, { 90, 149, 179, 2147483647, 6 }, { 90, 60, 29, 9704, 10 }, { 90, 165, 975, 2147483647, 7 }, { 90, 66, 38, 9206, 18 }, { 90, 63, 298, 2147483647, 20 }, { 96, 137, 49, 2147483647, 10 }, { 96, 54, 871, 2147483647, 2 }, { 137, 75, 1, 11, 20 }, { 137, 185, 1, 11, 2 }, { 137, 55, 15, 7453, 18 }, { 137, 168, 17, 2456, 8 }, { 137, 199, 39, 7134, 13 }, { 137, 111, 883, 2147483647, 20 }, { 137, 78, 391, 2147483647, 3 }, { 137, 58, 694, 2147483647, 16 }, { 137, 181, 261, 2147483647, 13 }, { 137, 172, 56, 6264, 8 }, { 137, 177, 111, 2147483647, 1 }, { 32, 102, 7, 2147483647, 20 }, { 32, 125, 640, 2147483647, 4 }, { 102, 125, 1, 13, 18 }, { 102, 191, 12, 1793, 4 }, { 102, 94, 162, 2147483647, 18 }, { 125, 187, 604, 2147483647, 9 }, { 125, 195, 363, 2147483647, 20 }, { 125, 181, 1, 58, 15 }, { 125, 92, 86, 8762, 18 }, { 125, 77, 44, 5257, 11 }, { 125, 70, 173, 2147483647, 8 }, { 125, 196, 914, 2147483647, 17 }, { 125, 119, 34, 9218, 8 }, { 125, 132, 23, 6566, 17 }, { 125, 65, 53, 5513, 6 }, { 125, 143, 8, 3469, 7 }, { 125, 179, 876, 2147483647, 9 }, { 125, 66, 734, 2147483647, 4 }, { 125, 133, 784, 2147483647, 11 }, { 125, 142, 575, 2147483647, 20 }, { 125, 150, 1, 196, 12 }, { 125, 81, 5, 1255, 19 }, { 33, 103, 1, 33, 8 }, { 33, 70, 988, 2147483647, 13 }, { 33, 191, 618, 2147483647, 2 }, { 33, 82, 870, 2147483647, 20 }, { 33, 173, 612, 2147483647, 3 }, { 33, 100, 502, 2147483647, 9 }, { 33, 61, 296, 2147483647, 10 }, { 33, 56, 428, 2147483647, 9 }, { 103, 152, 1, 33, 12 }, { 103, 158, 1, 33, 20 }, { 103, 68, 4, 8392, 5 }, { 103, 189, 539, 2147483647, 7 }, { 103, 124, 9, 1626, 9 }, { 103, 155, 132, 2147483647, 7 }, { 103, 83, 50, 6426, 8 }, { 103, 92, 51, 5812, 7 }, { 103, 140, 35, 5186, 16 }, { 103, 69, 31, 6988, 17 }, { 103, 78, 685, 2147483647, 4 }, { 103, 181, 3, 3565, 1 }, { 34, 88, 1, 30, 14 }, { 34, 190, 821, 2147483647, 1 }, { 34, 80, 723, 2147483647, 15 }, { 34, 136, 5, 1159, 8 }, { 34, 71, 6, 2508, 9 }, { 34, 62, 52, 2147483647, 20 }, { 34, 76, 1, 3246, 19 }, { 34, 174, 12, 4363, 4 }, { 34, 75, 9, 1971, 2 }, { 34, 185, 39, 8158, 18 }, { 34, 132, 1, 1058, 10 }, { 34, 127, 191, 2147483647, 16 }, { 34, 90, 903, 2147483647, 7 }, { 34, 65, 42, 7835, 5 }, { 88, 197, 297, 2147483647, 20 }, { 88, 187, 1, 30, 20 }, { 88, 57, 37, 8477, 8 }, { 88, 165, 46, 5275, 15 }, { 88, 188, 610, 2147483647, 2 }, { 88, 179, 367, 2147483647, 12 }, { 88, 127, 646, 2147483647, 6 }, { 88, 123, 331, 2147483647, 20 }, { 88, 59, 61, 9111, 5 }, { 88, 146, 398, 2147483647, 7 }, { 88, 77, 56, 9765, 17 }, { 88, 192, 57, 8343, 9 }, { 88, 148, 33, 6096, 17 }, { 35, 99, 1, 21, 20 }, { 35, 196, 24, 7424, 15 }, { 35, 134, 3, 600, 12 }, { 35, 88, 157, 2147483647, 7 }, { 35, 101, 72, 9024, 12 }, { 99, 182, 1, 21, 11 }, { 99, 156, 1, 21, 20 }, { 99, 189, 20, 5173, 9 }, { 99, 72, 5, 1573, 17 }, { 99, 104, 245, 2147483647, 19 }, { 99, 89, 35, 8314, 14 }, { 99, 143, 127, 2147483647, 19 }, { 36, 55, 1, 9, 20 }, { 36, 153, 517, 2147483647, 15 }, { 36, 109, 59, 8616, 9 }, { 36, 169, 77, 8475, 20 }, { 36, 93, 715, 2147483647, 15 }, { 55, 170, 1, 9, 20 }, { 55, 157, 1, 9, 20 }, { 55, 102, 32, 3970, 19 }, { 37, 74, 603, 2147483647, 20 }, { 37, 89, 15, 3377, 11 }, { 37, 106, 5, 1567, 20 }, { 37, 171, 248, 2147483647, 4 }, { 37, 139, 127, 2147483647, 18 }, { 37, 53, 6, 8252, 11 }, { 37, 93, 86, 8678, 12 }, { 37, 180, 436, 2147483647, 17 }, { 37, 192, 737, 2147483647, 17 }, { 37, 54, 11, 8707, 5 }, { 37, 198, 733, 2147483647, 7 }, { 37, 88, 320, 2147483647, 20 }, { 37, 124, 474, 2147483647, 11 }, { 37, 130, 2, 4507, 15 }, { 37, 100, 12, 2573, 13 }, { 37, 146, 24, 9339, 16 }, { 74, 187, 702, 2147483647, 20 }, { 74, 175, 659, 2147483647, 2 }, { 74, 84, 36, 4805, 3 }, { 74, 190, 592, 2147483647, 2 }, { 74, 55, 725, 2147483647, 1 }, { 74, 166, 596, 2147483647, 14 }, { 74, 104, 587, 2147483647, 11 }, { 74, 178, 3, 521, 13 }, { 74, 120, 72, 9257, 11 }, { 74, 158, 731, 2147483647, 13 }, { 74, 64, 24, 3850, 11 }, { 74, 87, 653, 2147483647, 17 }, { 74, 156, 416, 2147483647, 11 }, { 38, 78, 1, 21, 20 }, { 38, 127, 640, 2147483647, 3 }, { 38, 187, 503, 2147483647, 16 }, { 38, 107, 24, 10000, 2 }, { 38, 196, 442, 2147483647, 1 }, { 38, 60, 9, 6732, 3 }, { 38, 176, 155, 2147483647, 14 }, { 38, 70, 555, 2147483647, 14 }, { 38, 146, 10, 2438, 11 }, { 38, 190, 848, 2147483647, 18 }, { 38, 180, 644, 2147483647, 14 }, { 38, 98, 46, 4601, 1 }, { 78, 109, 731, 2147483647, 20 }, { 78, 64, 332, 2147483647, 3 }, { 78, 133, 108, 2147483647, 7 }, { 78, 196, 16, 5150, 2 }, { 78, 114, 80, 8642, 17 }, { 78, 73, 55, 6334, 8 }, { 78, 69, 19, 9419, 12 }, { 78, 92, 890, 2147483647, 7 }, { 78, 98, 774, 2147483647, 9 }, { 78, 130, 36, 6074, 6 }, { 109, 118, 1, 21, 20 }, { 109, 117, 9, 2061, 1 }, { 109, 113, 493, 2147483647, 17 }, { 109, 105, 90, 2147483647, 13 }, { 109, 71, 13, 3323, 16 }, { 109, 190, 757, 2147483647, 11 }, { 118, 199, 690, 2147483647, 20 }, { 118, 168, 66, 2147483647, 19 }, { 118, 194, 389, 2147483647, 20 }, { 118, 122, 19, 7315, 3 }, { 118, 91, 55, 2147483647, 13 }, { 118, 128, 119, 2147483647, 2 }, { 118, 179, 561, 2147483647, 4 }, { 118, 173, 64, 2147483647, 12 }, { 118, 187, 703, 2147483647, 2 }, { 118, 151, 959, 2147483647, 11 }, { 118, 85, 636, 2147483647, 18 }, { 118, 131, 572, 2147483647, 16 }, { 118, 153, 365, 2147483647, 11 }, { 118, 108, 8, 4829, 12 }, { 118, 163, 2, 785, 5 }, { 118, 70, 29, 5478, 15 }, { 118, 97, 394, 2147483647, 16 }, { 118, 175, 44, 7394, 1 }, { 118, 73, 503, 2147483647, 14 }, { 118, 189, 704, 2147483647, 10 }, { 118, 160, 29, 5908, 1 }, { 39, 111, 169, 2147483647, 3 }, { 39, 55, 292, 2147483647, 8 }, { 39, 91, 401, 2147483647, 17 }, { 39, 57, 44, 5133, 8 }, { 39, 126, 749, 2147483647, 8 }, { 39, 148, 118, 2147483647, 11 }, { 39, 82, 938, 2147483647, 19 }, { 39, 97, 21, 2655, 20 }, { 39, 83, 88, 2147483647, 15 }, { 39, 163, 37, 4624, 15 }, { 39, 78, 22, 3255, 8 }, { 39, 104, 597, 2147483647, 3 }, { 39, 131, 28, 8143, 11 }, { 39, 103, 8, 7183, 9 }, { 39, 136, 552, 2147483647, 12 }, { 39, 99, 17, 3783, 6 }, { 39, 105, 146, 2147483647, 1 }, { 111, 168, 546, 2147483647, 15 }, { 111, 155, 161, 2147483647, 19 }, { 111, 105, 181, 2147483647, 15 }, { 111, 180, 18, 2193, 19 }, { 111, 97, 268, 2147483647, 14 }, { 111, 87, 14, 2329, 18 }, { 111, 82, 222, 2147483647, 17 }, { 111, 160, 10, 3608, 5 }, { 111, 89, 783, 2147483647, 4 }, { 111, 98, 15, 7511, 10 }, { 111, 133, 35, 2147483647, 16 }, { 111, 199, 1, 2862, 13 }, { 111, 58, 49, 7107, 16 }, { 111, 72, 415, 2147483647, 14 }, { 111, 173, 66, 2147483647, 15 }, { 40, 116, 1, 20, 20 }, { 40, 145, 891, 2147483647, 17 }, { 40, 160, 25, 3800, 10 }, { 40, 187, 41, 4447, 15 }, { 40, 84, 72, 9245, 17 }, { 40, 169, 39, 6851, 9 }, { 40, 170, 9, 1141, 11 }, { 40, 86, 917, 2147483647, 2 }, { 40, 71, 5, 1024, 12 }, { 40, 94, 681, 2147483647, 5 }, { 40, 133, 48, 2147483647, 20 }, { 40, 105, 709, 2147483647, 1 }, { 40, 139, 778, 2147483647, 8 }, { 40, 101, 49, 5745, 13 }, { 40, 78, 31, 3333, 6 }, { 40, 137, 996, 2147483647, 14 }, { 40, 128, 439, 2147483647, 1 }, { 116, 194, 324, 2147483647, 20 }, { 116, 197, 860, 2147483647, 6 }, { 116, 69, 1, 1348, 13 }, { 116, 153, 72, 9550, 1 }, { 116, 123, 2, 4125, 5 }, { 116, 68, 676, 2147483647, 3 }, { 116, 144, 177, 2147483647, 4 }, { 116, 141, 27, 9845, 10 }, { 116, 120, 53, 7325, 9 }, { 116, 139, 919, 2147483647, 16 }, { 116, 60, 33, 6642, 10 }, { 116, 157, 43, 6755, 19 }, { 116, 98, 40, 9414, 7 }, { 116, 171, 556, 2147483647, 14 }, { 116, 72, 12, 1500, 3 }, { 116, 136, 32, 6735, 8 }, { 116, 64, 269, 2147483647, 11 }, { 41, 149, 289, 2147483647, 20 }, { 41, 111, 673, 2147483647, 9 }, { 41, 98, 7, 2077, 19 }, { 41, 59, 495, 2147483647, 16 }, { 41, 101, 1, 65, 19 }, { 149, 174, 57, 2147483647, 20 }, { 149, 156, 23, 2147483647, 2 }, { 149, 98, 516, 2147483647, 1 }, { 149, 126, 830, 2147483647, 6 }, { 149, 122, 9, 5264, 12 }, { 149, 106, 16, 1759, 1 }, { 149, 194, 494, 2147483647, 5 }, { 149, 186, 31, 3577, 2 }, { 149, 89, 34, 6530, 12 }, { 149, 75, 257, 2147483647, 15 }, { 149, 180, 49, 2147483647, 14 }, { 149, 143, 98, 2147483647, 3 }, { 149, 78, 514, 2147483647, 8 }, { 149, 178, 274, 2147483647, 11 }, { 149, 95, 25, 8987, 16 }, { 149, 87, 868, 2147483647, 13 }, { 149, 90, 21, 7688, 9 }, { 42, 145, 703, 2147483647, 17 }, { 42, 129, 327, 2147483647, 14 }, { 42, 72, 488, 2147483647, 1 }, { 42, 158, 469, 2147483647, 7 }, { 83, 159, 887, 2147483647, 18 }, { 83, 151, 87, 2147483647, 19 }, { 83, 131, 306, 2147483647, 3 }, { 83, 79, 4, 2147483647, 17 }, { 83, 113, 596, 2147483647, 18 }, { 83, 58, 60, 6646, 12 }, { 145, 83, 1, 13, 18 }, { 145, 73, 517, 2147483647, 14 }, { 145, 170, 4, 626, 5 }, { 145, 200, 41, 4028, 7 }, { 43, 113, 1, 19, 6 }, { 43, 63, 623, 2147483647, 16 }, { 43, 75, 9, 3437, 11 }, { 43, 158, 16, 8955, 8 }, { 113, 186, 1, 19, 20 }, { 113, 188, 1, 19, 3 }, { 113, 77, 34, 6088, 20 }, { 113, 146, 241, 2147483647, 17 }, { 113, 191, 305, 2147483647, 6 }, { 113, 117, 18, 7265, 15 }, { 113, 68, 35, 4778, 3 }, { 113, 122, 13, 2147483647, 8 }, { 113, 75, 42, 2147483647, 1 }, { 113, 167, 39, 3958, 18 }, { 113, 139, 9, 2940, 17 }, { 113, 138, 23, 5165, 12 }, { 113, 57, 18, 2486, 9 }, { 113, 112, 969, 2147483647, 10 }, { 113, 129, 21, 2147483647, 5 }, { 113, 81, 693, 2147483647, 1 }, { 113, 102, 53, 7582, 16 }, { 113, 115, 325, 2147483647, 11 }, { 44, 141, 196, 2147483647, 18 }, { 44, 136, 110, 2147483647, 4 }, { 44, 102, 20, 9448, 20 }, { 44, 191, 401, 2147483647, 19 }, { 44, 147, 6, 750, 2 }, { 44, 186, 32, 4029, 15 }, { 44, 122, 353, 2147483647, 13 }, { 44, 51, 669, 2147483647, 9 }, { 80, 169, 21, 2147483647, 5 }, { 80, 92, 8, 2227, 15 }, { 80, 136, 12, 9247, 15 }, { 80, 148, 376, 2147483647, 5 }, { 80, 79, 297, 2147483647, 13 }, { 80, 127, 655, 2147483647, 5 }, { 80, 179, 74, 2147483647, 8 }, { 80, 87, 3, 8558, 17 }, { 80, 171, 137, 2147483647, 10 }, { 80, 156, 88, 2147483647, 18 }, { 80, 155, 409, 2147483647, 20 }, { 141, 144, 1, 36, 20 }, { 141, 153, 1, 36, 20 }, { 141, 190, 536, 2147483647, 5 }, { 141, 108, 1, 19, 4 }, { 141, 115, 878, 2147483647, 13 }, { 141, 161, 292, 2147483647, 3 }, { 141, 184, 8, 4494, 4 }, { 141, 58, 824, 2147483647, 19 }, { 141, 183, 929, 2147483647, 11 }, { 141, 76, 972, 2147483647, 4 }, { 141, 189, 536, 2147483647, 5 }, { 141, 86, 26, 9260, 11 }, { 141, 120, 868, 2147483647, 7 }, { 144, 80, 999, 2147483647, 20 }, { 144, 162, 656, 2147483647, 17 }, { 144, 97, 53, 2147483647, 3 }, { 144, 86, 11, 4919, 3 }, { 144, 158, 47, 5225, 3 }, { 144, 171, 29, 6795, 1 }, { 144, 139, 936, 2147483647, 1 }, { 144, 133, 329, 2147483647, 5 }, { 144, 51, 770, 2147483647, 14 }, { 144, 73, 266, 2147483647, 14 }, { 45, 91, 1, 11, 20 }, { 45, 168, 349, 2147483647, 11 }, { 45, 142, 28, 2868, 7 }, { 45, 157, 551, 2147483647, 4 }, { 45, 67, 56, 5751, 11 }, { 45, 178, 422, 2147483647, 20 }, { 45, 189, 31, 3738, 2 }, { 45, 87, 48, 9568, 13 }, { 91, 131, 1, 11, 10 }, { 91, 130, 882, 2147483647, 18 }, { 91, 103, 500, 2147483647, 1 }, { 91, 185, 796, 2147483647, 20 }, { 91, 191, 880, 2147483647, 3 }, { 91, 86, 11, 2549, 13 }, { 131, 178, 723, 2147483647, 5 }, { 131, 188, 165, 2147483647, 11 }, { 131, 172, 352, 2147483647, 20 }, { 131, 64, 10, 2758, 8 }, { 131, 56, 15, 9576, 17 }, { 131, 88, 792, 2147483647, 14 }, { 131, 147, 266, 2147483647, 5 }, { 46, 120, 1, 23, 20 }, { 46, 183, 180, 2147483647, 17 }, { 46, 155, 31, 6775, 9 }, { 46, 192, 217, 2147483647, 19 }, { 46, 127, 207, 2147483647, 19 }, { 46, 184, 4, 853, 15 }, { 46, 139, 937, 2147483647, 3 }, { 46, 151, 6, 571, 20 }, { 46, 150, 439, 2147483647, 4 }, { 46, 104, 23, 2822, 8 }, { 46, 176, 4, 2547, 1 }, { 46, 148, 478, 2147483647, 14 }, { 46, 105, 2, 105, 6 }, { 46, 103, 31, 8875, 8 }, { 46, 197, 719, 2147483647, 5 }, { 46, 110, 29, 4129, 3 }, { 59, 112, 279, 2147483647, 20 }, { 59, 173, 1, 23, 8 }, { 59, 90, 415, 2147483647, 19 }, { 59, 142, 18, 9159, 15 }, { 59, 166, 69, 9704, 10 }, { 59, 116, 420, 2147483647, 19 }, { 59, 178, 5, 6707, 14 }, { 59, 109, 15, 1461, 19 }, { 59, 106, 65, 8261, 10 }, { 59, 70, 868, 2147483647, 13 }, { 112, 179, 1, 23, 19 }, { 112, 125, 13, 2147483647, 4 }, { 112, 57, 13, 4341, 17 }, { 112, 158, 919, 2147483647, 14 }, { 112, 59, 373, 2147483647, 2 }, { 112, 155, 49, 2147483647, 17 }, { 120, 59, 1, 23, 20 }, { 120, 190, 501, 2147483647, 19 }, { 120, 151, 18, 4559, 20 }, { 120, 75, 931, 2147483647, 10 }, { 120, 73, 5, 681, 8 }, { 120, 58, 798, 2147483647, 4 }, { 120, 188, 360, 2147483647, 16 }, { 120, 123, 898, 2147483647, 16 }, { 120, 108, 18, 2306, 9 }, { 120, 175, 7, 6384, 13 }, { 120, 128, 537, 2147483647, 12 }, { 120, 69, 603, 2147483647, 8 }, { 47, 146, 1, 19, 20 }, { 47, 127, 160, 2147483647, 20 }, { 47, 114, 56, 5536, 20 }, { 47, 79, 68, 2147483647, 9 }, { 47, 129, 716, 2147483647, 4 }, { 47, 95, 14, 2549, 1 }, { 47, 54, 100, 2147483647, 16 }, { 47, 117, 2, 3322, 5 }, { 47, 141, 10, 4284, 18 }, { 47, 175, 5, 1379, 19 }, { 146, 170, 1, 19, 13 }, { 146, 192, 1, 19, 20 }, { 146, 135, 12, 2798, 12 }, { 146, 152, 370, 2147483647, 1 }, { 146, 140, 72, 7940, 6 }, { 146, 145, 53, 8273, 10 }, { 146, 164, 18, 2147483647, 1 }, { 146, 73, 690, 2147483647, 13 }, { 146, 99, 46, 4805, 1 }, { 146, 186, 39, 4006, 5 }, { 146, 61, 13, 9031, 18 }, { 146, 180, 2, 498, 15 }, { 146, 168, 4, 2531, 14 }, { 146, 72, 37, 6218, 20 }, { 48, 132, 879, 2147483647, 2 }, { 48, 84, 7, 661, 1 }, { 48, 139, 174, 2147483647, 16 }, { 48, 126, 6, 1809, 8 }, { 48, 143, 86, 9131, 13 }, { 132, 189, 1, 4, 18 }, { 132, 190, 322, 2147483647, 20 }, { 132, 98, 839, 2147483647, 15 }, { 132, 155, 601, 2147483647, 16 }, { 132, 164, 67, 8467, 11 }, { 132, 70, 31, 9018, 11 }, { 132, 184, 309, 2147483647, 3 }, { 132, 153, 82, 8883, 19 }, { 132, 86, 1, 887, 11 }, { 49, 64, 342, 2147483647, 20 }, { 49, 182, 3, 432, 5 }, { 64, 67, 198, 2147483647, 2 }, { 64, 176, 1, 7, 20 }, { 64, 169, 44, 5750, 16 }, { 64, 73, 111, 2147483647, 1 }, { 64, 182, 670, 2147483647, 5 }, { 64, 90, 374, 2147483647, 18 }, { 67, 157, 1, 7, 19 }, { 67, 149, 356, 2147483647, 4 }, { 67, 194, 38, 8224, 11 }, { 67, 104, 651, 2147483647, 7 }, { 67, 155, 55, 6497, 16 }, { 67, 98, 416, 2147483647, 9 }, { 67, 189, 913, 2147483647, 6 }, { 67, 162, 931, 2147483647, 18 }, { 67, 144, 297, 2147483647, 20 }, { 67, 78, 30, 7754, 12 }, { 67, 148, 69, 7196, 3 }, { 67, 131, 4, 345, 18 }, { 67, 186, 32, 4220, 9 }, { 50, 124, 995, 2147483647, 20 }, { 50, 157, 94, 2147483647, 19 }, { 50, 183, 15, 6941, 6 }, { 50, 76, 9, 2147483647, 18 }, { 50, 162, 43, 2147483647, 3 }, { 100, 182, 251, 2147483647, 4 }, { 100, 155, 35, 3966, 11 }, { 100, 99, 990, 2147483647, 10 }, { 100, 95, 186, 2147483647, 18 }, { 100, 161, 50, 8035, 19 }, { 100, 149, 28, 8583, 18 }, { 100, 148, 186, 2147483647, 9 }, { 100, 154, 197, 2147483647, 5 }, { 100, 128, 171, 2147483647, 10 }, { 100, 112, 39, 9086, 5 }, { 100, 102, 10, 3342, 9 }, { 100, 153, 594, 2147483647, 11 }, { 100, 192, 635, 2147483647, 6 }, { 100, 184, 351, 2147483647, 19 }, { 100, 104, 61, 7477, 9 }, { 100, 110, 990, 2147483647, 17 }, { 100, 74, 14, 1894, 12 }, { 100, 108, 539, 2147483647, 15 }, { 100, 89, 141, 2147483647, 8 }, { 124, 100, 1, 26, 15 }, { 124, 198, 629, 2147483647, 9 }, { 124, 163, 22, 2147483647, 8 }, }; test(testCase, 4610258); } private void test(int[][] testCase, double cost) { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); Map supplyMap = new HashMap<>(); Map lowerMap = new HashMap<>(); Map upperMap = new HashMap<>(); for (int[] data : testCase) { if (data.length == 2) { // this is information about a supply of a node graph.addVertex(data[0]); supplyMap.put(data[0], data[1]); } else { // this is information about an edge DefaultWeightedEdge edge = Graphs.addEdgeWithVertices(graph, data[0], data[1], data[4]); lowerMap.put(edge, data[2]); upperMap.put(edge, data[3]); } } MinimumCostFlowProblem problem = new MinimumCostFlowProblem.MinimumCostFlowProblemImpl<>( graph, v -> supplyMap.getOrDefault(v, 0), upperMap::get, e -> lowerMap.getOrDefault(e, 0)); CapacityScalingMinimumCostFlow minimumCostFlowAlgorithm = new CapacityScalingMinimumCostFlow<>(scalingFactor); MinimumCostFlow minimumCostFlow = minimumCostFlowAlgorithm.getMinimumCostFlow(problem); assertEquals(cost, minimumCostFlow.getCost(), EPS); assertTrue(minimumCostFlowAlgorithm.testOptimality(EPS)); assertTrue( checkFlowAndDualSolution( minimumCostFlowAlgorithm.getDualSolution(), minimumCostFlow, problem)); } private boolean checkFlowAndDualSolution( Map dualVariables, MinimumCostFlow flow, MinimumCostFlowProblem problem) { Graph graph = problem.getGraph(); // check supply constraints for (V vertex : graph.vertexSet()) { int supply = problem.getNodeSupply().apply(vertex); int flowIn = 0; for (E edge : graph.incomingEdgesOf(vertex)) { flowIn += flow.getFlow(edge); } int flowOut = 0; for (E edge : graph.outgoingEdgesOf(vertex)) { flowOut += flow.getFlow(edge); } if (supply != flowOut - flowIn) { return false; } } // check capacity constraints for (E edge : graph.edgeSet()) { if (problem.getArcCapacityLowerBounds().apply(edge) > flow.getFlow(edge) || problem.getArcCapacityUpperBounds().apply(edge) < flow.getFlow(edge)) { return false; } } for (Map.Entry entry : flow.getFlowMap().entrySet()) { E edge = entry.getKey(); if (entry.getValue() < problem.getArcCapacityUpperBounds().apply(edge)) { // non-negative flow on arc => have to check reduced cost optimality conditions if (graph.getEdgeWeight(edge) + dualVariables.get(graph.getEdgeTarget(edge)) - dualVariables.get(graph.getEdgeSource(edge)) < -EPS) { return false; } } } return true; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/independentset/000077500000000000000000000000001402514743400276745ustar00rootroot00000000000000ChordalGraphIndependentSetFinderTest.java000066400000000000000000000071751402514743400376520ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/independentset/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.independentset; import org.jgrapht.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for the {@link ChordalGraphIndependentSetFinder} * * @author Timofey Chudakov */ public class ChordalGraphIndependentSetFinderTest { /** * Tests finding of maximum independent set of an empty graph */ @Test public void testGetMaximumIndependentSet1() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); ChordalityInspector inspector = new ChordalityInspector<>(graph); Set set = new ChordalGraphIndependentSetFinder<>(graph).getIndependentSet(); assertNotNull(set); assertEquals(0, set.size()); } /** * Tests finding of maximum independent set on a clique. */ @Test public void testGetMaximumIndependentSet2() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); Set set = new ChordalGraphIndependentSetFinder<>(graph).getIndependentSet(); assertNotNull(set); assertEquals(1, set.size()); } /** * Tests finding of a maximum independent set on a non-chordal graph */ @Test public void testGetMaximumIndependentSet3() { int[][] edges = { { 1, 2 }, { 1, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(edges); Set set = new ChordalGraphIndependentSetFinder<>(graph).getIndependentSet(); assertNull(set); } /** * Tests finding of a maximum independent set on a pseudograph */ @Test public void testGetMaximumIndependentSet4() { int[][] edges = { { 1, 1 }, { 1, 2 }, { 1, 2 }, { 2, 3 }, { 2, 3 }, { 1, 3 }, { 3, 3 }, { 3, 4 }, { 3, 4 }, { 4, 4 }, { 4, 4 }, { 4, 5 }, { 4, 5 }, }; Graph graph = TestUtil.createPseudograph(edges); ChordalityInspector inspector = new ChordalityInspector<>(graph); Set set = new ChordalGraphIndependentSetFinder<>(graph).getIndependentSet(); assertNotNull(set); assertEquals(2, set.size()); assertIsIndependentSet(graph, set); } /** * Checks whether every two vertices from {@code set} aren't adjacent. * * @param graph the tested graph. * @param set the tested set of vertices. * @param the graph vertex type. * @param the graph edge type. */ private void assertIsIndependentSet(Graph graph, Set set) { ArrayList vertices = new ArrayList<>(set); for (int i = 0; i < vertices.size(); i++) { for (int j = 0; j < i; j++) { assertFalse(graph.containsEdge(vertices.get(i), vertices.get(j))); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/000077500000000000000000000000001402514743400272345ustar00rootroot00000000000000AHUForestIsomorphismInspectorTest.java000066400000000000000000000225761402514743400366150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import java.util.stream.*; import static org.jgrapht.alg.isomorphism.IsomorphismTestUtil.*; /** * Tests for {@link AHUForestIsomorphismInspector} * * @author Alexandru Valeanu */ public class AHUForestIsomorphismInspectorTest { @Test(expected = UnsupportedOperationException.class) public void testMissingSupplier() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("1"); tree1.addVertex("2"); tree1.addEdge("1", "2"); tree1.addVertex("3"); AHUForestIsomorphismInspector forestIsomorphism = new AHUForestIsomorphismInspector<>(tree1, Set.of("1", "2"), tree1, Set.of("1", "2")); forestIsomorphism.isomorphismExists(); } @Test(expected = IllegalArgumentException.class) public void testEmptyGraph() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); Set roots = new HashSet<>(); AHUForestIsomorphismInspector isomorphism = new AHUForestIsomorphismInspector<>(tree1, roots, tree1, roots); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree1, treeMapping)); } @Test public void testSingleVertex() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("1"); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); tree2.addVertex("A"); AHUForestIsomorphismInspector isomorphism = new AHUForestIsomorphismInspector<>( tree1, Collections.singleton("1"), tree2, Collections.singleton("A")); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test(expected = NullPointerException.class) public void testNullGraphs() { new AHUForestIsomorphismInspector(null, new HashSet<>(), null, null); } @Test(expected = IllegalArgumentException.class) public void testInvalidRoot() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("a"); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("A"); AHUForestIsomorphismInspector isomorphism = new AHUForestIsomorphismInspector<>( tree1, Collections.singleton("b"), tree2, Collections.singleton("A")); isomorphism.getMapping(); } @Test public void testSmallForest() { Graph tree1 = new SimpleGraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); tree1.addVertex("a"); tree1.addVertex("b"); tree1.addVertex("c"); tree1.addEdge("a", "b"); tree1.addEdge("a", "c"); tree1.addVertex("d"); Graph tree2 = new SimpleGraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); tree2.addVertex("A"); tree2.addVertex("B"); tree2.addVertex("C"); tree2.addEdge("B", "A"); tree2.addEdge("A", "C"); tree2.addVertex("D"); AHUForestIsomorphismInspector forestIsomorphism = new AHUForestIsomorphismInspector<>(tree1, Set.of("b", "d"), tree2, Set.of("A", "D")); Assert.assertFalse(forestIsomorphism.isomorphismExists()); } @Test public void testSmallForest2() { Map map = new HashMap<>(); Pair, Graph> pair = parseGraph( "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]", "[{2,1}, {3,0}, {4,0}, {5,1}, {6,1}, {7,0}, {8,1}, {9,6}, {10,1}, {11,6}, " + "{12,0}, {13,7}, {14,5}, {15,1}, {16,0}, {17,0}, {18,17}, {19,7}]", "{0=12, 1=10, 2=0, 3=8, 4=3, 5=16, 6=7, 7=18, 8=11, 9=17, 10=6, 11=14, 12=9, " + "13=5, 14=15, 15=2, 16=19, 17=13, 18=4, 19=1}", map); Graph forest1 = pair.getFirst(); Graph forest2 = pair.getSecond(); Set roots1 = new ConnectivityInspector<>(forest1) .connectedSets().stream().map(x -> x.iterator().next()).collect(Collectors.toSet()); Set roots2 = roots1.stream().map(map::get).collect(Collectors.toSet()); AHUForestIsomorphismInspector isomorphism = new AHUForestIsomorphismInspector<>(forest1, roots1, forest2, roots2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(forest1, forest2, treeMapping)); } @Test @Category(SlowTests.class) public void testHugeNumberOfChildren() { final int N = 100_000; Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= N; i++) { tree1.addVertex(i); } for (int i = 2; i <= N; i++) { tree1.addEdge(1, i); } Pair, Map> pair = generateIsomorphicGraph(tree1, new Random(0x2882)); Graph tree2 = pair.getFirst(); Map mapping = pair.getSecond(); AHUForestIsomorphismInspector isomorphism = new AHUForestIsomorphismInspector<>( tree1, Collections.singleton(1), tree2, Collections.singleton(mapping.get(1))); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test @Category(SlowTests.class) public void testRandomForests() { Random random = new Random(0x2312); final int NUM_TESTS = 1000; for (int test = 0; test < NUM_TESTS; test++) { final int N = 10 + random.nextInt(200); Graph tree1 = generateForest(N, random); Pair, Map> pair = generateIsomorphicGraph(tree1, random); Graph tree2 = pair.getFirst(); Set roots1 = new ConnectivityInspector<>(tree1) .connectedSets().stream().map(x -> x.iterator().next()).collect(Collectors.toSet()); Set roots2 = roots1.stream().map(x -> pair.getSecond().get(x)).collect(Collectors.toSet()); AHUForestIsomorphismInspector isomorphism = new AHUForestIsomorphismInspector<>(tree1, roots1, tree2, roots2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } } @Test @Category(SlowTests.class) public void testHugeRandomForest() { final int N = 50_000; Graph tree1 = generateForest(N, new Random(0x88)); Pair, Map> pair = generateIsomorphicGraph(tree1, new Random(0x88)); Graph tree2 = pair.getFirst(); Set roots1 = new ConnectivityInspector<>(tree1) .connectedSets().stream().map(x -> x.iterator().next()).collect(Collectors.toSet()); Set roots2 = roots1.stream().map(x -> pair.getSecond().get(x)).collect(Collectors.toSet()); AHUForestIsomorphismInspector isomorphism = new AHUForestIsomorphismInspector<>(tree1, roots1, tree2, roots2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } } AHURootedTreeIsomorphismInspectorTest.java000066400000000000000000000303261402514743400374170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.isomorphism.IsomorphismTestUtil.*; /** * Tests for {@link AHURootedTreeIsomorphismInspector} * * @author Alexandru Valeanu */ public class AHURootedTreeIsomorphismInspectorTest { @Test public void testSingleVertex() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("1"); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); tree2.addVertex("A"); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, "1", tree2, "A"); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test(expected = NullPointerException.class) public void testNullGraphs() { AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(null, null, null, null); } @Test(expected = NullPointerException.class) public void testOnlyOneNullGraph() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, null, null, null); } @Test public void testCornerCase() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (int i = 0; i <= 10; i++) tree1.addVertex(i); tree1.addEdge(10, 0); tree1.addEdge(10, 1); tree1.addEdge(10, 2); tree1.addEdge(10, 3); tree1.addEdge(0, 4); tree1.addEdge(0, 6); tree1.addEdge(0, 7); tree1.addEdge(2, 5); tree1.addEdge(5, 8); tree1.addEdge(4, 9); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); for (int i = 0; i <= 9; i++) tree2.addVertex(i); tree2.addVertex(11); tree2.addEdge(11, 1); tree2.addEdge(11, 2); tree2.addEdge(11, 4); tree2.addEdge(11, 7); tree2.addEdge(4, 3); tree2.addEdge(4, 6); tree2.addEdge(4, 0); tree2.addEdge(6, 5); tree2.addEdge(7, 8); tree2.addEdge(8, 9); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, 10, tree2, 11); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testCornerCase2() { Graph tree1 = parseGraph( "[1, 2, 5, 6, 8, 9, 10, 11, 14, 15]", "[{2,1}, {5,1}, {6,1}, {8,1}, {9,6}, {10,1}, {11,6}, {14,5}, {15,1}]"); Graph tree2 = parseGraph( "[1, 18, 3, 19, 4, 5, 8, 9, 12, 13]", "[{8,12}, {3,12}, {18,12}, {9,12}, {5,18}, {19,12}, {13,12}, {4,13}, {1,18}]"); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, 1, tree2, 12); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testCornerCase3() { Graph tree1 = parseGraph( "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]", "[{1,0}, {2,0}, {3,0}, {4,2}, {5,0}, {6,5}, {7,2}, {8,5}, {9,4}, {10,6}, {11,4}, {12,0}, {13,0}]"); Graph tree2 = parseGraph( "[10, 2, 12, 7, 5, 3, 4, 0, 6, 1, 13, 9, 8, 11]", "[{2,10}, {12,10}, {7,10}, {5,12}, {3,10}, {4,3}, {0,12}, {6,3}, {1,5}, {13,4}, {9,5}, {8,10}, {11,10}]"); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, 0, tree2, 10); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testNonIsomorphic() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); for (char c = 'A'; c <= 'F'; c++) { tree1.addVertex(String.valueOf(c)); tree2.addVertex(String.valueOf((char) (c + ' '))); } tree1.addEdge("A", "B"); tree1.addEdge("A", "C"); tree1.addEdge("B", "F"); tree1.addEdge("C", "D"); tree1.addEdge("C", "E"); tree2.addEdge("a", "b"); tree2.addEdge("a", "c"); tree2.addEdge("c", "f"); tree2.addEdge("c", "d"); tree2.addEdge("c", "e"); // They are not isomorphic as rooted trees AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, "A", tree2, "a"); Assert.assertFalse(isomorphism.isomorphismExists()); Assert.assertNull(isomorphism.getMapping()); } @Test public void testSmall() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); for (char c = 'A'; c <= 'E'; c++) { tree1.addVertex(String.valueOf(c)); tree2.addVertex(String.valueOf((char) (c + ' '))); } tree1.addEdge("A", "B"); tree1.addEdge("A", "C"); tree1.addEdge("C", "D"); tree1.addEdge("C", "E"); tree2.addEdge("a", "b"); tree2.addEdge("a", "c"); tree2.addEdge("b", "e"); tree2.addEdge("b", "d"); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, "A", tree2, "a"); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testSmall2() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= 13; i++) { tree1.addVertex(i); } tree1.addEdge(1, 2); tree1.addEdge(1, 3); tree1.addEdge(2, 4); tree1.addEdge(2, 5); tree1.addEdge(2, 6); tree1.addEdge(3, 7); tree1.addEdge(3, 8); tree1.addEdge(3, 9); tree1.addEdge(8, 10); tree1.addEdge(8, 11); tree1.addEdge(9, 12); tree1.addEdge(9, 13); Pair, Map> pair = generateIsomorphicGraph(tree1, new Random(0x88)); Graph tree2 = pair.getFirst(); Map mapping = pair.getSecond(); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, 1, tree2, mapping.get(1)); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testDisconnectedTree() { Graph tree1 = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); tree1.addVertex(1); tree1.addVertex(2); tree1.addVertex(3); tree1.addEdge(1, 2); Graph tree2 = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); tree2.addVertex(11); tree2.addVertex(21); tree2.addVertex(31); tree2.addEdge(11, 21); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, 1, tree2, 11); Assert.assertFalse(isomorphism.isomorphismExists()); Assert.assertNull(isomorphism.getMapping()); // Test as forest AHUForestIsomorphismInspector forestIsomorphism = new AHUForestIsomorphismInspector<>(tree1, Set.of(1, 3), tree2, Set.of(11, 31)); Assert.assertTrue(forestIsomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = forestIsomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test(expected = IllegalArgumentException.class) public void testInvalidRoot() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("a"); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("A"); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, "b", tree2, "A"); isomorphism.getMapping(); } @Test public void testLineGraph() { final int N = 20_000; Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= N; i++) { tree1.addVertex(i); } for (int i = 1; i <= N - 1; i++) { tree1.addEdge(i, i + 1); } Pair, Map> pair = generateIsomorphicGraph(tree1, new Random(0x88)); Graph tree2 = pair.getFirst(); Map mapping = pair.getSecond(); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, 1, tree2, mapping.get(1)); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testHugeNumberOfChildren() { final int N = 100_000; Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= N; i++) { tree1.addVertex(i); } for (int i = 2; i <= N; i++) { tree1.addEdge(1, i); } Pair, Map> pair = generateIsomorphicGraph(tree1, new Random(0x2882)); Graph tree2 = pair.getFirst(); Map mapping = pair.getSecond(); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, 1, tree2, mapping.get(1)); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } } AHUUnrootedTreeIsomorphismInspectorTest.java000066400000000000000000000370741402514743400377710ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import static org.jgrapht.alg.isomorphism.IsomorphismTestUtil.*; /** * Tests for {@link AHUUnrootedTreeIsomorphismInspector} * * @author Alexandru Valeanu */ public class AHUUnrootedTreeIsomorphismInspectorTest { @Test(expected = IllegalArgumentException.class) public void testEmptyGraph() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree1); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree1, treeMapping)); } @Test public void testSingleVertex() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("1"); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); tree2.addVertex("A"); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test(expected = NullPointerException.class) public void testNullGraphs() { new AHUUnrootedTreeIsomorphismInspector<>(null, null); } @Test(expected = NullPointerException.class) public void testOnlyOneNullGraph() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("a"); new AHUUnrootedTreeIsomorphismInspector<>(tree1, null); } @Test public void testUnrootedIsomorphism() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("1"); tree1.addVertex("2"); tree1.addVertex("3"); tree1.addEdge("1", "2"); tree1.addEdge("2", "3"); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); tree2.addVertex("A"); tree2.addVertex("B"); tree2.addVertex("C"); tree2.addEdge("A", "B"); tree2.addEdge("B", "C"); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testCornerCase() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (int i = 0; i <= 10; i++) tree1.addVertex(i); tree1.addEdge(10, 0); tree1.addEdge(10, 1); tree1.addEdge(10, 2); tree1.addEdge(10, 3); tree1.addEdge(0, 4); tree1.addEdge(0, 6); tree1.addEdge(0, 7); tree1.addEdge(2, 5); tree1.addEdge(5, 8); tree1.addEdge(4, 9); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); for (int i = 0; i <= 9; i++) tree2.addVertex(i); tree2.addVertex(11); tree2.addEdge(11, 1); tree2.addEdge(11, 2); tree2.addEdge(11, 4); tree2.addEdge(11, 7); tree2.addEdge(4, 3); tree2.addEdge(4, 6); tree2.addEdge(4, 0); tree2.addEdge(6, 5); tree2.addEdge(7, 8); tree2.addEdge(8, 9); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testCornerCase2() { Graph tree1 = parseGraph( "[1, 2, 5, 6, 8, 9, 10, 11, 14, 15]", "[{2,1}, {5,1}, {6,1}, {8,1}, {9,6}, {10,1}, {11,6}, {14,5}, {15,1}]"); Graph tree2 = parseGraph( "[1, 18, 3, 19, 4, 5, 8, 9, 12, 13]", "[{8,12}, {3,12}, {18,12}, {9,12}, {5,18}, {19,12}, {13,12}, {4,13}, {1,18}]"); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testCornerCase3() { Graph tree1 = parseGraph( "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]", "[{1,0}, {2,0}, {3,0}, {4,2}, {5,0}, {6,5}, {7,2}, {8,5}, {9,4}, {10,6}, {11,4}, {12,0}, {13,0}]"); Graph tree2 = parseGraph( "[10, 2, 12, 7, 5, 3, 4, 0, 6, 1, 13, 9, 8, 11]", "[{2,10}, {12,10}, {7,10}, {5,12}, {3,10}, {4,3}, {0,12}, {6,3}, {1,5}, {13,4}, {9,5}, {8,10}, {11,10}]"); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testNonIsomorphicAsUnrootedButAsRooted() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); for (char c = 'A'; c <= 'C'; c++) { tree1.addVertex(String.valueOf(c)); tree2.addVertex(String.valueOf((char) (c + ' '))); } tree1.addEdge("A", "B"); tree1.addEdge("B", "C"); tree2.addEdge("a", "b"); tree2.addEdge("b", "c"); // They are not isomorphic as rooted trees AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>(tree1, "A", tree2, "b"); System.out.println(); Assert.assertFalse(isomorphism.isomorphismExists()); Assert.assertNull(isomorphism.getMapping()); // But they are isomorphic as unrooted trees AHUUnrootedTreeIsomorphismInspector isomorphism2 = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism2.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism2.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testSmall() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); for (char c = 'A'; c <= 'E'; c++) { tree1.addVertex(String.valueOf(c)); tree2.addVertex(String.valueOf((char) (c + ' '))); } tree1.addEdge("A", "B"); tree1.addEdge("A", "C"); tree1.addEdge("C", "D"); tree1.addEdge("C", "E"); tree2.addEdge("a", "b"); tree2.addEdge("a", "c"); tree2.addEdge("b", "e"); tree2.addEdge("b", "d"); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testSmall2() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= 13; i++) { tree1.addVertex(i); } tree1.addEdge(1, 2); tree1.addEdge(1, 3); tree1.addEdge(2, 4); tree1.addEdge(2, 5); tree1.addEdge(2, 6); tree1.addEdge(3, 7); tree1.addEdge(3, 8); tree1.addEdge(3, 9); tree1.addEdge(8, 10); tree1.addEdge(8, 11); tree1.addEdge(9, 12); tree1.addEdge(9, 13); Pair, Map> pair = generateIsomorphicGraph(tree1, new Random(0x88)); Graph tree2 = pair.getFirst(); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testDisconnectedTree() { Graph tree1 = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); tree1.addVertex(1); tree1.addVertex(2); tree1.addVertex(3); tree1.addEdge(1, 2); Graph tree2 = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); tree2.addVertex(11); tree2.addVertex(21); tree2.addVertex(31); tree2.addEdge(11, 21); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertFalse(isomorphism.isomorphismExists()); Assert.assertNull(isomorphism.getMapping()); // Test as forest AHUForestIsomorphismInspector forestIsomorphism = new AHUForestIsomorphismInspector<>(tree1, Set.of(1, 3), tree2, Set.of(11, 31)); Assert.assertTrue(forestIsomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = forestIsomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test(expected = IllegalArgumentException.class) public void testInvalidRoot() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("a"); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("A"); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); isomorphism.getMapping(); } @Test @Category(SlowTests.class) public void testLineGraph() { final int N = 20_000; Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= N; i++) { tree1.addVertex(i); } for (int i = 1; i <= N - 1; i++) { tree1.addEdge(i, i + 1); } Pair, Map> pair = generateIsomorphicGraph(tree1, new Random(0x88)); Graph tree2 = pair.getFirst(); pair.getSecond(); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test @Category(SlowTests.class) public void testHugeNumberOfChildren() { final int N = 100_000; Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= N; i++) { tree1.addVertex(i); } for (int i = 2; i <= N; i++) { tree1.addEdge(1, i); } Pair, Map> pair = generateIsomorphicGraph(tree1, new Random(0x2882)); Graph tree2 = pair.getFirst(); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test @Category(SlowTests.class) public void testHugeRandomTree() { final int N = 50_000; Graph tree1 = generateTree(N, new Random(0x88)); Pair, Map> pair = generateIsomorphicGraph(tree1, new Random(0x88)); Graph tree2 = pair.getFirst(); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } @Test public void testRandomTrees() { Random random = new Random(0x88_88); final int NUM_TESTS = 1500; for (int test = 0; test < NUM_TESTS; test++) { final int N = 10 + random.nextInt(100); Graph tree1 = generateTree(N, random); Pair, Map> pair = generateIsomorphicGraph(tree1, random); Graph tree2 = pair.getFirst(); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping treeMapping = isomorphism.getMapping(); Assert.assertTrue(areIsomorphic(tree1, tree2, treeMapping)); } } } ColorRefinementIsomorphismInspectorTest.java000066400000000000000000000427011402514743400401000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Abdallah Atouani and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for the color refinement isomorphism inspector. * * @author Abdallah Atouani */ public class ColorRefinementIsomorphismInspectorTest { @Test public void testGetMappingsForRegularGraphs() { Graph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graph graph2 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4, 5, 6)); graph1.addEdge(1, 2); graph1.addEdge(2, 3); graph1.addEdge(3, 1); graph1.addEdge(4, 5); graph1.addEdge(5, 6); graph1.addEdge(6, 4); Graphs.addAllVertices(graph2, Arrays.asList(1, 2, 3, 4, 5, 6)); graph2.addEdge(1, 2); graph2.addEdge(2, 3); graph2.addEdge(3, 4); graph2.addEdge(4, 5); graph2.addEdge(5, 6); graph2.addEdge(6, 1); ColorRefinementIsomorphismInspector isomorphismInspector = new ColorRefinementIsomorphismInspector<>(graph1, graph2); boolean testOK = false; try { isomorphismInspector.isomorphismExists(); } catch (IsomorphismUndecidableException e) { testOK = true; } assertTrue(testOK); assertFalse(isomorphismInspector.getMappings().hasNext()); assertFalse(isomorphismInspector.isColoringDiscrete()); assertFalse(isomorphismInspector.isForest()); } /** * test for two complete binary trees of size 7 */ @Test public void testGetMappingsForTrees() { Graph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graph graph2 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4, 5, 6, 7)); graph1.addEdge(1, 2); graph1.addEdge(1, 3); graph1.addEdge(2, 4); graph1.addEdge(2, 5); graph1.addEdge(3, 6); graph1.addEdge(3, 7); Graphs.addAllVertices(graph2, Arrays.asList(1, 2, 3, 4, 5, 6, 7)); graph2.addEdge(1, 2); graph2.addEdge(2, 3); graph2.addEdge(2, 4); graph2.addEdge(4, 5); graph2.addEdge(5, 6); graph2.addEdge(5, 7); ColorRefinementIsomorphismInspector isomorphismInspector = new ColorRefinementIsomorphismInspector<>(graph1, graph2); assertNotNull(isomorphismInspector.getMappings()); try { assertTrue(isomorphismInspector.isomorphismExists()); } catch (IsomorphismUndecidableException e) { fail(); } assertFalse(isomorphismInspector.isColoringDiscrete()); assertTrue(isomorphismInspector.isForest()); GraphMapping graphMapping = isomorphismInspector.getMappings().next(); assertEquals(4, graphMapping.getVertexCorrespondence(1, true).intValue()); assertTrue( graphMapping.getVertexCorrespondence(2, true) == 2 || graphMapping.getVertexCorrespondence(2, true) == 5); assertTrue( graphMapping.getVertexCorrespondence(3, true) == 2 || graphMapping.getVertexCorrespondence(3, true) == 5); assertTrue( graphMapping.getVertexCorrespondence(4, true) == 1 || graphMapping.getVertexCorrespondence(4, true) == 3 || graphMapping.getVertexCorrespondence(4, true) == 6 || graphMapping.getVertexCorrespondence(4, true) == 7); assertTrue( graphMapping.getVertexCorrespondence(5, true) == 1 || graphMapping.getVertexCorrespondence(5, true) == 3 || graphMapping.getVertexCorrespondence(5, true) == 6 || graphMapping.getVertexCorrespondence(5, true) == 7); assertTrue( graphMapping.getVertexCorrespondence(6, true) == 1 || graphMapping.getVertexCorrespondence(6, true) == 3 || graphMapping.getVertexCorrespondence(6, true) == 6 || graphMapping.getVertexCorrespondence(6, true) == 7); assertTrue( graphMapping.getVertexCorrespondence(7, true) == 1 || graphMapping.getVertexCorrespondence(7, true) == 3 || graphMapping.getVertexCorrespondence(7, true) == 6 || graphMapping.getVertexCorrespondence(7, true) == 7); assertEquals(1, graphMapping.getVertexCorrespondence(4, true).intValue()); assertTrue( graphMapping.getVertexCorrespondence(2, false) == 2 || graphMapping.getVertexCorrespondence(2, false) == 3); assertTrue( graphMapping.getVertexCorrespondence(5, false) == 2 || graphMapping.getVertexCorrespondence(5, false) == 3); assertTrue( graphMapping.getVertexCorrespondence(1, false) == 4 || graphMapping.getVertexCorrespondence(1, false) == 5 || graphMapping.getVertexCorrespondence(1, false) == 6 || graphMapping.getVertexCorrespondence(1, false) == 7); assertTrue( graphMapping.getVertexCorrespondence(3, false) == 4 || graphMapping.getVertexCorrespondence(3, false) == 5 || graphMapping.getVertexCorrespondence(3, false) == 6 || graphMapping.getVertexCorrespondence(3, false) == 7); assertTrue( graphMapping.getVertexCorrespondence(6, false) == 4 || graphMapping.getVertexCorrespondence(6, false) == 5 || graphMapping.getVertexCorrespondence(6, false) == 6 || graphMapping.getVertexCorrespondence(6, false) == 7); assertTrue( graphMapping.getVertexCorrespondence(7, false) == 4 || graphMapping.getVertexCorrespondence(7, false) == 5 || graphMapping.getVertexCorrespondence(7, false) == 6 || graphMapping.getVertexCorrespondence(7, false) == 7); for (int i = 1; i <= 7; ++i) { for (int j = i + 1; j <= 7; ++j) { assertNotEquals( graphMapping.getVertexCorrespondence(i, true).intValue(), graphMapping.getVertexCorrespondence(j, true).intValue()); assertNotEquals( graphMapping.getVertexCorrespondence(i, false).intValue(), graphMapping.getVertexCorrespondence(j, false).intValue()); } } } @Test public void testGetMappingsForIsomorphicGraphsOfSize6() { Graph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graph graph2 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4, 5, 6)); graph1.addEdge(1, 2); graph1.addEdge(1, 3); graph1.addEdge(1, 6); graph1.addEdge(2, 3); graph1.addEdge(3, 4); graph1.addEdge(4, 5); Graphs.addAllVertices(graph2, Arrays.asList(1, 2, 3, 4, 5, 6)); graph2.addEdge(1, 2); graph2.addEdge(1, 3); graph2.addEdge(1, 5); graph2.addEdge(2, 6); graph2.addEdge(3, 5); graph2.addEdge(3, 4); ColorRefinementIsomorphismInspector isomorphismInspector = new ColorRefinementIsomorphismInspector<>(graph1, graph2); GraphMapping graphMapping = isomorphismInspector.getMappings().next(); assertNotNull(isomorphismInspector.getMappings()); try { assertTrue(isomorphismInspector.isomorphismExists()); } catch (IsomorphismUndecidableException e) { fail(); } assertTrue(isomorphismInspector.isColoringDiscrete()); assertFalse(isomorphismInspector.isForest()); assertEquals(graphMapping.getVertexCorrespondence(1, true).intValue(), 3); assertEquals(graphMapping.getVertexCorrespondence(2, true).intValue(), 5); assertEquals(graphMapping.getVertexCorrespondence(3, true).intValue(), 1); assertEquals(graphMapping.getVertexCorrespondence(4, true).intValue(), 2); assertEquals(graphMapping.getVertexCorrespondence(5, true).intValue(), 6); assertEquals(graphMapping.getVertexCorrespondence(6, true).intValue(), 4); assertEquals(graphMapping.getVertexCorrespondence(1, false).intValue(), 3); assertEquals(graphMapping.getVertexCorrespondence(2, false).intValue(), 4); assertEquals(graphMapping.getVertexCorrespondence(3, false).intValue(), 1); assertEquals(graphMapping.getVertexCorrespondence(4, false).intValue(), 6); assertEquals(graphMapping.getVertexCorrespondence(5, false).intValue(), 2); assertEquals(graphMapping.getVertexCorrespondence(6, false).intValue(), 5); } @Test public void testGetMappingForGraphWithDifferentNumberOfNodes() { Graph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graph graph2 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4, 5)); graph1.addEdge(1, 3); graph1.addEdge(2, 5); graph1.addEdge(3, 4); graph1.addEdge(4, 5); Graphs.addAllVertices(graph2, Arrays.asList(1, 2, 3, 4, 5, 6)); graph2.addEdge(1, 3); graph2.addEdge(2, 5); graph2.addEdge(3, 4); graph2.addEdge(4, 5); ColorRefinementIsomorphismInspector isomorphismInspector = new ColorRefinementIsomorphismInspector<>(graph1, graph2); assertFalse(isomorphismInspector.getMappings().hasNext()); try { assertFalse(isomorphismInspector.isomorphismExists()); } catch (IsomorphismUndecidableException e) { fail(); } assertFalse(isomorphismInspector.isColoringDiscrete()); assertFalse(isomorphismInspector.isForest()); } @Test public void testGetMappingsForGraphWithDifferentNumberOfColorClasses() { Graph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graph graph2 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4)); graph1.addEdge(1, 2); graph1.addEdge(1, 3); graph1.addEdge(2, 3); graph1.addEdge(3, 4); Graphs.addAllVertices(graph2, Arrays.asList(1, 2, 3, 4)); graph1.addEdge(1, 2); graph1.addEdge(1, 3); graph1.addEdge(2, 4); graph1.addEdge(3, 4); ColorRefinementIsomorphismInspector isomorphismInspector = new ColorRefinementIsomorphismInspector<>(graph1, graph2); assertFalse(isomorphismInspector.getMappings().hasNext()); try { assertFalse(isomorphismInspector.isomorphismExists()); } catch (IsomorphismUndecidableException e) { fail(); } assertFalse(isomorphismInspector.isColoringDiscrete()); assertFalse(isomorphismInspector.isForest()); } @Test public void testGetMappingsForIsomorphicForests() { Graph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graph graph2 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4, 5, 6, 7)); graph1.addEdge(1, 2); graph1.addEdge(1, 3); graph1.addEdge(4, 5); graph1.addEdge(5, 6); graph1.addEdge(6, 7); Graphs.addAllVertices(graph2, Arrays.asList(1, 2, 3, 4, 5, 6, 7)); graph2.addEdge(1, 2); graph2.addEdge(1, 3); graph2.addEdge(3, 4); graph2.addEdge(5, 6); graph2.addEdge(6, 7); ColorRefinementIsomorphismInspector isomorphismInspector = new ColorRefinementIsomorphismInspector<>(graph1, graph2); assertNotNull(isomorphismInspector.getMappings()); try { assertTrue(isomorphismInspector.isomorphismExists()); } catch (IsomorphismUndecidableException e) { fail(); } assertFalse(isomorphismInspector.isColoringDiscrete()); assertTrue(isomorphismInspector.isForest()); GraphMapping graphMapping = isomorphismInspector.getMappings().next(); assertTrue(graphMapping.getVertexCorrespondence(1, true) == 6); assertTrue( (graphMapping.getVertexCorrespondence(2, true) == 5) || (graphMapping.getVertexCorrespondence(2, true) == 7)); assertTrue( (graphMapping.getVertexCorrespondence(3, true) == 5) || (graphMapping.getVertexCorrespondence(3, true) == 7)); assertTrue( (graphMapping.getVertexCorrespondence(4, true) == 2) || (graphMapping.getVertexCorrespondence(4, true) == 4)); assertTrue( (graphMapping.getVertexCorrespondence(5, true) == 1) || (graphMapping.getVertexCorrespondence(5, true) == 3)); assertTrue( (graphMapping.getVertexCorrespondence(6, true) == 1) || (graphMapping.getVertexCorrespondence(6, true) == 3)); assertTrue( (graphMapping.getVertexCorrespondence(7, true) == 2) || (graphMapping.getVertexCorrespondence(7, true) == 4)); for (int i = 1; i < graph1.vertexSet().size(); i++) { for (int j = i + 1; j <= graph1.vertexSet().size(); j++) { assertNotEquals( graphMapping.getVertexCorrespondence(i, true), (graphMapping.getVertexCorrespondence(j, true))); assertNotEquals( graphMapping.getVertexCorrespondence(i, false), (graphMapping.getVertexCorrespondence(j, false))); } } } @Test public void testGetMappingsForNotIsomorphicForests() { Graph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graph graph2 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4)); graph1.addEdge(1, 2); graph1.addEdge(1, 3); graph1.addEdge(1, 4); Graphs.addAllVertices(graph2, Arrays.asList(1, 2, 3, 4)); graph2.addEdge(1, 2); graph2.addEdge(1, 3); graph2.addEdge(2, 4); ColorRefinementIsomorphismInspector isomorphismInspector = new ColorRefinementIsomorphismInspector<>(graph1, graph2); assertFalse(isomorphismInspector.getMappings().hasNext()); try { assertFalse(isomorphismInspector.isomorphismExists()); } catch (IsomorphismUndecidableException e) { fail(); } assertFalse(isomorphismInspector.isColoringDiscrete()); assertFalse(isomorphismInspector.isForest()); } @Test public void testTwoDiscreteGraphsNonIsomorphic() { Graph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graph graph2 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4, 5, 6)); graph1.addEdge(1, 2); graph1.addEdge(2, 3); graph1.addEdge(3, 4); graph1.addEdge(3, 5); graph1.addEdge(4, 5); graph1.addEdge(5, 6); Graphs.addAllVertices(graph2, Arrays.asList(1, 2, 3, 4, 5, 6)); graph2.addEdge(1, 2); graph2.addEdge(2, 3); graph2.addEdge(2, 4); graph2.addEdge(2, 5); graph2.addEdge(3, 5); graph2.addEdge(3, 6); graph2.addEdge(4, 5); ColorRefinementIsomorphismInspector isomorphismInspector = new ColorRefinementIsomorphismInspector<>(graph1, graph2); assertFalse(isomorphismInspector.isomorphismExists()); assertFalse(isomorphismInspector.getMappings().hasNext()); } @Test public void testTwoEqualGraphs() { Graph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, Arrays.asList(1, 2, 3, 4, 5, 6)); graph1.addEdge(1, 2); graph1.addEdge(2, 3); graph1.addEdge(3, 4); graph1.addEdge(3, 5); graph1.addEdge(4, 5); graph1.addEdge(5, 6); ColorRefinementIsomorphismInspector isomorphismInspector = new ColorRefinementIsomorphismInspector<>(graph1, graph1); assertTrue(isomorphismInspector.isomorphismExists()); assertTrue(isomorphismInspector.getMappings().hasNext()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/GraphOrderingTest.java000066400000000000000000000223051402514743400334740ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; /** * Testing the class GraphOrdering */ public class GraphOrderingTest { @Test public void testUndirectedGraph() { /* * v1--v2 |\ | v5 | \ | v3 v4 * */ Graph g1 = new SimpleGraph<>(DefaultEdge.class); String v1 = "v1", v2 = "v2", v3 = "v3", v4 = "v4", v5 = "v5"; g1.addVertex(v1); g1.addVertex(v2); g1.addVertex(v3); g1.addVertex(v4); g1.addVertex(v5); g1.addEdge(v1, v2); g1.addEdge(v1, v3); g1.addEdge(v1, v4); g1.addEdge(v2, v4); GraphOrdering g1Ordering = new GraphOrdering<>(g1); assertEquals(5, g1Ordering.getVertexCount()); int v1o = g1Ordering.getVertexNumber(v1), v2o = g1Ordering.getVertexNumber(v2), v3o = g1Ordering.getVertexNumber(v3), v4o = g1Ordering.getVertexNumber(v4), v5o = g1Ordering.getVertexNumber(v5); int[] v1Outs = { v2o, v3o, v4o }; int[] v1Outs_ = g1Ordering.getOutEdges(v1o); Arrays.sort(v1Outs); Arrays.sort(v1Outs_); int[] v2Outs = { v1o, v4o }; int[] v2Outs_ = g1Ordering.getOutEdges(v2o); Arrays.sort(v2Outs); Arrays.sort(v2Outs_); int[] v3Outs = { v1o }; int[] v3Outs_ = g1Ordering.getOutEdges(v3o); Arrays.sort(v3Outs); Arrays.sort(v3Outs_); int[] v4Outs = { v1o, v2o }; int[] v4Outs_ = g1Ordering.getOutEdges(v4o); Arrays.sort(v4Outs); Arrays.sort(v4Outs_); int[] v5Outs = {}; int[] v5Outs_ = g1Ordering.getOutEdges(v5o); Arrays.sort(v5Outs); Arrays.sort(v5Outs_); assertArrayEquals(v1Outs, v1Outs_); assertArrayEquals(v2Outs, v2Outs_); assertArrayEquals(v3Outs, v3Outs_); assertArrayEquals(v4Outs, v4Outs_); assertArrayEquals(v5Outs, v5Outs_); int[] v1Ins = { v2o, v3o, v4o }; int[] v1Ins_ = g1Ordering.getOutEdges(v1o); Arrays.sort(v1Ins); Arrays.sort(v1Ins_); int[] v2Ins = { v1o, v4o }; int[] v2Ins_ = g1Ordering.getOutEdges(v2o); Arrays.sort(v2Ins); Arrays.sort(v2Ins_); int[] v3Ins = { v1o }; int[] v3Ins_ = g1Ordering.getOutEdges(v3o); Arrays.sort(v3Ins); Arrays.sort(v3Ins_); int[] v4Ins = { v1o, v2o }; int[] v4Ins_ = g1Ordering.getOutEdges(v4o); Arrays.sort(v4Ins); Arrays.sort(v4Ins_); int[] v5Ins = {}; int[] v5Ins_ = g1Ordering.getOutEdges(v5o); Arrays.sort(v5Ins); Arrays.sort(v5Ins_); assertArrayEquals(v1Ins, v1Ins_); assertArrayEquals(v2Ins, v2Ins_); assertArrayEquals(v3Ins, v3Ins_); assertArrayEquals(v4Ins, v4Ins_); assertArrayEquals(v5Ins, v5Ins_); assertEquals(false, g1Ordering.hasEdge(v1o, v1o)); assertEquals(true, g1Ordering.hasEdge(v1o, v2o)); assertEquals(true, g1Ordering.hasEdge(v1o, v3o)); assertEquals(true, g1Ordering.hasEdge(v1o, v4o)); assertEquals(false, g1Ordering.hasEdge(v1o, v5o)); assertEquals(true, g1Ordering.hasEdge(v2o, v1o)); assertEquals(false, g1Ordering.hasEdge(v2o, v2o)); assertEquals(false, g1Ordering.hasEdge(v2o, v3o)); assertEquals(true, g1Ordering.hasEdge(v2o, v4o)); assertEquals(false, g1Ordering.hasEdge(v2o, v5o)); assertEquals(true, g1Ordering.hasEdge(v3o, v1o)); assertEquals(false, g1Ordering.hasEdge(v3o, v2o)); assertEquals(false, g1Ordering.hasEdge(v3o, v3o)); assertEquals(false, g1Ordering.hasEdge(v3o, v4o)); assertEquals(false, g1Ordering.hasEdge(v3o, v5o)); assertEquals(true, g1Ordering.hasEdge(v4o, v1o)); assertEquals(true, g1Ordering.hasEdge(v4o, v2o)); assertEquals(false, g1Ordering.hasEdge(v4o, v3o)); assertEquals(false, g1Ordering.hasEdge(v4o, v4o)); assertEquals(false, g1Ordering.hasEdge(v4o, v5o)); assertEquals(false, g1Ordering.hasEdge(v5o, v1o)); assertEquals(false, g1Ordering.hasEdge(v5o, v2o)); assertEquals(false, g1Ordering.hasEdge(v5o, v3o)); assertEquals(false, g1Ordering.hasEdge(v5o, v4o)); assertEquals(false, g1Ordering.hasEdge(v5o, v5o)); } @Test public void testDirectedGraph() { /* * v1 ---> v2 <---> v3 ---> v4 v5 * */ Graph g1 = new DefaultDirectedGraph<>(DefaultEdge.class); String v1 = "v1", v2 = "v2", v3 = "v3", v4 = "v4", v5 = "v5"; g1.addVertex(v1); g1.addVertex(v2); g1.addVertex(v3); g1.addVertex(v4); g1.addVertex(v5); g1.addEdge(v1, v2); g1.addEdge(v2, v3); g1.addEdge(v3, v2); g1.addEdge(v3, v4); GraphOrdering g1Ordering = new GraphOrdering<>(g1); assertEquals(5, g1Ordering.getVertexCount()); int v1o = g1Ordering.getVertexNumber(v1), v2o = g1Ordering.getVertexNumber(v2), v3o = g1Ordering.getVertexNumber(v3), v4o = g1Ordering.getVertexNumber(v4), v5o = g1Ordering.getVertexNumber(v5); int[] v1Outs = { v2o }; int[] v1Outs_ = g1Ordering.getOutEdges(v1o); Arrays.sort(v1Outs); Arrays.sort(v1Outs_); int[] v2Outs = { v3o }; int[] v2Outs_ = g1Ordering.getOutEdges(v2o); Arrays.sort(v2Outs); Arrays.sort(v2Outs_); int[] v3Outs = { v2o, v4o }; int[] v3Outs_ = g1Ordering.getOutEdges(v3o); Arrays.sort(v3Outs); Arrays.sort(v3Outs_); int[] v4Outs = {}; int[] v4Outs_ = g1Ordering.getOutEdges(v4o); Arrays.sort(v4Outs); Arrays.sort(v4Outs_); int[] v5Outs = {}; int[] v5Outs_ = g1Ordering.getOutEdges(v5o); Arrays.sort(v5Outs); Arrays.sort(v5Outs_); assertArrayEquals(v1Outs, v1Outs_); assertArrayEquals(v2Outs, v2Outs_); assertArrayEquals(v3Outs, v3Outs_); assertArrayEquals(v4Outs, v4Outs_); assertArrayEquals(v5Outs, v5Outs_); int[] v1Ins = {}; int[] v1Ins_ = g1Ordering.getInEdges(v1o); Arrays.sort(v1Ins); Arrays.sort(v1Ins_); int[] v2Ins = { v1o, v3o }; int[] v2Ins_ = g1Ordering.getInEdges(v2o); Arrays.sort(v2Ins); Arrays.sort(v2Ins_); int[] v3Ins = { v2o }; int[] v3Ins_ = g1Ordering.getInEdges(v3o); Arrays.sort(v3Ins); Arrays.sort(v3Ins_); int[] v4Ins = { v3o }; int[] v4Ins_ = g1Ordering.getInEdges(v4o); Arrays.sort(v4Ins); Arrays.sort(v4Ins_); int[] v5Ins = {}; int[] v5Ins_ = g1Ordering.getInEdges(v5o); Arrays.sort(v5Ins); Arrays.sort(v5Ins_); assertArrayEquals(v1Ins, v1Ins_); assertArrayEquals(v2Ins, v2Ins_); assertArrayEquals(v3Ins, v3Ins_); assertArrayEquals(v4Ins, v4Ins_); assertArrayEquals(v5Ins, v5Ins_); assertEquals(false, g1Ordering.hasEdge(v1o, v1o)); assertEquals(true, g1Ordering.hasEdge(v1o, v2o)); assertEquals(false, g1Ordering.hasEdge(v1o, v3o)); assertEquals(false, g1Ordering.hasEdge(v1o, v4o)); assertEquals(false, g1Ordering.hasEdge(v1o, v5o)); assertEquals(false, g1Ordering.hasEdge(v2o, v1o)); assertEquals(false, g1Ordering.hasEdge(v2o, v2o)); assertEquals(true, g1Ordering.hasEdge(v2o, v3o)); assertEquals(false, g1Ordering.hasEdge(v2o, v4o)); assertEquals(false, g1Ordering.hasEdge(v2o, v5o)); assertEquals(false, g1Ordering.hasEdge(v3o, v1o)); assertEquals(true, g1Ordering.hasEdge(v3o, v2o)); assertEquals(false, g1Ordering.hasEdge(v3o, v3o)); assertEquals(true, g1Ordering.hasEdge(v3o, v4o)); assertEquals(false, g1Ordering.hasEdge(v3o, v5o)); assertEquals(false, g1Ordering.hasEdge(v4o, v1o)); assertEquals(false, g1Ordering.hasEdge(v4o, v2o)); assertEquals(false, g1Ordering.hasEdge(v4o, v3o)); assertEquals(false, g1Ordering.hasEdge(v4o, v4o)); assertEquals(false, g1Ordering.hasEdge(v4o, v5o)); assertEquals(false, g1Ordering.hasEdge(v5o, v1o)); assertEquals(false, g1Ordering.hasEdge(v5o, v2o)); assertEquals(false, g1Ordering.hasEdge(v5o, v3o)); assertEquals(false, g1Ordering.hasEdge(v5o, v4o)); assertEquals(false, g1Ordering.hasEdge(v5o, v5o)); } } IsomorphicGraphMappingTest.java000066400000000000000000000103511402514743400352720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.isomorphism.IsomorphismTestUtil.*; /** * Tests for {@link IsomorphicGraphMapping} * * @author Alexandru Valeanu */ public class IsomorphicGraphMappingTest { @Test public void testIdentity() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); for (char c = 'A'; c <= 'E'; c++) { tree1.addVertex(String.valueOf(c)); } tree1.addEdge("A", "B"); tree1.addEdge("A", "C"); tree1.addEdge("C", "D"); tree1.addEdge("C", "E"); IsomorphicGraphMapping identity = IsomorphicGraphMapping.identity(tree1); Graph tree2 = generateMappedGraph(tree1, identity.getForwardMapping()); AHURootedTreeIsomorphismInspector isomorphism = new AHURootedTreeIsomorphismInspector<>( tree1, "A", tree2, identity.getVertexCorrespondence("A", true)); Assert.assertTrue(isomorphism.isomorphismExists()); Assert.assertTrue(areIsomorphic(tree1, tree2, identity)); } @Test public void testCompositionOfMappings() { Graph tree1 = new SimpleGraph<>(DefaultEdge.class); tree1.addVertex("1"); tree1.addVertex("2"); tree1.addEdge("1", "2"); Graph tree2 = new SimpleGraph<>(DefaultEdge.class); tree2.addVertex("a"); tree2.addVertex("b"); tree2.addEdge("a", "b"); Graph tree3 = new SimpleGraph<>(DefaultEdge.class); tree3.addVertex("A"); tree3.addVertex("B"); tree3.addEdge("A", "B"); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping mapping12 = isomorphism.getMapping(); isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree2, tree3); Assert.assertTrue(isomorphism.isomorphismExists()); IsomorphicGraphMapping mapping23 = isomorphism.getMapping(); IsomorphicGraphMapping mapping13 = mapping12.compose(mapping23); Assert.assertTrue(areIsomorphic(tree1, tree3, mapping13)); } @Test public void testCompositionOfRandomMappings() { final int NUM_TESTS = 1000; Random random = new Random(0x11_88_11); for (int test = 0; test < NUM_TESTS; test++) { final int N = 10 + random.nextInt(150); Graph tree1 = generateTree(N, random); Graph tree2 = generateIsomorphicGraph(tree1, random).getFirst(); Graph tree3 = generateIsomorphicGraph(tree2, random).getFirst(); AHUUnrootedTreeIsomorphismInspector isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree1, tree2); IsomorphicGraphMapping mapping12 = isomorphism.getMapping(); isomorphism = new AHUUnrootedTreeIsomorphismInspector<>(tree2, tree3); IsomorphicGraphMapping mapping23 = isomorphism.getMapping(); IsomorphicGraphMapping mapping13 = mapping12.compose(mapping23); Assert.assertTrue(areIsomorphic(tree1, tree3, mapping13)); } } } IsomorphismTestUtil.java000066400000000000000000000116421402514743400340330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; /** * Utility class for isomorphism tests * * @author Alexandru Valeanu */ public class IsomorphismTestUtil { public static Graph parseGraph(String vertices, String edges) { Graph forest = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(-100), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); vertices = vertices.substring(1, vertices.length() - 1); for (String s : vertices.split(", ")) forest.addVertex(Integer.valueOf(s)); edges = edges.substring(1, edges.length() - 1); for (String s : edges.split(", ")) { String[] ends = s.substring(1, s.length() - 1).split(","); forest.addEdge(Integer.valueOf(ends[0]), Integer.valueOf(ends[1])); } return forest; } public static Pair, Graph> parseGraph( String vertices, String edges, String mapping, Map map) { Graph forest = parseGraph(vertices, edges); for (String s : mapping.substring(1, mapping.length() - 1).split(", ")) { String[] ends = s.split("="); map.put(Integer.valueOf(ends[0]), Integer.valueOf(ends[1])); } return Pair.of(forest, generateMappedGraph(forest, map)); } public static Graph generateForest(int N, Random random) { BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>(N / 10, N, random); Graph forest = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(forest); return forest; } public static Pair, Map> generateIsomorphicGraph( Graph graph, Random random) { List permutation = new ArrayList<>(graph.vertexSet().size()); for (int i = 0; i < graph.vertexSet().size(); i++) { permutation.add(i); } Collections.shuffle(permutation, random); List vertexList = new ArrayList<>(graph.vertexSet()); Map mapping = new HashMap<>(); for (int i = 0; i < graph.vertexSet().size(); i++) { mapping.put(vertexList.get(i), vertexList.get(permutation.get(i))); } return Pair.of(generateMappedGraph(graph, mapping), mapping); } public static Graph generateTree(int N, Random random) { BarabasiAlbertGraphGenerator generator = new BarabasiAlbertGraphGenerator<>(1, 1, N - 1, random); Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(tree); return tree; } public static boolean areIsomorphic( Graph graph1, Graph graph2, IsomorphicGraphMapping mapping) { // reapply the mapping onto the given graphs in case they // are not the same as the graphs over which the mapping // was originally constructed IsomorphicGraphMapping reappliedMapping = new IsomorphicGraphMapping<>( mapping.getForwardMapping(), mapping.getBackwardMapping(), graph1, graph2); return reappliedMapping.isValidIsomorphism(); } public static < V> Graph generateMappedGraph(Graph graph, Map mapping) { SimpleGraph isoGraph = new SimpleGraph<>(graph.getVertexSupplier(), graph.getEdgeSupplier(), false); for (V v : graph.vertexSet()) isoGraph.addVertex(mapping.get(v)); for (DefaultEdge edge : graph.edgeSet()) { V u = graph.getEdgeSource(edge); V v = graph.getEdgeTarget(edge); isoGraph.addEdge(mapping.get(u), mapping.get(v)); } return isoGraph; } } SubgraphIsomorphismTestUtils.java000066400000000000000000000217371402514743400357200ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; public class SubgraphIsomorphismTestUtils { private static boolean DEBUG = false; public static boolean allMatchingsCorrect( VF2SubgraphIsomorphismInspector vf2, Graph g1, Graph g2) { showLog(">> "); boolean isCorrect = true; for (Iterator> mappings = vf2.getMappings(); mappings.hasNext();) { isCorrect = isCorrect && isCorrectMatching(mappings.next(), g1, g2); showLog("."); } showLog("\n"); return isCorrect; } public static boolean isCorrectMatching( GraphMapping rel, Graph g1, Graph g2) { Set vertexSet = g2.vertexSet(); for (Integer u1 : vertexSet) { Integer v1 = rel.getVertexCorrespondence(u1, false); for (Integer u2 : vertexSet) { if (u1 == u2) continue; Integer v2 = rel.getVertexCorrespondence(u2, false); if (v1 == v2) { showLog(u1 + " and " + u2 + " are both mapped on " + v1 + "\n"); return false; } if (g1.containsEdge(v1, v2) != g2.containsEdge(u1, u2)) { if (g1.containsEdge(v1, v2)) showLog( "there is an edge from " + v1 + " to " + v2 + " in graph1 that does not exist from " + u1 + " to " + u2 + " in graph2"); else showLog( "there is an edge from " + u1 + " to " + u2 + "in graph2 that does not exist from " + v1 + " to " + v2 + " in graph1"); return false; } } } return true; } public static Graph randomSubgraph( Graph g1, int vertexCount, long seed) { Map map = new HashMap<>(); Graph g2 = new DefaultDirectedGraph<>(DefaultEdge.class); Set vertexSet = g1.vertexSet(); int n = vertexSet.size(); Random rnd = new Random(); rnd.setSeed(seed); for (int i = 0; i < vertexCount;) { for (Integer v : vertexSet) { if (rnd.nextInt(n) == 0 && !map.containsKey(v)) { Integer u = i++; g2.addVertex(u); map.put(v, u); } } } for (DefaultEdge e : g1.edgeSet()) { Integer v1 = g1.getEdgeSource(e), v2 = g1.getEdgeTarget(e); if (map.containsKey(v1) && map.containsKey(v2)) { Integer u1 = map.get(v1), u2 = map.get(v2); g2.addEdge(u1, u2); } } return g2; } public static Graph randomGraph(int vertexCount, int edgeCount, long seed) { Integer[] vertexes = new Integer[vertexCount]; Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); for (int i = 0; i < vertexCount; i++) g.addVertex(vertexes[i] = i); Random rnd = new Random(); rnd.setSeed(seed); for (int i = 0; i < edgeCount;) { Integer source = vertexes[rnd.nextInt(vertexCount)], target = vertexes[rnd.nextInt(vertexCount)]; if (source != target && !g.containsEdge(source, target)) { g.addEdge(source, target); i++; } } return g; } /** * Assuming g1 and g2 have vertexes labeled with 0, 1, ... No semantic check is done. Assuming * SubgraphIsomorphismRelation.equals and getMatchings are correct. * * @param vf2 the SubgraphIsomorphismInspector * @param g1 first Graph * @param g2 second Graph, smaller or equal to g1 * @return */ public static boolean containsAllMatchings( VF2SubgraphIsomorphismInspector vf2, Graph g1, Graph g2) { boolean correct = true; ArrayList> matchings = getMatchings(g1, g2); loop: for (Iterator> mappings = vf2.getMappings(); mappings.hasNext();) { IsomorphicGraphMapping rel1 = (IsomorphicGraphMapping) mappings.next(); showLog("> " + rel1 + " .."); for (IsomorphicGraphMapping rel2 : matchings) { if (rel1.isEqualMapping(rel2)) { matchings.remove(rel2); showLog("exists\n"); continue loop; } } correct = false; showLog("does not exist!\n"); } if (!matchings.isEmpty()) { correct = false; showLog("-- no counterpart for:\n"); for (IsomorphicGraphMapping match : matchings) showLog(" " + match + "\n"); } if (correct) showLog("-- ok\n"); return correct; } /** * Assuming g1 and g2 have vertexes labeled with 0, 1, ... No semantic check is done. * * @param g1 first Graph * @param g2 second Graph, smaller or equal to g1 * @return */ private static ArrayList> getMatchings( Graph g1, Graph g2) { int n1 = g1.vertexSet().size(), n2 = g2.vertexSet().size(); GraphOrdering g1o = new GraphOrdering(g1), g2o = new GraphOrdering(g2); ArrayList> perms = getPermutations(new boolean[n1], n2); ArrayList> rels = new ArrayList>(); loop: for (ArrayList perm : perms) { int[] core2 = new int[n2]; int i = 0; for (Integer p : perm) core2[i++] = p.intValue(); for (DefaultEdge edge : g2.edgeSet()) { Integer u1 = g2.getEdgeSource(edge), u2 = g2.getEdgeTarget(edge), v1 = core2[u1], v2 = core2[u2]; if (!g1.containsEdge(v1, v2)) continue loop; } int[] core1 = new int[n1]; Arrays.fill(core1, VF2State.NULL_NODE); for (i = 0; i < n2; i++) core1[core2[i]] = i; for (DefaultEdge edge : g1.edgeSet()) { Integer v1 = g1.getEdgeSource(edge), v2 = g1.getEdgeTarget(edge), u1 = core1[v1], u2 = core1[v2]; if (u1 == VF2State.NULL_NODE || u2 == VF2State.NULL_NODE) continue; if (!g2.containsEdge(u1, u2)) continue loop; } rels.add(new IsomorphicGraphMapping(g1o, g2o, core1, core2)); } return rels; } private static ArrayList> getPermutations(boolean[] vertexSet, int len) { ArrayList> perms = new ArrayList>(); if (len <= 0) { perms.add(new ArrayList()); return perms; } for (int i = 0; i < vertexSet.length; i++) { if (!vertexSet[i]) { vertexSet[i] = true; ArrayList> newPerms = getPermutations(vertexSet, len - 1); vertexSet[i] = false; for (ArrayList perm : newPerms) perm.add(i); perms.addAll(newPerms); } } return perms; } public static void showLog(String str) { if (!DEBUG) return; System.out.print(str); } } VF2GraphIsomorphismInspectorTest.java000066400000000000000000000133051402514743400363620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * This test class is fairly small compared with the tests for the VF2SubgraphIsomorphismInspector * class due to the similarities in both algorithms (SubgraphIsomorphism and GraphIsomorphism) */ public class VF2GraphIsomorphismInspectorTest { @Test public void testAutomorphism() { /* * v1-----v2 \ / \ / v3 */ SimpleGraph g1 = new SimpleGraph<>(DefaultEdge.class); String v1 = "v1", v2 = "v2", v3 = "v3"; g1.addVertex(v1); g1.addVertex(v2); g1.addVertex(v3); g1.addEdge(v1, v2); g1.addEdge(v2, v3); g1.addEdge(v3, v1); VF2GraphIsomorphismInspector vf2 = new VF2GraphIsomorphismInspector<>(g1, g1); Iterator> iter = vf2.getMappings(); Set mappings = new HashSet<>( Arrays .asList( "[v1=v1 v2=v2 v3=v3]", "[v1=v1 v2=v3 v3=v2]", "[v1=v2 v2=v1 v3=v3]", "[v1=v2 v2=v3 v3=v1]", "[v1=v3 v2=v1 v3=v2]", "[v1=v3 v2=v2 v3=v1]")); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(false, iter.hasNext()); /* * 1 ---> 2 <--- 3 */ DefaultDirectedGraph g2 = new DefaultDirectedGraph<>(DefaultEdge.class); g2.addVertex(1); g2.addVertex(2); g2.addVertex(3); g2.addEdge(1, 2); g2.addEdge(3, 2); VF2GraphIsomorphismInspector vf3 = new VF2GraphIsomorphismInspector<>(g2, g2); Iterator> iter2 = vf3.getMappings(); Set mappings2 = Set.of("[1=1 2=2 3=3]", "[1=3 2=2 3=1]"); assertTrue(mappings2.contains(iter2.next().toString())); assertTrue(mappings2.contains(iter2.next().toString())); assertFalse(iter2.hasNext()); } @Test public void testSubgraph() { Graph g1 = SubgraphIsomorphismTestUtils.randomGraph(10, 30, 12345); Graph g2 = SubgraphIsomorphismTestUtils.randomSubgraph(g1, 7, 54321); VF2GraphIsomorphismInspector vf2 = new VF2GraphIsomorphismInspector<>(g1, g2); assertEquals(false, vf2.isomorphismExists()); } @Test public void testSubgraph2() { Graph g1 = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); g1.addVertex(0); g1.addVertex(1); g1.addVertex(2); g1.addVertex(3); g1.addVertex(4); g1.addEdge(0, 1); g1.addEdge(0, 4); g1.addEdge(1, 2); g1.addEdge(1, 4); g1.addEdge(2, 3); g1.addEdge(2, 4); g1.addEdge(3, 4); Graph g2 = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); g2.addVertex(0); g2.addVertex(1); g2.addVertex(2); g2.addVertex(3); g2.addVertex(4); g2.addVertex(5); g2.addEdge(4, 2); g2.addEdge(2, 0); g2.addEdge(0, 1); g2.addEdge(1, 3); g2.addEdge(3, 4); g2.addEdge(4, 0); g2.addEdge(1, 4); Graph g3 = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); g3.addVertex(0); g3.addVertex(1); g3.addVertex(2); g3.addVertex(3); g3.addVertex(4); g3.addVertex(5); g3.addEdge(0, 1); g3.addEdge(1, 2); g3.addEdge(2, 3); g3.addEdge(3, 4); g3.addEdge(5, 2); g3.addEdge(5, 3); g3.addEdge(5, 4); Graph g4 = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); g4.addVertex(0); g4.addVertex(1); g4.addVertex(2); g4.addVertex(3); g4.addVertex(4); g4.addVertex(5); g4.addEdge(0, 1); g4.addEdge(1, 2); g4.addEdge(2, 3); g4.addEdge(4, 5); g4.addEdge(4, 2); g4.addEdge(5, 2); g4.addEdge(5, 3); VF2GraphIsomorphismInspector vf2 = new VF2GraphIsomorphismInspector<>(g2, g1), vf3 = new VF2GraphIsomorphismInspector<>(g1, g2), vf4 = new VF2GraphIsomorphismInspector<>(g3, g4); assertEquals(false, vf2.isomorphismExists()); assertEquals(false, vf3.isomorphismExists()); assertEquals(false, vf4.isomorphismExists()); } } VF2SubgraphIsomorphismInspectorTest.java000066400000000000000000000755071402514743400371100ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/isomorphism/* * (C) Copyright 2015-2021, by Fabian Späh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.isomorphism; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; public class VF2SubgraphIsomorphismInspectorTest { /** * Tests graph types: In case of invalid graph types or invalid combination of graph arguments * UnsupportedOperationException or InvalidArgumentException is expected */ @Test public void testGraphTypes() { Graph dg1 = new DefaultDirectedGraph<>(DefaultEdge.class); dg1.addVertex(1); dg1.addVertex(2); dg1.addEdge(1, 2); SimpleGraph sg1 = new SimpleGraph<>(DefaultEdge.class); sg1.addVertex(1); sg1.addVertex(2); sg1.addEdge(1, 2); Multigraph mg1 = new Multigraph<>(DefaultEdge.class); mg1.addVertex(1); mg1.addVertex(2); mg1.addEdge(1, 2); Pseudograph pg1 = new Pseudograph<>(DefaultEdge.class); pg1.addVertex(1); pg1.addVertex(2); pg1.addEdge(1, 2); /* GT-0 test graph=null */ try { new VF2SubgraphIsomorphismInspector<>(null, sg1); Assert.fail("Expected NullPointerException"); } catch (NullPointerException ex) { } /* GT-1: multigraphs */ try { new VF2SubgraphIsomorphismInspector<>(mg1, mg1); Assert.fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { } /* GT-2: pseudographs */ try { new VF2SubgraphIsomorphismInspector<>(pg1, pg1); Assert.fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { } /* GT-3: simple graphs */ VF2SubgraphIsomorphismInspector gt3 = new VF2SubgraphIsomorphismInspector<>(sg1, sg1); assertEquals(true, gt3.getMappings().hasNext()); /* GT-4: directed graphs */ VF2SubgraphIsomorphismInspector gt4 = new VF2SubgraphIsomorphismInspector<>(dg1, dg1); assertEquals("[1=1 2=2]", gt4.getMappings().next().toString()); /* GT-5: simple graph + multigraph */ try { new VF2SubgraphIsomorphismInspector<>(sg1, mg1); Assert.fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { } /* GT-6: simple graph + pseudograph */ try { new VF2SubgraphIsomorphismInspector<>(sg1, pg1); Assert.fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { } /* GT-7: directed graph + multigraph */ try { new VF2SubgraphIsomorphismInspector<>(dg1, mg1); Assert.fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { } /* GT-8: directed graph + pseudograph */ try { new VF2SubgraphIsomorphismInspector<>(dg1, pg1); Assert.fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { } /* GT-9: pseudograph + multigraph */ try { new VF2SubgraphIsomorphismInspector<>(pg1, mg1); Assert.fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { } /* GT-10: simple graph + directed graph */ try { new VF2SubgraphIsomorphismInspector<>(sg1, dg1); Assert.fail("Expected IllegalArgumentException"); } catch (IllegalArgumentException ex) { } } /** * Tests edge cases on simple graphs */ @Test public void testEdgeCasesSimpleGraph() { /* ECS-1: graph and subgraph empty */ SimpleGraph sg0v = new SimpleGraph<>(DefaultEdge.class), sg0v2 = new SimpleGraph<>(DefaultEdge.class); VF2SubgraphIsomorphismInspector vfs1 = new VF2SubgraphIsomorphismInspector<>(sg0v, sg0v2); assertEquals("[]", vfs1.getMappings().next().toString()); /* ECS-2: graph non-empty, subgraph empty */ SimpleGraph sg4v3e = new SimpleGraph<>(DefaultEdge.class); sg4v3e.addVertex(1); sg4v3e.addVertex(2); sg4v3e.addVertex(3); sg4v3e.addVertex(4); sg4v3e.addEdge(1, 2); sg4v3e.addEdge(3, 2); sg4v3e.addEdge(3, 4); VF2SubgraphIsomorphismInspector vfs2 = new VF2SubgraphIsomorphismInspector<>(sg4v3e, sg0v); assertEquals("[1=~~ 2=~~ 3=~~ 4=~~]", vfs2.getMappings().next().toString()); /* ECS-3: graph empty, subgraph non-empty */ VF2SubgraphIsomorphismInspector vfs3 = new VF2SubgraphIsomorphismInspector<>(sg0v, sg4v3e); assertEquals(false, vfs3.isomorphismExists()); /* ECS-4: graph non-empty, subgraph single vertex */ SimpleGraph sg1v = new SimpleGraph<>(DefaultEdge.class); sg1v.addVertex(5); VF2SubgraphIsomorphismInspector vfs4 = new VF2SubgraphIsomorphismInspector<>(sg4v3e, sg1v); Iterator> iter = vfs4.getMappings(); Set mappings = new HashSet<>( Arrays .asList( "[1=5 2=~~ 3=~~ 4=~~]", "[1=~~ 2=5 3=~~ 4=~~]", "[1=~~ 2=~~ 3=5 4=~~]", "[1=~~ 2=~~ 3=~~ 4=5]")); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(true, mappings.remove(iter.next().toString())); assertEquals(false, iter.hasNext()); /* ECS-5: graph empty, subgraph single vertex */ VF2SubgraphIsomorphismInspector vfs5 = new VF2SubgraphIsomorphismInspector<>(sg0v, sg1v); assertEquals(false, vfs5.isomorphismExists()); /* ECS-6: subgraph with vertices, but no edges */ SimpleGraph sg3v0e = new SimpleGraph<>(DefaultEdge.class); sg3v0e.addVertex(5); sg3v0e.addVertex(6); sg3v0e.addVertex(7); VF2SubgraphIsomorphismInspector vfs6 = new VF2SubgraphIsomorphismInspector<>(sg4v3e, sg3v0e); assertEquals(false, vfs6.isomorphismExists()); /* ECS-7: graph and subgraph with vertices, but no edges */ SimpleGraph sg2v0e = new SimpleGraph<>(DefaultEdge.class); sg2v0e.addVertex(1); sg2v0e.addVertex(2); VF2SubgraphIsomorphismInspector vfs7 = new VF2SubgraphIsomorphismInspector<>(sg3v0e, sg2v0e); Iterator> iter7 = vfs7.getMappings(); Set mappings7 = new HashSet<>( Arrays .asList( "[5=1 6=2 7=~~]", "[5=1 6=~~ 7=2]", "[5=2 6=1 7=~~]", "[5=~~ 6=1 7=2]", "[5=2 6=~~ 7=1]", "[5=~~ 6=2 7=1]")); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(false, iter7.hasNext()); /* ECS-8: graph no edges, subgraph contains single edge */ SimpleGraph sg2v1e = new SimpleGraph<>(DefaultEdge.class); sg2v1e.addVertex(5); sg2v1e.addVertex(6); sg2v1e.addEdge(5, 6); VF2SubgraphIsomorphismInspector vfs8 = new VF2SubgraphIsomorphismInspector<>(sg3v0e, sg2v1e); assertEquals(false, vfs8.isomorphismExists()); /* * ECS-9: complete graphs of different size, graph smaller than subgraph */ SimpleGraph sg5k = new SimpleGraph<>(DefaultEdge.class); sg5k.addVertex(0); sg5k.addVertex(1); sg5k.addVertex(2); sg5k.addVertex(3); sg5k.addVertex(4); sg5k.addEdge(0, 1); sg5k.addEdge(0, 2); sg5k.addEdge(0, 3); sg5k.addEdge(0, 4); sg5k.addEdge(1, 2); sg5k.addEdge(1, 3); sg5k.addEdge(1, 4); sg5k.addEdge(2, 3); sg5k.addEdge(2, 4); sg5k.addEdge(3, 4); SimpleGraph sg4k = new SimpleGraph<>(DefaultEdge.class); sg4k.addVertex(0); sg4k.addVertex(1); sg4k.addVertex(2); sg4k.addVertex(3); sg4k.addEdge(0, 1); sg4k.addEdge(0, 2); sg4k.addEdge(0, 3); sg4k.addEdge(1, 2); sg4k.addEdge(1, 3); sg4k.addEdge(2, 3); SimpleGraph sg3k = new SimpleGraph<>(DefaultEdge.class); sg3k.addVertex(0); sg3k.addVertex(1); sg3k.addVertex(2); sg3k.addEdge(0, 1); sg3k.addEdge(0, 2); sg3k.addEdge(1, 2); VF2SubgraphIsomorphismInspector vfs9 = new VF2SubgraphIsomorphismInspector<>(sg4k, sg5k); assertEquals(false, vfs9.isomorphismExists()); /* * ECS-10: complete graphs of different size, graph bigger than subgraph */ VF2SubgraphIsomorphismInspector vfs10 = new VF2SubgraphIsomorphismInspector<>(sg4k, sg3k); Iterator> iter10 = vfs10.getMappings(); Set mappings10 = Set .of( "[0=0 1=1 2=2 3=~~]", "[0=0 1=1 2=~~ 3=2]", "[0=0 1=~~ 2=1 3=2]", "[0=~~ 1=0 2=1 3=2]", "[0=1 1=0 2=2 3=~~]", "[0=1 1=0 2=~~ 3=2]", "[0=1 1=~~ 2=0 3=2]", "[0=~~ 1=1 2=0 3=2]", "[0=2 1=1 2=0 3=~~]", "[0=2 1=1 2=~~ 3=0]", "[0=2 1=~~ 2=1 3=0]", "[0=~~ 1=2 2=1 3=0]", "[0=0 1=2 2=1 3=~~]", "[0=0 1=2 2=~~ 3=1]", "[0=0 1=~~ 2=2 3=1]", "[0=~~ 1=0 2=2 3=1]", "[0=1 1=2 2=0 3=~~]", "[0=1 1=2 2=~~ 3=0]", "[0=1 1=~~ 2=2 3=0]", "[0=~~ 1=1 2=2 3=0]", "[0=2 1=0 2=1 3=~~]", "[0=2 1=0 2=~~ 3=1]", "[0=2 1=~~ 2=0 3=1]", "[0=~~ 1=2 2=0 3=1]"); for (int i = 0; i < 24; i++) { assertTrue(mappings10.contains(iter10.next().toString())); } assertEquals(false, iter10.hasNext()); /* ECS-11: isomorphic graphs */ VF2SubgraphIsomorphismInspector vfs11 = new VF2SubgraphIsomorphismInspector<>(sg4v3e, sg4v3e); Iterator> iter11 = vfs11.getMappings(); Set mappings11 = Set.of("[1=1 2=2 3=3 4=4]", "[1=4 2=3 3=2 4=1]"); assertTrue(mappings11.contains(iter11.next().toString())); assertTrue(mappings11.contains(iter11.next().toString())); assertFalse(iter11.hasNext()); /* ECS-12: not connected graphs of different size */ SimpleGraph sg6v4enc = new SimpleGraph<>(DefaultEdge.class); sg6v4enc.addVertex(0); sg6v4enc.addVertex(1); sg6v4enc.addVertex(2); sg6v4enc.addVertex(3); sg6v4enc.addVertex(4); sg6v4enc.addVertex(5); sg6v4enc.addEdge(1, 2); sg6v4enc.addEdge(2, 3); sg6v4enc.addEdge(3, 1); sg6v4enc.addEdge(4, 5); SimpleGraph sg5v4enc = new SimpleGraph<>(DefaultEdge.class); sg5v4enc.addVertex(6); sg5v4enc.addVertex(7); sg5v4enc.addVertex(8); sg5v4enc.addVertex(9); sg5v4enc.addVertex(10); sg5v4enc.addEdge(7, 6); sg5v4enc.addEdge(9, 8); sg5v4enc.addEdge(10, 9); sg5v4enc.addEdge(8, 10); VF2SubgraphIsomorphismInspector vfs12 = new VF2SubgraphIsomorphismInspector<>(sg6v4enc, sg5v4enc); Iterator> iter12 = vfs12.getMappings(); Set mappings12 = new HashSet<>( Arrays .asList( "[0=~~ 1=8 2=10 3=9 4=7 5=6]", "[0=~~ 1=9 2=8 3=10 4=7 5=6]", "[0=~~ 1=10 2=9 3=8 4=7 5=6]", "[0=~~ 1=8 2=10 3=9 4=6 5=7]", "[0=~~ 1=9 2=8 3=10 4=6 5=7]", "[0=~~ 1=10 2=9 3=8 4=6 5=7]", "[0=~~ 1=10 2=8 3=9 4=7 5=6]", "[0=~~ 1=8 2=9 3=10 4=7 5=6]", "[0=~~ 1=9 2=10 3=8 4=7 5=6]", "[0=~~ 1=10 2=8 3=9 4=6 5=7]", "[0=~~ 1=8 2=9 3=10 4=6 5=7]", "[0=~~ 1=9 2=10 3=8 4=6 5=7]")); for (int i = 0; i < 12; i++) { assertEquals(true, mappings12.remove(iter12.next().toString())); } assertEquals(false, iter12.hasNext()); } /** * Tests edge cases on directed graphs */ @Test public void testEdgeCasesDirectedGraph() { /* ECD-1: graph and subgraph empty */ Graph dg0v = new DefaultDirectedGraph<>(DefaultEdge.class), dg0v2 = new DefaultDirectedGraph<>(DefaultEdge.class); VF2SubgraphIsomorphismInspector vf1 = new VF2SubgraphIsomorphismInspector<>(dg0v, dg0v2); assertEquals("[]", vf1.getMappings().next().toString()); /* ECD-2: graph non-empty, subgraph empty */ Graph dg4v3e = new DefaultDirectedGraph<>(DefaultEdge.class); dg4v3e.addVertex(1); dg4v3e.addVertex(2); dg4v3e.addVertex(3); dg4v3e.addVertex(4); dg4v3e.addEdge(1, 2); dg4v3e.addEdge(3, 2); dg4v3e.addEdge(3, 4); VF2SubgraphIsomorphismInspector vf2 = new VF2SubgraphIsomorphismInspector<>(dg4v3e, dg0v); assertEquals("[1=~~ 2=~~ 3=~~ 4=~~]", vf2.getMappings().next().toString()); /* ECD-3: graph empty, subgraph non-empty */ VF2SubgraphIsomorphismInspector vf3 = new VF2SubgraphIsomorphismInspector<>(dg0v, dg4v3e); assertEquals(false, vf3.isomorphismExists()); /* ECD-4: graph non-empty, subgraph single vertex */ Graph dg1v = new DefaultDirectedGraph<>(DefaultEdge.class); dg1v.addVertex(5); VF2SubgraphIsomorphismInspector vf4 = new VF2SubgraphIsomorphismInspector<>(dg4v3e, dg1v); Iterator> iter4 = vf4.getMappings(); Set mappings = new HashSet<>( Arrays .asList( "[1=5 2=~~ 3=~~ 4=~~]", "[1=~~ 2=5 3=~~ 4=~~]", "[1=~~ 2=~~ 3=5 4=~~]", "[1=~~ 2=~~ 3=~~ 4=5]")); assertEquals(true, mappings.remove(iter4.next().toString())); assertEquals(true, mappings.remove(iter4.next().toString())); assertEquals(true, mappings.remove(iter4.next().toString())); assertEquals(true, mappings.remove(iter4.next().toString())); assertEquals(false, iter4.hasNext()); /* ECD-5: graph empty, subgraph single vertex */ VF2SubgraphIsomorphismInspector vf5 = new VF2SubgraphIsomorphismInspector<>(dg0v, dg1v); assertEquals(false, vf5.isomorphismExists()); /* ECD-6: subgraph with vertices, but no edges */ Graph dg3v0e = new DefaultDirectedGraph<>(DefaultEdge.class); dg3v0e.addVertex(5); dg3v0e.addVertex(6); dg3v0e.addVertex(7); VF2SubgraphIsomorphismInspector vf6 = new VF2SubgraphIsomorphismInspector<>(dg4v3e, dg3v0e); assertEquals(false, vf6.isomorphismExists()); /* ECD-7: graph and subgraph with vertices, but no edges */ Graph dg2v0e = new DefaultDirectedGraph<>(DefaultEdge.class); dg2v0e.addVertex(1); dg2v0e.addVertex(2); VF2SubgraphIsomorphismInspector vf7 = new VF2SubgraphIsomorphismInspector<>(dg3v0e, dg2v0e); Iterator> iter7 = vf7.getMappings(); Set mappings7 = new HashSet<>( Arrays .asList( "[5=1 6=2 7=~~]", "[5=1 6=~~ 7=2]", "[5=2 6=1 7=~~]", "[5=~~ 6=1 7=2]", "[5=2 6=~~ 7=1]", "[5=~~ 6=2 7=1]")); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(true, mappings7.remove(iter7.next().toString())); assertEquals(false, iter7.hasNext()); /* ECD-8: graph no edges, subgraph contains single edge */ Graph dg2v1e = new DefaultDirectedGraph<>(DefaultEdge.class); dg2v1e.addVertex(5); dg2v1e.addVertex(6); dg2v1e.addEdge(5, 6); VF2SubgraphIsomorphismInspector vf8 = new VF2SubgraphIsomorphismInspector<>(dg3v0e, dg2v1e); assertEquals(false, vf8.isomorphismExists()); /* * ECD-9: complete graphs of different size, graph smaller than subgraph */ Graph dg5c = new DefaultDirectedGraph<>(DefaultEdge.class); dg5c.addVertex(0); dg5c.addVertex(1); dg5c.addVertex(2); dg5c.addVertex(3); dg5c.addVertex(4); dg5c.addEdge(0, 1); dg5c.addEdge(0, 2); dg5c.addEdge(0, 3); dg5c.addEdge(0, 4); dg5c.addEdge(1, 2); dg5c.addEdge(1, 3); dg5c.addEdge(1, 4); dg5c.addEdge(2, 3); dg5c.addEdge(2, 4); dg5c.addEdge(3, 4); Graph dg4c = new DefaultDirectedGraph<>(DefaultEdge.class); dg4c.addVertex(0); dg4c.addVertex(1); dg4c.addVertex(2); dg4c.addVertex(3); dg4c.addEdge(0, 1); dg4c.addEdge(0, 2); dg4c.addEdge(0, 3); dg4c.addEdge(1, 2); dg4c.addEdge(1, 3); dg4c.addEdge(2, 3); VF2SubgraphIsomorphismInspector vf9 = new VF2SubgraphIsomorphismInspector<>(dg4c, dg5c); assertEquals(false, vf9.isomorphismExists()); /* * ECD-10: complete graphs of different size, graph bigger than subgraph */ VF2SubgraphIsomorphismInspector vf10 = new VF2SubgraphIsomorphismInspector<>(dg5c, dg4c); Iterator> iter10 = vf10.getMappings(); Set mappings10 = new HashSet<>( Arrays .asList( "[0=0 1=1 2=2 3=3 4=~~]", "[0=0 1=1 2=2 3=~~ 4=3]", "[0=0 1=1 2=~~ 3=2 4=3]", "[0=0 1=~~ 2=1 3=2 4=3]", "[0=~~ 1=0 2=1 3=2 4=3]")); assertEquals(true, mappings10.remove(iter10.next().toString())); assertEquals(true, mappings10.remove(iter10.next().toString())); assertEquals(true, mappings10.remove(iter10.next().toString())); assertEquals(true, mappings10.remove(iter10.next().toString())); assertEquals(true, mappings10.remove(iter10.next().toString())); assertEquals(false, iter10.hasNext()); /* ECD-11: isomorphic graphs */ VF2SubgraphIsomorphismInspector vf11 = new VF2SubgraphIsomorphismInspector<>(dg4v3e, dg4v3e); Iterator> iter11 = vf11.getMappings(); assertEquals("[1=1 2=2 3=3 4=4]", iter11.next().toString()); assertEquals(false, iter11.hasNext()); /* ECD-12: not connected graphs of different size */ Graph dg6v4enc = new DefaultDirectedGraph<>(DefaultEdge.class); dg6v4enc.addVertex(0); dg6v4enc.addVertex(1); dg6v4enc.addVertex(2); dg6v4enc.addVertex(3); dg6v4enc.addVertex(4); dg6v4enc.addVertex(5); dg6v4enc.addEdge(1, 2); dg6v4enc.addEdge(2, 3); dg6v4enc.addEdge(3, 1); dg6v4enc.addEdge(4, 5); Graph dg5v4enc = new DefaultDirectedGraph<>(DefaultEdge.class); dg5v4enc.addVertex(6); dg5v4enc.addVertex(7); dg5v4enc.addVertex(8); dg5v4enc.addVertex(9); dg5v4enc.addVertex(10); dg5v4enc.addEdge(7, 6); dg5v4enc.addEdge(9, 8); dg5v4enc.addEdge(10, 9); dg5v4enc.addEdge(8, 10); VF2SubgraphIsomorphismInspector vf12 = new VF2SubgraphIsomorphismInspector<>(dg6v4enc, dg5v4enc); Iterator> iter12 = vf12.getMappings(); Set mappings12 = new HashSet<>( Arrays .asList( "[0=~~ 1=8 2=10 3=9 4=7 5=6]", "[0=~~ 1=9 2=8 3=10 4=7 5=6]", "[0=~~ 1=10 2=9 3=8 4=7 5=6]")); assertEquals(true, mappings12.remove(iter12.next().toString())); assertEquals(true, mappings12.remove(iter12.next().toString())); assertEquals(true, mappings12.remove(iter12.next().toString())); assertEquals(false, iter12.hasNext()); } @Test public void testExhaustive() { /* * DET-1: * * 0 3 | /| 0 2 g1 = | 2 | g2 = |/ |/ | 1 1 4 */ SimpleGraph g1 = new SimpleGraph<>(DefaultEdge.class), g2 = new SimpleGraph<>(DefaultEdge.class); g1.addVertex(0); g1.addVertex(1); g1.addVertex(2); g1.addVertex(3); g1.addVertex(4); g2.addVertex(0); g2.addVertex(1); g2.addVertex(2); g1.addEdge(0, 1); g1.addEdge(1, 2); g1.addEdge(2, 3); g1.addEdge(3, 4); g2.addEdge(0, 1); g2.addEdge(1, 2); VF2SubgraphIsomorphismInspector vf2 = new VF2SubgraphIsomorphismInspector<>(g1, g2); assertEquals(true, SubgraphIsomorphismTestUtils.containsAllMatchings(vf2, g1, g2)); /* * DET-2: * * g3 = ... g4 = ... * */ Graph g3 = new DefaultDirectedGraph<>(DefaultEdge.class), g4 = new DefaultDirectedGraph<>(DefaultEdge.class); g3.addVertex(0); g3.addVertex(1); g3.addVertex(2); g3.addVertex(3); g3.addVertex(4); g3.addVertex(5); g4.addVertex(0); g4.addVertex(1); g4.addVertex(2); g4.addVertex(3); g3.addEdge(0, 1); g3.addEdge(0, 5); g3.addEdge(1, 4); g3.addEdge(2, 1); g3.addEdge(2, 4); g3.addEdge(3, 1); g3.addEdge(4, 0); g3.addEdge(5, 2); g3.addEdge(5, 4); g4.addEdge(0, 3); g4.addEdge(1, 2); g4.addEdge(1, 3); g4.addEdge(2, 3); g4.addEdge(2, 0); VF2SubgraphIsomorphismInspector vf3 = new VF2SubgraphIsomorphismInspector<>(g3, g4); assertEquals(true, SubgraphIsomorphismTestUtils.containsAllMatchings(vf3, g3, g4)); /* * DET-3: * * 1----0 0---2 | | / g5 = | g6 = | / | |/ 2----3 1 */ SimpleGraph g5 = new SimpleGraph<>(DefaultEdge.class), g6 = new SimpleGraph<>(DefaultEdge.class); g5.addVertex(0); g5.addVertex(1); g5.addVertex(2); g5.addVertex(3); g6.addVertex(0); g6.addVertex(1); g6.addVertex(2); g5.addEdge(0, 1); g5.addEdge(1, 2); g5.addEdge(2, 3); g6.addEdge(0, 1); g6.addEdge(1, 2); g6.addEdge(2, 0); VF2SubgraphIsomorphismInspector vf4 = new VF2SubgraphIsomorphismInspector<>(g5, g6); assertEquals(true, SubgraphIsomorphismTestUtils.containsAllMatchings(vf4, g5, g6)); } /** * RG-1: Tests if a all matchings are correct (on some random graphs). */ @Test public void testRandomGraphs() { Random rnd = new Random(); rnd.setSeed(54321); for (int i = 1; i < 50; i++) { int vertexCount = 2 + rnd.nextInt(i), edgeCount = vertexCount + rnd.nextInt(vertexCount * (vertexCount - 1)) / 2, subVertexCount = 1 + rnd.nextInt(vertexCount); Graph g1 = SubgraphIsomorphismTestUtils.randomGraph(vertexCount, edgeCount, i), g2 = SubgraphIsomorphismTestUtils.randomSubgraph(g1, subVertexCount, i); VF2SubgraphIsomorphismInspector vf2 = new VF2SubgraphIsomorphismInspector<>(g1, g2); SubgraphIsomorphismTestUtils.showLog(i + ": " + vertexCount + "v, " + edgeCount + "e "); for (Iterator> mappings = vf2.getMappings(); mappings.hasNext();) { assertEquals( true, SubgraphIsomorphismTestUtils.isCorrectMatching(mappings.next(), g1, g2)); SubgraphIsomorphismTestUtils.showLog("."); } SubgraphIsomorphismTestUtils.showLog("\n"); } } /** * RG-2: Tests if all matchings are correct and if every matching is found (on random graphs). */ @Test public void testRandomGraphsExhaustive() { Random rnd = new Random(); rnd.setSeed(12345); for (int i = 1; i < 100; i++) { int vertexCount = 3 + rnd.nextInt(5), edgeCount = rnd.nextInt(vertexCount * (vertexCount - 1)), subVertexCount = 2 + rnd.nextInt(vertexCount), subEdgeCount = rnd.nextInt(subVertexCount * (subVertexCount - 1)); Graph g1 = SubgraphIsomorphismTestUtils.randomGraph(vertexCount, edgeCount, i), g2 = SubgraphIsomorphismTestUtils.randomGraph(subVertexCount, subEdgeCount, i); VF2SubgraphIsomorphismInspector vf2 = new VF2SubgraphIsomorphismInspector<>(g1, g2); SubgraphIsomorphismTestUtils .showLog(i + ": " + vertexCount + "v, " + edgeCount + "e ....\n"); assertEquals(true, SubgraphIsomorphismTestUtils.containsAllMatchings(vf2, g1, g2)); } } /** * SEM Tests the edge- and vertex-comparator */ @Test public void testSemanticCheck() { /* * a---<3>---b | | g1 = <4> <1> g2 = A---<6>---b---<5>---B | | A---<2>---B */ SimpleGraph g1 = new SimpleGraph<>(Integer.class), g2 = new SimpleGraph<>(Integer.class); g1.addVertex("a"); g1.addVertex("b"); g1.addVertex("A"); g1.addVertex("B"); g1.addEdge("a", "b", 3); g1.addEdge("b", "B", 1); g1.addEdge("B", "A", 2); g1.addEdge("A", "a", 4); g2.addVertex("A"); g2.addVertex("b"); g2.addVertex("B"); g2.addEdge("A", "b", 6); g2.addEdge("b", "B", 5); /* * SEM-1 test vertex and edge comparator */ VF2SubgraphIsomorphismInspector vf2 = new VF2SubgraphIsomorphismInspector<>(g1, g2, new VertexComp(), new EdgeComp()); Iterator> iter = vf2.getMappings(); assertEquals("[A=A B=b a=~~ b=B]", iter.next().toString()); assertEquals(false, iter.hasNext()); /* * SEM-2 test vertex comparator */ VF2SubgraphIsomorphismInspector vf3 = new VF2SubgraphIsomorphismInspector<>(g1, g2, new VertexComp(), (t1, t2) -> 0); Iterator> iter2 = vf3.getMappings(); Set mappings = Set.of("[A=A B=b a=~~ b=B]", "[A=~~ B=B a=A b=b]"); assertTrue(mappings.contains(iter2.next().toString())); assertTrue(mappings.contains(iter2.next().toString())); assertFalse(iter2.hasNext()); /* * SEM-3 test edge comparator */ VF2SubgraphIsomorphismInspector vf4 = new VF2SubgraphIsomorphismInspector<>(g1, g2, (t1, t2) -> 0, new EdgeComp()); Iterator> iter3 = vf4.getMappings(); Set mappings2 = Set.of("[A=A B=b a=~~ b=B]", "[A=A B=~~ a=b b=B]"); assertTrue(mappings2.contains(iter3.next().toString())); assertTrue(mappings2.contains(iter3.next().toString())); assertFalse(iter3.hasNext()); } private class VertexComp implements Comparator { @Override public int compare(String o1, String o2) { if (o1.toLowerCase().equals(o2.toLowerCase())) { return 0; } else { return 1; } } } private class EdgeComp implements Comparator { @Override public int compare(Integer o1, Integer o2) { return (o1 % 2) - (o2 % 2); } } /** * HG: measures time needed to check a pair of huge random graphs */ @Test public void testHugeGraph() { int n = 700; long time = System.currentTimeMillis(); Graph g1 = SubgraphIsomorphismTestUtils.randomGraph(n, n * n / 50, 12345), g2 = SubgraphIsomorphismTestUtils.randomSubgraph(g1, n / 2, 54321); VF2SubgraphIsomorphismInspector vf2 = new VF2SubgraphIsomorphismInspector<>(g1, g2); assertEquals(true, vf2.isomorphismExists()); SubgraphIsomorphismTestUtils .showLog( "|V1| = " + g1.vertexSet().size() + ", |E1| = " + g1.edgeSet().size() + ", |V2| = " + g2.vertexSet().size() + ", |E2| = " + g2.edgeSet().size() + " - " + (System.currentTimeMillis() - time) + "ms"); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/lca/000077500000000000000000000000001402514743400254225ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/lca/BinaryLiftingLCAFinderTest.java000066400000000000000000000021071402514743400333360ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Tests for the {@link BinaryLiftingLCAFinder} * * @author Alexandru Valeanu */ public class BinaryLiftingLCAFinderTest extends LCATreeTestBase { @Override LowestCommonAncestorAlgorithm createSolver(Graph graph, Set roots) { return new BinaryLiftingLCAFinder<>(graph, roots); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/lca/EulerTourRMQLCAFinderTest.java000066400000000000000000000021041402514743400331000ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Tests for the {@link EulerTourRMQLCAFinder} * * @author Alexandru Valeanu */ public class EulerTourRMQLCAFinderTest extends LCATreeTestBase { @Override LowestCommonAncestorAlgorithm createSolver(Graph graph, Set roots) { return new EulerTourRMQLCAFinder<>(graph, roots); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/lca/HeavyPathLCAFinderTest.java000066400000000000000000000020731402514743400324700ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Tests for the {@link HeavyPathLCAFinder} * * @author Alexandru Valeanu */ public class HeavyPathLCAFinderTest extends LCATreeTestBase { @Override LowestCommonAncestorAlgorithm createSolver(Graph graph, Set roots) { return new HeavyPathLCAFinder<>(graph, roots); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/lca/LCATreeTestBase.java000066400000000000000000000453731402514743400311530ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.stream.*; /** * Tests for LCA algorithms on rooted trees and forests * * @author Alexandru Valeanu */ public abstract class LCATreeTestBase { abstract LowestCommonAncestorAlgorithm createSolver(Graph graph, Set roots); @Test(expected = IllegalArgumentException.class) public void testInvalidNode() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("b"); g.addVertex("a"); g.addVertex("c"); g.addEdge("a", "b"); g.addEdge("a", "c"); createSolver(g, Collections.singleton("a")).getLCA("d", "d"); } @Test public void testNotExploredNode() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("b"); g.addVertex("a"); g.addVertex("c"); g.addVertex("d"); g.addEdge("a", "b"); g.addEdge("a", "c"); Assert.assertNull(createSolver(g, Collections.singleton("a")).getLCA("a", "d")); } @Test public void testOneNode() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); Assert.assertEquals("a", createSolver(g, Collections.singleton("a")).getLCA("a", "a")); } @Test(expected = IllegalArgumentException.class) public void testTwoRootsInTheSameTree() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("b"); g.addVertex("a"); g.addVertex("c"); g.addVertex("d"); g.addEdge("a", "b"); g.addEdge("c", "d"); LinkedHashSet roots = new LinkedHashSet<>(); roots.add("a"); roots.add("b"); createSolver(g, roots).getLCA("a", "b"); } @Test(expected = IllegalArgumentException.class) public void testTwoRootsInTheSameTree2() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("b"); g.addVertex("a"); g.addVertex("c"); g.addVertex("d"); g.addEdge("a", "b"); g.addEdge("c", "d"); LinkedHashSet roots = new LinkedHashSet<>(); roots.add("b"); roots.add("a"); createSolver(g, roots).getLCA("a", "b"); } @Test public void testDisconnectSmallGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); LowestCommonAncestorAlgorithm lcaAlgorithm = createSolver(g, Collections.singleton("a")); Assert.assertNull(lcaAlgorithm.getLCA("a", "b")); Assert.assertNull(lcaAlgorithm.getLCA("b", "a")); Assert.assertEquals("a", lcaAlgorithm.getLCA("a", "a")); Assert.assertEquals("b", lcaAlgorithm.getLCA("b", "b")); } @Test public void testDisconnectSmallGraph2() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); LowestCommonAncestorAlgorithm lcaAlgorithm = createSolver(g, Collections.singleton("a")); Assert.assertNull(lcaAlgorithm.getLCA("b", "c")); Assert.assertNull(lcaAlgorithm.getLCA("c", "b")); Assert.assertNull(lcaAlgorithm.getLCA("c", "a")); Assert.assertNull(lcaAlgorithm.getLCA("a", "c")); Assert.assertNull(lcaAlgorithm.getLCA("a", "b")); Assert.assertNull(lcaAlgorithm.getLCA("b", "a")); Assert.assertEquals("a", lcaAlgorithm.getLCA("a", "a")); Assert.assertEquals("b", lcaAlgorithm.getLCA("b", "b")); } @Test(expected = IllegalArgumentException.class) public void testEmptyGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); createSolver(g, Collections.singleton("a")); } @Test(expected = IllegalArgumentException.class) public void testEmptySetOfRoots() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); createSolver(g, Collections.emptySet()); } @Test(expected = IllegalArgumentException.class) public void testRootNotInGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); createSolver(g, Collections.singleton("b")); } @Test public void testGraphAllPossibleQueries() { final int N = 100; Random random = new Random(0x88_88); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(0), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>(1, N, random); generator.generateGraph(g, null); Integer root = g.vertexSet().iterator().next(); LowestCommonAncestorAlgorithm lcaAlgorithm1 = createSolver(g, Collections.singleton(root)); LowestCommonAncestorAlgorithm lcaAlgorithm2; if (lcaAlgorithm1 instanceof EulerTourRMQLCAFinder) lcaAlgorithm2 = new BinaryLiftingLCAFinder<>(g, Collections.singleton(root)); else lcaAlgorithm2 = new EulerTourRMQLCAFinder<>(g, Collections.singleton(root)); List> queries = new ArrayList<>(N * N); for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { queries.add(Pair.of(i, j)); } } List lcas1 = lcaAlgorithm1.getBatchLCA(queries); List lcas2 = lcaAlgorithm2.getBatchLCA(queries); for (int i = 0; i < queries.size(); i++) { Assert.assertEquals(lcas1.get(i), lcas2.get(i)); } } @Test public void testLongChain() { final int N = 2_000; final int Q = 100_000; Random random = new Random(0x88); Graph g = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= N; i++) g.addVertex(i); for (int i = 1; i < N; i++) g.addEdge(i, i + 1); List> queries = generateQueries(Q, new ArrayList<>(g.vertexSet()), random); LowestCommonAncestorAlgorithm lcaAlgorithm = createSolver(g, Collections.singleton(N)); List lcas = lcaAlgorithm.getBatchLCA(queries); for (int i = 0; i < Q; i++) { int a = queries.get(i).getFirst(); int b = queries.get(i).getSecond(); Assert.assertEquals((int) lcas.get(i), Math.max(a, b)); } } @Test public void testBinaryTree() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); g.addEdge("a", "b"); g.addEdge("b", "c"); g.addEdge("b", "d"); g.addEdge("d", "e"); LowestCommonAncestorAlgorithm lcaAlgorithm = createSolver(g, Collections.singleton("a")); Assert.assertEquals("b", lcaAlgorithm.getLCA("c", "e")); Assert.assertEquals("b", lcaAlgorithm.getLCA("b", "d")); Assert.assertEquals("d", lcaAlgorithm.getLCA("d", "e")); } @Test public void testSmallTree() { Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= 11; i++) graph.addVertex(i); graph.addEdge(1, 2); graph.addEdge(2, 4); graph.addEdge(2, 5); graph.addEdge(2, 6); graph.addEdge(4, 7); graph.addEdge(4, 8); graph.addEdge(6, 9); graph.addEdge(1, 3); graph.addEdge(3, 10); graph.addEdge(3, 11); LowestCommonAncestorAlgorithm lcaAlgorithm = createSolver(graph, Collections.singleton(1)); Assert.assertEquals(3, (int) lcaAlgorithm.getLCA(10, 11)); Assert.assertEquals(2, (int) lcaAlgorithm.getLCA(8, 9)); Assert.assertEquals(1, (int) lcaAlgorithm.getLCA(5, 11)); Assert.assertEquals(2, (int) lcaAlgorithm.getLCA(5, 6)); Assert.assertEquals(2, (int) lcaAlgorithm.getLCA(4, 2)); Assert.assertEquals(2, (int) lcaAlgorithm.getLCA(4, 5)); Assert.assertEquals(2, (int) lcaAlgorithm.getLCA(2, 2)); Assert.assertEquals(2, (int) lcaAlgorithm.getLCA(8, 6)); Assert.assertEquals(4, (int) lcaAlgorithm.getLCA(7, 8)); } @Test public void testSmallTree2() { Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= 20; i++) graph.addVertex(i); graph.addEdge(2, 1); graph.addEdge(3, 1); graph.addEdge(4, 1); graph.addEdge(5, 1); graph.addEdge(6, 2); graph.addEdge(7, 5); graph.addEdge(8, 7); graph.addEdge(9, 3); graph.addEdge(10, 2); graph.addEdge(11, 9); graph.addEdge(12, 6); graph.addEdge(13, 4); graph.addEdge(14, 6); graph.addEdge(15, 2); graph.addEdge(16, 10); graph.addEdge(17, 15); graph.addEdge(18, 6); graph.addEdge(19, 14); graph.addEdge(20, 11); LowestCommonAncestorAlgorithm lcaAlgorithm = createSolver(graph, Collections.singleton(1)); Assert.assertEquals(1, (int) lcaAlgorithm.getLCA(9, 14)); Assert.assertEquals(1, (int) lcaAlgorithm.getLCA(10, 9)); Assert.assertEquals(15, (int) lcaAlgorithm.getLCA(15, 15)); Assert.assertEquals(1, (int) lcaAlgorithm.getLCA(1, 17)); Assert.assertEquals(3, (int) lcaAlgorithm.getLCA(3, 3)); Assert.assertEquals(1, (int) lcaAlgorithm.getLCA(3, 1)); Assert.assertEquals(1, (int) lcaAlgorithm.getLCA(11, 14)); Assert.assertEquals(6, (int) lcaAlgorithm.getLCA(18, 19)); Assert.assertEquals(2, (int) lcaAlgorithm.getLCA(12, 2)); Assert.assertEquals(2, (int) lcaAlgorithm.getLCA(16, 14)); } @Test public void testNonBinaryTreeBatch() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); g.addVertex("f"); g.addVertex("g"); g.addVertex("h"); g.addVertex("i"); g.addVertex("j"); g.addEdge("a", "b"); g.addEdge("b", "c"); g.addEdge("c", "d"); g.addEdge("d", "e"); g.addEdge("b", "f"); g.addEdge("b", "g"); g.addEdge("c", "h"); g.addEdge("c", "i"); g.addEdge("i", "j"); LowestCommonAncestorAlgorithm lcaAlgorithm = createSolver(g, Collections.singleton("a")); Assert.assertEquals("b", lcaAlgorithm.getLCA("b", "h")); Assert.assertEquals("b", lcaAlgorithm.getLCA("j", "f")); Assert.assertEquals("c", lcaAlgorithm.getLCA("j", "h")); // now all together in one call List> queries = new ArrayList<>(); queries.add(Pair.of("b", "h")); queries.add(Pair.of("j", "f")); queries.add(Pair.of("j", "h")); List lcas = lcaAlgorithm.getBatchLCA(queries); Assert.assertEquals(Arrays.asList("b", "b", "c"), lcas); // test it the other way around and starting from b Assert.assertEquals("b", createSolver(g, Collections.singleton("b")).getLCA("h", "b")); } @Test public void randomHugeConnectedTree() { final int N = 100_000; final int Q = 200_000; Random random = new Random(0x88); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>(1, N, random); generator.generateGraph(g, null); List vertexList = new ArrayList<>(g.vertexSet()); LowestCommonAncestorAlgorithm lcaAlgorithm1 = createSolver(g, Collections.singleton(vertexList.get(0))); LowestCommonAncestorAlgorithm lcaAlgorithm2; if (lcaAlgorithm1 instanceof EulerTourRMQLCAFinder) lcaAlgorithm2 = new BinaryLiftingLCAFinder<>(g, vertexList.get(0)); else lcaAlgorithm2 = new EulerTourRMQLCAFinder<>(g, vertexList.get(0)); List> queries = generateQueries(Q, vertexList, random); List lcas1 = lcaAlgorithm1.getBatchLCA(queries); List lcas2 = lcaAlgorithm2.getBatchLCA(queries); for (int i = 0; i < Q; i++) { Assert.assertEquals(lcas1.get(i), lcas2.get(i)); } } public static List> generateQueries(int Q, List vertexList, Random random) { List> queries = new ArrayList<>(Q); for (int i = 0; i < Q; i++) { V a = vertexList.get(random.nextInt(vertexList.size())); V b = vertexList.get(random.nextInt(vertexList.size())); queries.add(Pair.of(a, b)); } return queries; } @Test public void randomHugePossiblyDisconnectedTree() { final int N = 100_000; final int Q = 200_000; Random random = new Random(0x55); final int NUM_TREES = 100 + random.nextInt(200); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>(NUM_TREES, N, random); generator.generateGraph(g, null); List vertexList = new ArrayList<>(g.vertexSet()); ConnectivityInspector connectivityInspector = new ConnectivityInspector<>(g); List> connectedComponents = connectivityInspector.connectedSets(); Set roots = connectedComponents .stream().map(component -> component.iterator().next()).collect(Collectors.toSet()); LowestCommonAncestorAlgorithm lcaAlgorithm1 = createSolver(g, roots); LowestCommonAncestorAlgorithm lcaAlgorithm2; if (lcaAlgorithm1 instanceof EulerTourRMQLCAFinder) lcaAlgorithm2 = new BinaryLiftingLCAFinder<>(g, roots); else lcaAlgorithm2 = new EulerTourRMQLCAFinder<>(g, roots); List> queries = generateQueries(Q, vertexList, random); List lcas1 = lcaAlgorithm1.getBatchLCA(queries); List lcas2 = lcaAlgorithm2.getBatchLCA(queries); for (int i = 0; i < Q; i++) { Assert.assertEquals(lcas1.get(i), lcas2.get(i)); } } @Test public void testSmallConnectedTrees() { Random random = new Random(0x88); final int TESTS = 10_000; final int Q = 50; for (int test = 0; test < TESTS; test++) { final int N = 10 + random.nextInt(100); GraphGenerator gen = new BarabasiAlbertForestGenerator<>(1, N, random.nextInt()); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); List vertexList = new ArrayList<>(g.vertexSet()); Set roots = Collections.singleton(vertexList.get(0)); LowestCommonAncestorAlgorithm lcaAlgorithm1 = createSolver(g, roots); LowestCommonAncestorAlgorithm lcaAlgorithm2; if (lcaAlgorithm1 instanceof EulerTourRMQLCAFinder) lcaAlgorithm2 = new BinaryLiftingLCAFinder<>(g, roots); else lcaAlgorithm2 = new EulerTourRMQLCAFinder<>(g, roots); List> queries = generateQueries(Q, vertexList, random); List lcas1 = lcaAlgorithm1.getBatchLCA(queries); List lcas2 = lcaAlgorithm2.getBatchLCA(queries); for (int i = 0; i < Q; i++) { Assert.assertEquals(lcas1.get(i), lcas2.get(i)); } } } @Test public void testSmallDisconnectedTrees() { Random random = new Random(0x88); final int TESTS = 10_000; final int Q = 50; for (int test = 0; test < TESTS; test++) { final int N = 10 + random.nextInt(200); GraphGenerator gen = new BarabasiAlbertForestGenerator<>(N / 10, N, random.nextInt()); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); Set roots = new ConnectivityInspector<>(g) .connectedSets().stream().map(x -> x.iterator().next()).collect(Collectors.toSet()); LowestCommonAncestorAlgorithm lcaAlgorithm1 = createSolver(g, roots); LowestCommonAncestorAlgorithm lcaAlgorithm2; if (lcaAlgorithm1 instanceof EulerTourRMQLCAFinder) lcaAlgorithm2 = new BinaryLiftingLCAFinder<>(g, roots); else lcaAlgorithm2 = new EulerTourRMQLCAFinder<>(g, roots); List> queries = generateQueries(Q, new ArrayList<>(g.vertexSet()), random); List lcas1 = lcaAlgorithm1.getBatchLCA(queries); List lcas2 = lcaAlgorithm2.getBatchLCA(queries); for (int i = 0; i < Q; i++) { Assert.assertEquals(lcas1.get(i), lcas2.get(i)); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/lca/NaiveLCAFinderTest.java000066400000000000000000000152631402514743400316460ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.*; import java.util.*; /** * Tests for the {@link NaiveLCAFinder} * * @author Barak Naveh * @author Alexandru Valeanu */ public class NaiveLCAFinderTest { private static void checkLcas(NaiveLCAFinder finder, V a, V b, Collection expectedSet) { Set lcaSet = finder.getLCASet(a, b); Assert.assertTrue(lcaSet.containsAll(expectedSet)); Assert.assertEquals(lcaSet.size(), expectedSet.size()); } @Test public void testNormalCases() { SimpleDirectedGraph g = new SimpleDirectedGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); g.addVertex("f"); g.addVertex("g"); g.addVertex("h"); g.addEdge("a", "b"); g.addEdge("b", "c"); g.addEdge("c", "d"); g.addEdge("d", "e"); g.addEdge("b", "f"); g.addEdge("b", "g"); g.addEdge("f", "e"); g.addEdge("e", "h"); NaiveLCAFinder finder = new NaiveLCAFinder<>(g); Assert.assertEquals("f", finder.getLCA("f", "h")); Assert.assertEquals("f", finder.getLCA("h", "f")); Assert.assertEquals("b", finder.getLCA("g", "h")); Assert.assertEquals("c", finder.getLCA("c", "c")); Assert.assertEquals("a", finder.getLCA("a", "e")); // tests one path not descending checkLcas(finder, "f", "h", Arrays.asList("f")); checkLcas(finder, "h", "f", Arrays.asList("f")); checkLcas(finder, "g", "h", Arrays.asList("b")); checkLcas(finder, "c", "c", Arrays.asList("c")); checkLcas(finder, "a", "e", Arrays.asList("a")); } @Test public void testNoLca() { SimpleDirectedGraph g = new SimpleDirectedGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); g.addVertex("f"); g.addVertex("g"); g.addVertex("h"); g.addVertex("i"); g.addEdge("a", "b"); g.addEdge("b", "c"); g.addEdge("c", "d"); g.addEdge("d", "e"); g.addEdge("f", "g"); g.addEdge("f", "h"); g.addEdge("g", "i"); g.addEdge("h", "i"); NaiveLCAFinder finder = new NaiveLCAFinder<>(g); Assert.assertNull(finder.getLCA("i", "e")); Assert.assertTrue(finder.getLCASet("i", "e").isEmpty()); } @Test public void testLoops() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); g.addVertex("f"); g.addVertex("g"); g.addVertex("h"); g.addVertex("i"); g.addEdge("a", "b"); g.addEdge("b", "c"); g.addEdge("c", "d"); g.addEdge("d", "e"); g.addEdge("b", "f"); g.addEdge("b", "g"); g.addEdge("f", "e"); g.addEdge("e", "h"); g.addEdge("h", "e"); g.addEdge("h", "h"); g.addEdge("i", "i"); NaiveLCAFinder finder = new NaiveLCAFinder<>(g); Assert.assertEquals("f", finder.getLCA("h", "f")); Assert.assertNull(finder.getLCA("a", "i")); checkLcas(finder, "h", "f", Arrays.asList("f")); Assert.assertTrue(finder.getLCASet("a", "i").isEmpty()); } @Test public void testArrivalOrder() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("g"); g.addVertex("e"); g.addVertex("h"); g.addEdge("a", "b"); g.addEdge("b", "c"); g.addEdge("a", "g"); g.addEdge("b", "g"); g.addEdge("g", "e"); g.addEdge("e", "h"); NaiveLCAFinder finder = new NaiveLCAFinder<>(g); Assert.assertEquals("b", finder.getLCA("b", "h")); Assert.assertEquals("b", finder.getLCA("c", "e")); checkLcas(finder, "b", "h", Arrays.asList("b")); checkLcas(finder, "c", "e", Arrays.asList("b")); } @Test public void testTwoLcas() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addEdge("a", "c"); g.addEdge("a", "d"); g.addEdge("b", "c"); g.addEdge("b", "d"); NaiveLCAFinder finder = new NaiveLCAFinder<>(g); checkLcas(finder, "c", "d", Arrays.asList("a", "b")); } @Test public void testLcaIsOneOfTheNodes() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); g.addVertex(0); for (int i = 1; i <= 10; i++) { g.addVertex(i); g.addEdge(i - 1, i); } g.addEdge(0, 10); NaiveLCAFinder finder = new NaiveLCAFinder<>(g); checkLcas(finder, 1, 10, Collections.singleton(1)); } /** * See issue #953 */ @Test public void testLca() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); /* * a-->b-->c | ^ V | d-->e-->f * */ Graphs.addEdgeWithVertices(g, "a", "b"); Graphs.addEdgeWithVertices(g, "b", "c"); Graphs.addEdgeWithVertices(g, "a", "d"); Graphs.addEdgeWithVertices(g, "d", "e"); Graphs.addEdgeWithVertices(g, "e", "f"); Graphs.addEdgeWithVertices(g, "f", "c"); NaiveLCAFinder finder = new NaiveLCAFinder<>(g); checkLcas(finder, "c", "e", Collections.singleton("e")); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/lca/TarjanLCAFinderTest.java000066400000000000000000000020611402514743400320130ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.lca; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; /** * Tests for the {@link TarjanLCAFinder} * * @author Alexandru Valeanu */ public class TarjanLCAFinderTest extends LCATreeTestBase { @Override LowestCommonAncestorAlgorithm createSolver(Graph graph, Set roots) { return new TarjanLCAFinder<>(graph, roots); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/000077500000000000000000000000001402514743400277015ustar00rootroot00000000000000AdamicAdarIndexLinkPredictionTest.java000066400000000000000000000103441402514743400371240ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertArrayEquals; import java.util.ArrayList; import java.util.List; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.alg.util.Pair; import org.jgrapht.alg.util.Triple; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for {@link AdamicAdarIndexLinkPrediction} * * @author Dimitrios Michail */ public class AdamicAdarIndexLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); AdamicAdarIndexLinkPrediction alg = new AdamicAdarIndexLinkPrediction<>(g); double[] scores = new double[6]; int pos = 0; for (int u : g.vertexSet()) { for (int v : g.vertexSet()) { if (u >= v || g.containsEdge(u, v)) { continue; } double score = alg.predict(u, v); scores[pos++] = score; } } double[] expected = { 1.631586747071319, 1.631586747071319, 0.7213475204444817, 3.074281787960283, 0.7213475204444817, 1.4426950408889634 }; assertArrayEquals(expected, scores, 1e-9); } @Test public void testPredictionWithListOfPairs() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); AdamicAdarIndexLinkPrediction alg = new AdamicAdarIndexLinkPrediction<>(g); List> queries = new ArrayList<>(); for (int u : g.vertexSet()) { for (int v : g.vertexSet()) { if (u >= v || g.containsEdge(u, v)) { continue; } queries.add(Pair.of(u, v)); } } List> predictions = alg.predict(queries); double[] scores = predictions.stream().mapToDouble(x -> x.getThird()).toArray(); double[] expected = { 1.631586747071319, 1.631586747071319, 0.7213475204444817, 3.074281787960283, 0.7213475204444817, 1.4426950408889634 }; assertArrayEquals(expected, scores, 1e-9); } @Test(expected = LinkPredictionIndexNotWellDefinedException.class) public void testInvalidPrediction() { Graph g = GraphTypeBuilder .directed().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil.constructGraph(g, new int[][] { { 0, 2 }, { 1, 2 } }); AdamicAdarIndexLinkPrediction alg = new AdamicAdarIndexLinkPrediction<>(g); alg.predict(0, 1); } } CommonNeighborsLinkPredictionTest.java000066400000000000000000000034741402514743400372650ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertEquals; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for the {@link CommonNeighborsLinkPrediction} * * @author Dimitrios Michail */ public class CommonNeighborsLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); CommonNeighborsLinkPrediction alg = new CommonNeighborsLinkPrediction<>(g); assertEquals(2d, alg.predict(0, 2), 1e-9); assertEquals(2, alg.predict(0, 4), 1e-9); assertEquals(1, alg.predict(3, 2), 1e-9); } } HubDepressedIndexLinkPredictionTest.java000066400000000000000000000043011402514743400375270ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertArrayEquals; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for {@link HubDepressedIndexLinkPrediction} * * @author Dimitrios Michail */ public class HubDepressedIndexLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); HubDepressedIndexLinkPrediction alg = new HubDepressedIndexLinkPrediction<>(g); double[] scores = new double[6]; int pos = 0; for (int u : g.vertexSet()) { for (int v : g.vertexSet()) { if (u >= v || g.containsEdge(u, v)) { continue; } double score = alg.predict(u, v); scores[pos++] = score; } } double[] expected = { 0.6666666666666666, 0.5, 0.5, 0.75, 0.3333333333333333, 0.6666666666666666 }; assertArrayEquals(expected, scores, 1e-9); } } HubPromotedIndexLinkPredictionTest.java000066400000000000000000000042031402514743400374030ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertArrayEquals; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for {@link HubPromotedIndexLinkPrediction} * * @author Dimitrios Michail */ public class HubPromotedIndexLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); HubPromotedIndexLinkPrediction alg = new HubPromotedIndexLinkPrediction<>(g); double[] scores = new double[6]; int pos = 0; for (int u : g.vertexSet()) { for (int v : g.vertexSet()) { if (u >= v || g.containsEdge(u, v)) { continue; } double score = alg.predict(u, v); scores[pos++] = score; } } double[] expected = { 1.0, 1.0, 0.5, 1.0, 0.5, 1.0 }; assertArrayEquals(expected, scores, 1e-9); } } JaccardCoefficientLinkPredictionTest.java000066400000000000000000000046241402514743400376600ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertEquals; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for the {@link JaccardCoefficientLinkPrediction} * * @author Dimitrios Michail */ public class JaccardCoefficientLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); JaccardCoefficientLinkPrediction alg = new JaccardCoefficientLinkPrediction<>(g); assertEquals(2d / 3, alg.predict(0, 2), 1e-9); assertEquals(2d / 4, alg.predict(0, 4), 1e-9); assertEquals(1d / 6, alg.predict(3, 2), 1e-9); } @Test(expected = LinkPredictionIndexNotWellDefinedException.class) public void testInvalidPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); g.addVertex(0); g.addVertex(1); JaccardCoefficientLinkPrediction alg = new JaccardCoefficientLinkPrediction<>(g); alg.predict(0, 1); } } LeichtHolmeNewmanIndexLinkPredictionTest.java000066400000000000000000000054001402514743400405160ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertArrayEquals; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for {@link LeichtHolmeNewmanIndexLinkPrediction} * * @author Dimitrios Michail */ public class LeichtHolmeNewmanIndexLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); LeichtHolmeNewmanIndexLinkPrediction alg = new LeichtHolmeNewmanIndexLinkPrediction<>(g); double[] scores = new double[6]; int pos = 0; for (int u : g.vertexSet()) { for (int v : g.vertexSet()) { if (u >= v || g.containsEdge(u, v)) { continue; } double score = alg.predict(u, v); scores[pos++] = score; } } double[] expected = { 3.0, 4.0, 1.0, 4.0, 0.6666666666666666, 1.3333333333333333 }; assertArrayEquals(expected, scores, 1e-9); } @Test(expected = LinkPredictionIndexNotWellDefinedException.class) public void testInvalidPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); g.addVertex(0); g.addVertex(1); LeichtHolmeNewmanIndexLinkPrediction alg = new LeichtHolmeNewmanIndexLinkPrediction<>(g); alg.predict(0, 1); } } PreferentialAttachmentIndexLinkPredictionTest.java000066400000000000000000000042421402514743400416070ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertArrayEquals; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for {@link PreferentialAttachmentLinkPrediction} * * @author Dimitrios Michail */ public class PreferentialAttachmentIndexLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); PreferentialAttachmentLinkPrediction alg = new PreferentialAttachmentLinkPrediction<>(g); double[] scores = new double[6]; int pos = 0; for (int u : g.vertexSet()) { for (int v : g.vertexSet()) { if (u >= v || g.containsEdge(u, v)) { continue; } double score = alg.predict(u, v); scores[pos++] = score; } } double[] expected = { 6.0, 8.0, 4.0, 12.0, 6.0, 6.0 }; assertArrayEquals(expected, scores, 1e-9); } } ResourceAllocationIndexLinkPredictionTest.java000066400000000000000000000055541402514743400407620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertArrayEquals; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for {@link ResourceAllocationIndexLinkPrediction} * * @author Dimitrios Michail */ public class ResourceAllocationIndexLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); ResourceAllocationIndexLinkPrediction alg = new ResourceAllocationIndexLinkPrediction<>(g); double[] scores = new double[6]; int pos = 0; for (int u : g.vertexSet()) { for (int v : g.vertexSet()) { if (u >= v || g.containsEdge(u, v)) { continue; } double score = alg.predict(u, v); scores[pos++] = score; } } double[] expected = { 0.5833333333333333, 0.5833333333333333, 0.25, 1.0833333333333333, 0.25, 0.5 }; assertArrayEquals(expected, scores, 1e-9); } @Test(expected = LinkPredictionIndexNotWellDefinedException.class) public void testInvalidPrediction() { Graph g = GraphTypeBuilder .directed().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); g.addVertex(0); g.addVertex(1); g.addVertex(2); g.addEdge(0, 2); g.addEdge(1, 2); ResourceAllocationIndexLinkPrediction alg = new ResourceAllocationIndexLinkPrediction<>(g); alg.predict(0, 1); } } SaltonIndexLinkPredictionTest.java000066400000000000000000000053351402514743400364220ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertArrayEquals; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for {@link SaltonIndexLinkPrediction} * * @author Dimitrios Michail */ public class SaltonIndexLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); SaltonIndexLinkPrediction alg = new SaltonIndexLinkPrediction<>(g); double[] scores = new double[6]; int pos = 0; for (int u : g.vertexSet()) { for (int v : g.vertexSet()) { if (u >= v || g.containsEdge(u, v)) { continue; } double score = alg.predict(u, v); scores[pos++] = score; } } double[] expected = { 0.8164965809277261, 0.7071067811865475, 0.5, 0.8660254037844387, 0.4082482904638631, 0.8164965809277261 }; assertArrayEquals(expected, scores, 1e-9); } @Test(expected = LinkPredictionIndexNotWellDefinedException.class) public void testInvalidPrediction() { Graph g = GraphTypeBuilder .directed().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); g.addVertex(0); g.addVertex(1); SaltonIndexLinkPrediction alg = new SaltonIndexLinkPrediction<>(g); alg.predict(0, 1); } } SørensenIndexLinkPredictionTest.java000066400000000000000000000053131402514743400373660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/linkprediction/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.linkprediction; import static org.junit.Assert.assertArrayEquals; import org.jgrapht.Graph; import org.jgrapht.TestUtil; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for class SørensenIndexLinkPrediction * * @author Dimitrios Michail */ public class SørensenIndexLinkPredictionTest { @Test public void testPrediction() { Graph g = GraphTypeBuilder .undirected().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); TestUtil .constructGraph( g, new int[][] { { 0, 1 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, { 3, 5 }, { 4, 5 } }); SørensenIndexLinkPrediction alg = new SørensenIndexLinkPrediction<>(g); double[] scores = new double[6]; int pos = 0; for (int u : g.vertexSet()) { for (int v : g.vertexSet()) { if (u >= v || g.containsEdge(u, v)) { continue; } double score = alg.predict(u, v); scores[pos++] = score; } } double[] expected = { 0.8, 0.6666666666666666, 0.5, 0.8571428571428571, 0.4, 0.8 }; assertArrayEquals(expected, scores, 1e-9); } @Test(expected = LinkPredictionIndexNotWellDefinedException.class) public void testInvalidPrediction() { Graph g = GraphTypeBuilder .directed().weighted(false).vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); g.addVertex(0); g.addVertex(1); SørensenIndexLinkPrediction alg = new SørensenIndexLinkPrediction<>(g); alg.predict(0, 1); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/000077500000000000000000000000001402514743400264555ustar00rootroot00000000000000ApproximateWeightedMatchingTest.java000066400000000000000000000347731402514743400355440ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.MatchingAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit tests for the approximate weighted matching algorithms. * * @author Dimitrios Michail */ public abstract class ApproximateWeightedMatchingTest { public abstract MatchingAlgorithm getApproximationAlgorithm( Graph graph); @Test public void testPath1() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 4, 1.0); Graphs.addEdge(g, 4, 5, 1.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(3, m.getEdges().size()); assertEquals(3.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(m.getEdges().contains(g.getEdge(0, 1))); assertTrue(m.getEdges().contains(g.getEdge(2, 3))); assertTrue(m.getEdges().contains(g.getEdge(4, 5))); assertTrue(isMatching(g, m)); } @Test public void testPath2() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 5.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 4, 5.0); Graphs.addEdge(g, 4, 5, 1.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(2, m.getEdges().size()); assertEquals(10.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(m.getEdges().contains(g.getEdge(1, 2))); assertTrue(m.getEdges().contains(g.getEdge(3, 4))); assertTrue(isMatching(g, m)); } @Test public void testNegativeAndZeroEdges() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, -1.0); Graphs.addEdge(g, 1, 2, -5.0); Graphs.addEdge(g, 2, 3, -1.0); Graphs.addEdge(g, 3, 0, -1.0); Graphs.addEdge(g, 3, 1, 0d); Graphs.addEdge(g, 0, 2, 0d); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(0, m.getEdges().size()); assertEquals(0d, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Test public void testNegativeAndZeroEdges1() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, -1.0); Graphs.addEdge(g, 1, 2, -5.0); Graphs.addEdge(g, 2, 3, -1.0); Graphs.addEdge(g, 3, 0, -1.0); Graphs.addEdge(g, 3, 1, -1.0d); Graphs.addEdge(g, 0, 2, -1.0d); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(0, m.getEdges().size()); assertEquals(0d, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Test public void testNegativeAndZeroEdges2() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 3, 1.0); Graphs.addEdge(g, 2, 4, -1.0); Graphs.addEdge(g, 3, 5, -1.0); Graphs.addEdge(g, 4, 6, -1.0); Graphs.addEdge(g, 5, 6, -1.0); Graphs.addEdge(g, 6, 7, -1.0); Graphs.addEdge(g, 6, 8, -1.0); Graphs.addEdge(g, 7, 9, -1.0); Graphs.addEdge(g, 8, 10, -1.0); Graphs.addEdge(g, 9, 11, 1.0); Graphs.addEdge(g, 10, 12, 1.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertTrue(isMatching(g, m)); assertTrue(m.getWeight() >= 2d); assertTrue(m.getEdges().size() >= 2); } @Test public void testGraph1() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, IntStream.range(0, 15).boxed().collect(Collectors.toList())); Graphs.addEdge(g, 0, 1, 5.0); Graphs.addEdge(g, 1, 2, 2.5); Graphs.addEdge(g, 2, 3, 5.0); Graphs.addEdge(g, 3, 4, 2.5); Graphs.addEdge(g, 4, 0, 2.5); Graphs.addEdge(g, 0, 13, 2.5); Graphs.addEdge(g, 13, 14, 5.0); Graphs.addEdge(g, 1, 11, 2.5); Graphs.addEdge(g, 11, 12, 5.0); Graphs.addEdge(g, 2, 9, 2.5); Graphs.addEdge(g, 9, 10, 5.0); Graphs.addEdge(g, 3, 7, 2.5); Graphs.addEdge(g, 7, 8, 5.0); Graphs.addEdge(g, 4, 5, 2.5); Graphs.addEdge(g, 5, 6, 5.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(7, m.getEdges().size()); assertEquals(35.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Test public void test3over4Approximation() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 0, 1.0); Graphs.addAllVertices(g, Arrays.asList(4, 5, 6, 7)); Graphs.addEdge(g, 4, 5, 1.0); Graphs.addEdge(g, 5, 6, 1.0); Graphs.addEdge(g, 6, 7, 1.0); Graphs.addEdge(g, 7, 4, 1.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(4, m.getEdges().size()); assertEquals(4.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Test public void testSelfLoops() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 0, 1.0); Graphs.addAllVertices(g, Arrays.asList(4, 5, 6, 7)); Graphs.addEdge(g, 4, 5, 1.0); Graphs.addEdge(g, 5, 6, 1.0); Graphs.addEdge(g, 6, 7, 1.0); Graphs.addEdge(g, 7, 4, 1.0); // add self loops Graphs.addEdge(g, 0, 0, 100.0); Graphs.addEdge(g, 1, 1, 200.0); Graphs.addEdge(g, 2, 2, -200.0); Graphs.addEdge(g, 3, 3, -100.0); Graphs.addEdge(g, 4, 4, 0.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(4, m.getEdges().size()); assertEquals(4.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Test public void testMultiGraph() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 0, 1.0); Graphs.addAllVertices(g, Arrays.asList(4, 5, 6, 7)); Graphs.addEdge(g, 4, 5, 1.0); Graphs.addEdge(g, 5, 6, 1.0); Graphs.addEdge(g, 6, 7, 1.0); Graphs.addEdge(g, 7, 4, 1.0); // add multiple edges Graphs.addEdge(g, 0, 1, 2.0); Graphs.addEdge(g, 1, 2, 2.0); Graphs.addEdge(g, 2, 3, 2.0); Graphs.addEdge(g, 3, 0, 2.0); Graphs.addEdge(g, 4, 5, 2.0); Graphs.addEdge(g, 5, 6, 2.0); Graphs.addEdge(g, 6, 7, 2.0); Graphs.addEdge(g, 7, 4, 2.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); // greedy finds maximum here 8.0 assertEquals(4, m.getEdges().size()); assertEquals(8.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Test public void testDirected() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 0, 1.0); Graphs.addAllVertices(g, Arrays.asList(4, 5, 6, 7)); Graphs.addEdge(g, 4, 5, 1.0); Graphs.addEdge(g, 5, 6, 1.0); Graphs.addEdge(g, 6, 7, 1.0); Graphs.addEdge(g, 7, 4, 1.0); // add multiple edges Graphs.addEdge(g, 0, 1, 2.0); Graphs.addEdge(g, 1, 2, 2.0); Graphs.addEdge(g, 2, 3, 2.0); Graphs.addEdge(g, 3, 0, 2.0); Graphs.addEdge(g, 4, 5, 2.0); Graphs.addEdge(g, 5, 6, 2.0); Graphs.addEdge(g, 6, 7, 2.0); Graphs.addEdge(g, 7, 4, 2.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(4, m.getEdges().size()); assertEquals(8.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Test public void testDisconnectedAndIsolatedVertices() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 0, 1.0); Graphs.addAllVertices(g, Arrays.asList(4, 5, 6, 7)); Graphs.addEdge(g, 4, 5, 1.0); Graphs.addEdge(g, 5, 6, 1.0); Graphs.addEdge(g, 6, 7, 1.0); Graphs.addEdge(g, 7, 4, 1.0); Graphs.addAllVertices(g, Arrays.asList(8, 9, 10, 11)); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertTrue(m.getWeight() >= 2.0); assertTrue(isMatching(g, m)); } @Test public void testSelfLoop() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 4.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 8.0); Graphs.addEdge(g, 3, 0, 3.0); Graphs.addEdge(g, 3, 3, 100.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertTrue(isMatching(g, m)); } @Test public void testBnGraph() { // create graphs which have a perfect matching for (int size = 1; size < 100; size++) { SimpleGraph graph = new SimpleGraph<>(DefaultWeightedEdge.class); for (int i = 0; i < size; i++) { graph.addVertex(i); } for (int i = 0; i < size; i++) { graph.addVertex(i + size); graph.addEdge(i, i + size); } for (int i = 0; i < size; i++) { for (int j = i + 1; j < size; j++) { graph.addEdge(i, j); } } MatchingAlgorithm maxAlg = getApproximationAlgorithm(graph); Matching matching = maxAlg.getMatching(); double weight = matching.getWeight(); assertTrue(isMatching(graph, matching)); assertTrue(weight >= size / 2.0); } } protected boolean isMatching(Graph g, Matching m) { Set matched = new HashSet<>(); for (E e : m.getEdges()) { V source = g.getEdgeSource(e); V target = g.getEdgeTarget(e); if (matched.contains(source)) { return false; } matched.add(source); if (matched.contains(target)) { return false; } matched.add(target); } return matched.size() == m.getEdges().size() * 2; } } BasePathGrowingWeightedMatchingTest.java000066400000000000000000000145161402514743400362700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.MatchingAlgorithm.*; import org.jgrapht.alg.util.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public abstract class BasePathGrowingWeightedMatchingTest extends ApproximateWeightedMatchingTest { public BasePathGrowingWeightedMatchingTest() { super(); } public abstract MatchingAlgorithm getApproximationAlgorithm( Graph graph); @Test public void testDynamicProgrammingOnPaths() { // test 0 WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); PathGrowingWeightedMatching pathGrowingAlgo = new PathGrowingWeightedMatching<>(g); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); LinkedList path = new LinkedList<>(); path.add(Graphs.addEdge(g, 0, 1, 5.0)); path.add(Graphs.addEdge(g, 1, 2, 2.5)); path.add(Graphs.addEdge(g, 2, 3, 5.0)); path.add(Graphs.addEdge(g, 3, 4, 2.5)); path.add(Graphs.addEdge(g, 4, 5, 2.5)); path.add(Graphs.addEdge(g, 5, 6, 5.0)); PathGrowingWeightedMatching.DynamicProgrammingPathSolver pathSolver = pathGrowingAlgo.new DynamicProgrammingPathSolver(); Pair> result = pathSolver.getMaximumWeightMatching(g, path); double weight = result.getFirst(); assertEquals(15.0, weight, MatchingAlgorithm.DEFAULT_EPSILON); Set matching = result.getSecond(); assertEquals(3, matching.size()); assertTrue(matching.contains(g.getEdge(0, 1))); assertTrue(matching.contains(g.getEdge(2, 3))); assertTrue(matching.contains(g.getEdge(5, 6))); // test 1 (empty path) LinkedList path1 = new LinkedList<>(); Pair> result1 = pathSolver.getMaximumWeightMatching(g, path1); double weight1 = result1.getFirst(); assertEquals(0.0, weight1, MatchingAlgorithm.DEFAULT_EPSILON); Set matching1 = result1.getSecond(); assertEquals(0, matching1.size()); // test 2 (single edge) Graphs.addAllVertices(g, Arrays.asList(7, 8)); LinkedList path2 = new LinkedList<>(); path2.add(Graphs.addEdge(g, 7, 8, 100.0)); Pair> result2 = pathSolver.getMaximumWeightMatching(g, path2); double weight2 = result2.getFirst(); assertEquals(100.0, weight2, MatchingAlgorithm.DEFAULT_EPSILON); Set matching2 = result2.getSecond(); assertEquals(1, matching2.size()); assertTrue(matching2.contains(g.getEdge(7, 8))); // test 3 (two edges) Graphs.addAllVertices(g, Arrays.asList(9, 10, 11)); LinkedList path3 = new LinkedList<>(); path3.add(Graphs.addEdge(g, 9, 10, 10.0)); path3.add(Graphs.addEdge(g, 10, 11, 15.0)); Pair> result3 = pathSolver.getMaximumWeightMatching(g, path3); double weight3 = result3.getFirst(); assertEquals(15.0, weight3, MatchingAlgorithm.DEFAULT_EPSILON); Set matching3 = result3.getSecond(); assertEquals(1, matching3.size()); assertTrue(matching3.contains(g.getEdge(10, 11))); } @Test public void testApproximationFactorOnRandomInstances() { final int seed = 33; final double edgeProbability = 0.7; final int numberVertices = 100; final int repeat = 10; GraphGenerator gg = new GnpRandomGraphGenerator( numberVertices, edgeProbability, seed, false); for (int i = 0; i < repeat; i++) { WeightedPseudograph g = new WeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); gg.generateGraph(g); MatchingAlgorithm alg1 = new PathGrowingWeightedMatching<>(g); Matching m1 = alg1.getMatching(); MatchingAlgorithm alg2 = new PathGrowingWeightedMatching<>(g, false); Matching m2 = alg2.getMatching(); MatchingAlgorithm alg3 = new SparseEdmondsMaximumCardinalityMatching<>(g); Matching m3 = alg3.getMatching(); MatchingAlgorithm alg4 = new GreedyWeightedMatching<>(g, false); Matching m4 = alg4.getMatching(); assertTrue(isMatching(g, m1)); assertTrue(isMatching(g, m2)); assertTrue(isMatching(g, m3)); assertTrue(isMatching(g, m4)); assertTrue(m1.getEdges().size() >= 0.5 * m3.getEdges().size()); assertTrue(m2.getEdges().size() >= 0.5 * m3.getEdges().size()); assertTrue(m4.getEdges().size() >= 0.5 * m3.getEdges().size()); } } } DenseEdmondsMaximumCardinalityMatchingTest.java000066400000000000000000002465741402514743400376700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.MatchingAlgorithm.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for EdmondsMaximumCardinalityMatching * * @author Joris Kinable */ public final class DenseEdmondsMaximumCardinalityMatchingTest { @Test public void testDisconnectedGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); int[][] edges = { { 0, 1 }, { 1, 2 }, { 0, 2 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 3, 6 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); this.verifyMatching(g, match, 3); assertTrue(matcher.isMaximumMatching(match)); } @Test public void testPseudoGraph() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 }, { 3, 3 }, { 2, 3 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); assertEquals(6, g.edgeSet().size()); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); this.verifyMatching(g, match, 2); assertTrue(matcher.isMaximumMatching(match)); } @Test public void testGraph15() { // graph: ([0, 1, 2, 3, 4, 5, 6, 7], [{5,1}, {4,3}, {0,6}, {4,2}, {2,1}, {3,6}, {5,0}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7)); int[][] edges = { { 5, 1 }, { 4, 3 }, { 0, 6 }, { 4, 2 }, { 2, 1 }, { 3, 6 }, { 5, 0 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); assertTrue(matcher.isMaximumMatching(match)); } @Test public void testGraph14() { // graph: ([0, 1, 2, 3, 4, 5, 6, 7], [{2,0}, {2,6}, {4,6}, {4,3}, {6,7}, {3,6}, {5,0}, // {2,5}, {3,7}, {2,4}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7)); int[][] edges = { { 2, 0 }, { 2, 6 }, { 4, 6 }, { 4, 3 }, { 6, 7 }, { 3, 6 }, { 5, 0 }, { 2, 5 }, { 3, 7 }, { 2, 4 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); assertTrue(matcher.isMaximumMatching(match)); } @Test public void testGraph13() { // graph: ([0, 1, 2, 3, 4], [{0,3}, {0,2}, {4,2}, {0,1}, {1,3}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4)); int[][] edges = { { 0, 3 }, { 0, 2 }, { 4, 2 }, { 0, 1 }, { 1, 3 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); assertTrue(matcher.isMaximumMatching(match)); } @Test public void testGraph12() { // graph: ([0, 1, 2, 3], [{3,2}, {3,1}, {0,3}, {0,1}, {2,1}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); int[][] edges = { { 3, 2 }, { 3, 1 }, { 0, 3 }, { 0, 1 }, { 2, 1 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); assertTrue(matcher.isMaximumMatching(match)); } @Test public void testGraph11() { // graph: ([0, 1, 2, 3], []) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); int[][] edges = { { 0, 2 }, { 1, 0 }, { 2, 1 }, { 0, 3 }, { 2, 3 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); assertTrue(matcher.isMaximumMatching(match)); } @Test public void testIsMaximumMatching4() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); DefaultEdge e12 = g.addEdge(1, 2); g.addEdge(2, 3); DefaultEdge e34 = g.addEdge(3, 4); g.addEdge(4, 1); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); // Perfect matching Matching m1 = new MatchingAlgorithm.MatchingImpl<>(g, Set.of(e12, e34), 2); assertTrue(matcher.isMaximumMatching(m1)); // Maximum matching in graph with odd number of vertices g.addVertex(5); g.addEdge(2, 5); assertTrue(matcher.isMaximumMatching(m1)); // Not a maximum matching: augmenting path exists Matching m2 = new MatchingAlgorithm.MatchingImpl<>(g, Set.of(e12), 2); assertFalse(matcher.isMaximumMatching(m2)); } @Test public void testIsMaximumMatching3() { // graph: ([0, 1, 2, 3, 4, 5, 6], [{4,0}, {2,3}, {2,0}, {2,5}, {2,6}, {0,1}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); g.addEdge(4, 0); g.addEdge(2, 3); g.addEdge(2, 0); g.addEdge(2, 5); g.addEdge(2, 6); g.addEdge(0, 1); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); assertTrue(matcher.isMaximumMatching(match)); } @Test public void testIsMaximumMatching2() { // Graph contains one isolated vertex: 6 Graph graph = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3, 4, 5, 7, 8, 9)); int[][] edges = { { 0, 8 }, { 9, 7 }, { 5, 3 }, { 9, 4 }, { 3, 2 }, { 5, 4 }, { 1, 0 }, { 3, 8 }, { 4, 7 }, { 2, 0 }, { 8, 5 }, { 0, 5 }, { 8, 1 } }; for (int[] edge : edges) graph.addEdge(edge[0], edge[1]); Set mEdges = new HashSet<>(); mEdges.add(graph.getEdge(0, 1)); mEdges.add(graph.getEdge(2, 3)); mEdges.add(graph.getEdge(4, 9)); mEdges.add(graph.getEdge(5, 8)); Matching m = new MatchingAlgorithm.MatchingImpl<>(graph, mEdges, 4); verifyMatching(graph, m, 4); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); assertTrue(matcher.isMaximumMatching(m)); } @Test public void testIsMaximum1() { // graph: ([0, 1, 2, 3, 4, 5, 6], [{5,6}, {1,2}, {0,6}, {4,6}, {2,6}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); g.addEdge(5, 6); g.addEdge(1, 2); g.addEdge(0, 6); g.addEdge(4, 6); g.addEdge(2, 6); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); assertTrue(matcher.isMaximumMatching(matcher.getMatching())); } @Test public void testRandomGraphsLarge() { Random random = new Random(1); int vertices = 100; for (int k = 0; k < 100; k++) { int edges = random.nextInt(maxEdges(vertices) / 2); GraphGenerator generator = new GnmRandomGraphGenerator<>(vertices, edges, 0); Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(graph); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); Matching m = matcher.getMatching(); this.verifyMatching(graph, m, m.getEdges().size()); assertTrue(matcher.isMaximumMatching(m)); } } @Test public void testRandomGraphsSmall() { for (int n = 4; n < 12; n++) { for (int m = 5; m < maxEdges(n); m++) { GraphGenerator generator = new GnmRandomGraphGenerator<>(n, m); for (int i = 0; i < 25; i++) { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(graph); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); Matching m1 = matcher.getMatching(); assertTrue(matcher.isMaximumMatching(m1)); } } } } @Test public void testGraph1() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 97, 22 }, { 56, 105 }, { 89, 132 }, { 117, 25 }, { 83, 106 }, { 171, 49 }, { 162, 138 }, { 90, 120 }, { 152, 33 }, { 47, 81 }, { 70, 191 }, { 23, 142 }, { 80, 53 }, { 106, 111 }, { 7, 9 }, { 11, 71 }, { 186, 177 }, { 196, 28 }, { 55, 106 }, { 134, 89 }, { 178, 123 }, { 109, 169 }, { 104, 27 }, { 162, 42 }, { 102, 164 }, { 51, 92 }, { 26, 10 }, { 141, 165 }, { 107, 164 }, { 41, 2 }, { 125, 46 }, { 189, 59 }, { 68, 104 }, { 161, 36 }, { 154, 143 }, { 129, 92 }, { 139, 110 }, { 43, 76 }, { 197, 1 }, { 118, 38 }, { 6, 53 }, { 123, 62 }, { 125, 55 }, { 183, 81 }, { 67, 120 }, { 54, 57 }, { 34, 25 }, { 156, 171 }, { 139, 49 }, { 108, 142 }, { 54, 184 }, { 124, 199 }, { 82, 191 }, { 23, 85 }, { 181, 71 }, { 154, 102 }, { 69, 98 }, { 131, 52 }, { 36, 33 }, { 146, 160 }, { 114, 75 }, { 92, 137 }, { 172, 31 }, { 188, 25 }, { 123, 119 }, { 178, 21 }, { 96, 97 }, { 72, 118 }, { 34, 106 }, { 175, 185 }, { 138, 121 }, { 185, 183 }, { 46, 62 }, { 135, 25 }, { 66, 21 }, { 194, 109 }, { 125, 123 }, { 62, 181 }, { 198, 156 }, { 99, 34 }, { 87, 174 }, { 165, 45 }, { 114, 125 }, { 164, 101 }, { 9, 36 }, { 102, 146 }, { 138, 189 }, { 159, 117 }, { 78, 69 }, { 50, 66 }, { 27, 63 }, { 122, 107 }, { 151, 11 }, { 58, 34 }, { 77, 195 }, { 122, 186 }, { 84, 98 }, { 171, 91 }, { 19, 33 }, { 41, 16 }, { 81, 40 }, { 87, 7 }, { 65, 4 }, { 178, 155 }, { 130, 106 }, { 131, 42 }, { 174, 71 }, { 30, 103 }, { 186, 83 }, { 108, 185 }, { 112, 77 }, { 62, 56 }, { 198, 34 }, { 4, 17 }, { 182, 139 }, { 159, 25 }, { 96, 9 }, { 192, 38 }, { 187, 104 }, { 27, 157 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 58); } @Test public void testGraph2() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 73, 143 }, { 139, 145 }, { 185, 74 }, { 23, 28 }, { 62, 4 }, { 86, 106 }, { 159, 60 }, { 55, 119 }, { 30, 36 }, { 79, 58 }, { 21, 11 }, { 83, 134 }, { 166, 33 }, { 128, 122 }, { 46, 147 }, { 48, 67 }, { 182, 93 }, { 92, 105 }, { 198, 4 }, { 168, 38 }, { 21, 142 }, { 72, 19 }, { 97, 96 }, { 37, 85 }, { 47, 119 }, { 139, 108 }, { 88, 83 }, { 162, 69 }, { 41, 45 }, { 127, 125 }, { 197, 34 }, { 63, 189 }, { 153, 135 }, { 184, 102 }, { 19, 44 }, { 75, 2 }, { 168, 165 }, { 121, 17 }, { 49, 179 }, { 169, 69 }, { 137, 58 }, { 52, 85 }, { 14, 179 }, { 133, 134 }, { 169, 82 }, { 139, 114 }, { 14, 196 }, { 198, 12 }, { 122, 164 }, { 94, 62 }, { 124, 98 }, { 34, 8 }, { 199, 178 }, { 59, 194 }, { 56, 196 }, { 15, 121 }, { 5, 1 }, { 26, 128 }, { 120, 111 }, { 187, 11 }, { 175, 191 }, { 62, 71 }, { 58, 52 }, { 67, 53 }, { 64, 62 }, { 186, 142 }, { 141, 156 }, { 40, 96 }, { 132, 32 }, { 35, 15 }, { 45, 149 }, { 199, 143 }, { 100, 147 }, { 130, 29 }, { 38, 155 }, { 26, 77 }, { 16, 22 }, { 49, 125 }, { 158, 199 }, { 146, 9 }, { 140, 65 }, { 91, 176 }, { 116, 83 }, { 70, 116 }, { 21, 17 }, { 190, 119 }, { 13, 116 }, { 83, 0 }, { 11, 33 }, { 113, 13 }, { 195, 150 }, { 179, 139 }, { 71, 56 }, { 128, 40 }, { 22, 91 }, { 63, 106 }, { 123, 161 }, { 53, 40 }, { 189, 172 }, { 56, 70 }, { 158, 86 }, { 174, 166 }, { 75, 69 }, { 135, 9 }, { 127, 157 }, { 76, 102 }, { 116, 173 }, { 90, 145 }, { 167, 75 }, { 135, 29 }, { 7, 179 }, { 96, 44 }, { 178, 50 }, { 60, 194 }, { 107, 74 }, { 9, 26 }, { 171, 14 }, { 153, 136 }, { 179, 197 }, { 16, 78 }, { 156, 19 }, { 45, 26 }, { 172, 83 }, { 51, 132 }, { 190, 65 }, { 26, 6 }, { 4, 59 }, { 63, 84 }, { 155, 8 }, { 125, 154 }, { 42, 97 }, { 182, 69 }, { 184, 196 }, { 42, 122 }, { 146, 159 }, { 26, 66 }, { 134, 105 }, { 98, 23 }, { 128, 92 }, { 97, 152 }, { 182, 167 }, { 22, 24 }, { 41, 8 }, { 97, 13 }, { 123, 70 }, { 74, 173 }, { 143, 77 }, { 90, 18 }, { 97, 59 }, { 21, 94 }, { 83, 15 }, { 49, 90 }, { 65, 105 }, { 189, 120 }, { 75, 124 }, { 177, 176 }, { 140, 152 }, { 70, 135 }, { 155, 157 }, { 17, 196 }, { 190, 130 }, { 9, 21 }, { 109, 66 }, { 98, 174 }, { 146, 112 }, { 6, 33 }, { 83, 138 }, { 40, 17 }, { 185, 64 }, { 84, 136 }, { 153, 16 }, { 134, 1 }, { 45, 106 }, { 165, 48 }, { 198, 150 }, { 60, 77 }, { 103, 150 }, { 117, 15 }, { 113, 5 }, { 106, 77 }, { 15, 111 }, { 170, 144 }, { 173, 50 }, { 180, 20 }, { 170, 114 }, { 34, 117 }, { 111, 131 }, { 173, 46 }, { 19, 175 }, { 89, 149 }, { 52, 15 }, { 5, 195 }, { 114, 23 }, { 166, 41 }, { 182, 122 }, { 131, 3 }, { 99, 77 }, { 40, 22 }, { 176, 35 }, { 12, 186 }, { 112, 89 }, { 10, 76 }, { 115, 172 }, { 30, 84 }, { 180, 93 }, { 98, 11 }, { 160, 120 }, { 141, 18 }, { 112, 11 }, { 73, 114 }, { 28, 42 }, { 103, 29 }, { 1, 175 }, { 161, 52 }, { 118, 150 }, { 148, 187 }, { 137, 47 }, { 192, 55 }, { 145, 149 }, { 198, 87 }, { 191, 139 }, { 21, 40 }, { 134, 174 }, { 136, 162 }, { 4, 46 }, { 39, 64 }, { 68, 38 }, { 73, 109 }, { 74, 34 }, { 43, 130 }, { 10, 56 }, { 24, 140 }, { 117, 144 }, { 180, 178 }, { 185, 37 }, { 14, 180 }, { 131, 45 }, { 18, 6 }, { 4, 61 }, { 57, 132 }, { 189, 94 }, { 38, 176 }, { 104, 124 }, { 125, 31 }, { 45, 156 }, { 145, 170 }, { 182, 125 }, { 106, 185 }, { 168, 152 }, { 146, 145 }, { 62, 90 }, { 99, 125 }, { 195, 64 }, { 135, 50 }, { 4, 81 }, { 186, 149 }, { 104, 175 }, { 112, 144 }, { 189, 47 }, { 55, 17 }, { 0, 41 }, { 1, 19 }, { 192, 97 }, { 192, 37 }, { 111, 99 }, { 197, 137 }, { 174, 38 }, { 64, 80 }, { 20, 7 }, { 6, 96 }, { 19, 166 }, { 129, 71 }, { 72, 149 }, { 34, 145 }, { 173, 141 }, { 27, 78 }, { 84, 171 }, { 36, 199 }, { 82, 144 }, { 13, 190 }, { 85, 30 }, { 67, 157 }, { 44, 126 }, { 24, 23 }, { 163, 187 }, { 61, 39 }, { 105, 152 }, { 49, 165 }, { 103, 42 }, { 5, 72 }, { 166, 73 }, { 135, 40 }, { 121, 50 }, { 193, 181 }, { 55, 196 }, { 13, 170 }, { 51, 181 }, { 170, 72 }, { 47, 33 }, { 179, 80 }, { 135, 186 }, { 127, 10 }, { 184, 114 }, { 60, 12 }, { 121, 157 }, { 42, 16 }, { 148, 131 }, { 106, 65 }, { 25, 93 }, { 164, 132 }, { 104, 145 }, { 106, 100 }, { 8, 25 }, { 23, 21 }, { 49, 5 }, { 162, 194 }, { 186, 193 }, { 109, 123 }, { 72, 81 }, { 141, 126 }, { 37, 56 }, { 5, 77 }, { 144, 192 }, { 35, 32 }, { 6, 100 }, { 151, 25 }, { 28, 26 }, { 108, 174 }, { 96, 28 }, { 196, 84 }, { 44, 29 }, { 100, 78 }, { 109, 97 }, { 193, 69 }, { 118, 189 }, { 101, 161 }, { 172, 197 }, { 10, 94 }, { 198, 76 }, { 178, 170 }, { 4, 158 }, { 97, 50 }, { 112, 194 }, { 39, 189 }, { 157, 131 }, { 62, 96 }, { 146, 101 }, { 4, 40 }, { 107, 181 }, { 181, 102 }, { 53, 98 }, { 185, 116 }, { 76, 119 }, { 45, 94 }, { 83, 178 }, { 1, 156 }, { 38, 78 }, { 157, 84 }, { 103, 181 }, { 161, 60 }, { 156, 186 }, { 71, 165 }, { 10, 90 }, { 111, 27 }, { 32, 2 }, { 135, 24 }, { 58, 92 }, { 53, 65 }, { 103, 195 }, { 3, 113 }, { 66, 22 }, { 125, 73 }, { 58, 42 }, { 9, 137 }, { 70, 87 }, { 116, 95 }, { 121, 4 }, { 53, 46 }, { 61, 7 }, { 112, 35 }, { 175, 125 }, { 194, 33 }, { 183, 167 }, { 172, 156 }, { 27, 173 }, { 142, 98 }, { 23, 165 }, { 25, 30 }, { 99, 92 }, { 48, 134 }, { 16, 109 }, { 100, 60 }, { 176, 63 }, { 83, 57 }, { 137, 120 }, { 61, 157 }, { 78, 32 }, { 154, 132 }, { 179, 72 }, { 99, 90 }, { 194, 1 }, { 11, 127 }, { 159, 129 }, { 114, 198 }, { 156, 49 }, { 111, 79 }, { 18, 42 }, { 46, 156 }, { 157, 37 }, { 21, 73 }, { 114, 9 }, { 151, 12 }, { 124, 140 }, { 183, 34 }, { 134, 63 }, { 194, 75 }, { 151, 99 }, { 85, 120 }, { 108, 85 }, { 106, 68 }, { 48, 24 }, { 15, 42 }, { 118, 198 }, { 91, 131 }, { 88, 146 }, { 123, 55 }, { 77, 173 }, { 176, 16 }, { 66, 103 }, { 153, 42 }, { 64, 182 }, { 85, 150 }, { 5, 34 }, { 69, 174 }, { 34, 129 }, { 74, 179 }, { 49, 86 }, { 195, 90 }, { 88, 99 }, { 184, 29 }, { 110, 80 }, { 144, 173 }, { 49, 12 }, { 27, 157 }, { 98, 16 }, { 157, 170 }, { 126, 27 }, { 64, 55 }, { 17, 16 }, { 180, 157 }, { 33, 100 }, { 6, 88 }, { 107, 124 }, { 175, 66 }, { 71, 158 }, { 85, 111 }, { 166, 143 }, { 8, 100 }, { 5, 59 }, { 111, 11 }, { 22, 104 }, { 183, 194 }, { 135, 185 }, { 110, 43 }, { 147, 192 }, { 79, 140 }, { 130, 126 }, { 96, 85 }, { 107, 67 }, { 160, 122 }, { 149, 178 }, { 10, 150 }, { 140, 172 }, { 128, 111 }, { 77, 170 }, { 102, 11 }, { 60, 65 }, { 30, 163 }, { 5, 94 }, { 181, 45 }, { 76, 68 }, { 95, 24 }, { 89, 152 }, { 2, 96 }, { 50, 1 }, { 173, 81 }, { 174, 42 }, { 136, 38 }, { 120, 60 }, { 21, 107 }, { 0, 197 }, { 74, 148 }, { 96, 186 }, { 114, 57 }, { 184, 24 }, { 194, 99 }, { 86, 16 }, { 135, 144 }, { 110, 177 }, { 58, 170 }, { 33, 104 }, { 164, 77 }, { 29, 98 }, { 188, 103 }, { 105, 26 }, { 26, 179 }, { 163, 101 }, { 95, 118 }, { 120, 123 }, { 187, 178 }, { 8, 176 }, { 35, 64 }, { 67, 104 }, { 5, 48 }, { 44, 114 }, { 105, 45 }, { 32, 171 }, { 134, 164 }, { 99, 19 }, { 93, 98 }, { 128, 117 }, { 22, 77 }, { 42, 143 }, { 94, 67 }, { 147, 122 }, { 130, 87 }, { 96, 27 }, { 42, 90 }, { 72, 104 }, { 52, 53 }, { 168, 134 }, { 164, 109 }, { 76, 199 }, { 6, 127 }, { 11, 49 }, { 8, 19 }, { 167, 121 }, { 158, 46 }, { 120, 167 }, { 43, 167 }, { 149, 179 }, { 152, 115 }, { 86, 5 }, { 61, 147 }, { 72, 162 }, { 138, 51 }, { 54, 146 }, { 88, 190 }, { 88, 199 }, { 8, 117 }, { 11, 48 }, { 144, 78 }, { 77, 19 }, { 38, 161 }, { 115, 9 }, { 74, 126 }, { 113, 178 }, { 116, 146 }, { 124, 10 }, { 39, 17 }, { 16, 148 }, { 81, 197 }, { 114, 138 }, { 55, 84 }, { 65, 111 }, { 154, 176 }, { 189, 35 }, { 96, 175 }, { 92, 182 }, { 73, 67 }, { 141, 152 }, { 167, 100 }, { 67, 172 }, { 16, 73 }, { 32, 93 }, { 12, 126 }, { 35, 1 }, { 13, 167 }, { 55, 98 }, { 15, 163 }, { 18, 11 }, { 78, 132 }, { 95, 104 }, { 174, 170 }, { 16, 30 }, { 148, 87 }, { 91, 41 }, { 111, 189 }, { 135, 172 }, { 113, 60 }, { 196, 147 }, { 64, 88 }, { 141, 5 }, { 19, 62 }, { 179, 142 }, { 155, 111 }, { 87, 48 }, { 25, 158 }, { 67, 41 }, { 118, 131 }, { 53, 167 }, { 26, 106 }, { 145, 144 }, { 172, 142 }, { 82, 135 }, { 91, 110 }, { 167, 193 }, { 63, 86 }, { 17, 97 }, { 104, 157 }, { 133, 177 }, { 30, 129 }, { 38, 91 }, { 186, 190 }, { 144, 38 }, { 176, 7 }, { 139, 57 }, { 52, 193 }, { 96, 64 }, { 57, 84 }, { 40, 8 }, { 93, 103 }, { 32, 92 }, { 164, 90 }, { 180, 8 }, { 168, 23 }, { 95, 34 }, { 154, 58 }, { 92, 17 }, { 176, 112 }, { 101, 110 }, { 109, 23 }, { 154, 59 }, { 44, 98 }, { 32, 41 }, { 39, 119 }, { 159, 141 }, { 177, 46 }, { 120, 71 }, { 114, 199 }, { 160, 66 }, { 81, 56 }, { 73, 28 }, { 89, 185 }, { 130, 91 }, { 158, 67 }, { 68, 74 }, { 59, 143 }, { 130, 64 }, { 74, 124 }, { 19, 170 }, { 103, 80 }, { 136, 94 }, { 121, 143 }, { 20, 176 }, { 173, 10 }, { 53, 106 }, { 72, 78 }, { 46, 140 }, { 105, 125 }, { 86, 36 }, { 9, 151 }, { 41, 182 }, { 80, 77 }, { 55, 63 }, { 127, 82 }, { 67, 160 }, { 164, 64 }, { 164, 60 }, { 191, 10 }, { 74, 20 }, { 10, 172 }, { 104, 136 }, { 166, 30 }, { 128, 10 }, { 119, 151 }, { 154, 150 }, { 93, 190 }, { 155, 85 }, { 161, 132 }, { 46, 153 }, { 96, 70 }, { 89, 75 }, { 15, 150 }, { 31, 0 }, { 155, 152 }, { 189, 18 }, { 123, 154 }, { 67, 51 }, { 77, 29 }, { 34, 140 }, { 113, 1 }, { 68, 19 }, { 190, 100 }, { 68, 43 }, { 167, 154 }, { 91, 133 }, { 154, 169 }, { 76, 165 }, { 149, 28 }, { 117, 23 }, { 13, 15 }, { 170, 0 }, { 156, 58 }, { 40, 57 }, { 62, 6 }, { 50, 110 }, { 40, 116 }, { 52, 69 }, { 6, 69 }, { 26, 146 }, { 72, 68 }, { 75, 32 }, { 72, 56 }, { 72, 33 }, { 194, 125 }, { 51, 190 }, { 184, 26 }, { 101, 170 }, { 145, 126 }, { 32, 176 }, { 22, 60 }, { 14, 198 }, { 17, 189 }, { 148, 177 }, { 194, 84 }, { 87, 55 }, { 71, 172 }, { 41, 164 }, { 183, 46 }, { 155, 199 }, { 84, 56 }, { 137, 150 }, { 138, 155 }, { 23, 116 }, { 9, 20 }, { 104, 115 }, { 64, 158 }, { 150, 57 }, { 75, 58 }, { 117, 54 }, { 72, 62 }, { 160, 184 }, { 2, 167 }, { 170, 56 }, { 137, 194 }, { 42, 181 }, { 84, 137 }, { 148, 175 }, { 29, 33 }, { 16, 126 }, { 135, 191 }, { 186, 1 }, { 70, 43 }, { 77, 178 }, { 194, 124 }, { 28, 30 }, { 114, 195 }, { 111, 146 }, { 137, 116 }, { 6, 41 }, { 168, 63 }, { 149, 91 }, { 0, 125 }, { 180, 96 }, { 57, 78 }, { 154, 42 }, { 92, 87 }, { 198, 63 }, { 2, 40 }, { 153, 141 }, { 191, 60 }, { 131, 1 }, { 119, 146 }, { 96, 184 }, { 83, 56 }, { 111, 158 }, { 119, 135 }, { 71, 93 }, { 155, 25 }, { 78, 109 }, { 140, 55 }, { 80, 34 }, { 119, 111 }, { 152, 171 }, { 193, 91 }, { 6, 23 }, { 110, 21 }, { 125, 146 }, { 20, 70 }, { 187, 78 }, { 72, 139 }, { 59, 45 }, { 196, 63 }, { 130, 128 }, { 78, 11 }, { 80, 151 }, { 22, 68 }, { 177, 68 }, { 77, 145 }, { 87, 150 }, { 16, 18 }, { 129, 107 }, { 87, 126 }, { 77, 12 }, { 121, 196 }, { 173, 162 }, { 126, 163 }, { 192, 110 }, { 129, 149 }, { 144, 30 }, { 191, 198 }, { 27, 140 }, { 128, 114 }, { 136, 56 }, { 137, 37 }, { 64, 46 }, { 44, 36 }, { 111, 106 }, { 165, 160 }, { 13, 8 }, { 97, 115 }, { 118, 152 }, { 141, 179 }, { 114, 6 }, { 80, 139 }, { 121, 120 }, { 102, 169 }, { 95, 136 }, { 3, 178 }, { 80, 97 }, { 112, 129 }, { 24, 39 }, { 36, 159 }, { 137, 159 }, { 178, 61 }, { 10, 109 }, { 107, 5 }, { 137, 61 }, { 136, 110 }, { 157, 17 }, { 159, 125 }, { 154, 9 }, { 39, 152 }, { 185, 136 }, { 105, 23 }, { 91, 3 }, { 117, 148 }, { 195, 109 }, { 150, 153 }, { 13, 45 }, { 171, 40 }, { 183, 137 }, { 187, 181 }, { 34, 148 }, { 84, 192 }, { 77, 74 }, { 1, 135 }, { 14, 5 }, { 149, 49 }, { 77, 104 }, { 160, 68 }, { 160, 88 }, { 93, 72 }, { 189, 195 }, { 173, 69 }, { 71, 96 }, { 172, 30 }, { 15, 158 }, { 189, 185 }, { 102, 185 }, { 101, 62 }, { 165, 15 }, { 178, 0 }, { 69, 29 }, { 7, 132 }, { 123, 79 }, { 0, 30 }, { 108, 17 }, { 81, 190 }, { 181, 78 }, { 162, 29 }, { 112, 74 }, { 168, 41 }, { 150, 44 }, { 145, 88 }, { 23, 148 }, { 187, 34 }, { 174, 12 }, { 33, 63 }, { 179, 138 }, { 27, 199 }, { 198, 83 }, { 65, 192 }, { 197, 10 }, { 92, 81 }, { 12, 22 }, { 56, 34 }, { 101, 190 }, { 21, 5 }, { 153, 54 }, { 191, 197 }, { 106, 140 }, { 45, 14 }, { 189, 164 }, { 151, 139 }, { 61, 140 }, { 171, 67 }, { 0, 28 }, { 188, 106 }, { 68, 7 }, { 72, 120 }, { 73, 94 }, { 136, 182 }, { 155, 102 }, { 141, 60 }, { 47, 16 }, { 17, 54 }, { 23, 102 }, { 1, 197 }, { 68, 158 }, { 69, 73 }, { 112, 188 }, { 43, 138 }, { 19, 183 }, { 49, 61 }, { 196, 174 }, { 190, 84 }, { 158, 154 }, { 105, 195 }, { 61, 54 }, { 35, 167 }, { 134, 29 }, { 74, 96 }, { 104, 109 }, { 87, 136 }, { 176, 78 }, { 33, 18 }, { 176, 161 }, { 163, 100 }, { 158, 190 }, { 153, 23 }, { 61, 85 }, { 130, 109 }, { 162, 110 }, { 29, 4 }, { 31, 66 }, { 58, 165 }, { 118, 194 }, { 147, 77 }, { 146, 139 }, { 180, 183 }, { 144, 186 }, { 58, 157 }, { 137, 19 }, { 153, 175 }, { 112, 95 }, { 176, 172 }, { 163, 29 }, { 156, 75 }, { 29, 15 }, { 147, 149 }, { 104, 48 }, { 135, 152 }, { 67, 65 }, { 153, 127 }, { 70, 14 }, { 114, 141 }, { 29, 183 }, { 144, 115 }, { 191, 150 }, { 73, 76 }, { 130, 168 }, { 181, 165 }, { 116, 12 }, { 91, 10 }, { 184, 177 }, { 123, 156 }, { 36, 157 }, { 123, 191 }, { 67, 87 }, { 184, 151 }, { 44, 70 }, { 139, 98 }, { 85, 163 }, { 61, 128 }, { 29, 0 }, { 192, 0 }, { 108, 189 }, { 170, 87 }, { 147, 117 }, { 179, 112 }, { 162, 57 }, { 41, 126 }, { 68, 112 }, { 135, 74 }, { 92, 15 }, { 159, 20 }, { 23, 123 }, { 87, 5 }, { 83, 135 }, { 6, 169 }, { 8, 145 }, { 7, 103 }, { 2, 118 }, { 1, 25 }, { 48, 86 }, { 176, 158 }, { 12, 10 }, { 28, 111 }, { 55, 50 }, { 171, 191 }, { 80, 27 }, { 166, 147 }, { 33, 22 }, { 125, 48 }, { 71, 123 }, { 156, 108 }, { 3, 69 }, { 178, 190 }, { 4, 126 }, { 37, 2 }, { 140, 112 }, { 118, 147 }, { 176, 61 }, { 176, 175 }, { 12, 64 }, { 73, 24 }, { 11, 24 }, { 111, 141 }, { 77, 82 }, { 52, 166 }, { 119, 141 }, { 165, 114 }, { 160, 91 }, { 24, 101 }, { 196, 115 }, { 75, 166 }, { 131, 140 }, { 51, 165 }, { 20, 122 }, { 34, 65 }, { 19, 30 }, { 140, 108 }, { 65, 78 }, { 126, 155 }, { 137, 42 }, { 167, 79 }, { 25, 122 }, { 81, 57 }, { 49, 140 }, { 60, 163 }, { 163, 193 }, { 128, 185 }, { 182, 7 }, { 100, 181 }, { 33, 185 }, { 120, 178 }, { 97, 2 }, { 91, 137 }, { 143, 40 }, { 50, 127 }, { 57, 157 }, { 38, 0 }, { 32, 44 }, { 186, 196 }, { 132, 87 }, { 77, 64 }, { 199, 151 }, { 192, 106 }, { 135, 30 }, { 118, 169 }, { 158, 88 }, { 66, 71 }, { 17, 174 }, { 178, 156 }, { 19, 152 }, { 25, 2 }, { 121, 90 }, { 136, 12 }, { 50, 197 }, { 24, 191 }, { 22, 10 }, { 23, 156 }, { 171, 154 }, { 39, 51 }, { 31, 38 }, { 144, 93 }, { 114, 82 }, { 83, 8 }, { 166, 87 }, { 118, 67 }, { 172, 166 }, { 172, 18 }, { 98, 109 }, { 74, 121 }, { 92, 68 }, { 50, 53 }, { 106, 125 }, { 84, 179 }, { 34, 199 }, { 96, 132 }, { 107, 127 }, { 124, 62 }, { 75, 59 }, { 152, 131 }, { 198, 171 }, { 0, 173 }, { 35, 99 }, { 127, 113 }, { 44, 194 }, { 129, 49 }, { 187, 125 }, { 134, 180 }, { 92, 53 }, { 14, 80 }, { 11, 23 }, { 37, 136 }, { 10, 178 }, { 125, 56 }, { 158, 11 }, { 114, 13 }, { 135, 4 }, { 182, 106 }, { 58, 11 }, { 18, 12 }, { 93, 54 }, { 165, 31 }, { 119, 53 }, { 36, 58 }, { 65, 96 }, { 6, 126 }, { 61, 71 }, { 67, 12 }, { 161, 56 }, { 73, 164 }, { 128, 107 }, { 26, 65 }, { 70, 162 }, { 133, 193 }, { 37, 116 }, { 193, 121 }, { 158, 33 }, { 92, 103 }, { 106, 85 }, { 190, 146 }, { 189, 131 }, { 29, 149 }, { 53, 111 }, { 99, 53 }, { 77, 8 }, { 67, 187 }, { 78, 121 }, { 184, 171 }, { 178, 152 }, { 191, 82 }, { 190, 152 }, { 67, 136 }, { 103, 32 }, { 40, 91 }, { 95, 70 }, { 174, 152 }, { 178, 90 }, { 136, 119 }, { 25, 22 }, { 57, 119 }, { 107, 94 }, { 129, 48 }, { 63, 108 }, { 136, 113 }, { 70, 72 }, { 53, 79 }, { 96, 140 }, { 115, 183 }, { 174, 165 }, { 49, 162 }, { 0, 109 }, { 60, 55 }, { 106, 166 }, { 172, 23 }, { 79, 65 }, { 160, 130 }, { 114, 197 }, { 174, 199 }, { 187, 24 }, { 97, 69 }, { 11, 35 }, { 104, 103 }, { 110, 189 }, { 194, 109 }, { 112, 170 }, { 194, 172 }, { 34, 40 }, { 100, 38 }, { 18, 169 }, { 77, 13 }, { 188, 54 }, { 111, 144 }, { 88, 91 }, { 81, 194 }, { 22, 192 }, { 198, 155 }, { 97, 172 }, { 68, 141 }, { 8, 182 }, { 69, 149 }, { 137, 198 }, { 167, 56 }, { 70, 61 }, { 186, 120 }, { 101, 125 }, { 124, 176 }, { 178, 57 }, { 77, 108 }, { 49, 198 }, { 66, 83 }, { 14, 72 }, { 14, 43 }, { 82, 0 }, { 91, 90 }, { 103, 131 }, { 7, 192 }, { 53, 62 }, { 20, 125 }, { 144, 39 }, { 116, 187 }, { 144, 129 }, { 51, 136 }, { 95, 90 }, { 155, 6 }, { 69, 183 }, { 179, 10 }, { 80, 154 }, { 126, 197 }, { 139, 152 }, { 46, 119 }, { 18, 111 }, { 181, 23 }, { 127, 68 }, { 96, 15 }, { 44, 50 }, { 9, 76 }, { 134, 55 }, { 63, 4 }, { 147, 80 }, { 156, 72 }, { 24, 141 }, { 10, 1 }, { 161, 31 }, { 11, 39 }, { 141, 86 }, { 189, 84 }, { 164, 119 }, { 94, 142 }, { 97, 163 }, { 17, 12 }, { 78, 179 }, { 28, 120 }, { 169, 126 }, { 5, 186 }, { 49, 93 }, { 65, 38 }, { 185, 100 }, { 145, 52 }, { 194, 101 }, { 52, 120 }, { 167, 133 }, { 132, 65 }, { 125, 164 }, { 109, 134 }, { 187, 166 }, { 186, 60 }, { 52, 138 }, { 99, 124 }, { 162, 16 }, { 24, 67 }, { 93, 37 }, { 76, 31 }, { 156, 68 }, { 94, 138 }, { 154, 88 }, { 155, 22 }, { 192, 58 }, { 106, 2 }, { 155, 11 }, { 27, 55 }, { 9, 163 }, { 0, 169 }, { 21, 137 }, { 83, 93 }, { 103, 151 }, { 117, 14 }, { 63, 131 }, { 5, 88 }, { 197, 199 }, { 34, 39 }, { 103, 164 }, { 144, 79 }, { 94, 35 }, { 139, 165 }, { 181, 140 }, { 197, 18 }, { 22, 87 }, { 125, 64 }, { 19, 160 }, { 71, 53 }, { 54, 144 }, { 17, 149 }, { 94, 38 }, { 161, 108 }, { 6, 60 }, { 1, 40 }, { 12, 134 }, { 36, 28 }, { 0, 34 }, { 45, 162 }, { 176, 96 }, { 184, 166 }, { 108, 3 }, { 99, 86 }, { 39, 105 }, { 190, 20 }, { 189, 80 }, { 172, 24 }, { 103, 76 }, { 126, 157 }, { 21, 182 }, { 100, 40 }, { 154, 84 }, { 103, 44 }, { 94, 196 }, { 162, 42 }, { 137, 141 }, { 18, 109 }, { 152, 117 }, { 147, 14 }, { 156, 35 }, { 5, 181 }, { 42, 13 }, { 47, 6 }, { 40, 52 }, { 166, 199 }, { 83, 189 }, { 121, 139 }, { 75, 97 }, { 36, 141 }, { 119, 17 }, { 11, 156 }, { 198, 108 }, { 104, 59 }, { 194, 186 }, { 54, 57 }, { 143, 171 }, { 158, 19 }, { 8, 72 }, { 179, 117 }, { 143, 151 }, { 35, 111 }, { 80, 91 }, { 92, 186 }, { 197, 109 }, { 174, 45 }, { 34, 124 }, { 60, 51 }, { 3, 96 }, { 187, 41 }, { 106, 57 }, { 50, 105 }, { 79, 119 }, { 8, 181 }, { 177, 24 }, { 69, 139 }, { 155, 84 }, { 66, 16 }, { 177, 5 }, { 6, 89 }, { 86, 90 }, { 141, 52 }, { 28, 163 }, { 103, 61 }, { 134, 94 }, { 27, 116 }, { 133, 160 }, { 78, 172 }, { 45, 183 }, { 148, 79 }, { 108, 124 }, { 135, 94 }, { 181, 123 }, { 24, 161 }, { 170, 199 }, { 95, 46 }, { 19, 18 }, { 188, 20 }, { 59, 39 }, { 167, 28 }, { 86, 128 }, { 44, 130 }, { 80, 7 }, { 113, 26 }, { 119, 116 }, { 57, 153 }, { 30, 80 }, { 36, 131 }, { 62, 80 }, { 109, 147 }, { 179, 123 }, { 135, 163 }, { 147, 68 }, { 80, 124 }, { 49, 167 }, { 18, 4 }, { 132, 143 }, { 93, 139 }, { 113, 170 }, { 46, 76 }, { 71, 64 }, { 118, 112 }, { 127, 178 }, { 194, 69 }, { 50, 126 }, { 1, 107 }, { 167, 5 }, { 132, 89 }, { 102, 36 }, { 151, 46 }, { 100, 184 }, { 181, 39 }, { 37, 6 }, { 66, 138 }, { 198, 8 }, { 168, 178 }, { 130, 176 }, { 150, 23 }, { 164, 157 }, { 182, 170 }, { 27, 147 }, { 177, 118 }, { 10, 153 }, { 141, 101 }, { 76, 26 }, { 117, 84 }, { 64, 108 }, { 180, 6 }, { 102, 192 }, { 138, 164 }, { 177, 157 }, { 46, 114 }, { 153, 86 }, { 113, 75 }, { 131, 174 }, { 184, 112 }, { 29, 95 }, { 48, 1 }, { 25, 150 }, { 132, 13 }, { 2, 188 }, { 97, 162 }, { 68, 173 }, { 118, 117 }, { 98, 163 }, { 159, 66 }, { 131, 159 }, { 82, 133 }, { 100, 110 }, { 128, 50 }, { 72, 141 }, { 9, 55 }, { 195, 44 }, { 38, 50 }, { 163, 196 }, { 46, 74 }, { 75, 139 }, { 122, 183 }, { 5, 27 }, { 111, 166 }, { 112, 61 }, { 129, 130 }, { 6, 85 }, { 91, 191 }, { 197, 69 }, { 31, 41 }, { 75, 154 }, { 135, 111 }, { 56, 36 }, { 37, 148 }, { 139, 130 }, { 158, 24 }, { 80, 196 }, { 14, 114 }, { 189, 23 }, { 78, 74 }, { 153, 76 }, { 46, 185 }, { 168, 16 }, { 40, 35 }, { 183, 118 }, { 139, 33 }, { 95, 55 }, { 132, 150 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); MatchingAlgorithm matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); Matching m = matcher.getMatching(); verifyMatching(graph, m, 100); assertTrue(m.isPerfect()); for (Integer v : graph.vertexSet()) assertTrue(m.isMatched(v)); } @Test public void testGraph3() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 4, 141 }, { 63, 132 }, { 129, 144 }, { 6, 88 }, { 62, 79 }, { 4, 79 }, { 125, 88 }, { 26, 133 }, { 21, 152 }, { 98, 80 }, { 107, 55 }, { 8, 33 }, { 153, 74 }, { 179, 6 }, { 79, 42 }, { 148, 146 }, { 27, 197 }, { 43, 22 }, { 154, 21 }, { 184, 26 }, { 197, 199 }, { 144, 102 }, { 136, 155 }, { 131, 163 }, { 118, 117 }, { 74, 34 }, { 168, 166 }, { 119, 72 }, { 148, 7 }, { 84, 46 }, { 34, 156 }, { 133, 97 }, { 42, 193 }, { 66, 122 }, { 81, 108 }, { 36, 132 }, { 3, 134 }, { 153, 44 }, { 98, 111 }, { 75, 122 }, { 116, 189 }, { 50, 36 }, { 43, 33 }, { 26, 73 }, { 13, 102 }, { 15, 121 }, { 188, 166 }, { 93, 102 }, { 8, 99 }, { 60, 78 }, { 32, 143 }, { 152, 168 }, { 72, 65 }, { 38, 153 }, { 117, 125 }, { 139, 186 }, { 195, 38 }, { 71, 40 }, { 15, 178 }, { 118, 183 }, { 112, 10 }, { 15, 148 }, { 152, 181 }, { 6, 190 }, { 177, 48 }, { 52, 47 }, { 11, 180 }, { 30, 61 }, { 186, 187 }, { 131, 167 }, { 84, 40 }, { 198, 126 }, { 135, 139 }, { 84, 3 }, { 161, 86 }, { 39, 63 }, { 186, 144 }, { 137, 154 }, { 195, 91 }, { 165, 187 }, { 170, 155 }, { 79, 121 }, { 85, 5 }, { 179, 124 }, { 100, 49 }, { 58, 51 }, { 59, 62 }, { 58, 91 }, { 85, 17 }, { 85, 0 }, { 68, 154 }, { 185, 171 }, { 13, 11 }, { 192, 32 }, { 169, 157 }, { 133, 19 }, { 93, 112 }, { 23, 71 }, { 59, 79 }, { 171, 170 }, { 41, 182 }, { 97, 24 }, { 71, 162 }, { 105, 3 }, { 183, 91 }, { 78, 172 }, { 165, 96 }, { 120, 184 }, { 182, 159 }, { 184, 34 }, { 85, 143 }, { 156, 129 }, { 151, 36 }, { 114, 94 }, { 16, 14 }, { 33, 12 }, { 47, 23 }, { 107, 180 }, { 108, 119 }, { 64, 27 }, { 186, 30 }, { 196, 51 }, { 104, 117 }, { 15, 99 }, { 73, 17 }, { 53, 132 }, { 35, 37 }, { 76, 169 }, { 165, 186 }, { 35, 129 }, { 97, 54 }, { 83, 77 }, { 65, 71 }, { 85, 192 }, { 77, 58 }, { 42, 176 }, { 195, 149 }, { 58, 144 }, { 160, 117 }, { 164, 135 }, { 170, 196 }, { 108, 17 }, { 144, 26 }, { 186, 15 }, { 161, 127 }, { 167, 173 }, { 145, 75 }, { 171, 57 }, { 50, 146 }, { 74, 131 }, { 7, 191 }, { 101, 149 }, { 60, 140 }, { 116, 120 }, { 193, 115 }, { 89, 128 }, { 109, 37 }, { 64, 37 }, { 127, 60 }, { 154, 104 }, { 192, 118 }, { 57, 174 }, { 69, 153 }, { 78, 76 }, { 120, 181 }, { 142, 47 }, { 69, 123 }, { 171, 110 }, { 26, 32 }, { 38, 39 }, { 72, 93 }, { 61, 102 }, { 174, 110 }, { 24, 78 }, { 63, 12 }, { 13, 64 }, { 40, 115 }, { 135, 106 }, { 46, 11 }, { 157, 177 }, { 188, 112 }, { 9, 87 }, { 138, 4 }, { 189, 128 }, { 153, 54 }, { 61, 145 }, { 170, 38 }, { 7, 126 }, { 46, 19 }, { 87, 79 }, { 88, 140 }, { 191, 190 }, { 55, 127 }, { 68, 183 }, { 64, 49 }, { 180, 164 }, { 64, 139 }, { 91, 124 }, { 118, 53 }, { 148, 16 }, { 23, 73 }, { 100, 114 }, { 59, 183 }, { 35, 42 }, { 45, 17 }, { 84, 86 }, { 65, 194 }, { 92, 109 }, { 181, 119 }, { 183, 128 }, { 130, 162 }, { 165, 197 }, { 156, 127 }, { 76, 90 }, { 180, 198 }, { 127, 122 }, { 103, 100 }, { 188, 39 }, { 55, 93 }, { 188, 69 }, { 191, 90 }, { 83, 183 }, { 20, 90 }, { 95, 144 }, { 15, 145 }, { 175, 74 }, { 23, 128 }, { 60, 178 }, { 145, 3 }, { 174, 35 }, { 155, 164 }, { 172, 129 }, { 193, 158 }, { 72, 157 }, { 22, 180 }, { 31, 43 }, { 24, 6 }, { 175, 10 }, { 124, 164 }, { 169, 7 }, { 2, 114 }, { 117, 126 }, { 179, 80 }, { 149, 63 }, { 183, 13 }, { 66, 153 }, { 35, 160 }, { 130, 29 }, { 15, 2 }, { 124, 58 }, { 38, 27 }, { 146, 168 }, { 150, 7 }, { 76, 83 }, { 32, 45 }, { 182, 14 }, { 1, 84 }, { 63, 169 }, { 23, 114 }, { 162, 9 }, { 31, 83 }, { 146, 19 }, { 67, 186 }, { 103, 101 }, { 10, 103 }, { 189, 136 }, { 79, 77 }, { 147, 181 }, { 59, 127 }, { 161, 11 }, { 173, 38 }, { 10, 58 }, { 8, 89 }, { 185, 152 }, { 22, 74 }, { 56, 118 }, { 120, 89 }, { 84, 6 }, { 175, 71 }, { 76, 115 }, { 101, 73 }, { 88, 92 }, { 149, 143 }, { 119, 86 }, { 17, 160 }, { 176, 165 }, { 49, 52 }, { 74, 71 }, { 113, 166 }, { 71, 94 }, { 92, 27 }, { 3, 160 }, { 173, 179 }, { 187, 5 }, { 172, 115 }, { 16, 4 }, { 37, 85 }, { 26, 113 }, { 12, 37 }, { 1, 103 }, { 133, 80 }, { 183, 22 }, { 136, 91 }, { 50, 65 }, { 193, 53 }, { 101, 112 }, { 141, 10 }, { 46, 61 }, { 73, 142 }, { 186, 60 }, { 109, 66 }, { 29, 91 }, { 94, 21 }, { 54, 124 }, { 153, 106 }, { 110, 68 }, { 58, 82 }, { 169, 193 }, { 28, 14 }, { 165, 132 }, { 108, 140 }, { 103, 128 }, { 46, 51 }, { 22, 111 }, { 49, 164 }, { 7, 32 }, { 126, 191 }, { 63, 190 }, { 171, 7 }, { 79, 80 }, { 71, 147 }, { 161, 104 }, { 166, 2 }, { 185, 179 }, { 83, 146 }, { 87, 180 }, { 141, 101 }, { 137, 125 }, { 66, 89 }, { 14, 107 }, { 9, 35 }, { 13, 164 }, { 140, 15 }, { 179, 120 }, { 138, 70 }, { 19, 25 }, { 130, 116 }, { 175, 161 }, { 99, 12 }, { 117, 71 }, { 121, 11 }, { 22, 149 }, { 57, 46 }, { 8, 184 }, { 46, 153 }, { 178, 85 }, { 52, 166 }, { 103, 197 }, { 114, 181 }, { 28, 29 }, { 101, 110 }, { 188, 92 }, { 103, 88 }, { 132, 73 }, { 150, 77 }, { 96, 169 }, { 120, 164 }, { 131, 90 }, { 108, 50 }, { 182, 127 }, { 100, 63 }, { 128, 25 }, { 184, 9 }, { 19, 86 }, { 132, 87 }, { 143, 184 }, { 105, 91 }, { 16, 68 }, { 16, 84 }, { 163, 86 }, { 66, 87 }, { 14, 62 }, { 78, 2 }, { 148, 89 }, { 2, 22 }, { 176, 198 }, { 178, 30 }, { 1, 50 }, { 47, 104 }, { 100, 11 }, { 144, 38 }, { 33, 137 }, { 74, 102 }, { 179, 44 }, { 40, 10 }, { 117, 16 }, { 91, 57 }, { 110, 25 }, { 141, 92 }, { 167, 188 }, { 26, 120 }, { 116, 107 }, { 60, 94 }, { 62, 151 }, { 118, 177 }, { 77, 105 }, { 194, 124 }, { 43, 13 }, { 174, 125 }, { 180, 163 }, { 56, 34 }, { 9, 91 }, { 58, 38 }, { 116, 108 }, { 58, 176 }, { 190, 154 }, { 124, 26 }, { 170, 56 }, { 136, 35 }, { 45, 35 }, { 100, 106 }, { 81, 52 }, { 57, 81 }, { 15, 30 }, { 165, 182 }, { 95, 114 }, { 107, 140 }, { 129, 122 }, { 149, 40 }, { 101, 145 }, { 196, 106 }, { 191, 166 }, { 168, 30 }, { 106, 43 }, { 83, 62 }, { 45, 174 }, { 135, 6 }, { 2, 3 }, { 80, 35 }, { 171, 188 }, { 116, 25 }, { 192, 182 }, { 87, 15 }, { 27, 25 }, { 116, 129 }, { 173, 84 }, { 141, 26 }, { 185, 82 }, { 155, 196 }, { 198, 45 }, { 18, 29 }, { 59, 80 }, { 153, 29 }, { 92, 126 }, { 109, 83 }, { 77, 151 }, { 95, 26 }, { 65, 73 }, { 188, 38 }, { 69, 2 }, { 44, 163 }, { 109, 45 }, { 107, 65 }, { 1, 160 }, { 34, 24 }, { 71, 198 }, { 160, 125 }, { 35, 133 }, { 97, 126 }, { 41, 118 }, { 49, 48 }, { 34, 117 }, { 18, 82 }, { 4, 140 }, { 184, 125 }, { 116, 192 }, { 86, 98 }, { 168, 7 }, { 135, 69 }, { 131, 113 }, { 57, 162 }, { 115, 88 }, { 163, 65 }, { 26, 63 }, { 27, 54 }, { 129, 126 }, { 66, 1 }, { 38, 198 }, { 19, 18 }, { 150, 111 }, { 0, 151 }, { 25, 93 }, { 104, 27 }, { 16, 40 }, { 188, 77 }, { 179, 14 }, { 151, 29 }, { 79, 0 }, { 134, 29 }, { 28, 22 }, { 23, 97 }, { 181, 160 }, { 37, 141 }, { 129, 26 }, { 185, 130 }, { 182, 10 }, { 189, 197 }, { 53, 25 }, { 195, 4 }, { 32, 164 }, { 66, 62 }, { 96, 199 }, { 80, 85 }, { 84, 45 }, { 83, 90 }, { 139, 21 }, { 153, 6 }, { 154, 84 }, { 135, 169 }, { 89, 132 }, { 110, 121 }, { 176, 22 }, { 90, 120 }, { 8, 153 }, { 69, 9 }, { 28, 182 }, { 105, 177 }, { 101, 31 }, { 106, 127 }, { 173, 68 }, { 81, 15 }, { 19, 162 }, { 173, 81 }, { 165, 41 }, { 99, 136 }, { 52, 152 }, { 199, 34 }, { 185, 47 }, { 91, 83 }, { 61, 64 }, { 164, 134 }, { 158, 90 }, { 116, 17 }, { 126, 132 }, { 153, 132 }, { 6, 59 }, { 149, 174 }, { 63, 48 }, { 7, 108 }, { 193, 25 }, { 150, 127 }, { 28, 58 }, { 166, 81 }, { 84, 128 }, { 155, 91 }, { 178, 170 }, { 154, 134 }, { 109, 44 }, { 199, 140 }, { 15, 1 }, { 185, 178 }, { 11, 148 }, { 106, 133 }, { 13, 179 }, { 179, 165 }, { 90, 25 }, { 60, 123 }, { 182, 151 }, { 88, 154 }, { 133, 198 }, { 191, 189 }, { 129, 179 }, { 181, 61 }, { 50, 143 }, { 103, 117 }, { 3, 114 }, { 142, 180 }, { 33, 20 }, { 45, 134 }, { 191, 159 }, { 61, 184 }, { 180, 20 }, { 183, 38 }, { 142, 169 }, { 153, 178 }, { 0, 84 }, { 74, 91 }, { 167, 127 }, { 119, 136 }, { 34, 96 }, { 152, 175 }, { 16, 107 }, { 133, 119 }, { 123, 86 }, { 89, 166 }, { 162, 121 }, { 41, 72 }, { 60, 128 }, { 54, 173 }, { 128, 70 }, { 165, 133 }, { 34, 183 }, { 160, 34 }, { 152, 115 }, { 158, 146 }, { 74, 18 }, { 109, 104 }, { 72, 48 }, { 88, 126 }, { 125, 143 }, { 35, 17 }, { 1, 11 }, { 147, 177 }, { 59, 140 }, { 56, 177 }, { 41, 198 }, { 150, 83 }, { 159, 190 }, { 199, 89 }, { 198, 138 }, { 67, 18 }, { 16, 94 }, { 60, 158 }, { 188, 91 }, { 191, 11 }, { 42, 91 }, { 191, 72 }, { 140, 45 }, { 122, 159 }, { 65, 62 }, { 95, 129 }, { 152, 108 }, { 144, 147 }, { 10, 191 }, { 135, 109 }, { 0, 36 }, { 77, 27 }, { 35, 71 }, { 54, 26 }, { 131, 93 }, { 136, 152 }, { 191, 164 }, { 81, 176 }, { 19, 31 }, { 104, 17 }, { 32, 81 }, { 132, 75 }, { 133, 29 }, { 114, 157 }, { 35, 32 }, { 194, 85 }, { 70, 36 }, { 40, 117 }, { 136, 70 }, { 102, 2 }, { 34, 132 }, { 101, 146 }, { 182, 94 }, { 80, 65 }, { 121, 112 }, { 97, 47 }, { 21, 183 }, { 40, 171 }, { 14, 168 }, { 167, 0 }, { 153, 157 }, { 115, 133 }, { 47, 125 }, { 174, 39 }, { 79, 31 }, { 114, 102 }, { 162, 147 }, { 184, 25 }, { 8, 53 }, { 94, 126 }, { 136, 143 }, { 167, 58 }, { 180, 81 }, { 149, 49 }, { 43, 80 }, { 169, 155 }, { 72, 192 }, { 147, 108 }, { 87, 39 }, { 13, 101 }, { 48, 64 }, { 177, 188 }, { 148, 96 }, { 163, 117 }, { 172, 41 }, { 106, 59 }, { 113, 193 }, { 152, 16 }, { 95, 54 }, { 24, 156 }, { 154, 176 }, { 31, 117 }, { 114, 19 }, { 131, 156 }, { 187, 143 }, { 128, 144 }, { 45, 22 }, { 137, 25 }, { 123, 113 }, { 84, 50 }, { 199, 111 }, { 142, 12 }, { 138, 67 }, { 14, 148 }, { 136, 41 }, { 150, 56 }, { 82, 142 }, { 3, 35 }, { 61, 73 }, { 141, 90 }, { 192, 129 }, { 18, 138 }, { 68, 4 }, { 78, 1 }, { 28, 136 }, { 99, 122 }, { 16, 28 }, { 4, 114 }, { 158, 114 }, { 58, 65 }, { 85, 124 }, { 122, 72 }, { 172, 142 }, { 80, 90 }, { 98, 145 }, { 153, 17 }, { 135, 78 }, { 34, 191 }, { 59, 9 }, { 180, 160 }, { 181, 40 }, { 35, 70 }, { 96, 147 }, { 0, 162 }, { 199, 71 }, { 160, 23 }, { 59, 7 }, { 45, 199 }, { 156, 186 }, { 191, 79 }, { 188, 41 }, { 187, 176 }, { 1, 36 }, { 1, 42 }, { 34, 1 }, { 4, 164 }, { 121, 34 }, { 123, 172 }, { 191, 74 }, { 188, 183 }, { 157, 148 }, { 8, 126 }, { 38, 7 }, { 178, 118 }, { 114, 154 }, { 25, 18 }, { 141, 128 }, { 108, 135 }, { 167, 104 }, { 69, 148 }, { 92, 52 }, { 198, 76 }, { 67, 172 }, { 128, 137 }, { 172, 95 }, { 83, 22 }, { 131, 101 }, { 88, 17 }, { 161, 163 }, { 146, 27 }, { 121, 107 }, { 66, 197 }, { 56, 120 }, { 82, 26 }, { 109, 75 }, { 137, 195 }, { 177, 197 }, { 21, 115 }, { 104, 18 }, { 31, 71 }, { 157, 14 }, { 80, 41 }, { 132, 55 }, { 2, 138 }, { 128, 38 }, { 57, 121 }, { 37, 187 }, { 16, 181 }, { 0, 196 }, { 79, 18 }, { 186, 1 }, { 170, 195 }, { 115, 47 }, { 46, 173 }, { 83, 80 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); MatchingAlgorithm matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 100); } @Test public void testGraph4() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 142, 104 }, { 176, 103 }, { 117, 140 }, { 9, 160 }, { 23, 106 }, { 120, 11 }, { 55, 110 }, { 9, 176 }, { 171, 183 }, { 27, 42 }, { 101, 122 }, { 179, 12 }, { 59, 122 }, { 10, 7 }, { 48, 68 }, { 48, 64 }, { 20, 1 }, { 155, 86 }, { 111, 45 }, { 56, 137 }, { 29, 149 }, { 77, 110 }, { 135, 86 }, { 192, 87 }, { 198, 199 }, { 96, 143 }, { 28, 72 }, { 94, 163 }, { 65, 196 }, { 159, 20 }, { 151, 90 }, { 137, 146 }, { 74, 18 }, { 55, 146 }, { 95, 74 }, { 195, 95 }, { 112, 80 }, { 47, 95 }, { 2, 10 }, { 168, 188 }, { 179, 137 }, { 48, 147 }, { 179, 68 }, { 39, 81 }, { 102, 9 }, { 12, 89 }, { 50, 102 }, { 133, 27 }, { 12, 150 }, { 193, 31 }, { 66, 159 }, { 78, 118 }, { 52, 15 }, { 149, 153 }, { 139, 175 }, { 126, 59 }, { 54, 176 }, { 32, 65 }, { 118, 34 }, { 129, 18 }, { 61, 188 }, { 87, 122 }, { 47, 21 }, { 185, 136 }, { 12, 1 }, { 141, 159 }, { 114, 119 }, { 150, 58 }, { 75, 79 }, { 25, 121 }, { 40, 105 }, { 108, 0 }, { 130, 89 }, { 188, 174 }, { 64, 198 }, { 50, 3 }, { 42, 105 }, { 2, 194 }, { 105, 187 }, { 119, 118 }, { 185, 191 }, { 38, 17 }, { 196, 175 }, { 77, 87 }, { 43, 107 }, { 56, 122 }, { 108, 52 }, { 80, 7 }, { 27, 70 }, { 72, 45 }, { 30, 36 }, { 29, 70 }, { 186, 109 }, { 89, 45 }, { 19, 12 }, { 181, 39 }, { 92, 141 }, { 41, 7 }, { 91, 75 }, { 193, 106 }, { 184, 23 }, { 69, 185 }, { 90, 11 }, { 149, 12 }, { 166, 165 }, { 101, 199 }, { 167, 152 }, { 0, 3 }, { 121, 168 }, { 107, 131 }, { 190, 1 }, { 195, 182 }, { 129, 54 }, { 149, 31 }, { 141, 173 }, { 61, 80 }, { 5, 153 }, { 88, 60 }, { 143, 187 }, { 86, 97 }, { 22, 163 }, { 143, 108 }, { 50, 45 }, { 9, 87 }, { 6, 103 }, { 75, 125 }, { 166, 111 }, { 9, 159 }, { 27, 57 }, { 101, 175 }, { 37, 125 }, { 22, 113 }, { 68, 71 }, { 48, 113 }, { 122, 168 }, { 136, 135 }, { 136, 18 }, { 89, 31 }, { 164, 193 }, { 64, 53 }, { 124, 117 }, { 16, 22 }, { 154, 140 }, { 179, 122 }, { 107, 108 }, { 70, 166 }, { 189, 118 }, { 64, 54 }, { 197, 62 }, { 139, 127 }, { 55, 169 }, { 106, 20 }, { 135, 172 }, { 24, 192 }, { 97, 66 }, { 54, 199 }, { 78, 186 }, { 52, 198 }, { 20, 45 }, { 45, 117 }, { 158, 177 }, { 162, 21 }, { 158, 35 }, { 165, 51 }, { 17, 41 }, { 167, 118 }, { 80, 116 }, { 101, 62 }, { 2, 23 }, { 17, 81 }, { 41, 192 }, { 10, 93 }, { 42, 95 }, { 129, 179 }, { 156, 13 }, { 15, 172 }, { 174, 164 }, { 21, 117 }, { 192, 58 }, { 187, 84 }, { 117, 103 }, { 183, 42 }, { 62, 192 }, { 19, 70 }, { 32, 173 }, { 77, 114 }, { 166, 77 }, { 5, 41 }, { 189, 2 }, { 39, 74 }, { 183, 0 }, { 144, 182 }, { 153, 30 }, { 198, 101 }, { 11, 137 }, { 132, 49 }, { 191, 15 }, { 97, 100 }, { 184, 48 }, { 164, 54 }, { 24, 145 }, { 174, 70 }, { 174, 83 }, { 36, 145 }, { 3, 128 }, { 104, 17 }, { 143, 29 }, { 147, 149 }, { 133, 75 }, { 153, 110 }, { 48, 192 }, { 112, 1 }, { 88, 91 }, { 14, 104 }, { 140, 28 }, { 159, 180 }, { 133, 113 }, { 136, 21 }, { 197, 125 }, { 27, 105 }, { 195, 18 }, { 87, 179 }, { 60, 168 }, { 107, 35 }, { 184, 62 }, { 143, 36 }, { 54, 173 }, { 198, 18 }, { 44, 101 }, { 12, 50 }, { 7, 54 }, { 137, 12 }, { 99, 104 }, { 191, 27 }, { 95, 78 }, { 93, 133 }, { 153, 77 }, { 8, 21 }, { 66, 187 }, { 115, 110 }, { 85, 123 }, { 75, 146 }, { 145, 197 }, { 18, 185 }, { 192, 153 }, { 30, 189 }, { 27, 124 }, { 188, 122 }, { 85, 19 }, { 190, 67 }, { 97, 36 }, { 183, 111 }, { 184, 133 }, { 63, 43 }, { 139, 100 }, { 192, 193 }, { 193, 21 }, { 171, 78 }, { 21, 194 }, { 167, 105 }, { 96, 108 }, { 63, 118 }, { 86, 48 }, { 191, 171 }, { 64, 189 }, { 3, 98 }, { 149, 162 }, { 108, 165 }, { 53, 37 }, { 128, 96 }, { 156, 69 }, { 140, 88 }, { 48, 137 }, { 145, 2 }, { 199, 17 }, { 17, 150 }, { 31, 130 }, { 172, 73 }, { 51, 184 }, { 67, 122 }, { 183, 107 }, { 104, 140 }, { 113, 156 }, { 192, 50 }, { 36, 81 }, { 23, 66 }, { 122, 156 }, { 62, 48 }, { 29, 2 }, { 195, 179 }, { 74, 47 }, { 45, 44 }, { 42, 158 }, { 49, 58 }, { 86, 62 }, { 134, 171 }, { 127, 9 }, { 67, 5 }, { 104, 54 }, { 88, 43 }, { 104, 198 }, { 111, 59 }, { 88, 147 }, { 152, 108 }, { 157, 4 }, { 115, 12 }, { 170, 166 }, { 54, 119 }, { 85, 61 }, { 179, 189 }, { 196, 160 }, { 36, 18 }, { 4, 138 }, { 150, 33 }, { 62, 92 }, { 7, 146 }, { 158, 135 }, { 86, 56 }, { 154, 24 }, { 118, 32 }, { 51, 101 }, { 62, 91 }, { 91, 52 }, { 16, 188 }, { 35, 34 }, { 132, 77 }, { 175, 72 }, { 160, 156 }, { 185, 170 }, { 54, 195 }, { 47, 66 }, { 26, 5 }, { 154, 177 }, { 38, 84 }, { 100, 189 }, { 156, 64 }, { 125, 190 }, { 40, 138 }, { 57, 131 }, { 40, 134 }, { 105, 90 }, { 128, 31 }, { 197, 172 }, { 38, 92 }, { 19, 134 }, { 95, 88 }, { 191, 4 }, { 140, 184 }, { 24, 168 }, { 53, 93 }, { 106, 168 }, { 140, 102 }, { 5, 78 }, { 168, 193 }, { 129, 42 }, { 11, 144 }, { 165, 175 }, { 9, 23 }, { 91, 151 }, { 182, 34 }, { 173, 148 }, { 75, 174 }, { 9, 133 }, { 179, 47 }, { 37, 197 }, { 160, 100 }, { 139, 46 }, { 167, 39 }, { 113, 27 }, { 133, 24 }, { 112, 27 }, { 14, 8 }, { 111, 36 }, { 138, 151 }, { 126, 9 }, { 44, 115 }, { 125, 52 }, { 142, 50 }, { 35, 177 }, { 139, 44 }, { 120, 181 }, { 112, 12 }, { 59, 158 }, { 0, 157 }, { 177, 184 }, { 199, 176 }, { 187, 169 }, { 184, 162 }, { 158, 55 }, { 95, 96 }, { 187, 146 }, { 79, 74 }, { 106, 87 }, { 131, 157 }, { 21, 150 }, { 43, 93 }, { 20, 69 }, { 13, 31 }, { 109, 133 }, { 77, 180 }, { 70, 130 }, { 171, 73 }, { 137, 121 }, { 24, 187 }, { 146, 42 }, { 116, 105 }, { 192, 164 }, { 54, 194 }, { 190, 7 }, { 57, 21 }, { 60, 21 }, { 176, 111 }, { 135, 66 }, { 54, 62 }, { 33, 19 }, { 76, 188 }, { 30, 11 }, { 88, 176 }, { 197, 127 }, { 110, 31 }, { 184, 115 }, { 62, 136 }, { 176, 134 }, { 17, 20 }, { 63, 33 }, { 177, 164 }, { 51, 53 }, { 53, 157 }, { 92, 9 }, { 157, 78 }, { 43, 51 }, { 56, 138 }, { 150, 6 }, { 16, 185 }, { 12, 97 }, { 74, 129 }, { 152, 65 }, { 159, 188 }, { 20, 126 }, { 2, 126 }, { 55, 103 }, { 14, 18 }, { 142, 155 }, { 56, 62 }, { 120, 123 }, { 69, 40 }, { 6, 9 }, { 154, 39 }, { 160, 15 }, { 1, 146 }, { 182, 157 }, { 100, 133 }, { 71, 186 }, { 10, 179 }, { 130, 171 }, { 91, 141 }, { 199, 130 }, { 2, 63 }, { 144, 118 }, { 198, 20 }, { 185, 176 }, { 180, 96 }, { 129, 78 }, { 5, 91 }, { 4, 184 }, { 112, 70 }, { 127, 7 }, { 148, 150 }, { 16, 21 }, { 83, 13 }, { 151, 16 }, { 46, 31 }, { 52, 57 }, { 73, 10 }, { 78, 105 }, { 131, 143 }, { 173, 18 }, { 21, 38 }, { 38, 3 }, { 164, 86 }, { 149, 177 }, { 199, 84 }, { 4, 173 }, { 109, 80 }, { 96, 127 }, { 160, 72 }, { 54, 179 }, { 44, 47 }, { 33, 126 }, { 53, 184 }, { 155, 36 }, { 129, 21 }, { 43, 118 }, { 16, 54 }, { 67, 43 }, { 144, 62 }, { 108, 103 }, { 178, 174 }, { 184, 81 }, { 139, 21 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); MatchingAlgorithm matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 99); } @Test public void testGraph5() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 55, 4 }, { 9, 118 }, { 70, 115 }, { 179, 146 }, { 122, 136 }, { 192, 91 }, { 100, 158 }, { 5, 22 }, { 72, 118 }, { 88, 10 }, { 192, 10 }, { 73, 133 }, { 144, 187 }, { 189, 153 }, { 69, 154 }, { 89, 2 }, { 63, 144 }, { 187, 126 }, { 38, 115 }, { 19, 10 }, { 128, 77 }, { 49, 45 }, { 176, 50 }, { 185, 60 }, { 34, 22 }, { 105, 82 }, { 179, 8 }, { 107, 120 }, { 102, 103 }, { 157, 80 }, { 49, 0 }, { 174, 130 }, { 158, 33 }, { 195, 98 }, { 109, 93 }, { 64, 31 }, { 39, 132 }, { 26, 88 }, { 77, 78 }, { 8, 164 }, { 143, 141 }, { 162, 110 }, { 128, 188 }, { 194, 148 }, { 183, 39 }, { 0, 19 }, { 185, 128 }, { 129, 144 }, { 73, 51 }, { 151, 5 }, { 121, 175 }, { 75, 182 }, { 130, 178 }, { 79, 159 }, { 32, 167 }, { 128, 92 }, { 193, 103 }, { 1, 84 }, { 68, 177 }, { 115, 179 }, { 134, 183 }, { 192, 99 }, { 191, 79 }, { 39, 142 }, { 99, 42 }, { 81, 155 }, { 93, 133 }, { 106, 194 }, { 62, 65 }, { 107, 21 }, { 43, 137 }, { 148, 142 }, { 132, 143 }, { 160, 119 }, { 17, 44 }, { 153, 90 }, { 7, 51 }, { 129, 141 }, { 40, 88 }, { 26, 193 }, { 169, 74 }, { 62, 128 }, { 189, 89 }, { 80, 120 }, { 54, 86 }, { 139, 104 }, { 43, 23 }, { 169, 94 }, { 37, 43 }, { 107, 35 }, { 28, 24 }, { 24, 20 }, { 15, 166 }, { 145, 110 }, { 1, 191 }, { 73, 132 }, { 6, 30 }, { 153, 144 }, { 76, 34 }, { 137, 84 }, { 175, 53 }, { 195, 20 }, { 82, 18 }, { 16, 110 }, { 40, 92 }, { 90, 41 }, { 132, 94 }, { 34, 70 }, { 186, 0 }, { 60, 41 }, { 63, 20 }, { 16, 7 }, { 48, 193 }, { 138, 177 }, { 164, 122 }, { 79, 11 }, { 3, 135 }, { 43, 52 }, { 160, 43 }, { 145, 15 }, { 93, 180 }, { 42, 148 }, { 83, 85 }, { 194, 9 }, { 55, 185 }, { 100, 13 }, { 16, 14 }, { 101, 18 }, { 92, 84 }, { 174, 52 }, { 82, 137 }, { 139, 146 }, { 35, 26 }, { 160, 48 }, { 107, 102 }, { 178, 172 }, { 165, 145 }, { 71, 128 }, { 122, 60 }, { 36, 196 }, { 185, 91 }, { 187, 170 }, { 133, 27 }, { 52, 119 }, { 145, 105 }, { 53, 62 }, { 130, 38 }, { 79, 58 }, { 142, 20 }, { 89, 143 }, { 194, 31 }, { 70, 86 }, { 145, 66 }, { 9, 51 }, { 65, 109 }, { 41, 77 }, { 48, 169 }, { 159, 162 }, { 156, 16 }, { 4, 84 }, { 183, 52 }, { 8, 44 }, { 137, 146 }, { 181, 185 }, { 55, 25 }, { 138, 61 }, { 106, 197 }, { 99, 157 }, { 35, 99 }, { 142, 43 }, { 186, 73 }, { 144, 161 }, { 77, 52 }, { 182, 155 }, { 85, 132 }, { 184, 146 }, { 53, 96 }, { 103, 73 }, { 132, 17 }, { 7, 54 }, { 178, 118 }, { 168, 6 }, { 94, 44 }, { 174, 37 }, { 14, 184 }, { 97, 74 }, { 40, 114 }, { 175, 35 }, { 69, 167 }, { 28, 49 }, { 22, 139 }, { 156, 42 }, { 46, 41 }, { 63, 135 }, { 55, 58 }, { 187, 122 }, { 72, 77 }, { 120, 191 }, { 156, 144 }, { 28, 43 }, { 14, 52 }, { 95, 69 }, { 0, 174 }, { 160, 111 }, { 91, 119 }, { 62, 192 }, { 1, 10 }, { 36, 130 }, { 46, 109 }, { 164, 52 }, { 101, 142 }, { 180, 67 }, { 119, 147 }, { 189, 130 }, { 134, 102 }, { 168, 106 }, { 191, 99 }, { 187, 151 }, { 86, 96 }, { 177, 122 }, { 171, 32 }, { 184, 180 }, { 35, 123 }, { 36, 22 }, { 50, 14 }, { 33, 50 }, { 43, 42 }, { 109, 53 }, { 138, 188 }, { 108, 27 }, { 104, 160 }, { 101, 31 }, { 190, 131 }, { 50, 62 }, { 190, 196 }, { 45, 15 }, { 154, 125 }, { 63, 116 }, { 72, 41 }, { 140, 80 }, { 138, 102 }, { 21, 115 }, { 116, 75 }, { 181, 147 }, { 192, 152 }, { 168, 44 }, { 161, 101 }, { 102, 142 }, { 63, 173 }, { 147, 142 }, { 63, 10 }, { 163, 139 }, { 34, 67 }, { 123, 184 }, { 164, 111 }, { 83, 113 }, { 60, 76 }, { 47, 3 }, { 100, 25 }, { 53, 165 }, { 46, 100 }, { 56, 85 }, { 14, 153 }, { 27, 128 }, { 127, 63 }, { 74, 98 }, { 45, 72 }, { 98, 126 }, { 114, 166 }, { 193, 186 }, { 60, 197 }, { 24, 83 }, { 179, 176 }, { 29, 128 }, { 136, 35 }, { 28, 141 }, { 81, 90 }, { 38, 7 }, { 170, 29 }, { 138, 127 }, { 133, 18 }, { 87, 164 }, { 50, 45 }, { 164, 1 }, { 82, 77 }, { 38, 113 }, { 76, 158 }, { 97, 194 }, { 10, 118 }, { 42, 157 }, { 142, 190 }, { 1, 144 }, { 94, 16 }, { 44, 78 }, { 8, 168 }, { 21, 37 }, { 22, 88 }, { 182, 105 }, { 50, 75 }, { 75, 9 }, { 149, 22 }, { 174, 30 }, { 184, 86 }, { 89, 156 }, { 102, 82 }, { 35, 78 }, { 1, 62 }, { 45, 178 }, { 105, 168 }, { 62, 14 }, { 59, 67 }, { 91, 70 }, { 174, 190 }, { 10, 124 }, { 17, 33 }, { 181, 146 }, { 72, 83 }, { 101, 54 }, { 141, 146 }, { 124, 75 }, { 130, 96 }, { 20, 128 }, { 197, 166 }, { 126, 127 }, { 109, 48 }, { 122, 76 }, { 81, 20 }, { 29, 87 }, { 64, 136 }, { 113, 105 }, { 67, 56 }, { 86, 7 }, { 158, 81 }, { 102, 166 }, { 93, 37 }, { 46, 131 }, { 59, 107 }, { 1, 125 }, { 6, 146 }, { 63, 90 }, { 87, 82 }, { 61, 103 }, { 81, 164 }, { 128, 195 }, { 37, 60 }, { 139, 86 }, { 128, 173 }, { 60, 36 }, { 38, 72 }, { 61, 116 }, { 116, 1 }, { 188, 137 }, { 149, 179 }, { 0, 183 }, { 164, 64 }, { 130, 155 }, { 131, 6 }, { 155, 7 }, { 2, 177 }, { 27, 169 }, { 95, 182 }, { 161, 88 }, { 117, 136 }, { 49, 90 }, { 82, 50 }, { 121, 153 }, { 130, 156 }, { 158, 133 }, { 199, 160 }, { 9, 20 }, { 26, 7 }, { 113, 99 }, { 38, 136 }, { 44, 81 }, { 21, 46 }, { 190, 180 }, { 74, 181 }, { 84, 115 }, { 198, 97 }, { 115, 103 }, { 14, 20 }, { 90, 183 }, { 113, 2 }, { 182, 142 }, { 191, 73 }, { 139, 3 }, { 148, 138 }, { 160, 29 }, { 68, 7 }, { 43, 73 }, { 41, 0 }, { 36, 178 }, { 136, 134 }, { 78, 139 }, { 146, 147 }, { 175, 52 }, { 22, 100 }, { 113, 78 }, { 116, 133 }, { 73, 93 }, { 52, 199 }, { 5, 97 }, { 20, 80 }, { 171, 153 }, { 152, 143 }, { 100, 165 }, { 36, 122 }, { 47, 29 }, { 165, 182 }, { 98, 4 }, { 62, 178 }, { 99, 147 }, { 191, 153 }, { 188, 43 }, { 143, 146 }, { 34, 45 }, { 140, 169 }, { 21, 189 }, { 127, 121 }, { 102, 84 }, { 66, 160 }, { 105, 176 }, { 12, 3 }, { 197, 64 }, { 12, 129 }, { 158, 178 }, { 163, 141 }, { 54, 106 }, { 103, 157 }, { 148, 5 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); MatchingAlgorithm matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 98); } @Test public void testGraph6() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 54, 119 }, { 97, 64 }, { 94, 171 }, { 128, 13 }, { 123, 174 }, { 48, 159 }, { 117, 36 }, { 175, 155 }, { 89, 172 }, { 22, 155 }, { 123, 61 }, { 64, 18 }, { 132, 44 }, { 154, 61 }, { 36, 0 }, { 150, 61 }, { 197, 76 }, { 83, 186 }, { 180, 91 }, { 4, 121 }, { 92, 123 }, { 195, 109 }, { 58, 76 }, { 172, 56 }, { 62, 104 }, { 169, 63 }, { 49, 174 }, { 131, 177 }, { 122, 139 }, { 193, 140 }, { 75, 178 }, { 193, 97 }, { 87, 3 }, { 101, 135 }, { 46, 21 }, { 14, 79 }, { 166, 60 }, { 67, 151 }, { 151, 190 }, { 126, 110 }, { 148, 103 }, { 51, 118 }, { 153, 36 }, { 62, 87 }, { 157, 140 }, { 176, 63 }, { 165, 155 }, { 117, 96 }, { 2, 56 }, { 70, 98 }, { 89, 86 }, { 134, 32 }, { 5, 96 }, { 123, 167 }, { 147, 142 }, { 18, 120 }, { 162, 4 }, { 31, 94 }, { 189, 145 }, { 8, 27 }, { 198, 165 }, { 173, 109 }, { 152, 131 }, { 95, 118 }, { 177, 78 }, { 58, 49 }, { 130, 72 }, { 189, 85 }, { 195, 83 }, { 50, 119 }, { 174, 74 }, { 110, 107 }, { 48, 172 }, { 184, 128 }, { 79, 64 }, { 177, 56 }, { 192, 46 }, { 145, 46 }, { 95, 191 }, { 45, 103 }, { 117, 158 }, { 160, 140 }, { 17, 88 }, { 55, 175 }, { 192, 166 }, { 116, 10 }, { 171, 96 }, { 11, 155 }, { 32, 126 }, { 85, 27 }, { 114, 34 }, { 123, 86 }, { 24, 65 }, { 41, 150 }, { 184, 129 }, { 92, 104 }, { 110, 117 }, { 145, 184 }, { 44, 31 }, { 184, 94 }, { 5, 39 }, { 115, 7 }, { 102, 174 }, { 167, 177 }, { 110, 175 }, { 100, 90 }, { 77, 128 }, { 113, 96 }, { 144, 46 }, { 59, 112 }, { 104, 112 }, { 97, 95 }, { 117, 3 }, { 61, 120 }, { 38, 164 }, { 130, 15 }, { 40, 12 }, { 133, 20 }, { 49, 109 }, { 9, 51 }, { 144, 75 }, { 131, 89 }, { 106, 30 }, { 54, 25 }, { 67, 140 }, { 76, 196 }, { 80, 11 }, { 139, 142 }, { 29, 164 }, { 135, 53 }, { 72, 131 }, { 105, 77 }, { 144, 179 }, { 36, 191 }, { 43, 127 }, { 143, 152 }, { 51, 82 }, { 4, 197 }, { 165, 168 }, { 77, 117 }, { 22, 110 }, { 142, 151 }, { 161, 67 }, { 186, 65 }, { 17, 66 }, { 101, 122 }, { 112, 40 }, { 43, 112 }, { 10, 88 }, { 108, 171 }, { 129, 30 }, { 117, 179 }, { 13, 97 }, { 84, 44 }, { 168, 65 }, { 128, 175 }, { 27, 135 }, { 114, 13 }, { 96, 20 }, { 60, 140 }, { 198, 42 }, { 116, 60 }, { 162, 191 }, { 100, 35 }, { 144, 87 }, { 66, 148 }, { 174, 177 }, { 183, 167 }, { 185, 138 }, { 183, 194 }, { 95, 166 }, { 92, 20 }, { 88, 93 }, { 110, 34 }, { 65, 145 }, { 195, 51 }, { 94, 54 }, { 191, 150 }, { 4, 115 }, { 160, 99 }, { 25, 191 }, { 191, 2 }, { 105, 169 }, { 68, 2 }, { 23, 121 }, { 15, 58 }, { 149, 121 }, { 128, 83 }, { 21, 75 }, { 136, 127 }, { 108, 193 }, { 79, 67 }, { 146, 108 }, { 8, 152 }, { 3, 140 }, { 133, 188 }, { 142, 175 }, { 40, 5 }, { 136, 102 }, { 82, 55 }, { 124, 162 }, { 150, 55 }, { 127, 101 }, { 92, 195 }, { 56, 97 }, { 131, 60 }, { 84, 78 }, { 90, 147 }, { 34, 11 }, { 1, 154 }, { 179, 17 }, { 76, 112 }, { 117, 64 }, { 164, 174 }, { 2, 72 }, { 124, 151 }, { 41, 57 }, { 109, 13 }, { 65, 166 }, { 134, 110 }, { 158, 28 }, { 100, 70 }, { 25, 41 }, { 170, 21 }, { 0, 112 }, { 117, 73 }, { 175, 112 }, { 47, 182 }, { 169, 44 }, { 86, 82 }, { 183, 110 }, { 112, 197 }, { 85, 14 }, { 58, 100 }, { 16, 17 }, { 125, 132 }, { 75, 18 }, { 95, 80 }, { 77, 36 }, { 99, 174 }, { 60, 54 }, { 89, 7 }, { 183, 139 }, { 114, 106 }, { 162, 86 }, { 190, 6 }, { 81, 165 }, { 63, 106 }, { 125, 103 }, { 194, 59 }, { 100, 17 }, { 156, 171 }, { 84, 48 }, { 34, 86 }, { 91, 56 }, { 45, 13 }, { 102, 51 }, { 48, 149 }, { 188, 22 }, { 95, 82 }, { 31, 181 }, { 54, 116 }, { 126, 55 }, { 193, 100 }, { 145, 120 }, { 11, 114 }, { 34, 178 }, { 133, 47 }, { 157, 17 }, { 71, 67 }, { 146, 129 }, { 147, 193 }, { 154, 151 }, { 154, 16 }, { 34, 198 }, { 174, 178 }, { 73, 168 }, { 34, 62 }, { 33, 108 }, { 93, 21 }, { 139, 35 }, { 119, 97 }, { 71, 171 }, { 111, 33 }, { 13, 43 }, { 23, 74 }, { 99, 133 }, { 14, 24 }, { 3, 33 }, { 0, 122 }, { 151, 174 }, { 147, 123 }, { 180, 187 }, { 72, 28 }, { 49, 68 }, { 27, 158 }, { 98, 128 }, { 185, 190 }, { 149, 183 }, { 174, 10 }, { 64, 121 }, { 112, 111 }, { 53, 66 }, { 108, 149 }, { 44, 145 }, { 155, 58 }, { 131, 104 }, { 24, 83 }, { 124, 182 }, { 177, 26 }, { 155, 15 }, { 23, 176 }, { 154, 77 }, { 91, 99 }, { 60, 176 }, { 23, 91 }, { 154, 160 }, { 111, 103 }, { 13, 140 }, { 42, 77 }, { 105, 35 }, { 9, 198 }, { 105, 24 }, { 146, 135 }, { 117, 67 }, { 145, 140 }, { 124, 47 }, { 81, 37 }, { 154, 150 }, { 119, 48 }, { 191, 123 }, { 79, 165 }, { 118, 180 }, { 86, 39 }, { 92, 115 }, { 37, 195 }, { 52, 193 }, { 6, 98 }, { 77, 91 }, { 131, 151 }, { 76, 54 }, { 147, 143 }, { 95, 198 }, { 89, 134 }, { 104, 90 }, { 26, 197 }, { 42, 164 }, { 35, 113 }, { 187, 172 }, { 173, 168 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); MatchingAlgorithm matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 96); } @Test public void testGraph7() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 101, 127 }, { 65, 51 }, { 15, 137 }, { 166, 180 }, { 123, 77 }, { 55, 145 }, { 174, 183 }, { 1, 136 }, { 137, 59 }, { 60, 72 }, { 10, 109 }, { 80, 15 }, { 66, 55 }, { 165, 195 }, { 166, 37 }, { 166, 44 }, { 20, 18 }, { 56, 136 }, { 172, 189 }, { 181, 1 }, { 88, 109 }, { 191, 25 }, { 114, 25 }, { 11, 37 }, { 153, 141 }, { 156, 112 }, { 54, 71 }, { 129, 94 }, { 49, 184 }, { 68, 129 }, { 116, 142 }, { 64, 120 }, { 96, 157 }, { 78, 35 }, { 60, 61 }, { 148, 28 }, { 191, 167 }, { 123, 175 }, { 54, 90 }, { 187, 50 }, { 158, 34 }, { 85, 119 }, { 16, 24 }, { 172, 38 }, { 12, 180 }, { 97, 79 }, { 35, 46 }, { 194, 30 }, { 45, 53 }, { 63, 183 }, { 107, 119 }, { 105, 121 }, { 123, 135 }, { 30, 167 }, { 182, 36 }, { 109, 161 }, { 103, 6 }, { 178, 57 }, { 114, 163 }, { 183, 162 }, { 70, 24 }, { 72, 99 }, { 88, 155 }, { 105, 40 }, { 54, 157 }, { 126, 129 }, { 109, 197 }, { 39, 172 }, { 160, 7 }, { 141, 94 }, { 109, 20 }, { 69, 159 }, { 93, 43 }, { 25, 36 }, { 144, 189 }, { 61, 141 }, { 163, 22 }, { 101, 102 }, { 87, 176 }, { 16, 115 }, { 175, 169 }, { 72, 141 }, { 190, 148 }, { 50, 29 }, { 180, 128 }, { 41, 166 }, { 184, 73 }, { 158, 23 }, { 163, 122 }, { 96, 10 }, { 122, 173 }, { 144, 20 }, { 11, 199 }, { 93, 136 }, { 147, 180 }, { 189, 197 }, { 177, 54 }, { 178, 40 }, { 190, 181 }, { 14, 36 }, { 31, 80 }, { 157, 189 }, { 152, 49 }, { 134, 125 }, { 95, 63 }, { 85, 174 }, { 10, 141 }, { 48, 22 }, { 86, 168 }, { 60, 168 }, { 142, 45 }, { 155, 38 }, { 196, 9 }, { 100, 84 }, { 135, 98 }, { 176, 49 }, { 153, 154 }, { 164, 175 }, { 51, 133 }, { 96, 73 }, { 7, 152 }, { 66, 172 }, { 186, 177 }, { 112, 62 }, { 172, 141 }, { 145, 91 }, { 69, 180 }, { 102, 159 }, { 38, 57 }, { 138, 30 }, { 169, 133 }, { 150, 76 }, { 27, 102 }, { 196, 199 }, { 24, 56 }, { 48, 144 }, { 85, 1 }, { 12, 37 }, { 179, 106 }, { 15, 147 }, { 7, 167 }, { 61, 11 }, { 185, 181 }, { 179, 178 }, { 38, 128 }, { 41, 27 }, { 27, 97 }, { 4, 135 }, { 111, 15 }, { 71, 117 }, { 43, 13 }, { 181, 68 }, { 168, 121 }, { 182, 12 }, { 53, 181 }, { 148, 109 }, { 100, 118 }, { 176, 26 }, { 86, 65 }, { 102, 167 }, { 18, 142 }, { 148, 46 }, { 101, 9 }, { 138, 158 }, { 32, 161 }, { 172, 20 }, { 139, 31 }, { 145, 32 }, { 59, 108 }, { 131, 52 }, { 6, 184 }, { 123, 157 }, { 100, 37 }, { 56, 36 }, { 116, 50 }, { 172, 118 }, { 176, 28 }, { 107, 183 }, { 174, 30 }, { 177, 190 }, { 35, 33 }, { 175, 34 }, { 142, 46 }, { 138, 194 }, { 71, 160 }, { 96, 65 }, { 66, 32 }, { 175, 176 }, { 36, 88 }, { 4, 54 }, { 9, 120 }, { 53, 11 }, { 183, 31 }, { 140, 178 }, { 194, 193 }, { 0, 68 }, { 29, 7 }, { 89, 74 }, { 178, 125 }, { 176, 58 }, { 46, 164 }, { 185, 2 }, { 84, 160 }, { 182, 195 }, { 76, 171 }, { 41, 173 }, { 24, 168 }, { 117, 120 }, { 171, 156 }, { 106, 154 }, { 174, 63 }, { 43, 173 }, { 72, 41 }, { 37, 136 }, { 146, 95 }, { 199, 117 }, { 116, 100 }, { 1, 187 }, { 127, 52 }, { 106, 42 }, { 112, 116 }, { 114, 51 }, { 126, 117 }, { 8, 122 }, { 96, 160 }, { 1, 156 }, { 78, 19 }, { 14, 178 }, { 122, 170 }, { 32, 176 }, { 114, 48 }, { 115, 143 }, { 110, 60 }, { 28, 6 }, { 0, 25 }, { 88, 120 }, { 77, 142 }, { 19, 38 }, { 182, 108 }, { 122, 77 }, { 99, 126 }, { 157, 170 }, { 117, 138 }, { 45, 90 }, { 54, 141 }, { 13, 79 }, { 32, 110 }, { 112, 92 }, { 198, 184 }, { 79, 145 }, { 107, 67 }, { 133, 10 }, { 125, 108 }, { 9, 26 }, { 197, 193 }, { 183, 125 }, { 183, 193 }, { 4, 90 }, { 184, 80 }, { 171, 55 }, { 110, 74 }, { 9, 55 }, { 10, 132 }, { 77, 15 }, { 67, 197 }, { 195, 116 }, { 190, 20 }, { 191, 153 }, { 95, 143 }, { 58, 189 }, { 183, 120 }, { 115, 56 }, { 198, 63 }, { 132, 62 }, { 112, 74 }, { 84, 190 }, { 3, 116 }, { 13, 20 }, { 47, 137 }, { 19, 33 }, { 130, 137 }, { 16, 58 }, { 9, 130 }, { 17, 106 }, { 116, 30 }, { 177, 94 }, { 56, 44 }, { 55, 90 }, { 27, 56 }, { 156, 66 }, { 60, 27 }, { 91, 133 }, { 101, 3 }, { 173, 199 }, { 56, 167 }, { 13, 165 }, { 195, 55 }, { 182, 32 }, { 129, 136 }, { 78, 170 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); MatchingAlgorithm matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 91); } @Test public void testGraph8() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 165, 24 }, { 192, 81 }, { 78, 195 }, { 88, 12 }, { 172, 77 }, { 58, 166 }, { 197, 94 }, { 187, 43 }, { 191, 11 }, { 130, 44 }, { 150, 116 }, { 131, 41 }, { 83, 170 }, { 25, 129 }, { 168, 159 }, { 160, 65 }, { 15, 41 }, { 23, 87 }, { 139, 156 }, { 188, 49 }, { 198, 67 }, { 170, 79 }, { 97, 195 }, { 46, 10 }, { 82, 84 }, { 47, 175 }, { 8, 141 }, { 68, 180 }, { 34, 147 }, { 63, 54 }, { 45, 182 }, { 167, 29 }, { 188, 112 }, { 43, 124 }, { 26, 50 }, { 130, 48 }, { 195, 124 }, { 136, 141 }, { 0, 57 }, { 99, 40 }, { 17, 101 }, { 84, 188 }, { 125, 92 }, { 152, 4 }, { 29, 9 }, { 166, 10 }, { 111, 47 }, { 59, 162 }, { 111, 119 }, { 193, 46 }, { 191, 23 }, { 6, 62 }, { 46, 3 }, { 193, 115 }, { 175, 195 }, { 159, 145 }, { 184, 17 }, { 68, 23 }, { 83, 13 }, { 173, 188 }, { 2, 55 }, { 49, 56 }, { 59, 96 }, { 8, 116 }, { 147, 53 }, { 76, 183 }, { 23, 33 }, { 28, 13 }, { 149, 53 }, { 64, 70 }, { 193, 127 }, { 78, 97 }, { 164, 117 }, { 122, 139 }, { 54, 188 }, { 13, 176 }, { 76, 73 }, { 21, 69 }, { 29, 83 }, { 114, 79 }, { 134, 27 }, { 104, 3 }, { 141, 66 }, { 136, 27 }, { 91, 29 }, { 9, 106 }, { 123, 191 }, { 124, 52 }, { 63, 12 }, { 133, 141 }, { 49, 101 }, { 53, 189 }, { 95, 28 }, { 140, 100 }, { 152, 77 }, { 188, 135 }, { 123, 160 }, { 89, 79 }, { 182, 151 }, { 189, 83 }, { 148, 168 }, { 104, 170 }, { 24, 96 }, { 116, 47 }, { 94, 130 }, { 38, 9 }, { 9, 83 }, { 89, 69 }, { 159, 107 }, { 116, 122 }, { 8, 75 }, { 116, 57 }, { 5, 53 }, { 84, 55 }, { 70, 60 }, { 168, 145 }, { 156, 41 }, { 154, 75 }, { 77, 191 }, { 11, 77 }, { 117, 108 }, { 115, 42 }, { 114, 164 }, { 140, 6 }, { 112, 3 }, { 144, 91 }, { 42, 71 }, { 116, 64 }, { 26, 120 }, { 12, 71 }, { 0, 21 }, { 157, 17 }, { 95, 92 }, { 65, 81 }, { 133, 158 }, { 165, 137 }, { 177, 157 }, { 175, 37 }, { 134, 138 }, { 107, 106 }, { 198, 143 }, { 181, 42 }, { 42, 102 }, { 40, 32 }, { 37, 180 }, { 109, 194 }, { 137, 150 }, { 112, 152 }, { 193, 158 }, { 180, 79 }, { 189, 146 }, { 118, 66 }, { 84, 41 }, { 134, 69 }, { 196, 147 }, { 106, 39 }, { 29, 172 }, { 22, 141 }, { 123, 196 }, { 38, 189 }, { 98, 38 }, { 52, 157 }, { 132, 3 }, { 36, 48 }, { 70, 26 }, { 196, 10 }, { 33, 63 }, { 17, 41 }, { 171, 21 }, { 173, 0 }, { 46, 185 }, { 81, 189 }, { 199, 85 }, { 90, 93 }, { 72, 51 }, { 197, 193 }, { 171, 4 }, { 110, 7 }, { 150, 167 }, { 122, 133 }, { 159, 69 }, { 115, 104 }, { 36, 171 }, { 123, 68 }, { 119, 48 }, { 176, 113 }, { 24, 74 }, { 46, 158 }, { 92, 113 }, { 178, 164 }, { 180, 199 }, { 138, 122 }, { 104, 178 }, { 18, 40 }, { 66, 160 }, { 153, 138 }, { 0, 94 }, { 98, 51 }, { 137, 53 }, { 126, 147 }, { 136, 185 }, { 47, 31 }, { 118, 199 }, { 192, 52 }, { 18, 91 }, { 0, 167 }, { 84, 99 }, { 133, 99 }, { 5, 8 }, { 156, 175 }, { 55, 141 }, { 115, 191 }, { 120, 107 }, { 109, 113 }, { 170, 157 }, { 173, 40 }, { 119, 39 }, { 84, 133 }, { 123, 162 }, { 108, 24 }, { 111, 193 }, { 180, 149 }, { 26, 43 }, { 186, 5 }, { 42, 13 }, { 80, 192 }, { 184, 83 }, { 173, 156 }, { 89, 139 }, { 51, 173 }, { 89, 47 }, { 16, 33 }, { 195, 85 }, { 150, 70 }, { 67, 76 }, { 38, 91 }, { 108, 189 }, { 146, 88 }, { 61, 132 }, { 23, 90 }, { 142, 169 }, { 9, 55 }, { 72, 175 }, { 96, 74 }, { 99, 17 }, { 169, 4 }, { 17, 44 }, { 64, 168 }, { 103, 197 }, { 176, 56 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); MatchingAlgorithm matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 86); } @Test public void testGraph9() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 9, 158 }, { 114, 119 }, { 136, 45 }, { 119, 69 }, { 95, 67 }, { 93, 158 }, { 136, 137 }, { 62, 67 }, { 155, 70 }, { 136, 190 }, { 165, 104 }, { 136, 55 }, { 180, 125 }, { 18, 49 }, { 105, 157 }, { 187, 120 }, { 120, 53 }, { 183, 154 }, { 91, 187 }, { 166, 111 }, { 26, 177 }, { 186, 142 }, { 47, 160 }, { 124, 197 }, { 30, 91 }, { 196, 116 }, { 74, 76 }, { 142, 7 }, { 43, 23 }, { 121, 135 }, { 107, 73 }, { 180, 43 }, { 23, 156 }, { 34, 72 }, { 59, 10 }, { 188, 138 }, { 38, 27 }, { 165, 78 }, { 181, 22 }, { 193, 22 }, { 51, 192 }, { 142, 111 }, { 150, 155 }, { 165, 123 }, { 13, 94 }, { 178, 110 }, { 189, 109 }, { 158, 159 }, { 51, 149 }, { 198, 149 }, { 116, 142 }, { 124, 35 }, { 112, 197 }, { 83, 154 }, { 61, 5 }, { 41, 49 }, { 15, 194 }, { 37, 75 }, { 29, 65 }, { 7, 38 }, { 55, 79 }, { 151, 195 }, { 83, 5 }, { 157, 143 }, { 39, 77 }, { 40, 165 }, { 49, 28 }, { 10, 189 }, { 43, 195 }, { 32, 45 }, { 170, 139 }, { 128, 35 }, { 37, 116 }, { 131, 92 }, { 66, 59 }, { 42, 52 }, { 84, 110 }, { 188, 122 }, { 81, 13 }, { 53, 151 }, { 16, 191 }, { 35, 115 }, { 79, 94 }, { 130, 69 }, { 187, 88 }, { 7, 189 }, { 145, 123 }, { 42, 63 }, { 17, 60 }, { 92, 6 }, { 34, 67 }, { 0, 154 }, { 80, 47 }, { 38, 31 }, { 50, 42 }, { 170, 44 }, { 144, 192 }, { 60, 165 }, { 138, 170 }, { 80, 133 }, { 92, 57 }, { 61, 148 }, { 22, 33 }, { 11, 105 }, { 87, 92 }, { 37, 108 }, { 65, 143 }, { 110, 163 }, { 199, 189 }, { 81, 102 }, { 99, 126 }, { 136, 33 }, { 133, 20 }, { 198, 126 }, { 30, 170 }, { 8, 28 }, { 99, 89 }, { 149, 32 }, { 20, 41 }, { 183, 110 }, { 188, 88 }, { 42, 28 }, { 155, 58 }, { 193, 187 }, { 14, 181 }, { 0, 11 }, { 56, 199 }, { 11, 122 }, { 130, 102 }, { 102, 89 }, { 47, 156 }, { 54, 92 }, { 10, 102 }, { 108, 99 }, { 144, 47 }, { 122, 177 }, { 114, 45 }, { 126, 56 }, { 83, 8 }, { 100, 191 }, { 72, 18 }, { 127, 146 }, { 77, 168 }, { 56, 148 }, { 148, 139 }, { 15, 196 }, { 176, 147 }, { 110, 161 }, { 136, 41 }, { 86, 10 }, { 15, 8 }, { 136, 87 }, { 112, 95 }, { 165, 94 }, { 174, 13 }, { 18, 187 }, { 73, 146 }, { 75, 111 }, { 86, 109 }, { 161, 51 }, { 142, 103 }, { 110, 121 }, { 46, 155 }, { 100, 143 }, { 158, 65 }, { 165, 177 }, { 67, 6 }, { 62, 83 }, { 167, 42 }, { 21, 184 }, { 120, 21 }, { 57, 193 }, { 150, 86 }, { 88, 109 }, { 158, 10 }, { 107, 129 }, { 180, 126 }, { 86, 37 }, { 117, 89 }, { 116, 171 }, { 122, 64 }, { 176, 109 }, { 96, 71 }, { 30, 17 }, { 61, 1 }, { 191, 99 }, { 69, 173 }, { 59, 55 }, { 146, 37 }, { 129, 18 }, { 2, 179 }, { 194, 197 }, { 82, 131 }, { 124, 28 }, { 81, 103 }, { 114, 193 }, { 191, 139 }, { 49, 191 }, { 92, 38 }, { 101, 70 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); MatchingAlgorithm matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 75); } @Test public void testGraph10() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 50, 128 }, { 164, 132 }, { 185, 71 }, { 77, 85 }, { 0, 77 }, { 114, 172 }, { 114, 131 }, { 167, 34 }, { 143, 58 }, { 16, 0 }, { 86, 34 }, { 116, 180 }, { 147, 36 }, { 120, 7 }, { 100, 105 }, { 125, 114 }, { 85, 101 }, { 107, 50 }, { 171, 100 }, { 12, 47 }, { 134, 191 }, { 61, 4 }, { 95, 74 }, { 7, 140 }, { 73, 173 }, { 36, 106 }, { 20, 109 }, { 69, 18 }, { 76, 62 }, { 184, 154 }, { 40, 152 }, { 143, 95 }, { 190, 132 }, { 100, 125 }, { 109, 81 }, { 112, 174 }, { 98, 182 }, { 115, 70 }, { 108, 198 }, { 85, 9 }, { 91, 172 }, { 123, 58 }, { 2, 137 }, { 94, 160 }, { 173, 145 }, { 93, 103 }, { 78, 54 }, { 114, 49 }, { 154, 135 }, { 122, 7 }, { 88, 50 }, { 86, 152 }, { 58, 65 }, { 39, 156 }, { 108, 27 }, { 110, 149 }, { 65, 114 }, { 25, 171 }, { 52, 76 }, { 34, 83 }, { 28, 192 }, { 26, 147 }, { 9, 87 }, { 34, 4 }, { 179, 13 }, { 74, 164 }, { 187, 2 }, { 186, 104 }, { 113, 98 }, { 37, 171 }, { 43, 61 }, { 30, 85 }, { 95, 155 }, { 91, 2 }, { 199, 120 }, { 150, 109 }, { 36, 8 }, { 67, 97 }, { 62, 63 }, { 131, 69 }, { 199, 47 }, { 38, 130 }, { 95, 55 }, { 24, 162 }, { 34, 181 }, { 42, 46 }, { 54, 176 }, { 41, 19 }, { 161, 196 }, { 44, 19 }, { 191, 138 }, { 54, 148 }, { 168, 59 }, { 196, 7 }, { 176, 178 }, { 17, 110 }, { 49, 155 }, { 116, 51 }, { 35, 100 }, { 83, 114 }, { 91, 46 }, { 1, 2 }, { 97, 71 }, { 171, 109 }, { 59, 152 }, { 8, 177 }, { 111, 94 }, { 102, 26 }, { 174, 144 }, { 177, 54 }, { 52, 83 }, { 31, 181 }, { 44, 133 }, { 87, 59 }, { 73, 108 }, { 136, 4 }, { 15, 10 }, { 142, 179 }, { 151, 160 }, { 31, 166 }, { 113, 132 }, { 195, 41 }, { 156, 96 }, { 98, 165 }, { 17, 56 }, { 135, 165 }, { 54, 160 }, { 18, 165 }, { 86, 160 }, { 100, 24 }, { 109, 77 }, { 155, 92 }, { 73, 100 }, { 6, 124 }, { 93, 30 }, { 90, 194 }, { 199, 131 }, { 98, 134 }, { 49, 36 }, { 157, 0 }, { 189, 97 }, { 121, 30 }, { 121, 100 }, { 110, 194 }, { 178, 24 }, { 110, 84 }, { 92, 65 }, { 32, 143 }, { 79, 73 }, { 11, 146 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); MatchingAlgorithm matcher = new DenseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 66); } private void verifyMatching(Graph g, Matching m, int cardinality) { Set matched = new HashSet<>(); double weight = 0; for (E e : m.getEdges()) { V source = g.getEdgeSource(e); V target = g.getEdgeTarget(e); if (matched.contains(source)) fail("vertex is incident to multiple matches in the matching"); matched.add(source); if (matched.contains(target)) fail("vertex is incident to multiple matches in the matching"); matched.add(target); weight += g.getEdgeWeight(e); } assertEquals(m.getWeight(), weight, 0.0000001); assertEquals(cardinality, m.getEdges().size()); assertEquals(m.getEdges().size() * 2, matched.size()); // Ensure that there are no // self-loops DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); assertTrue(matcher.isMaximumMatching(m)); } private static int maxEdges(int n) { if (n % 2 == 0) { return Math.multiplyExact(n / 2, n - 1); } else { return Math.multiplyExact(n, (n - 1) / 2); } } } GreedyMaximumCardinalityMatchingTest.java000066400000000000000000000077661402514743400365350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Tests for GreedyMaximumCardinalityMatching * * @author Joris Kinable */ public class GreedyMaximumCardinalityMatchingTest { /** * Generate a number of random graphs, find a random matching and check whether the matching * returned is valid. Not sorted */ @Test public void testRandomGraphs() { GraphGenerator generator = new GnmRandomGraphGenerator<>(200, 120); Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); for (int i = 0; i < 100; i++) { generator.generateGraph(graph); MatchingAlgorithm matcher = new GreedyMaximumCardinalityMatching<>(graph, false); MatchingAlgorithm.Matching m = matcher.getMatching(); Set matched = new HashSet<>(); double weight = 0; for (DefaultEdge e : m.getEdges()) { Integer source = graph.getEdgeSource(e); Integer target = graph.getEdgeTarget(e); if (matched.contains(source)) fail("vertex is incident to multiple matches in the matching"); matched.add(source); if (matched.contains(target)) fail("vertex is incident to multiple matches in the matching"); matched.add(target); weight += graph.getEdgeWeight(e); } assertEquals(m.getWeight(), weight, 0.0000001); } } /** * Generate a number of random graphs, find a random matching and check whether the matching * returned is valid. Sorted. */ @Test public void testRandomGraphs2() { GraphGenerator generator = new GnmRandomGraphGenerator<>(200, 120); Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); for (int i = 0; i < 1; i++) { generator.generateGraph(graph); MatchingAlgorithm matcher = new GreedyMaximumCardinalityMatching<>(graph, true); MatchingAlgorithm.Matching m = matcher.getMatching(); Set matched = new HashSet<>(); double weight = 0; for (DefaultEdge e : m.getEdges()) { Integer source = graph.getEdgeSource(e); Integer target = graph.getEdgeTarget(e); if (matched.contains(source)) fail("vertex is incident to multiple matches in the matching"); matched.add(source); if (matched.contains(target)) fail("vertex is incident to multiple matches in the matching"); matched.add(target); weight += graph.getEdgeWeight(e); } assertEquals(m.getWeight(), weight, 0.0000001); } } } GreedyWeightedMatchingTest.java000066400000000000000000000022351402514743400344560ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; /** * Unit tests for the GreedyWeightedMatching algorithm. * * @author Dimitrios Michail */ public class GreedyWeightedMatchingTest extends ApproximateWeightedMatchingTest { @Override public MatchingAlgorithm getApproximationAlgorithm( Graph graph) { return new GreedyWeightedMatching<>(graph, false); }; } HopcroftKarpMaximumCardinalityBipartiteMatchingTest.java000066400000000000000000000024461402514743400415520ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2012-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * Unit test for the HopcroftKarpMaximumCardinalityBipartiteMatching class * * @author Joris Kinable * */ public class HopcroftKarpMaximumCardinalityBipartiteMatchingTest extends MaximumCardinalityBipartiteMatchingTest { @Override public MatchingAlgorithm getMatchingAlgorithm( Graph graph, Set partition1, Set partition2) { return new HopcroftKarpMaximumCardinalityBipartiteMatching<>(graph, partition1, partition2); } } KuhnMunkresMinimalWeightBipartitePerfectMatchingTest.java000066400000000000000000000451031402514743400416650ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2016-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.MatchingAlgorithm.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; public class KuhnMunkresMinimalWeightBipartitePerfectMatchingTest { interface V { } /** * First partition */ private enum FIRST_PARTITION implements V { A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V } private static List firstPartition = Arrays.asList(FIRST_PARTITION.values()); /** * Second partition */ private enum SECOND_PARTITION implements V { A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V } private static List secondPartition = Arrays.asList(SECOND_PARTITION.values()); private static Matching match( final double[][] costMatrix, final int partitionCardinality) { List first = firstPartition.subList(0, partitionCardinality); List second = secondPartition.subList(0, partitionCardinality); Graph target = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); GraphGenerator generator = new SimpleWeightedBipartiteGraphMatrixGenerator() .first(first).second(second).weights(costMatrix); generator.generateGraph(target); return new KuhnMunkresMinimalWeightBipartitePerfectMatching<>( target, new LinkedHashSet<>(first), new LinkedHashSet<>(second)).getMatching(); } @Test public void testForEmptyGraph() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Set emptyList = Collections.emptySet(); MatchingAlgorithm alg = new KuhnMunkresMinimalWeightBipartitePerfectMatching<>(graph, emptyList, emptyList); Assert.assertTrue(alg.getMatching().getEdges().isEmpty()); } @Test public void test3x3SimpleAssignmentTask() { // Obvious case: // Optimal selection being disposed on the diagonal of the given matrix double[][] costMatrix = new double[][] { { 1, 2, 3 }, { 5, 4, 6 }, { 8, 9, 7 } }; double w = match(costMatrix, costMatrix.length).getWeight(); Assert.assertTrue(w == 12); } @Test public void test3x3SimpleAssignmentTaskNo2() { // Simple case: // Every selection gives the same value of 15 double[][] costMatrix = new double[][] { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } }; double w = match(costMatrix, costMatrix.length).getWeight(); Assert.assertTrue(w == 15); } @Test public void test5x5AssignmentTask() { // Not so obvious case double[][] costMatrix = new double[][] { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 7, 2 }, { 1, 3, 4, 4, 5 }, { 3, 6, 2, 8, 7 }, { 4, 1, 3, 5, 4 } }; double w = match(costMatrix, costMatrix.length).getWeight(); Assert.assertTrue(w == 10); } @Test public void test5x5InvertedAssignmentTask() { // Assignment minimizing total cost according to given cost-matrix // maximizes total-cost according to the following cost-matrix: // // { 1, 2, 3, 4, 5 } // { 6, 7, 8, 7, 2 } // { 1, 3, 4, 4, 5 } // { 3, 6, 2, 8, 7 } // { 4, 1, 3, 5, 4 } // // NOTE: // Cost-matrix being under test derived from the listed above // by subtraction from the maximal element double[][] costMatrix = new double[][] { { 7, 6, 5, 4, 3 }, { 2, 1, 0, 1, 6 }, { 7, 5, 4, 4, 3 }, { 5, 2, 6, 0, 1 }, { 4, 7, 5, 3, 4 } }; double w = match(costMatrix, costMatrix.length).getWeight(); Assert.assertTrue(w == 12); } @Test public void test6x6DegeneratedAssignmentTask() { // First DEGENERATED case: // Degenerated worker and degenerated task added // // NOTE: // Answer have to stay the same as in previous case #4 double[][] costMatrix = new double[][] { { 7, 6, 5, 4, 3, 9 }, { 2, 1, 0, 1, 6, 9 }, { 7, 5, 4, 4, 3, 9 }, { 5, 2, 6, 0, 1, 9 }, { 4, 7, 5, 3, 4, 9 }, { 9, 9, 9, 9, 9, 9 } }; double w = match(costMatrix, costMatrix.length).getWeight(); Assert.assertTrue(w == 21); } @Test public void test6x6DegeneratedAssignmentTaskNo2() { // Second DEGENERATED case: // // |Workers| > |Tasks| // // degenerated task added double[][] costMatrix = new double[][] { { 7, 6, 5, 4, 3, 9 }, { 2, 1, 0, 1, 6, 9 }, { 7, 5, 4, 4, 3, 9 }, { 5, 2, 6, 0, 1, 9 }, { 4, 7, 5, 3, 4, 9 }, { 3, 5, 8, 7, 1, 9 } }; double w = match(costMatrix, costMatrix.length).getWeight(); Assert.assertTrue(w == 19); } @Test public void test5x5DegeneratedAssignmentTask() { // Third DEGENERATED case: // // Task #1 can't be performed by the worker #1 (designated by the MAX + 1 value (9)) // Task #3 can't be performed by the worker #3 (designated by the MAX + 1 value (9)) // Task #4 can't be performed by the worker #1 (designated by the MAX + 1 value (9)) // Task #5 can't be performed by the workers #2, #4 (designated by the MAX + 1 value (9)) // // degenerated task added double[][] costMatrix = new double[][] { { 9, 6, 5, 9, 3 }, { 2, 1, 0, 1, 6 }, { 7, 5, 9, 4, 3 }, { 9, 2, 6, 0, 1 }, { 4, 9, 5, 9, 4 }, }; double w = match(costMatrix, costMatrix.length).getWeight(); Assert.assertTrue(w == 12); } @Test public void test8x8BulkyAssignmentTask() { double[][] costMatrix = new double[][] { { 233160, 1485901, 3245737, 25965896, 25965896, 25965896, 25965896, 25965896 }, { 238594, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896 }, { 242403, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896 }, { 233408, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896 }, { 233160, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896 }, { 258074, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896 }, { 233160, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896 }, { 233625, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896, 25965896 } }; // Case entailing set-cover algo drastically degenerating, therefore need just to pass match(costMatrix, costMatrix.length); } @Test public void test21x21BulkyAssignmentTask() { double[][] costMatrix = new double[][] { { 284169900, 16680, 27111, 0, 25914, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 16680, 27305, 0, 25914, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 16834, 60173, 0, 25981, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 16680, 43679, 0, 32979, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 16656, 270745874, 270739560, 270769776, 270686589, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 271120238, 271113924, 271144140, 271060953, 374364, 374364, 374364, 374364, 374364, 374364, 374364, 374364, 374364, 374364, 374364, 374364, 374364, 374364, 374364, 374364 }, { 0, 270959812, 270953498, 270983714, 270900527, 213938, 213938, 213938, 213938, 213938, 213938, 213938, 213938, 213938, 213938, 213938, 213938, 213938, 213938, 213938, 213938 }, { 284182260, 0, 33241, 12360, 0, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484, 13412484 }, { 284169900, 16680, 27305, 0, 25914, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 17630, 25747, 0, 31348, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 34668, 28398, 0, 35157, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 17503, 20151, 0, 0, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 18099, 77279, 0, 26162, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 16804, 27869, 0, 25914, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284175472, 6700, 0, 5572, 24108, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696, 13405696 }, { 284169900, 31543, 22343, 0, 50828, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 16623, 27215, 0, 25830, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 16680, 27305, 0, 14463, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284174196, 0, 4296, 4296, 30210, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420, 13404420 }, { 284169900, 17377, 27999, 0, 25914, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 }, { 284169900, 76034, 27305, 0, 26379, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124, 13400124 } }; // Case entailing set-cover algo drastically degenerating, therefore need just to pass match(costMatrix, costMatrix.length); } @Test public void test20x20BulkyAssignmentTask() { double[][] costMatrix = new double[][] { { 284309466, 162348, 179093, 121766, 230867, 175501, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 162348, 179287, 121766, 230867, 133304, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 162502, 212155, 121766, 230934, 192658, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 162348, 195661, 121766, 237932, 175347, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 13538546, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 13147526, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 13307952, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 133308, 172863, 121766, 192593, 145039, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 162348, 179287, 121766, 230867, 175287, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 163298, 177729, 121766, 236301, 184972, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 180336, 180380, 121766, 240110, 181736, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 163171, 172133, 121766, 204953, 175649, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 163767, 229261, 121766, 231115, 205427, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 162472, 179851, 121766, 230867, 221341, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 146796, 146410, 121766, 223489, 156487, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 177211, 174325, 121766, 255781, 235876, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 162291, 179197, 121766, 230783, 175287, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 162348, 179287, 121766, 219416, 175460, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 141372, 151982, 121766, 230867, 161161, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 163045, 179981, 121766, 230867, 175740, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 }, { 284309466, 221702, 179287, 121766, 231332, 175287, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466, 284309466 } }; // Case entailing set-cover algo drastically degenerating, therefore need just to pass match(costMatrix, costMatrix.length); } } MaximumCardinalityBipartiteMatchingTest.java000066400000000000000000000233751402514743400372330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.*; /** * Test class for maximum cardinality bipartite matching algorithms * * @author Joris Kinable */ public abstract class MaximumCardinalityBipartiteMatchingTest { public abstract MatchingAlgorithm getMatchingAlgorithm( Graph graph, Set partition1, Set partition2); /** * Random test graph 1 */ @Test public void testBipartiteMatching1() { Graph graph = new SimpleGraph<>(DefaultEdge.class); List partition1 = Arrays.asList(0, 1, 2, 3); List partition2 = Arrays.asList(4, 5, 6, 7); Graphs.addAllVertices(graph, partition1); Graphs.addAllVertices(graph, partition2); DefaultEdge e02 = graph.addEdge(partition1.get(0), partition2.get(2)); DefaultEdge e11 = graph.addEdge(partition1.get(1), partition2.get(1)); DefaultEdge e20 = graph.addEdge(partition1.get(2), partition2.get(0)); MatchingAlgorithm bm = getMatchingAlgorithm(graph, new HashSet<>(partition1), new HashSet<>(partition2)); List l1 = Arrays.asList(e11, e02, e20); Set matching = new HashSet<>(l1); MatchingAlgorithm.Matching bmMatching = bm.getMatching(); assertEquals(3, bmMatching.getEdges().size(), 0); assertEquals(matching, bmMatching.getEdges()); } /** * Random test graph 2 */ @Test public void testBipartiteMatching2() { Graph graph = new SimpleGraph<>(DefaultEdge.class); List partition1 = Arrays.asList(0, 1, 2, 3, 4, 5); List partition2 = Arrays.asList(6, 7, 8, 9, 10, 11); Graphs.addAllVertices(graph, partition1); Graphs.addAllVertices(graph, partition2); DefaultEdge e00 = graph.addEdge(partition1.get(0), partition2.get(0)); DefaultEdge e13 = graph.addEdge(partition1.get(1), partition2.get(3)); DefaultEdge e21 = graph.addEdge(partition1.get(2), partition2.get(1)); DefaultEdge e34 = graph.addEdge(partition1.get(3), partition2.get(4)); DefaultEdge e42 = graph.addEdge(partition1.get(4), partition2.get(2)); DefaultEdge e55 = graph.addEdge(partition1.get(5), partition2.get(5)); MatchingAlgorithm bm = getMatchingAlgorithm(graph, new HashSet<>(partition1), new HashSet<>(partition2)); MatchingAlgorithm.Matching bmMatching = bm.getMatching(); assertEquals(6, bmMatching.getEdges().size(), 0); List l1 = Arrays.asList(e21, e13, e00, e42, e34, e55); Set matching = new HashSet<>(l1); assertEquals(matching, bmMatching.getEdges()); } /** * Find a maximum matching on a graph without edges */ @Test public void testEmptyMatching() { Graph graph = new SimpleGraph<>(DefaultEdge.class); List partition1 = Collections.singletonList(0); List partition2 = Collections.singletonList(1); Graphs.addAllVertices(graph, partition1); Graphs.addAllVertices(graph, partition2); MatchingAlgorithm bm = getMatchingAlgorithm(graph, new HashSet<>(partition1), new HashSet<>(partition2)); MatchingAlgorithm.Matching bmMatching = bm.getMatching(); assertEquals(Collections.EMPTY_SET, bmMatching.getEdges()); } @Test public void testGraph1() { Graph graph = new SimpleGraph<>(DefaultEdge.class); Set partition1 = Set.of(0, 1, 2, 3, 4, 5, 6); Set partition2 = Set.of(7, 8, 9); Graphs.addAllVertices(graph, partition1); Graphs.addAllVertices(graph, partition2); int[][] edges = { { 5, 8 }, { 4, 9 }, { 2, 7 }, { 6, 9 }, { 1, 9 } }; for (int[] edge : edges) graph.addEdge(edge[0], edge[1]); MatchingAlgorithm matcher = getMatchingAlgorithm(graph, partition1, partition2); MatchingAlgorithm.Matching matching = matcher.getMatching(); assertEquals(3, matching.getEdges().size()); } @Test public void testGraph2() { Graph graph = new SimpleGraph<>(DefaultEdge.class); Set partition1 = Set.of(0, 1, 2, 3, 4, 5, 6); Set partition2 = Set.of(7, 8, 9); Graphs.addAllVertices(graph, partition1); Graphs.addAllVertices(graph, partition2); int[][] edges = { { 5, 8 }, { 4, 9 }, { 2, 7 }, { 6, 9 }, { 1, 9 }, { 0, 8 }, { 3, 7 }, { 1, 7 } }; for (int[] edge : edges) graph.addEdge(edge[0], edge[1]); MatchingAlgorithm matcher = getMatchingAlgorithm(graph, partition1, partition2); MatchingAlgorithm.Matching matching = matcher.getMatching(); this.verifyMatching(graph, matching, matching.getEdges().size()); } /** * Issue 233 instance */ @Test public void testBipartiteMatchingIssue233() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, IntStream.rangeClosed(0, 3).boxed().collect(Collectors.toList())); Set left = Set.of(0, 1); Set right = Set.of(2, 3); g.addEdge(0, 2); g.addEdge(0, 3); g.addEdge(1, 2); MatchingAlgorithm.Matching m = getMatchingAlgorithm(g, left, right).getMatching(); assertTrue(m.getEdges().contains(g.getEdge(1, 2))); assertTrue(m.getEdges().contains(g.getEdge(0, 3))); assertEquals(2, m.getEdges().size()); } @Test public void testPseudoGraph() { Graph graph = new Pseudograph<>(DefaultEdge.class); Set partition1 = Set.of(0, 1, 2); Set partition2 = Set.of(3, 4, 5); Graphs.addAllVertices(graph, partition1); Graphs.addAllVertices(graph, partition2); int[][] edges = { { 0, 3 }, { 1, 4 }, { 2, 5 }, { 0, 3 }, { 0, 0 } }; for (int[] edge : edges) graph.addEdge(edge[0], edge[1]); MatchingAlgorithm matcher = getMatchingAlgorithm(graph, partition1, partition2); MatchingAlgorithm.Matching matching = matcher.getMatching(); this.verifyMatching(graph, matching, 3); } @Test public void testRandomBipartiteGraphs() { Random random = new Random(1); int vertices = 100; for (int k = 0; k < 100; k++) { int edges = random.nextInt(maxEdges(vertices) / 2); GnmRandomBipartiteGraphGenerator generator = new GnmRandomBipartiteGraphGenerator<>(vertices, vertices / 2, edges, 0); Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(graph); MatchingAlgorithm matcher = getMatchingAlgorithm( graph, generator.getFirstPartition(), generator.getSecondPartition()); MatchingAlgorithm.Matching m = matcher.getMatching(); this.verifyMatching(graph, m, m.getEdges().size()); } } private void verifyMatching(Graph g, MatchingAlgorithm.Matching m, int cardinality) { Set matched = new HashSet<>(); double weight = 0; for (E e : m.getEdges()) { V source = g.getEdgeSource(e); V target = g.getEdgeTarget(e); if (matched.contains(source)) fail("vertex is incident to multiple matches in the matching"); matched.add(source); if (matched.contains(target)) fail("vertex is incident to multiple matches in the matching"); matched.add(target); weight += g.getEdgeWeight(e); } assertEquals(m.getWeight(), weight, 0.0000001); assertEquals(cardinality, m.getEdges().size()); assertEquals(m.getEdges().size() * 2, matched.size()); // Ensure that there are no // self-loops DenseEdmondsMaximumCardinalityMatching matcher = new DenseEdmondsMaximumCardinalityMatching<>(g); assertTrue(matcher.isMaximumMatching(m)); // Certify that the matching is indeed maximum } private static int maxEdges(int n) { if (n % 2 == 0) { return Math.multiplyExact(n / 2, n - 1); } else { return Math.multiplyExact(n, (n - 1) / 2); } } } MaximumWeightBipartiteMatchingTest.java000066400000000000000000000222161402514743400362100ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2015-2021, by Graeme Ahokas and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.MatchingAlgorithm.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.experimental.categories.*; import java.math.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class MaximumWeightBipartiteMatchingTest { private SimpleWeightedGraph graph; private Set partition1; private Set partition2; private MaximumWeightBipartiteMatching matcher; @Before public void setUpGraph() { graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex("s1"); graph.addVertex("s2"); graph.addVertex("s3"); graph.addVertex("s4"); graph.addVertex("t1"); graph.addVertex("t2"); graph.addVertex("t3"); graph.addVertex("t4"); partition1 = new HashSet<>(); partition1.add("s1"); partition1.add("s2"); partition1.add("s3"); partition1.add("s4"); partition2 = new HashSet<>(); partition2.add("t1"); partition2.add("t2"); partition2.add("t3"); partition2.add("t4"); } @Test public void maximumWeightBipartiteMatching1() { DefaultWeightedEdge e1 = graph.addEdge("s1", "t1"); graph.setEdgeWeight(e1, 1); matcher = new MaximumWeightBipartiteMatching<>(graph, partition1, partition2); Matching matchings = matcher.getMatching(); assertEquals(1, matchings.getEdges().size()); assertTrue(matchings.getEdges().contains(e1)); } @Test public void maximumWeightBipartiteMatching2() { DefaultWeightedEdge e1 = graph.addEdge("s1", "t1"); graph.setEdgeWeight(e1, 1); DefaultWeightedEdge e2 = graph.addEdge("s2", "t1"); graph.setEdgeWeight(e2, 2); matcher = new MaximumWeightBipartiteMatching<>(graph, partition1, partition2); Matching matchings = matcher.getMatching(); assertEquals(1, matchings.getEdges().size()); assertTrue(matchings.getEdges().contains(e2)); } @Test public void maximumWeightBipartiteMatching3() { DefaultWeightedEdge e1 = graph.addEdge("s1", "t1"); graph.setEdgeWeight(e1, 2); DefaultWeightedEdge e2 = graph.addEdge("s1", "t2"); graph.setEdgeWeight(e2, 1); DefaultWeightedEdge e3 = graph.addEdge("s2", "t1"); graph.setEdgeWeight(e3, 2); matcher = new MaximumWeightBipartiteMatching<>(graph, partition1, partition2); Matching matchings = matcher.getMatching(); assertEquals(2, matchings.getEdges().size()); assertTrue(matchings.getEdges().contains(e2)); assertTrue(matchings.getEdges().contains(e3)); } @Test public void maximumWeightBipartiteMatching4() { DefaultWeightedEdge e1 = graph.addEdge("s1", "t1"); graph.setEdgeWeight(e1, 1); DefaultWeightedEdge e2 = graph.addEdge("s1", "t2"); graph.setEdgeWeight(e2, 1); DefaultWeightedEdge e3 = graph.addEdge("s2", "t2"); graph.setEdgeWeight(e3, 1); matcher = new MaximumWeightBipartiteMatching<>(graph, partition1, partition2); Matching matchings = matcher.getMatching(); assertEquals(2, matchings.getEdges().size()); assertTrue(matchings.getEdges().contains(e1)); assertTrue(matchings.getEdges().contains(e3)); } @Test public void maximumWeightBipartiteMatching5() { DefaultWeightedEdge e1 = graph.addEdge("s1", "t1"); graph.setEdgeWeight(e1, 1); DefaultWeightedEdge e2 = graph.addEdge("s1", "t2"); graph.setEdgeWeight(e2, 2); DefaultWeightedEdge e3 = graph.addEdge("s2", "t2"); graph.setEdgeWeight(e3, 2); DefaultWeightedEdge e4 = graph.addEdge("s3", "t2"); graph.setEdgeWeight(e4, 2); DefaultWeightedEdge e5 = graph.addEdge("s3", "t3"); graph.setEdgeWeight(e5, 1); DefaultWeightedEdge e6 = graph.addEdge("s4", "t1"); graph.setEdgeWeight(e6, 1); DefaultWeightedEdge e7 = graph.addEdge("s4", "t4"); graph.setEdgeWeight(e7, 1); matcher = new MaximumWeightBipartiteMatching<>(graph, partition1, partition2); Matching matchings = matcher.getMatching(); assertEquals(4, matchings.getEdges().size()); assertTrue(matchings.getEdges().contains(e1)); assertTrue(matchings.getEdges().contains(e3)); assertTrue(matchings.getEdges().contains(e5)); assertTrue(matchings.getEdges().contains(e7)); assertEquals(5d, matchings.getWeight(), 1e-9); } @Test @Category(SlowTests.class) public void testRandomInstancesFixedSeed() { testRandomInstance(new Random(17), 100, 0.7, 2); } @Test @Category(SlowTests.class) public void testRandomInstances() { Random rng = new Random(); testRandomInstance(rng, 100, 0.8, 1); testRandomInstance(rng, 1000, 0.8, 1); } private void testRandomInstance(Random rng, int n, double p, int repeat) { for (int a = 0; a < repeat; a++) { // generate random bipartite Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Set partitionA = CollectionUtil.newLinkedHashSetWithExpectedSize(n); for (int i = 0; i < n; i++) { g.addVertex(i); partitionA.add(i); } Set partitionB = CollectionUtil.newLinkedHashSetWithExpectedSize(n); for (int i = 0; i < n; i++) { g.addVertex(n + i); partitionB.add(n + i); } // create edges for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { // s->t if (rng.nextDouble() < p) { g.addEdge(i, n + j); } } } // assign random weights for (DefaultWeightedEdge e : g.edgeSet()) { g.setEdgeWeight(e, 1000 * rng.nextInt()); } // compute maximum weight matching MaximumWeightBipartiteMatching alg = new MaximumWeightBipartiteMatching<>(g, partitionA, partitionB); Matching matching = alg.getMatching(); Map pot = alg.getPotentials(); Comparator comparator = Comparator. naturalOrder(); // assert matching Map degree = new HashMap<>(); for (Integer v : g.vertexSet()) { degree.put(v, 0); } for (DefaultWeightedEdge e : matching.getEdges()) { Integer s = g.getEdgeSource(e); Integer t = g.getEdgeTarget(e); degree.put(s, degree.get(s) + 1); degree.put(t, degree.get(t) + 1); } for (Integer v : g.vertexSet()) { assertTrue(degree.get(v) <= 1); } // assert non-negative potentials for (Integer v : g.vertexSet()) { assertTrue(comparator.compare(pot.get(v), BigDecimal.ZERO) >= 0); } // assert non-negative reduced cost for edges for (DefaultWeightedEdge e : g.edgeSet()) { Integer s = g.getEdgeSource(e); Integer t = g.getEdgeTarget(e); BigDecimal w = BigDecimal.valueOf(g.getEdgeWeight(e)); assertTrue(comparator.compare(w, pot.get(s).add(pot.get(t))) <= 0); } // assert tight edges in matching for (DefaultWeightedEdge e : matching.getEdges()) { Integer s = g.getEdgeSource(e); Integer t = g.getEdgeTarget(e); BigDecimal w = BigDecimal.valueOf(g.getEdgeWeight(e)); assertTrue(comparator.compare(w, pot.get(s).add(pot.get(t))) == 0); } // assert free nodes have zero potential for (Integer v : g.vertexSet()) { if (degree.get(v) == 0) { assertEquals(pot.get(v), BigDecimal.ZERO); } } } } } NoHeuristicsPathGrowingWeightedMatchingTest.java000066400000000000000000000167371402514743400400440ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.MatchingAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit tests for the PathGrowingWeightedMatching without heuristics algorithm * * @author Dimitrios Michail */ public class NoHeuristicsPathGrowingWeightedMatchingTest extends BasePathGrowingWeightedMatchingTest { @Override public MatchingAlgorithm getApproximationAlgorithm( Graph graph) { return new PathGrowingWeightedMatching<>(graph, false); }; @Override @Test public void testGraph1() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, IntStream.range(0, 15).boxed().collect(Collectors.toList())); Graphs.addEdge(g, 0, 1, 5.0); Graphs.addEdge(g, 1, 2, 2.5); Graphs.addEdge(g, 2, 3, 5.0); Graphs.addEdge(g, 3, 4, 2.5); Graphs.addEdge(g, 4, 0, 2.5); Graphs.addEdge(g, 0, 13, 2.5); Graphs.addEdge(g, 13, 14, 5.0); Graphs.addEdge(g, 1, 11, 2.5); Graphs.addEdge(g, 11, 12, 5.0); Graphs.addEdge(g, 2, 9, 2.5); Graphs.addEdge(g, 9, 10, 5.0); Graphs.addEdge(g, 3, 7, 2.5); Graphs.addEdge(g, 7, 8, 5.0); Graphs.addEdge(g, 4, 5, 2.5); Graphs.addEdge(g, 5, 6, 5.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(5, m.getEdges().size()); assertEquals(22.5, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Override @Test public void testSelfLoops() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 0, 1.0); Graphs.addAllVertices(g, Arrays.asList(4, 5, 6, 7)); Graphs.addEdge(g, 4, 5, 1.0); Graphs.addEdge(g, 5, 6, 1.0); Graphs.addEdge(g, 6, 7, 1.0); Graphs.addEdge(g, 7, 4, 1.0); // add self loops Graphs.addEdge(g, 0, 0, 100.0); Graphs.addEdge(g, 1, 1, 200.0); Graphs.addEdge(g, 2, 2, -200.0); Graphs.addEdge(g, 3, 3, -100.0); Graphs.addEdge(g, 4, 4, 0.0); MatchingAlgorithm mm = getApproximationAlgorithm(g); Matching m = mm.getMatching(); assertEquals(3, m.getEdges().size()); assertEquals(3.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Override @Test public void test3over4Approximation() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 0, 1.0); Graphs.addAllVertices(g, Arrays.asList(4, 5, 6, 7)); Graphs.addEdge(g, 4, 5, 1.0); Graphs.addEdge(g, 5, 6, 1.0); Graphs.addEdge(g, 6, 7, 1.0); Graphs.addEdge(g, 7, 4, 1.0); MatchingAlgorithm mm = new PathGrowingWeightedMatching<>(g, false); Matching m = mm.getMatching(); // maximum here is 4.0 // path growing algorithm gets 3.0 assertEquals(3, m.getEdges().size()); assertEquals(3.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Override @Test public void testMultiGraph() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 0, 1.0); Graphs.addAllVertices(g, Arrays.asList(4, 5, 6, 7)); Graphs.addEdge(g, 4, 5, 1.0); Graphs.addEdge(g, 5, 6, 1.0); Graphs.addEdge(g, 6, 7, 1.0); Graphs.addEdge(g, 7, 4, 1.0); // add multiple edges Graphs.addEdge(g, 0, 1, 2.0); Graphs.addEdge(g, 1, 2, 2.0); Graphs.addEdge(g, 2, 3, 2.0); Graphs.addEdge(g, 3, 0, 2.0); Graphs.addEdge(g, 4, 5, 2.0); Graphs.addEdge(g, 5, 6, 2.0); Graphs.addEdge(g, 6, 7, 2.0); Graphs.addEdge(g, 7, 4, 2.0); MatchingAlgorithm mm = new PathGrowingWeightedMatching<>(g, false); Matching m = mm.getMatching(); // maximum here is 8.0 // path growing algorithm gets 6.0 assertEquals(3, m.getEdges().size()); assertEquals(6.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } @Override @Test public void testDirected() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(g, 0, 1, 1.0); Graphs.addEdge(g, 1, 2, 1.0); Graphs.addEdge(g, 2, 3, 1.0); Graphs.addEdge(g, 3, 0, 1.0); Graphs.addAllVertices(g, Arrays.asList(4, 5, 6, 7)); Graphs.addEdge(g, 4, 5, 1.0); Graphs.addEdge(g, 5, 6, 1.0); Graphs.addEdge(g, 6, 7, 1.0); Graphs.addEdge(g, 7, 4, 1.0); // add multiple edges Graphs.addEdge(g, 0, 1, 2.0); Graphs.addEdge(g, 1, 2, 2.0); Graphs.addEdge(g, 2, 3, 2.0); Graphs.addEdge(g, 3, 0, 2.0); Graphs.addEdge(g, 4, 5, 2.0); Graphs.addEdge(g, 5, 6, 2.0); Graphs.addEdge(g, 6, 7, 2.0); Graphs.addEdge(g, 7, 4, 2.0); MatchingAlgorithm mm = new PathGrowingWeightedMatching<>(g, false); Matching m = mm.getMatching(); // maximum here is 8.0 // path growing algorithm gets 6.0 assertEquals(3, m.getEdges().size()); assertEquals(6.0, m.getWeight(), MatchingAlgorithm.DEFAULT_EPSILON); assertTrue(isMatching(g, m)); } } PathGrowingWeightedMatchingTest.java000066400000000000000000000022501402514743400354650ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; /** * Unit tests for the PathGrowingWeightedMatching algorithm * * @author Dimitrios Michail */ public class PathGrowingWeightedMatchingTest extends BasePathGrowingWeightedMatchingTest { @Override public MatchingAlgorithm getApproximationAlgorithm( Graph graph) { return new PathGrowingWeightedMatching<>(graph); }; } SparseEdmondsMaximumCardinalityMatchingTest.java000066400000000000000000002575021402514743400400600ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.MatchingAlgorithm.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for EdmondsMaximumCardinalityMatching * * @author Joris Kinable */ public final class SparseEdmondsMaximumCardinalityMatchingTest { @Test public void testDisconnectedGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); int[][] edges = { { 0, 1 }, { 1, 2 }, { 0, 2 }, { 3, 4 }, { 4, 5 }, { 5, 6 }, { 3, 6 } }; for (int[] edge : edges) { g.addEdge(edge[0], edge[1]); } SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); this.verifyMatching(g, match, 3); Map oddSetCover = matcher.getOddSetCover(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(g, match.getEdges(), oddSetCover)); } @Test public void testPseudoGraph() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 }, { 3, 3 }, { 2, 3 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); assertEquals(6, g.edgeSet().size()); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); this.verifyMatching(g, match, 2); Map oddSetCover = matcher.getOddSetCover(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(g, match.getEdges(), oddSetCover)); } @Test public void testGraph15() { // graph: ([0, 1, 2, 3, 4, 5, 6, 7], [{5,1}, {4,3}, {0,6}, {4,2}, {2,1}, {3,6}, {5,0}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7)); int[][] edges = { { 5, 1 }, { 4, 3 }, { 0, 6 }, { 4, 2 }, { 2, 1 }, { 3, 6 }, { 5, 0 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); this.verifyMatching(g, match, 3); Map oddSetCover = matcher.getOddSetCover(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(g, match.getEdges(), oddSetCover)); } @Test public void testGraph14() { // graph: ([0, 1, 2, 3, 4, 5, 6, 7], [{2,0}, {2,6}, {4,6}, {4,3}, {6,7}, {3,6}, {5,0}, // {2,5}, {3,7}, {2,4}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7)); int[][] edges = { { 2, 0 }, { 2, 6 }, { 4, 6 }, { 4, 3 }, { 6, 7 }, { 3, 6 }, { 5, 0 }, { 2, 5 }, { 3, 7 }, { 2, 4 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); Map oddSetCover = matcher.getOddSetCover(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(g, match.getEdges(), oddSetCover)); } @Test public void testGraph13() { // graph: ([0, 1, 2, 3, 4], [{0,3}, {0,2}, {4,2}, {0,1}, {1,3}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4)); int[][] edges = { { 0, 3 }, { 0, 2 }, { 4, 2 }, { 0, 1 }, { 1, 3 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); Map oddSetCover = matcher.getOddSetCover(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(g, match.getEdges(), oddSetCover)); } @Test public void testGraph12() { // graph: ([0, 1, 2, 3], [{3,2}, {3,1}, {0,3}, {0,1}, {2,1}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); int[][] edges = { { 3, 2 }, { 3, 1 }, { 0, 3 }, { 0, 1 }, { 2, 1 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); Map oddSetCover = matcher.getOddSetCover(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(g, match.getEdges(), oddSetCover)); } @Test public void testGraph11() { // graph: ([0, 1, 2, 3], []) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); int[][] edges = { { 0, 2 }, { 1, 0 }, { 2, 1 }, { 0, 3 }, { 2, 3 } }; for (int[] edge : edges) g.addEdge(edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); Map oddSetCover = matcher.getOddSetCover(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(g, match.getEdges(), oddSetCover)); } @Test public void testIsMaximumMatching3() { // graph: ([0, 1, 2, 3, 4, 5, 6], [{4,0}, {2,3}, {2,0}, {2,5}, {2,6}, {0,1}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); g.addEdge(4, 0); g.addEdge(2, 3); g.addEdge(2, 0); g.addEdge(2, 5); g.addEdge(2, 6); g.addEdge(0, 1); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(g); Matching match = matcher.getMatching(); Map oddSetCover = matcher.getOddSetCover(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(g, match.getEdges(), oddSetCover)); } @Test public void testIsMaximumMatching2() { // Graph contains one isolated vertex: 6 Graph graph = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3, 4, 5, 7, 8, 9)); int[][] edges = { { 0, 8 }, { 9, 7 }, { 5, 3 }, { 9, 4 }, { 3, 2 }, { 5, 4 }, { 1, 0 }, { 3, 8 }, { 4, 7 }, { 2, 0 }, { 8, 5 }, { 0, 5 }, { 8, 1 } }; for (int[] edge : edges) graph.addEdge(edge[0], edge[1]); Set mEdges = new HashSet<>(); mEdges.add(graph.getEdge(0, 1)); mEdges.add(graph.getEdge(2, 3)); mEdges.add(graph.getEdge(4, 9)); mEdges.add(graph.getEdge(5, 8)); Matching m = new MatchingAlgorithm.MatchingImpl<>(graph, mEdges, 4); verifyMatching(graph, m, 4); } @Test public void testIsMaximum1() { // graph: ([0, 1, 2, 3, 4, 5, 6], [{5,6}, {1,2}, {0,6}, {4,6}, {2,6}]) Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); g.addEdge(5, 6); g.addEdge(1, 2); g.addEdge(0, 6); g.addEdge(4, 6); g.addEdge(2, 6); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(g); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(g, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testRandomGraphsLarge() { Random random = new Random(1); int vertices = 100; for (int k = 0; k < 100; k++) { int edges = random.nextInt(maxEdges(vertices) / 2); GraphGenerator generator = new GnmRandomGraphGenerator<>(vertices, edges, 0); Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(graph); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); Matching m = matcher.getMatching(); this.verifyMatching(graph, m, m.getEdges().size()); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(graph, m.getEdges(), matcher.getOddSetCover())); } } @Test public void testRandomGraphsBarabasiLarge() { Random random = new Random(1324); int vertices = 250; for (int k = 0; k < 10; k++) { BarabasiAlbertGraphGenerator generator = new BarabasiAlbertGraphGenerator<>(6, 6, vertices, random); Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).weighted(false).buildGraph(); generator.generateGraph(graph); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); Matching m = matcher.getMatching(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(graph, m.getEdges(), matcher.getOddSetCover())); } } @Test public void testRandomGraphsBarabasiLargeNoSeed() { int vertices = 250; for (int k = 0; k < 10; k++) { BarabasiAlbertGraphGenerator generator = new BarabasiAlbertGraphGenerator<>(6, 6, vertices); Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).weighted(false).buildGraph(); generator.generateGraph(graph); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); Matching m = matcher.getMatching(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(graph, m.getEdges(), matcher.getOddSetCover())); } } @Test public void testRandomGraphsSmall() { for (int n = 4; n < 12; n++) { for (int m = 5; m < maxEdges(n); m++) { GraphGenerator generator = new GnmRandomGraphGenerator<>(n, m); for (int i = 0; i < 25; i++) { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(graph); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); Matching m1 = matcher.getMatching(); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching(graph, m1.getEdges(), matcher.getOddSetCover())); } } } } @Test public void testGraph1() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 97, 22 }, { 56, 105 }, { 89, 132 }, { 117, 25 }, { 83, 106 }, { 171, 49 }, { 162, 138 }, { 90, 120 }, { 152, 33 }, { 47, 81 }, { 70, 191 }, { 23, 142 }, { 80, 53 }, { 106, 111 }, { 7, 9 }, { 11, 71 }, { 186, 177 }, { 196, 28 }, { 55, 106 }, { 134, 89 }, { 178, 123 }, { 109, 169 }, { 104, 27 }, { 162, 42 }, { 102, 164 }, { 51, 92 }, { 26, 10 }, { 141, 165 }, { 107, 164 }, { 41, 2 }, { 125, 46 }, { 189, 59 }, { 68, 104 }, { 161, 36 }, { 154, 143 }, { 129, 92 }, { 139, 110 }, { 43, 76 }, { 197, 1 }, { 118, 38 }, { 6, 53 }, { 123, 62 }, { 125, 55 }, { 183, 81 }, { 67, 120 }, { 54, 57 }, { 34, 25 }, { 156, 171 }, { 139, 49 }, { 108, 142 }, { 54, 184 }, { 124, 199 }, { 82, 191 }, { 23, 85 }, { 181, 71 }, { 154, 102 }, { 69, 98 }, { 131, 52 }, { 36, 33 }, { 146, 160 }, { 114, 75 }, { 92, 137 }, { 172, 31 }, { 188, 25 }, { 123, 119 }, { 178, 21 }, { 96, 97 }, { 72, 118 }, { 34, 106 }, { 175, 185 }, { 138, 121 }, { 185, 183 }, { 46, 62 }, { 135, 25 }, { 66, 21 }, { 194, 109 }, { 125, 123 }, { 62, 181 }, { 198, 156 }, { 99, 34 }, { 87, 174 }, { 165, 45 }, { 114, 125 }, { 164, 101 }, { 9, 36 }, { 102, 146 }, { 138, 189 }, { 159, 117 }, { 78, 69 }, { 50, 66 }, { 27, 63 }, { 122, 107 }, { 151, 11 }, { 58, 34 }, { 77, 195 }, { 122, 186 }, { 84, 98 }, { 171, 91 }, { 19, 33 }, { 41, 16 }, { 81, 40 }, { 87, 7 }, { 65, 4 }, { 178, 155 }, { 130, 106 }, { 131, 42 }, { 174, 71 }, { 30, 103 }, { 186, 83 }, { 108, 185 }, { 112, 77 }, { 62, 56 }, { 198, 34 }, { 4, 17 }, { 182, 139 }, { 159, 25 }, { 96, 9 }, { 192, 38 }, { 187, 104 }, { 27, 157 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 58); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testGraph2() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 73, 143 }, { 139, 145 }, { 185, 74 }, { 23, 28 }, { 62, 4 }, { 86, 106 }, { 159, 60 }, { 55, 119 }, { 30, 36 }, { 79, 58 }, { 21, 11 }, { 83, 134 }, { 166, 33 }, { 128, 122 }, { 46, 147 }, { 48, 67 }, { 182, 93 }, { 92, 105 }, { 198, 4 }, { 168, 38 }, { 21, 142 }, { 72, 19 }, { 97, 96 }, { 37, 85 }, { 47, 119 }, { 139, 108 }, { 88, 83 }, { 162, 69 }, { 41, 45 }, { 127, 125 }, { 197, 34 }, { 63, 189 }, { 153, 135 }, { 184, 102 }, { 19, 44 }, { 75, 2 }, { 168, 165 }, { 121, 17 }, { 49, 179 }, { 169, 69 }, { 137, 58 }, { 52, 85 }, { 14, 179 }, { 133, 134 }, { 169, 82 }, { 139, 114 }, { 14, 196 }, { 198, 12 }, { 122, 164 }, { 94, 62 }, { 124, 98 }, { 34, 8 }, { 199, 178 }, { 59, 194 }, { 56, 196 }, { 15, 121 }, { 5, 1 }, { 26, 128 }, { 120, 111 }, { 187, 11 }, { 175, 191 }, { 62, 71 }, { 58, 52 }, { 67, 53 }, { 64, 62 }, { 186, 142 }, { 141, 156 }, { 40, 96 }, { 132, 32 }, { 35, 15 }, { 45, 149 }, { 199, 143 }, { 100, 147 }, { 130, 29 }, { 38, 155 }, { 26, 77 }, { 16, 22 }, { 49, 125 }, { 158, 199 }, { 146, 9 }, { 140, 65 }, { 91, 176 }, { 116, 83 }, { 70, 116 }, { 21, 17 }, { 190, 119 }, { 13, 116 }, { 83, 0 }, { 11, 33 }, { 113, 13 }, { 195, 150 }, { 179, 139 }, { 71, 56 }, { 128, 40 }, { 22, 91 }, { 63, 106 }, { 123, 161 }, { 53, 40 }, { 189, 172 }, { 56, 70 }, { 158, 86 }, { 174, 166 }, { 75, 69 }, { 135, 9 }, { 127, 157 }, { 76, 102 }, { 116, 173 }, { 90, 145 }, { 167, 75 }, { 135, 29 }, { 7, 179 }, { 96, 44 }, { 178, 50 }, { 60, 194 }, { 107, 74 }, { 9, 26 }, { 171, 14 }, { 153, 136 }, { 179, 197 }, { 16, 78 }, { 156, 19 }, { 45, 26 }, { 172, 83 }, { 51, 132 }, { 190, 65 }, { 26, 6 }, { 4, 59 }, { 63, 84 }, { 155, 8 }, { 125, 154 }, { 42, 97 }, { 182, 69 }, { 184, 196 }, { 42, 122 }, { 146, 159 }, { 26, 66 }, { 134, 105 }, { 98, 23 }, { 128, 92 }, { 97, 152 }, { 182, 167 }, { 22, 24 }, { 41, 8 }, { 97, 13 }, { 123, 70 }, { 74, 173 }, { 143, 77 }, { 90, 18 }, { 97, 59 }, { 21, 94 }, { 83, 15 }, { 49, 90 }, { 65, 105 }, { 189, 120 }, { 75, 124 }, { 177, 176 }, { 140, 152 }, { 70, 135 }, { 155, 157 }, { 17, 196 }, { 190, 130 }, { 9, 21 }, { 109, 66 }, { 98, 174 }, { 146, 112 }, { 6, 33 }, { 83, 138 }, { 40, 17 }, { 185, 64 }, { 84, 136 }, { 153, 16 }, { 134, 1 }, { 45, 106 }, { 165, 48 }, { 198, 150 }, { 60, 77 }, { 103, 150 }, { 117, 15 }, { 113, 5 }, { 106, 77 }, { 15, 111 }, { 170, 144 }, { 173, 50 }, { 180, 20 }, { 170, 114 }, { 34, 117 }, { 111, 131 }, { 173, 46 }, { 19, 175 }, { 89, 149 }, { 52, 15 }, { 5, 195 }, { 114, 23 }, { 166, 41 }, { 182, 122 }, { 131, 3 }, { 99, 77 }, { 40, 22 }, { 176, 35 }, { 12, 186 }, { 112, 89 }, { 10, 76 }, { 115, 172 }, { 30, 84 }, { 180, 93 }, { 98, 11 }, { 160, 120 }, { 141, 18 }, { 112, 11 }, { 73, 114 }, { 28, 42 }, { 103, 29 }, { 1, 175 }, { 161, 52 }, { 118, 150 }, { 148, 187 }, { 137, 47 }, { 192, 55 }, { 145, 149 }, { 198, 87 }, { 191, 139 }, { 21, 40 }, { 134, 174 }, { 136, 162 }, { 4, 46 }, { 39, 64 }, { 68, 38 }, { 73, 109 }, { 74, 34 }, { 43, 130 }, { 10, 56 }, { 24, 140 }, { 117, 144 }, { 180, 178 }, { 185, 37 }, { 14, 180 }, { 131, 45 }, { 18, 6 }, { 4, 61 }, { 57, 132 }, { 189, 94 }, { 38, 176 }, { 104, 124 }, { 125, 31 }, { 45, 156 }, { 145, 170 }, { 182, 125 }, { 106, 185 }, { 168, 152 }, { 146, 145 }, { 62, 90 }, { 99, 125 }, { 195, 64 }, { 135, 50 }, { 4, 81 }, { 186, 149 }, { 104, 175 }, { 112, 144 }, { 189, 47 }, { 55, 17 }, { 0, 41 }, { 1, 19 }, { 192, 97 }, { 192, 37 }, { 111, 99 }, { 197, 137 }, { 174, 38 }, { 64, 80 }, { 20, 7 }, { 6, 96 }, { 19, 166 }, { 129, 71 }, { 72, 149 }, { 34, 145 }, { 173, 141 }, { 27, 78 }, { 84, 171 }, { 36, 199 }, { 82, 144 }, { 13, 190 }, { 85, 30 }, { 67, 157 }, { 44, 126 }, { 24, 23 }, { 163, 187 }, { 61, 39 }, { 105, 152 }, { 49, 165 }, { 103, 42 }, { 5, 72 }, { 166, 73 }, { 135, 40 }, { 121, 50 }, { 193, 181 }, { 55, 196 }, { 13, 170 }, { 51, 181 }, { 170, 72 }, { 47, 33 }, { 179, 80 }, { 135, 186 }, { 127, 10 }, { 184, 114 }, { 60, 12 }, { 121, 157 }, { 42, 16 }, { 148, 131 }, { 106, 65 }, { 25, 93 }, { 164, 132 }, { 104, 145 }, { 106, 100 }, { 8, 25 }, { 23, 21 }, { 49, 5 }, { 162, 194 }, { 186, 193 }, { 109, 123 }, { 72, 81 }, { 141, 126 }, { 37, 56 }, { 5, 77 }, { 144, 192 }, { 35, 32 }, { 6, 100 }, { 151, 25 }, { 28, 26 }, { 108, 174 }, { 96, 28 }, { 196, 84 }, { 44, 29 }, { 100, 78 }, { 109, 97 }, { 193, 69 }, { 118, 189 }, { 101, 161 }, { 172, 197 }, { 10, 94 }, { 198, 76 }, { 178, 170 }, { 4, 158 }, { 97, 50 }, { 112, 194 }, { 39, 189 }, { 157, 131 }, { 62, 96 }, { 146, 101 }, { 4, 40 }, { 107, 181 }, { 181, 102 }, { 53, 98 }, { 185, 116 }, { 76, 119 }, { 45, 94 }, { 83, 178 }, { 1, 156 }, { 38, 78 }, { 157, 84 }, { 103, 181 }, { 161, 60 }, { 156, 186 }, { 71, 165 }, { 10, 90 }, { 111, 27 }, { 32, 2 }, { 135, 24 }, { 58, 92 }, { 53, 65 }, { 103, 195 }, { 3, 113 }, { 66, 22 }, { 125, 73 }, { 58, 42 }, { 9, 137 }, { 70, 87 }, { 116, 95 }, { 121, 4 }, { 53, 46 }, { 61, 7 }, { 112, 35 }, { 175, 125 }, { 194, 33 }, { 183, 167 }, { 172, 156 }, { 27, 173 }, { 142, 98 }, { 23, 165 }, { 25, 30 }, { 99, 92 }, { 48, 134 }, { 16, 109 }, { 100, 60 }, { 176, 63 }, { 83, 57 }, { 137, 120 }, { 61, 157 }, { 78, 32 }, { 154, 132 }, { 179, 72 }, { 99, 90 }, { 194, 1 }, { 11, 127 }, { 159, 129 }, { 114, 198 }, { 156, 49 }, { 111, 79 }, { 18, 42 }, { 46, 156 }, { 157, 37 }, { 21, 73 }, { 114, 9 }, { 151, 12 }, { 124, 140 }, { 183, 34 }, { 134, 63 }, { 194, 75 }, { 151, 99 }, { 85, 120 }, { 108, 85 }, { 106, 68 }, { 48, 24 }, { 15, 42 }, { 118, 198 }, { 91, 131 }, { 88, 146 }, { 123, 55 }, { 77, 173 }, { 176, 16 }, { 66, 103 }, { 153, 42 }, { 64, 182 }, { 85, 150 }, { 5, 34 }, { 69, 174 }, { 34, 129 }, { 74, 179 }, { 49, 86 }, { 195, 90 }, { 88, 99 }, { 184, 29 }, { 110, 80 }, { 144, 173 }, { 49, 12 }, { 27, 157 }, { 98, 16 }, { 157, 170 }, { 126, 27 }, { 64, 55 }, { 17, 16 }, { 180, 157 }, { 33, 100 }, { 6, 88 }, { 107, 124 }, { 175, 66 }, { 71, 158 }, { 85, 111 }, { 166, 143 }, { 8, 100 }, { 5, 59 }, { 111, 11 }, { 22, 104 }, { 183, 194 }, { 135, 185 }, { 110, 43 }, { 147, 192 }, { 79, 140 }, { 130, 126 }, { 96, 85 }, { 107, 67 }, { 160, 122 }, { 149, 178 }, { 10, 150 }, { 140, 172 }, { 128, 111 }, { 77, 170 }, { 102, 11 }, { 60, 65 }, { 30, 163 }, { 5, 94 }, { 181, 45 }, { 76, 68 }, { 95, 24 }, { 89, 152 }, { 2, 96 }, { 50, 1 }, { 173, 81 }, { 174, 42 }, { 136, 38 }, { 120, 60 }, { 21, 107 }, { 0, 197 }, { 74, 148 }, { 96, 186 }, { 114, 57 }, { 184, 24 }, { 194, 99 }, { 86, 16 }, { 135, 144 }, { 110, 177 }, { 58, 170 }, { 33, 104 }, { 164, 77 }, { 29, 98 }, { 188, 103 }, { 105, 26 }, { 26, 179 }, { 163, 101 }, { 95, 118 }, { 120, 123 }, { 187, 178 }, { 8, 176 }, { 35, 64 }, { 67, 104 }, { 5, 48 }, { 44, 114 }, { 105, 45 }, { 32, 171 }, { 134, 164 }, { 99, 19 }, { 93, 98 }, { 128, 117 }, { 22, 77 }, { 42, 143 }, { 94, 67 }, { 147, 122 }, { 130, 87 }, { 96, 27 }, { 42, 90 }, { 72, 104 }, { 52, 53 }, { 168, 134 }, { 164, 109 }, { 76, 199 }, { 6, 127 }, { 11, 49 }, { 8, 19 }, { 167, 121 }, { 158, 46 }, { 120, 167 }, { 43, 167 }, { 149, 179 }, { 152, 115 }, { 86, 5 }, { 61, 147 }, { 72, 162 }, { 138, 51 }, { 54, 146 }, { 88, 190 }, { 88, 199 }, { 8, 117 }, { 11, 48 }, { 144, 78 }, { 77, 19 }, { 38, 161 }, { 115, 9 }, { 74, 126 }, { 113, 178 }, { 116, 146 }, { 124, 10 }, { 39, 17 }, { 16, 148 }, { 81, 197 }, { 114, 138 }, { 55, 84 }, { 65, 111 }, { 154, 176 }, { 189, 35 }, { 96, 175 }, { 92, 182 }, { 73, 67 }, { 141, 152 }, { 167, 100 }, { 67, 172 }, { 16, 73 }, { 32, 93 }, { 12, 126 }, { 35, 1 }, { 13, 167 }, { 55, 98 }, { 15, 163 }, { 18, 11 }, { 78, 132 }, { 95, 104 }, { 174, 170 }, { 16, 30 }, { 148, 87 }, { 91, 41 }, { 111, 189 }, { 135, 172 }, { 113, 60 }, { 196, 147 }, { 64, 88 }, { 141, 5 }, { 19, 62 }, { 179, 142 }, { 155, 111 }, { 87, 48 }, { 25, 158 }, { 67, 41 }, { 118, 131 }, { 53, 167 }, { 26, 106 }, { 145, 144 }, { 172, 142 }, { 82, 135 }, { 91, 110 }, { 167, 193 }, { 63, 86 }, { 17, 97 }, { 104, 157 }, { 133, 177 }, { 30, 129 }, { 38, 91 }, { 186, 190 }, { 144, 38 }, { 176, 7 }, { 139, 57 }, { 52, 193 }, { 96, 64 }, { 57, 84 }, { 40, 8 }, { 93, 103 }, { 32, 92 }, { 164, 90 }, { 180, 8 }, { 168, 23 }, { 95, 34 }, { 154, 58 }, { 92, 17 }, { 176, 112 }, { 101, 110 }, { 109, 23 }, { 154, 59 }, { 44, 98 }, { 32, 41 }, { 39, 119 }, { 159, 141 }, { 177, 46 }, { 120, 71 }, { 114, 199 }, { 160, 66 }, { 81, 56 }, { 73, 28 }, { 89, 185 }, { 130, 91 }, { 158, 67 }, { 68, 74 }, { 59, 143 }, { 130, 64 }, { 74, 124 }, { 19, 170 }, { 103, 80 }, { 136, 94 }, { 121, 143 }, { 20, 176 }, { 173, 10 }, { 53, 106 }, { 72, 78 }, { 46, 140 }, { 105, 125 }, { 86, 36 }, { 9, 151 }, { 41, 182 }, { 80, 77 }, { 55, 63 }, { 127, 82 }, { 67, 160 }, { 164, 64 }, { 164, 60 }, { 191, 10 }, { 74, 20 }, { 10, 172 }, { 104, 136 }, { 166, 30 }, { 128, 10 }, { 119, 151 }, { 154, 150 }, { 93, 190 }, { 155, 85 }, { 161, 132 }, { 46, 153 }, { 96, 70 }, { 89, 75 }, { 15, 150 }, { 31, 0 }, { 155, 152 }, { 189, 18 }, { 123, 154 }, { 67, 51 }, { 77, 29 }, { 34, 140 }, { 113, 1 }, { 68, 19 }, { 190, 100 }, { 68, 43 }, { 167, 154 }, { 91, 133 }, { 154, 169 }, { 76, 165 }, { 149, 28 }, { 117, 23 }, { 13, 15 }, { 170, 0 }, { 156, 58 }, { 40, 57 }, { 62, 6 }, { 50, 110 }, { 40, 116 }, { 52, 69 }, { 6, 69 }, { 26, 146 }, { 72, 68 }, { 75, 32 }, { 72, 56 }, { 72, 33 }, { 194, 125 }, { 51, 190 }, { 184, 26 }, { 101, 170 }, { 145, 126 }, { 32, 176 }, { 22, 60 }, { 14, 198 }, { 17, 189 }, { 148, 177 }, { 194, 84 }, { 87, 55 }, { 71, 172 }, { 41, 164 }, { 183, 46 }, { 155, 199 }, { 84, 56 }, { 137, 150 }, { 138, 155 }, { 23, 116 }, { 9, 20 }, { 104, 115 }, { 64, 158 }, { 150, 57 }, { 75, 58 }, { 117, 54 }, { 72, 62 }, { 160, 184 }, { 2, 167 }, { 170, 56 }, { 137, 194 }, { 42, 181 }, { 84, 137 }, { 148, 175 }, { 29, 33 }, { 16, 126 }, { 135, 191 }, { 186, 1 }, { 70, 43 }, { 77, 178 }, { 194, 124 }, { 28, 30 }, { 114, 195 }, { 111, 146 }, { 137, 116 }, { 6, 41 }, { 168, 63 }, { 149, 91 }, { 0, 125 }, { 180, 96 }, { 57, 78 }, { 154, 42 }, { 92, 87 }, { 198, 63 }, { 2, 40 }, { 153, 141 }, { 191, 60 }, { 131, 1 }, { 119, 146 }, { 96, 184 }, { 83, 56 }, { 111, 158 }, { 119, 135 }, { 71, 93 }, { 155, 25 }, { 78, 109 }, { 140, 55 }, { 80, 34 }, { 119, 111 }, { 152, 171 }, { 193, 91 }, { 6, 23 }, { 110, 21 }, { 125, 146 }, { 20, 70 }, { 187, 78 }, { 72, 139 }, { 59, 45 }, { 196, 63 }, { 130, 128 }, { 78, 11 }, { 80, 151 }, { 22, 68 }, { 177, 68 }, { 77, 145 }, { 87, 150 }, { 16, 18 }, { 129, 107 }, { 87, 126 }, { 77, 12 }, { 121, 196 }, { 173, 162 }, { 126, 163 }, { 192, 110 }, { 129, 149 }, { 144, 30 }, { 191, 198 }, { 27, 140 }, { 128, 114 }, { 136, 56 }, { 137, 37 }, { 64, 46 }, { 44, 36 }, { 111, 106 }, { 165, 160 }, { 13, 8 }, { 97, 115 }, { 118, 152 }, { 141, 179 }, { 114, 6 }, { 80, 139 }, { 121, 120 }, { 102, 169 }, { 95, 136 }, { 3, 178 }, { 80, 97 }, { 112, 129 }, { 24, 39 }, { 36, 159 }, { 137, 159 }, { 178, 61 }, { 10, 109 }, { 107, 5 }, { 137, 61 }, { 136, 110 }, { 157, 17 }, { 159, 125 }, { 154, 9 }, { 39, 152 }, { 185, 136 }, { 105, 23 }, { 91, 3 }, { 117, 148 }, { 195, 109 }, { 150, 153 }, { 13, 45 }, { 171, 40 }, { 183, 137 }, { 187, 181 }, { 34, 148 }, { 84, 192 }, { 77, 74 }, { 1, 135 }, { 14, 5 }, { 149, 49 }, { 77, 104 }, { 160, 68 }, { 160, 88 }, { 93, 72 }, { 189, 195 }, { 173, 69 }, { 71, 96 }, { 172, 30 }, { 15, 158 }, { 189, 185 }, { 102, 185 }, { 101, 62 }, { 165, 15 }, { 178, 0 }, { 69, 29 }, { 7, 132 }, { 123, 79 }, { 0, 30 }, { 108, 17 }, { 81, 190 }, { 181, 78 }, { 162, 29 }, { 112, 74 }, { 168, 41 }, { 150, 44 }, { 145, 88 }, { 23, 148 }, { 187, 34 }, { 174, 12 }, { 33, 63 }, { 179, 138 }, { 27, 199 }, { 198, 83 }, { 65, 192 }, { 197, 10 }, { 92, 81 }, { 12, 22 }, { 56, 34 }, { 101, 190 }, { 21, 5 }, { 153, 54 }, { 191, 197 }, { 106, 140 }, { 45, 14 }, { 189, 164 }, { 151, 139 }, { 61, 140 }, { 171, 67 }, { 0, 28 }, { 188, 106 }, { 68, 7 }, { 72, 120 }, { 73, 94 }, { 136, 182 }, { 155, 102 }, { 141, 60 }, { 47, 16 }, { 17, 54 }, { 23, 102 }, { 1, 197 }, { 68, 158 }, { 69, 73 }, { 112, 188 }, { 43, 138 }, { 19, 183 }, { 49, 61 }, { 196, 174 }, { 190, 84 }, { 158, 154 }, { 105, 195 }, { 61, 54 }, { 35, 167 }, { 134, 29 }, { 74, 96 }, { 104, 109 }, { 87, 136 }, { 176, 78 }, { 33, 18 }, { 176, 161 }, { 163, 100 }, { 158, 190 }, { 153, 23 }, { 61, 85 }, { 130, 109 }, { 162, 110 }, { 29, 4 }, { 31, 66 }, { 58, 165 }, { 118, 194 }, { 147, 77 }, { 146, 139 }, { 180, 183 }, { 144, 186 }, { 58, 157 }, { 137, 19 }, { 153, 175 }, { 112, 95 }, { 176, 172 }, { 163, 29 }, { 156, 75 }, { 29, 15 }, { 147, 149 }, { 104, 48 }, { 135, 152 }, { 67, 65 }, { 153, 127 }, { 70, 14 }, { 114, 141 }, { 29, 183 }, { 144, 115 }, { 191, 150 }, { 73, 76 }, { 130, 168 }, { 181, 165 }, { 116, 12 }, { 91, 10 }, { 184, 177 }, { 123, 156 }, { 36, 157 }, { 123, 191 }, { 67, 87 }, { 184, 151 }, { 44, 70 }, { 139, 98 }, { 85, 163 }, { 61, 128 }, { 29, 0 }, { 192, 0 }, { 108, 189 }, { 170, 87 }, { 147, 117 }, { 179, 112 }, { 162, 57 }, { 41, 126 }, { 68, 112 }, { 135, 74 }, { 92, 15 }, { 159, 20 }, { 23, 123 }, { 87, 5 }, { 83, 135 }, { 6, 169 }, { 8, 145 }, { 7, 103 }, { 2, 118 }, { 1, 25 }, { 48, 86 }, { 176, 158 }, { 12, 10 }, { 28, 111 }, { 55, 50 }, { 171, 191 }, { 80, 27 }, { 166, 147 }, { 33, 22 }, { 125, 48 }, { 71, 123 }, { 156, 108 }, { 3, 69 }, { 178, 190 }, { 4, 126 }, { 37, 2 }, { 140, 112 }, { 118, 147 }, { 176, 61 }, { 176, 175 }, { 12, 64 }, { 73, 24 }, { 11, 24 }, { 111, 141 }, { 77, 82 }, { 52, 166 }, { 119, 141 }, { 165, 114 }, { 160, 91 }, { 24, 101 }, { 196, 115 }, { 75, 166 }, { 131, 140 }, { 51, 165 }, { 20, 122 }, { 34, 65 }, { 19, 30 }, { 140, 108 }, { 65, 78 }, { 126, 155 }, { 137, 42 }, { 167, 79 }, { 25, 122 }, { 81, 57 }, { 49, 140 }, { 60, 163 }, { 163, 193 }, { 128, 185 }, { 182, 7 }, { 100, 181 }, { 33, 185 }, { 120, 178 }, { 97, 2 }, { 91, 137 }, { 143, 40 }, { 50, 127 }, { 57, 157 }, { 38, 0 }, { 32, 44 }, { 186, 196 }, { 132, 87 }, { 77, 64 }, { 199, 151 }, { 192, 106 }, { 135, 30 }, { 118, 169 }, { 158, 88 }, { 66, 71 }, { 17, 174 }, { 178, 156 }, { 19, 152 }, { 25, 2 }, { 121, 90 }, { 136, 12 }, { 50, 197 }, { 24, 191 }, { 22, 10 }, { 23, 156 }, { 171, 154 }, { 39, 51 }, { 31, 38 }, { 144, 93 }, { 114, 82 }, { 83, 8 }, { 166, 87 }, { 118, 67 }, { 172, 166 }, { 172, 18 }, { 98, 109 }, { 74, 121 }, { 92, 68 }, { 50, 53 }, { 106, 125 }, { 84, 179 }, { 34, 199 }, { 96, 132 }, { 107, 127 }, { 124, 62 }, { 75, 59 }, { 152, 131 }, { 198, 171 }, { 0, 173 }, { 35, 99 }, { 127, 113 }, { 44, 194 }, { 129, 49 }, { 187, 125 }, { 134, 180 }, { 92, 53 }, { 14, 80 }, { 11, 23 }, { 37, 136 }, { 10, 178 }, { 125, 56 }, { 158, 11 }, { 114, 13 }, { 135, 4 }, { 182, 106 }, { 58, 11 }, { 18, 12 }, { 93, 54 }, { 165, 31 }, { 119, 53 }, { 36, 58 }, { 65, 96 }, { 6, 126 }, { 61, 71 }, { 67, 12 }, { 161, 56 }, { 73, 164 }, { 128, 107 }, { 26, 65 }, { 70, 162 }, { 133, 193 }, { 37, 116 }, { 193, 121 }, { 158, 33 }, { 92, 103 }, { 106, 85 }, { 190, 146 }, { 189, 131 }, { 29, 149 }, { 53, 111 }, { 99, 53 }, { 77, 8 }, { 67, 187 }, { 78, 121 }, { 184, 171 }, { 178, 152 }, { 191, 82 }, { 190, 152 }, { 67, 136 }, { 103, 32 }, { 40, 91 }, { 95, 70 }, { 174, 152 }, { 178, 90 }, { 136, 119 }, { 25, 22 }, { 57, 119 }, { 107, 94 }, { 129, 48 }, { 63, 108 }, { 136, 113 }, { 70, 72 }, { 53, 79 }, { 96, 140 }, { 115, 183 }, { 174, 165 }, { 49, 162 }, { 0, 109 }, { 60, 55 }, { 106, 166 }, { 172, 23 }, { 79, 65 }, { 160, 130 }, { 114, 197 }, { 174, 199 }, { 187, 24 }, { 97, 69 }, { 11, 35 }, { 104, 103 }, { 110, 189 }, { 194, 109 }, { 112, 170 }, { 194, 172 }, { 34, 40 }, { 100, 38 }, { 18, 169 }, { 77, 13 }, { 188, 54 }, { 111, 144 }, { 88, 91 }, { 81, 194 }, { 22, 192 }, { 198, 155 }, { 97, 172 }, { 68, 141 }, { 8, 182 }, { 69, 149 }, { 137, 198 }, { 167, 56 }, { 70, 61 }, { 186, 120 }, { 101, 125 }, { 124, 176 }, { 178, 57 }, { 77, 108 }, { 49, 198 }, { 66, 83 }, { 14, 72 }, { 14, 43 }, { 82, 0 }, { 91, 90 }, { 103, 131 }, { 7, 192 }, { 53, 62 }, { 20, 125 }, { 144, 39 }, { 116, 187 }, { 144, 129 }, { 51, 136 }, { 95, 90 }, { 155, 6 }, { 69, 183 }, { 179, 10 }, { 80, 154 }, { 126, 197 }, { 139, 152 }, { 46, 119 }, { 18, 111 }, { 181, 23 }, { 127, 68 }, { 96, 15 }, { 44, 50 }, { 9, 76 }, { 134, 55 }, { 63, 4 }, { 147, 80 }, { 156, 72 }, { 24, 141 }, { 10, 1 }, { 161, 31 }, { 11, 39 }, { 141, 86 }, { 189, 84 }, { 164, 119 }, { 94, 142 }, { 97, 163 }, { 17, 12 }, { 78, 179 }, { 28, 120 }, { 169, 126 }, { 5, 186 }, { 49, 93 }, { 65, 38 }, { 185, 100 }, { 145, 52 }, { 194, 101 }, { 52, 120 }, { 167, 133 }, { 132, 65 }, { 125, 164 }, { 109, 134 }, { 187, 166 }, { 186, 60 }, { 52, 138 }, { 99, 124 }, { 162, 16 }, { 24, 67 }, { 93, 37 }, { 76, 31 }, { 156, 68 }, { 94, 138 }, { 154, 88 }, { 155, 22 }, { 192, 58 }, { 106, 2 }, { 155, 11 }, { 27, 55 }, { 9, 163 }, { 0, 169 }, { 21, 137 }, { 83, 93 }, { 103, 151 }, { 117, 14 }, { 63, 131 }, { 5, 88 }, { 197, 199 }, { 34, 39 }, { 103, 164 }, { 144, 79 }, { 94, 35 }, { 139, 165 }, { 181, 140 }, { 197, 18 }, { 22, 87 }, { 125, 64 }, { 19, 160 }, { 71, 53 }, { 54, 144 }, { 17, 149 }, { 94, 38 }, { 161, 108 }, { 6, 60 }, { 1, 40 }, { 12, 134 }, { 36, 28 }, { 0, 34 }, { 45, 162 }, { 176, 96 }, { 184, 166 }, { 108, 3 }, { 99, 86 }, { 39, 105 }, { 190, 20 }, { 189, 80 }, { 172, 24 }, { 103, 76 }, { 126, 157 }, { 21, 182 }, { 100, 40 }, { 154, 84 }, { 103, 44 }, { 94, 196 }, { 162, 42 }, { 137, 141 }, { 18, 109 }, { 152, 117 }, { 147, 14 }, { 156, 35 }, { 5, 181 }, { 42, 13 }, { 47, 6 }, { 40, 52 }, { 166, 199 }, { 83, 189 }, { 121, 139 }, { 75, 97 }, { 36, 141 }, { 119, 17 }, { 11, 156 }, { 198, 108 }, { 104, 59 }, { 194, 186 }, { 54, 57 }, { 143, 171 }, { 158, 19 }, { 8, 72 }, { 179, 117 }, { 143, 151 }, { 35, 111 }, { 80, 91 }, { 92, 186 }, { 197, 109 }, { 174, 45 }, { 34, 124 }, { 60, 51 }, { 3, 96 }, { 187, 41 }, { 106, 57 }, { 50, 105 }, { 79, 119 }, { 8, 181 }, { 177, 24 }, { 69, 139 }, { 155, 84 }, { 66, 16 }, { 177, 5 }, { 6, 89 }, { 86, 90 }, { 141, 52 }, { 28, 163 }, { 103, 61 }, { 134, 94 }, { 27, 116 }, { 133, 160 }, { 78, 172 }, { 45, 183 }, { 148, 79 }, { 108, 124 }, { 135, 94 }, { 181, 123 }, { 24, 161 }, { 170, 199 }, { 95, 46 }, { 19, 18 }, { 188, 20 }, { 59, 39 }, { 167, 28 }, { 86, 128 }, { 44, 130 }, { 80, 7 }, { 113, 26 }, { 119, 116 }, { 57, 153 }, { 30, 80 }, { 36, 131 }, { 62, 80 }, { 109, 147 }, { 179, 123 }, { 135, 163 }, { 147, 68 }, { 80, 124 }, { 49, 167 }, { 18, 4 }, { 132, 143 }, { 93, 139 }, { 113, 170 }, { 46, 76 }, { 71, 64 }, { 118, 112 }, { 127, 178 }, { 194, 69 }, { 50, 126 }, { 1, 107 }, { 167, 5 }, { 132, 89 }, { 102, 36 }, { 151, 46 }, { 100, 184 }, { 181, 39 }, { 37, 6 }, { 66, 138 }, { 198, 8 }, { 168, 178 }, { 130, 176 }, { 150, 23 }, { 164, 157 }, { 182, 170 }, { 27, 147 }, { 177, 118 }, { 10, 153 }, { 141, 101 }, { 76, 26 }, { 117, 84 }, { 64, 108 }, { 180, 6 }, { 102, 192 }, { 138, 164 }, { 177, 157 }, { 46, 114 }, { 153, 86 }, { 113, 75 }, { 131, 174 }, { 184, 112 }, { 29, 95 }, { 48, 1 }, { 25, 150 }, { 132, 13 }, { 2, 188 }, { 97, 162 }, { 68, 173 }, { 118, 117 }, { 98, 163 }, { 159, 66 }, { 131, 159 }, { 82, 133 }, { 100, 110 }, { 128, 50 }, { 72, 141 }, { 9, 55 }, { 195, 44 }, { 38, 50 }, { 163, 196 }, { 46, 74 }, { 75, 139 }, { 122, 183 }, { 5, 27 }, { 111, 166 }, { 112, 61 }, { 129, 130 }, { 6, 85 }, { 91, 191 }, { 197, 69 }, { 31, 41 }, { 75, 154 }, { 135, 111 }, { 56, 36 }, { 37, 148 }, { 139, 130 }, { 158, 24 }, { 80, 196 }, { 14, 114 }, { 189, 23 }, { 78, 74 }, { 153, 76 }, { 46, 185 }, { 168, 16 }, { 40, 35 }, { 183, 118 }, { 139, 33 }, { 95, 55 }, { 132, 150 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); Matching m = matcher.getMatching(); verifyMatching(graph, m, 100); assertTrue(m.isPerfect()); for (Integer v : graph.vertexSet()) assertTrue(m.isMatched(v)); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testGraph3() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 4, 141 }, { 63, 132 }, { 129, 144 }, { 6, 88 }, { 62, 79 }, { 4, 79 }, { 125, 88 }, { 26, 133 }, { 21, 152 }, { 98, 80 }, { 107, 55 }, { 8, 33 }, { 153, 74 }, { 179, 6 }, { 79, 42 }, { 148, 146 }, { 27, 197 }, { 43, 22 }, { 154, 21 }, { 184, 26 }, { 197, 199 }, { 144, 102 }, { 136, 155 }, { 131, 163 }, { 118, 117 }, { 74, 34 }, { 168, 166 }, { 119, 72 }, { 148, 7 }, { 84, 46 }, { 34, 156 }, { 133, 97 }, { 42, 193 }, { 66, 122 }, { 81, 108 }, { 36, 132 }, { 3, 134 }, { 153, 44 }, { 98, 111 }, { 75, 122 }, { 116, 189 }, { 50, 36 }, { 43, 33 }, { 26, 73 }, { 13, 102 }, { 15, 121 }, { 188, 166 }, { 93, 102 }, { 8, 99 }, { 60, 78 }, { 32, 143 }, { 152, 168 }, { 72, 65 }, { 38, 153 }, { 117, 125 }, { 139, 186 }, { 195, 38 }, { 71, 40 }, { 15, 178 }, { 118, 183 }, { 112, 10 }, { 15, 148 }, { 152, 181 }, { 6, 190 }, { 177, 48 }, { 52, 47 }, { 11, 180 }, { 30, 61 }, { 186, 187 }, { 131, 167 }, { 84, 40 }, { 198, 126 }, { 135, 139 }, { 84, 3 }, { 161, 86 }, { 39, 63 }, { 186, 144 }, { 137, 154 }, { 195, 91 }, { 165, 187 }, { 170, 155 }, { 79, 121 }, { 85, 5 }, { 179, 124 }, { 100, 49 }, { 58, 51 }, { 59, 62 }, { 58, 91 }, { 85, 17 }, { 85, 0 }, { 68, 154 }, { 185, 171 }, { 13, 11 }, { 192, 32 }, { 169, 157 }, { 133, 19 }, { 93, 112 }, { 23, 71 }, { 59, 79 }, { 171, 170 }, { 41, 182 }, { 97, 24 }, { 71, 162 }, { 105, 3 }, { 183, 91 }, { 78, 172 }, { 165, 96 }, { 120, 184 }, { 182, 159 }, { 184, 34 }, { 85, 143 }, { 156, 129 }, { 151, 36 }, { 114, 94 }, { 16, 14 }, { 33, 12 }, { 47, 23 }, { 107, 180 }, { 108, 119 }, { 64, 27 }, { 186, 30 }, { 196, 51 }, { 104, 117 }, { 15, 99 }, { 73, 17 }, { 53, 132 }, { 35, 37 }, { 76, 169 }, { 165, 186 }, { 35, 129 }, { 97, 54 }, { 83, 77 }, { 65, 71 }, { 85, 192 }, { 77, 58 }, { 42, 176 }, { 195, 149 }, { 58, 144 }, { 160, 117 }, { 164, 135 }, { 170, 196 }, { 108, 17 }, { 144, 26 }, { 186, 15 }, { 161, 127 }, { 167, 173 }, { 145, 75 }, { 171, 57 }, { 50, 146 }, { 74, 131 }, { 7, 191 }, { 101, 149 }, { 60, 140 }, { 116, 120 }, { 193, 115 }, { 89, 128 }, { 109, 37 }, { 64, 37 }, { 127, 60 }, { 154, 104 }, { 192, 118 }, { 57, 174 }, { 69, 153 }, { 78, 76 }, { 120, 181 }, { 142, 47 }, { 69, 123 }, { 171, 110 }, { 26, 32 }, { 38, 39 }, { 72, 93 }, { 61, 102 }, { 174, 110 }, { 24, 78 }, { 63, 12 }, { 13, 64 }, { 40, 115 }, { 135, 106 }, { 46, 11 }, { 157, 177 }, { 188, 112 }, { 9, 87 }, { 138, 4 }, { 189, 128 }, { 153, 54 }, { 61, 145 }, { 170, 38 }, { 7, 126 }, { 46, 19 }, { 87, 79 }, { 88, 140 }, { 191, 190 }, { 55, 127 }, { 68, 183 }, { 64, 49 }, { 180, 164 }, { 64, 139 }, { 91, 124 }, { 118, 53 }, { 148, 16 }, { 23, 73 }, { 100, 114 }, { 59, 183 }, { 35, 42 }, { 45, 17 }, { 84, 86 }, { 65, 194 }, { 92, 109 }, { 181, 119 }, { 183, 128 }, { 130, 162 }, { 165, 197 }, { 156, 127 }, { 76, 90 }, { 180, 198 }, { 127, 122 }, { 103, 100 }, { 188, 39 }, { 55, 93 }, { 188, 69 }, { 191, 90 }, { 83, 183 }, { 20, 90 }, { 95, 144 }, { 15, 145 }, { 175, 74 }, { 23, 128 }, { 60, 178 }, { 145, 3 }, { 174, 35 }, { 155, 164 }, { 172, 129 }, { 193, 158 }, { 72, 157 }, { 22, 180 }, { 31, 43 }, { 24, 6 }, { 175, 10 }, { 124, 164 }, { 169, 7 }, { 2, 114 }, { 117, 126 }, { 179, 80 }, { 149, 63 }, { 183, 13 }, { 66, 153 }, { 35, 160 }, { 130, 29 }, { 15, 2 }, { 124, 58 }, { 38, 27 }, { 146, 168 }, { 150, 7 }, { 76, 83 }, { 32, 45 }, { 182, 14 }, { 1, 84 }, { 63, 169 }, { 23, 114 }, { 162, 9 }, { 31, 83 }, { 146, 19 }, { 67, 186 }, { 103, 101 }, { 10, 103 }, { 189, 136 }, { 79, 77 }, { 147, 181 }, { 59, 127 }, { 161, 11 }, { 173, 38 }, { 10, 58 }, { 8, 89 }, { 185, 152 }, { 22, 74 }, { 56, 118 }, { 120, 89 }, { 84, 6 }, { 175, 71 }, { 76, 115 }, { 101, 73 }, { 88, 92 }, { 149, 143 }, { 119, 86 }, { 17, 160 }, { 176, 165 }, { 49, 52 }, { 74, 71 }, { 113, 166 }, { 71, 94 }, { 92, 27 }, { 3, 160 }, { 173, 179 }, { 187, 5 }, { 172, 115 }, { 16, 4 }, { 37, 85 }, { 26, 113 }, { 12, 37 }, { 1, 103 }, { 133, 80 }, { 183, 22 }, { 136, 91 }, { 50, 65 }, { 193, 53 }, { 101, 112 }, { 141, 10 }, { 46, 61 }, { 73, 142 }, { 186, 60 }, { 109, 66 }, { 29, 91 }, { 94, 21 }, { 54, 124 }, { 153, 106 }, { 110, 68 }, { 58, 82 }, { 169, 193 }, { 28, 14 }, { 165, 132 }, { 108, 140 }, { 103, 128 }, { 46, 51 }, { 22, 111 }, { 49, 164 }, { 7, 32 }, { 126, 191 }, { 63, 190 }, { 171, 7 }, { 79, 80 }, { 71, 147 }, { 161, 104 }, { 166, 2 }, { 185, 179 }, { 83, 146 }, { 87, 180 }, { 141, 101 }, { 137, 125 }, { 66, 89 }, { 14, 107 }, { 9, 35 }, { 13, 164 }, { 140, 15 }, { 179, 120 }, { 138, 70 }, { 19, 25 }, { 130, 116 }, { 175, 161 }, { 99, 12 }, { 117, 71 }, { 121, 11 }, { 22, 149 }, { 57, 46 }, { 8, 184 }, { 46, 153 }, { 178, 85 }, { 52, 166 }, { 103, 197 }, { 114, 181 }, { 28, 29 }, { 101, 110 }, { 188, 92 }, { 103, 88 }, { 132, 73 }, { 150, 77 }, { 96, 169 }, { 120, 164 }, { 131, 90 }, { 108, 50 }, { 182, 127 }, { 100, 63 }, { 128, 25 }, { 184, 9 }, { 19, 86 }, { 132, 87 }, { 143, 184 }, { 105, 91 }, { 16, 68 }, { 16, 84 }, { 163, 86 }, { 66, 87 }, { 14, 62 }, { 78, 2 }, { 148, 89 }, { 2, 22 }, { 176, 198 }, { 178, 30 }, { 1, 50 }, { 47, 104 }, { 100, 11 }, { 144, 38 }, { 33, 137 }, { 74, 102 }, { 179, 44 }, { 40, 10 }, { 117, 16 }, { 91, 57 }, { 110, 25 }, { 141, 92 }, { 167, 188 }, { 26, 120 }, { 116, 107 }, { 60, 94 }, { 62, 151 }, { 118, 177 }, { 77, 105 }, { 194, 124 }, { 43, 13 }, { 174, 125 }, { 180, 163 }, { 56, 34 }, { 9, 91 }, { 58, 38 }, { 116, 108 }, { 58, 176 }, { 190, 154 }, { 124, 26 }, { 170, 56 }, { 136, 35 }, { 45, 35 }, { 100, 106 }, { 81, 52 }, { 57, 81 }, { 15, 30 }, { 165, 182 }, { 95, 114 }, { 107, 140 }, { 129, 122 }, { 149, 40 }, { 101, 145 }, { 196, 106 }, { 191, 166 }, { 168, 30 }, { 106, 43 }, { 83, 62 }, { 45, 174 }, { 135, 6 }, { 2, 3 }, { 80, 35 }, { 171, 188 }, { 116, 25 }, { 192, 182 }, { 87, 15 }, { 27, 25 }, { 116, 129 }, { 173, 84 }, { 141, 26 }, { 185, 82 }, { 155, 196 }, { 198, 45 }, { 18, 29 }, { 59, 80 }, { 153, 29 }, { 92, 126 }, { 109, 83 }, { 77, 151 }, { 95, 26 }, { 65, 73 }, { 188, 38 }, { 69, 2 }, { 44, 163 }, { 109, 45 }, { 107, 65 }, { 1, 160 }, { 34, 24 }, { 71, 198 }, { 160, 125 }, { 35, 133 }, { 97, 126 }, { 41, 118 }, { 49, 48 }, { 34, 117 }, { 18, 82 }, { 4, 140 }, { 184, 125 }, { 116, 192 }, { 86, 98 }, { 168, 7 }, { 135, 69 }, { 131, 113 }, { 57, 162 }, { 115, 88 }, { 163, 65 }, { 26, 63 }, { 27, 54 }, { 129, 126 }, { 66, 1 }, { 38, 198 }, { 19, 18 }, { 150, 111 }, { 0, 151 }, { 25, 93 }, { 104, 27 }, { 16, 40 }, { 188, 77 }, { 179, 14 }, { 151, 29 }, { 79, 0 }, { 134, 29 }, { 28, 22 }, { 23, 97 }, { 181, 160 }, { 37, 141 }, { 129, 26 }, { 185, 130 }, { 182, 10 }, { 189, 197 }, { 53, 25 }, { 195, 4 }, { 32, 164 }, { 66, 62 }, { 96, 199 }, { 80, 85 }, { 84, 45 }, { 83, 90 }, { 139, 21 }, { 153, 6 }, { 154, 84 }, { 135, 169 }, { 89, 132 }, { 110, 121 }, { 176, 22 }, { 90, 120 }, { 8, 153 }, { 69, 9 }, { 28, 182 }, { 105, 177 }, { 101, 31 }, { 106, 127 }, { 173, 68 }, { 81, 15 }, { 19, 162 }, { 173, 81 }, { 165, 41 }, { 99, 136 }, { 52, 152 }, { 199, 34 }, { 185, 47 }, { 91, 83 }, { 61, 64 }, { 164, 134 }, { 158, 90 }, { 116, 17 }, { 126, 132 }, { 153, 132 }, { 6, 59 }, { 149, 174 }, { 63, 48 }, { 7, 108 }, { 193, 25 }, { 150, 127 }, { 28, 58 }, { 166, 81 }, { 84, 128 }, { 155, 91 }, { 178, 170 }, { 154, 134 }, { 109, 44 }, { 199, 140 }, { 15, 1 }, { 185, 178 }, { 11, 148 }, { 106, 133 }, { 13, 179 }, { 179, 165 }, { 90, 25 }, { 60, 123 }, { 182, 151 }, { 88, 154 }, { 133, 198 }, { 191, 189 }, { 129, 179 }, { 181, 61 }, { 50, 143 }, { 103, 117 }, { 3, 114 }, { 142, 180 }, { 33, 20 }, { 45, 134 }, { 191, 159 }, { 61, 184 }, { 180, 20 }, { 183, 38 }, { 142, 169 }, { 153, 178 }, { 0, 84 }, { 74, 91 }, { 167, 127 }, { 119, 136 }, { 34, 96 }, { 152, 175 }, { 16, 107 }, { 133, 119 }, { 123, 86 }, { 89, 166 }, { 162, 121 }, { 41, 72 }, { 60, 128 }, { 54, 173 }, { 128, 70 }, { 165, 133 }, { 34, 183 }, { 160, 34 }, { 152, 115 }, { 158, 146 }, { 74, 18 }, { 109, 104 }, { 72, 48 }, { 88, 126 }, { 125, 143 }, { 35, 17 }, { 1, 11 }, { 147, 177 }, { 59, 140 }, { 56, 177 }, { 41, 198 }, { 150, 83 }, { 159, 190 }, { 199, 89 }, { 198, 138 }, { 67, 18 }, { 16, 94 }, { 60, 158 }, { 188, 91 }, { 191, 11 }, { 42, 91 }, { 191, 72 }, { 140, 45 }, { 122, 159 }, { 65, 62 }, { 95, 129 }, { 152, 108 }, { 144, 147 }, { 10, 191 }, { 135, 109 }, { 0, 36 }, { 77, 27 }, { 35, 71 }, { 54, 26 }, { 131, 93 }, { 136, 152 }, { 191, 164 }, { 81, 176 }, { 19, 31 }, { 104, 17 }, { 32, 81 }, { 132, 75 }, { 133, 29 }, { 114, 157 }, { 35, 32 }, { 194, 85 }, { 70, 36 }, { 40, 117 }, { 136, 70 }, { 102, 2 }, { 34, 132 }, { 101, 146 }, { 182, 94 }, { 80, 65 }, { 121, 112 }, { 97, 47 }, { 21, 183 }, { 40, 171 }, { 14, 168 }, { 167, 0 }, { 153, 157 }, { 115, 133 }, { 47, 125 }, { 174, 39 }, { 79, 31 }, { 114, 102 }, { 162, 147 }, { 184, 25 }, { 8, 53 }, { 94, 126 }, { 136, 143 }, { 167, 58 }, { 180, 81 }, { 149, 49 }, { 43, 80 }, { 169, 155 }, { 72, 192 }, { 147, 108 }, { 87, 39 }, { 13, 101 }, { 48, 64 }, { 177, 188 }, { 148, 96 }, { 163, 117 }, { 172, 41 }, { 106, 59 }, { 113, 193 }, { 152, 16 }, { 95, 54 }, { 24, 156 }, { 154, 176 }, { 31, 117 }, { 114, 19 }, { 131, 156 }, { 187, 143 }, { 128, 144 }, { 45, 22 }, { 137, 25 }, { 123, 113 }, { 84, 50 }, { 199, 111 }, { 142, 12 }, { 138, 67 }, { 14, 148 }, { 136, 41 }, { 150, 56 }, { 82, 142 }, { 3, 35 }, { 61, 73 }, { 141, 90 }, { 192, 129 }, { 18, 138 }, { 68, 4 }, { 78, 1 }, { 28, 136 }, { 99, 122 }, { 16, 28 }, { 4, 114 }, { 158, 114 }, { 58, 65 }, { 85, 124 }, { 122, 72 }, { 172, 142 }, { 80, 90 }, { 98, 145 }, { 153, 17 }, { 135, 78 }, { 34, 191 }, { 59, 9 }, { 180, 160 }, { 181, 40 }, { 35, 70 }, { 96, 147 }, { 0, 162 }, { 199, 71 }, { 160, 23 }, { 59, 7 }, { 45, 199 }, { 156, 186 }, { 191, 79 }, { 188, 41 }, { 187, 176 }, { 1, 36 }, { 1, 42 }, { 34, 1 }, { 4, 164 }, { 121, 34 }, { 123, 172 }, { 191, 74 }, { 188, 183 }, { 157, 148 }, { 8, 126 }, { 38, 7 }, { 178, 118 }, { 114, 154 }, { 25, 18 }, { 141, 128 }, { 108, 135 }, { 167, 104 }, { 69, 148 }, { 92, 52 }, { 198, 76 }, { 67, 172 }, { 128, 137 }, { 172, 95 }, { 83, 22 }, { 131, 101 }, { 88, 17 }, { 161, 163 }, { 146, 27 }, { 121, 107 }, { 66, 197 }, { 56, 120 }, { 82, 26 }, { 109, 75 }, { 137, 195 }, { 177, 197 }, { 21, 115 }, { 104, 18 }, { 31, 71 }, { 157, 14 }, { 80, 41 }, { 132, 55 }, { 2, 138 }, { 128, 38 }, { 57, 121 }, { 37, 187 }, { 16, 181 }, { 0, 196 }, { 79, 18 }, { 186, 1 }, { 170, 195 }, { 115, 47 }, { 46, 173 }, { 83, 80 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 100); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testGraph4() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 142, 104 }, { 176, 103 }, { 117, 140 }, { 9, 160 }, { 23, 106 }, { 120, 11 }, { 55, 110 }, { 9, 176 }, { 171, 183 }, { 27, 42 }, { 101, 122 }, { 179, 12 }, { 59, 122 }, { 10, 7 }, { 48, 68 }, { 48, 64 }, { 20, 1 }, { 155, 86 }, { 111, 45 }, { 56, 137 }, { 29, 149 }, { 77, 110 }, { 135, 86 }, { 192, 87 }, { 198, 199 }, { 96, 143 }, { 28, 72 }, { 94, 163 }, { 65, 196 }, { 159, 20 }, { 151, 90 }, { 137, 146 }, { 74, 18 }, { 55, 146 }, { 95, 74 }, { 195, 95 }, { 112, 80 }, { 47, 95 }, { 2, 10 }, { 168, 188 }, { 179, 137 }, { 48, 147 }, { 179, 68 }, { 39, 81 }, { 102, 9 }, { 12, 89 }, { 50, 102 }, { 133, 27 }, { 12, 150 }, { 193, 31 }, { 66, 159 }, { 78, 118 }, { 52, 15 }, { 149, 153 }, { 139, 175 }, { 126, 59 }, { 54, 176 }, { 32, 65 }, { 118, 34 }, { 129, 18 }, { 61, 188 }, { 87, 122 }, { 47, 21 }, { 185, 136 }, { 12, 1 }, { 141, 159 }, { 114, 119 }, { 150, 58 }, { 75, 79 }, { 25, 121 }, { 40, 105 }, { 108, 0 }, { 130, 89 }, { 188, 174 }, { 64, 198 }, { 50, 3 }, { 42, 105 }, { 2, 194 }, { 105, 187 }, { 119, 118 }, { 185, 191 }, { 38, 17 }, { 196, 175 }, { 77, 87 }, { 43, 107 }, { 56, 122 }, { 108, 52 }, { 80, 7 }, { 27, 70 }, { 72, 45 }, { 30, 36 }, { 29, 70 }, { 186, 109 }, { 89, 45 }, { 19, 12 }, { 181, 39 }, { 92, 141 }, { 41, 7 }, { 91, 75 }, { 193, 106 }, { 184, 23 }, { 69, 185 }, { 90, 11 }, { 149, 12 }, { 166, 165 }, { 101, 199 }, { 167, 152 }, { 0, 3 }, { 121, 168 }, { 107, 131 }, { 190, 1 }, { 195, 182 }, { 129, 54 }, { 149, 31 }, { 141, 173 }, { 61, 80 }, { 5, 153 }, { 88, 60 }, { 143, 187 }, { 86, 97 }, { 22, 163 }, { 143, 108 }, { 50, 45 }, { 9, 87 }, { 6, 103 }, { 75, 125 }, { 166, 111 }, { 9, 159 }, { 27, 57 }, { 101, 175 }, { 37, 125 }, { 22, 113 }, { 68, 71 }, { 48, 113 }, { 122, 168 }, { 136, 135 }, { 136, 18 }, { 89, 31 }, { 164, 193 }, { 64, 53 }, { 124, 117 }, { 16, 22 }, { 154, 140 }, { 179, 122 }, { 107, 108 }, { 70, 166 }, { 189, 118 }, { 64, 54 }, { 197, 62 }, { 139, 127 }, { 55, 169 }, { 106, 20 }, { 135, 172 }, { 24, 192 }, { 97, 66 }, { 54, 199 }, { 78, 186 }, { 52, 198 }, { 20, 45 }, { 45, 117 }, { 158, 177 }, { 162, 21 }, { 158, 35 }, { 165, 51 }, { 17, 41 }, { 167, 118 }, { 80, 116 }, { 101, 62 }, { 2, 23 }, { 17, 81 }, { 41, 192 }, { 10, 93 }, { 42, 95 }, { 129, 179 }, { 156, 13 }, { 15, 172 }, { 174, 164 }, { 21, 117 }, { 192, 58 }, { 187, 84 }, { 117, 103 }, { 183, 42 }, { 62, 192 }, { 19, 70 }, { 32, 173 }, { 77, 114 }, { 166, 77 }, { 5, 41 }, { 189, 2 }, { 39, 74 }, { 183, 0 }, { 144, 182 }, { 153, 30 }, { 198, 101 }, { 11, 137 }, { 132, 49 }, { 191, 15 }, { 97, 100 }, { 184, 48 }, { 164, 54 }, { 24, 145 }, { 174, 70 }, { 174, 83 }, { 36, 145 }, { 3, 128 }, { 104, 17 }, { 143, 29 }, { 147, 149 }, { 133, 75 }, { 153, 110 }, { 48, 192 }, { 112, 1 }, { 88, 91 }, { 14, 104 }, { 140, 28 }, { 159, 180 }, { 133, 113 }, { 136, 21 }, { 197, 125 }, { 27, 105 }, { 195, 18 }, { 87, 179 }, { 60, 168 }, { 107, 35 }, { 184, 62 }, { 143, 36 }, { 54, 173 }, { 198, 18 }, { 44, 101 }, { 12, 50 }, { 7, 54 }, { 137, 12 }, { 99, 104 }, { 191, 27 }, { 95, 78 }, { 93, 133 }, { 153, 77 }, { 8, 21 }, { 66, 187 }, { 115, 110 }, { 85, 123 }, { 75, 146 }, { 145, 197 }, { 18, 185 }, { 192, 153 }, { 30, 189 }, { 27, 124 }, { 188, 122 }, { 85, 19 }, { 190, 67 }, { 97, 36 }, { 183, 111 }, { 184, 133 }, { 63, 43 }, { 139, 100 }, { 192, 193 }, { 193, 21 }, { 171, 78 }, { 21, 194 }, { 167, 105 }, { 96, 108 }, { 63, 118 }, { 86, 48 }, { 191, 171 }, { 64, 189 }, { 3, 98 }, { 149, 162 }, { 108, 165 }, { 53, 37 }, { 128, 96 }, { 156, 69 }, { 140, 88 }, { 48, 137 }, { 145, 2 }, { 199, 17 }, { 17, 150 }, { 31, 130 }, { 172, 73 }, { 51, 184 }, { 67, 122 }, { 183, 107 }, { 104, 140 }, { 113, 156 }, { 192, 50 }, { 36, 81 }, { 23, 66 }, { 122, 156 }, { 62, 48 }, { 29, 2 }, { 195, 179 }, { 74, 47 }, { 45, 44 }, { 42, 158 }, { 49, 58 }, { 86, 62 }, { 134, 171 }, { 127, 9 }, { 67, 5 }, { 104, 54 }, { 88, 43 }, { 104, 198 }, { 111, 59 }, { 88, 147 }, { 152, 108 }, { 157, 4 }, { 115, 12 }, { 170, 166 }, { 54, 119 }, { 85, 61 }, { 179, 189 }, { 196, 160 }, { 36, 18 }, { 4, 138 }, { 150, 33 }, { 62, 92 }, { 7, 146 }, { 158, 135 }, { 86, 56 }, { 154, 24 }, { 118, 32 }, { 51, 101 }, { 62, 91 }, { 91, 52 }, { 16, 188 }, { 35, 34 }, { 132, 77 }, { 175, 72 }, { 160, 156 }, { 185, 170 }, { 54, 195 }, { 47, 66 }, { 26, 5 }, { 154, 177 }, { 38, 84 }, { 100, 189 }, { 156, 64 }, { 125, 190 }, { 40, 138 }, { 57, 131 }, { 40, 134 }, { 105, 90 }, { 128, 31 }, { 197, 172 }, { 38, 92 }, { 19, 134 }, { 95, 88 }, { 191, 4 }, { 140, 184 }, { 24, 168 }, { 53, 93 }, { 106, 168 }, { 140, 102 }, { 5, 78 }, { 168, 193 }, { 129, 42 }, { 11, 144 }, { 165, 175 }, { 9, 23 }, { 91, 151 }, { 182, 34 }, { 173, 148 }, { 75, 174 }, { 9, 133 }, { 179, 47 }, { 37, 197 }, { 160, 100 }, { 139, 46 }, { 167, 39 }, { 113, 27 }, { 133, 24 }, { 112, 27 }, { 14, 8 }, { 111, 36 }, { 138, 151 }, { 126, 9 }, { 44, 115 }, { 125, 52 }, { 142, 50 }, { 35, 177 }, { 139, 44 }, { 120, 181 }, { 112, 12 }, { 59, 158 }, { 0, 157 }, { 177, 184 }, { 199, 176 }, { 187, 169 }, { 184, 162 }, { 158, 55 }, { 95, 96 }, { 187, 146 }, { 79, 74 }, { 106, 87 }, { 131, 157 }, { 21, 150 }, { 43, 93 }, { 20, 69 }, { 13, 31 }, { 109, 133 }, { 77, 180 }, { 70, 130 }, { 171, 73 }, { 137, 121 }, { 24, 187 }, { 146, 42 }, { 116, 105 }, { 192, 164 }, { 54, 194 }, { 190, 7 }, { 57, 21 }, { 60, 21 }, { 176, 111 }, { 135, 66 }, { 54, 62 }, { 33, 19 }, { 76, 188 }, { 30, 11 }, { 88, 176 }, { 197, 127 }, { 110, 31 }, { 184, 115 }, { 62, 136 }, { 176, 134 }, { 17, 20 }, { 63, 33 }, { 177, 164 }, { 51, 53 }, { 53, 157 }, { 92, 9 }, { 157, 78 }, { 43, 51 }, { 56, 138 }, { 150, 6 }, { 16, 185 }, { 12, 97 }, { 74, 129 }, { 152, 65 }, { 159, 188 }, { 20, 126 }, { 2, 126 }, { 55, 103 }, { 14, 18 }, { 142, 155 }, { 56, 62 }, { 120, 123 }, { 69, 40 }, { 6, 9 }, { 154, 39 }, { 160, 15 }, { 1, 146 }, { 182, 157 }, { 100, 133 }, { 71, 186 }, { 10, 179 }, { 130, 171 }, { 91, 141 }, { 199, 130 }, { 2, 63 }, { 144, 118 }, { 198, 20 }, { 185, 176 }, { 180, 96 }, { 129, 78 }, { 5, 91 }, { 4, 184 }, { 112, 70 }, { 127, 7 }, { 148, 150 }, { 16, 21 }, { 83, 13 }, { 151, 16 }, { 46, 31 }, { 52, 57 }, { 73, 10 }, { 78, 105 }, { 131, 143 }, { 173, 18 }, { 21, 38 }, { 38, 3 }, { 164, 86 }, { 149, 177 }, { 199, 84 }, { 4, 173 }, { 109, 80 }, { 96, 127 }, { 160, 72 }, { 54, 179 }, { 44, 47 }, { 33, 126 }, { 53, 184 }, { 155, 36 }, { 129, 21 }, { 43, 118 }, { 16, 54 }, { 67, 43 }, { 144, 62 }, { 108, 103 }, { 178, 174 }, { 184, 81 }, { 139, 21 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 99); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testGraph5() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 55, 4 }, { 9, 118 }, { 70, 115 }, { 179, 146 }, { 122, 136 }, { 192, 91 }, { 100, 158 }, { 5, 22 }, { 72, 118 }, { 88, 10 }, { 192, 10 }, { 73, 133 }, { 144, 187 }, { 189, 153 }, { 69, 154 }, { 89, 2 }, { 63, 144 }, { 187, 126 }, { 38, 115 }, { 19, 10 }, { 128, 77 }, { 49, 45 }, { 176, 50 }, { 185, 60 }, { 34, 22 }, { 105, 82 }, { 179, 8 }, { 107, 120 }, { 102, 103 }, { 157, 80 }, { 49, 0 }, { 174, 130 }, { 158, 33 }, { 195, 98 }, { 109, 93 }, { 64, 31 }, { 39, 132 }, { 26, 88 }, { 77, 78 }, { 8, 164 }, { 143, 141 }, { 162, 110 }, { 128, 188 }, { 194, 148 }, { 183, 39 }, { 0, 19 }, { 185, 128 }, { 129, 144 }, { 73, 51 }, { 151, 5 }, { 121, 175 }, { 75, 182 }, { 130, 178 }, { 79, 159 }, { 32, 167 }, { 128, 92 }, { 193, 103 }, { 1, 84 }, { 68, 177 }, { 115, 179 }, { 134, 183 }, { 192, 99 }, { 191, 79 }, { 39, 142 }, { 99, 42 }, { 81, 155 }, { 93, 133 }, { 106, 194 }, { 62, 65 }, { 107, 21 }, { 43, 137 }, { 148, 142 }, { 132, 143 }, { 160, 119 }, { 17, 44 }, { 153, 90 }, { 7, 51 }, { 129, 141 }, { 40, 88 }, { 26, 193 }, { 169, 74 }, { 62, 128 }, { 189, 89 }, { 80, 120 }, { 54, 86 }, { 139, 104 }, { 43, 23 }, { 169, 94 }, { 37, 43 }, { 107, 35 }, { 28, 24 }, { 24, 20 }, { 15, 166 }, { 145, 110 }, { 1, 191 }, { 73, 132 }, { 6, 30 }, { 153, 144 }, { 76, 34 }, { 137, 84 }, { 175, 53 }, { 195, 20 }, { 82, 18 }, { 16, 110 }, { 40, 92 }, { 90, 41 }, { 132, 94 }, { 34, 70 }, { 186, 0 }, { 60, 41 }, { 63, 20 }, { 16, 7 }, { 48, 193 }, { 138, 177 }, { 164, 122 }, { 79, 11 }, { 3, 135 }, { 43, 52 }, { 160, 43 }, { 145, 15 }, { 93, 180 }, { 42, 148 }, { 83, 85 }, { 194, 9 }, { 55, 185 }, { 100, 13 }, { 16, 14 }, { 101, 18 }, { 92, 84 }, { 174, 52 }, { 82, 137 }, { 139, 146 }, { 35, 26 }, { 160, 48 }, { 107, 102 }, { 178, 172 }, { 165, 145 }, { 71, 128 }, { 122, 60 }, { 36, 196 }, { 185, 91 }, { 187, 170 }, { 133, 27 }, { 52, 119 }, { 145, 105 }, { 53, 62 }, { 130, 38 }, { 79, 58 }, { 142, 20 }, { 89, 143 }, { 194, 31 }, { 70, 86 }, { 145, 66 }, { 9, 51 }, { 65, 109 }, { 41, 77 }, { 48, 169 }, { 159, 162 }, { 156, 16 }, { 4, 84 }, { 183, 52 }, { 8, 44 }, { 137, 146 }, { 181, 185 }, { 55, 25 }, { 138, 61 }, { 106, 197 }, { 99, 157 }, { 35, 99 }, { 142, 43 }, { 186, 73 }, { 144, 161 }, { 77, 52 }, { 182, 155 }, { 85, 132 }, { 184, 146 }, { 53, 96 }, { 103, 73 }, { 132, 17 }, { 7, 54 }, { 178, 118 }, { 168, 6 }, { 94, 44 }, { 174, 37 }, { 14, 184 }, { 97, 74 }, { 40, 114 }, { 175, 35 }, { 69, 167 }, { 28, 49 }, { 22, 139 }, { 156, 42 }, { 46, 41 }, { 63, 135 }, { 55, 58 }, { 187, 122 }, { 72, 77 }, { 120, 191 }, { 156, 144 }, { 28, 43 }, { 14, 52 }, { 95, 69 }, { 0, 174 }, { 160, 111 }, { 91, 119 }, { 62, 192 }, { 1, 10 }, { 36, 130 }, { 46, 109 }, { 164, 52 }, { 101, 142 }, { 180, 67 }, { 119, 147 }, { 189, 130 }, { 134, 102 }, { 168, 106 }, { 191, 99 }, { 187, 151 }, { 86, 96 }, { 177, 122 }, { 171, 32 }, { 184, 180 }, { 35, 123 }, { 36, 22 }, { 50, 14 }, { 33, 50 }, { 43, 42 }, { 109, 53 }, { 138, 188 }, { 108, 27 }, { 104, 160 }, { 101, 31 }, { 190, 131 }, { 50, 62 }, { 190, 196 }, { 45, 15 }, { 154, 125 }, { 63, 116 }, { 72, 41 }, { 140, 80 }, { 138, 102 }, { 21, 115 }, { 116, 75 }, { 181, 147 }, { 192, 152 }, { 168, 44 }, { 161, 101 }, { 102, 142 }, { 63, 173 }, { 147, 142 }, { 63, 10 }, { 163, 139 }, { 34, 67 }, { 123, 184 }, { 164, 111 }, { 83, 113 }, { 60, 76 }, { 47, 3 }, { 100, 25 }, { 53, 165 }, { 46, 100 }, { 56, 85 }, { 14, 153 }, { 27, 128 }, { 127, 63 }, { 74, 98 }, { 45, 72 }, { 98, 126 }, { 114, 166 }, { 193, 186 }, { 60, 197 }, { 24, 83 }, { 179, 176 }, { 29, 128 }, { 136, 35 }, { 28, 141 }, { 81, 90 }, { 38, 7 }, { 170, 29 }, { 138, 127 }, { 133, 18 }, { 87, 164 }, { 50, 45 }, { 164, 1 }, { 82, 77 }, { 38, 113 }, { 76, 158 }, { 97, 194 }, { 10, 118 }, { 42, 157 }, { 142, 190 }, { 1, 144 }, { 94, 16 }, { 44, 78 }, { 8, 168 }, { 21, 37 }, { 22, 88 }, { 182, 105 }, { 50, 75 }, { 75, 9 }, { 149, 22 }, { 174, 30 }, { 184, 86 }, { 89, 156 }, { 102, 82 }, { 35, 78 }, { 1, 62 }, { 45, 178 }, { 105, 168 }, { 62, 14 }, { 59, 67 }, { 91, 70 }, { 174, 190 }, { 10, 124 }, { 17, 33 }, { 181, 146 }, { 72, 83 }, { 101, 54 }, { 141, 146 }, { 124, 75 }, { 130, 96 }, { 20, 128 }, { 197, 166 }, { 126, 127 }, { 109, 48 }, { 122, 76 }, { 81, 20 }, { 29, 87 }, { 64, 136 }, { 113, 105 }, { 67, 56 }, { 86, 7 }, { 158, 81 }, { 102, 166 }, { 93, 37 }, { 46, 131 }, { 59, 107 }, { 1, 125 }, { 6, 146 }, { 63, 90 }, { 87, 82 }, { 61, 103 }, { 81, 164 }, { 128, 195 }, { 37, 60 }, { 139, 86 }, { 128, 173 }, { 60, 36 }, { 38, 72 }, { 61, 116 }, { 116, 1 }, { 188, 137 }, { 149, 179 }, { 0, 183 }, { 164, 64 }, { 130, 155 }, { 131, 6 }, { 155, 7 }, { 2, 177 }, { 27, 169 }, { 95, 182 }, { 161, 88 }, { 117, 136 }, { 49, 90 }, { 82, 50 }, { 121, 153 }, { 130, 156 }, { 158, 133 }, { 199, 160 }, { 9, 20 }, { 26, 7 }, { 113, 99 }, { 38, 136 }, { 44, 81 }, { 21, 46 }, { 190, 180 }, { 74, 181 }, { 84, 115 }, { 198, 97 }, { 115, 103 }, { 14, 20 }, { 90, 183 }, { 113, 2 }, { 182, 142 }, { 191, 73 }, { 139, 3 }, { 148, 138 }, { 160, 29 }, { 68, 7 }, { 43, 73 }, { 41, 0 }, { 36, 178 }, { 136, 134 }, { 78, 139 }, { 146, 147 }, { 175, 52 }, { 22, 100 }, { 113, 78 }, { 116, 133 }, { 73, 93 }, { 52, 199 }, { 5, 97 }, { 20, 80 }, { 171, 153 }, { 152, 143 }, { 100, 165 }, { 36, 122 }, { 47, 29 }, { 165, 182 }, { 98, 4 }, { 62, 178 }, { 99, 147 }, { 191, 153 }, { 188, 43 }, { 143, 146 }, { 34, 45 }, { 140, 169 }, { 21, 189 }, { 127, 121 }, { 102, 84 }, { 66, 160 }, { 105, 176 }, { 12, 3 }, { 197, 64 }, { 12, 129 }, { 158, 178 }, { 163, 141 }, { 54, 106 }, { 103, 157 }, { 148, 5 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 98); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testGraph6() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 54, 119 }, { 97, 64 }, { 94, 171 }, { 128, 13 }, { 123, 174 }, { 48, 159 }, { 117, 36 }, { 175, 155 }, { 89, 172 }, { 22, 155 }, { 123, 61 }, { 64, 18 }, { 132, 44 }, { 154, 61 }, { 36, 0 }, { 150, 61 }, { 197, 76 }, { 83, 186 }, { 180, 91 }, { 4, 121 }, { 92, 123 }, { 195, 109 }, { 58, 76 }, { 172, 56 }, { 62, 104 }, { 169, 63 }, { 49, 174 }, { 131, 177 }, { 122, 139 }, { 193, 140 }, { 75, 178 }, { 193, 97 }, { 87, 3 }, { 101, 135 }, { 46, 21 }, { 14, 79 }, { 166, 60 }, { 67, 151 }, { 151, 190 }, { 126, 110 }, { 148, 103 }, { 51, 118 }, { 153, 36 }, { 62, 87 }, { 157, 140 }, { 176, 63 }, { 165, 155 }, { 117, 96 }, { 2, 56 }, { 70, 98 }, { 89, 86 }, { 134, 32 }, { 5, 96 }, { 123, 167 }, { 147, 142 }, { 18, 120 }, { 162, 4 }, { 31, 94 }, { 189, 145 }, { 8, 27 }, { 198, 165 }, { 173, 109 }, { 152, 131 }, { 95, 118 }, { 177, 78 }, { 58, 49 }, { 130, 72 }, { 189, 85 }, { 195, 83 }, { 50, 119 }, { 174, 74 }, { 110, 107 }, { 48, 172 }, { 184, 128 }, { 79, 64 }, { 177, 56 }, { 192, 46 }, { 145, 46 }, { 95, 191 }, { 45, 103 }, { 117, 158 }, { 160, 140 }, { 17, 88 }, { 55, 175 }, { 192, 166 }, { 116, 10 }, { 171, 96 }, { 11, 155 }, { 32, 126 }, { 85, 27 }, { 114, 34 }, { 123, 86 }, { 24, 65 }, { 41, 150 }, { 184, 129 }, { 92, 104 }, { 110, 117 }, { 145, 184 }, { 44, 31 }, { 184, 94 }, { 5, 39 }, { 115, 7 }, { 102, 174 }, { 167, 177 }, { 110, 175 }, { 100, 90 }, { 77, 128 }, { 113, 96 }, { 144, 46 }, { 59, 112 }, { 104, 112 }, { 97, 95 }, { 117, 3 }, { 61, 120 }, { 38, 164 }, { 130, 15 }, { 40, 12 }, { 133, 20 }, { 49, 109 }, { 9, 51 }, { 144, 75 }, { 131, 89 }, { 106, 30 }, { 54, 25 }, { 67, 140 }, { 76, 196 }, { 80, 11 }, { 139, 142 }, { 29, 164 }, { 135, 53 }, { 72, 131 }, { 105, 77 }, { 144, 179 }, { 36, 191 }, { 43, 127 }, { 143, 152 }, { 51, 82 }, { 4, 197 }, { 165, 168 }, { 77, 117 }, { 22, 110 }, { 142, 151 }, { 161, 67 }, { 186, 65 }, { 17, 66 }, { 101, 122 }, { 112, 40 }, { 43, 112 }, { 10, 88 }, { 108, 171 }, { 129, 30 }, { 117, 179 }, { 13, 97 }, { 84, 44 }, { 168, 65 }, { 128, 175 }, { 27, 135 }, { 114, 13 }, { 96, 20 }, { 60, 140 }, { 198, 42 }, { 116, 60 }, { 162, 191 }, { 100, 35 }, { 144, 87 }, { 66, 148 }, { 174, 177 }, { 183, 167 }, { 185, 138 }, { 183, 194 }, { 95, 166 }, { 92, 20 }, { 88, 93 }, { 110, 34 }, { 65, 145 }, { 195, 51 }, { 94, 54 }, { 191, 150 }, { 4, 115 }, { 160, 99 }, { 25, 191 }, { 191, 2 }, { 105, 169 }, { 68, 2 }, { 23, 121 }, { 15, 58 }, { 149, 121 }, { 128, 83 }, { 21, 75 }, { 136, 127 }, { 108, 193 }, { 79, 67 }, { 146, 108 }, { 8, 152 }, { 3, 140 }, { 133, 188 }, { 142, 175 }, { 40, 5 }, { 136, 102 }, { 82, 55 }, { 124, 162 }, { 150, 55 }, { 127, 101 }, { 92, 195 }, { 56, 97 }, { 131, 60 }, { 84, 78 }, { 90, 147 }, { 34, 11 }, { 1, 154 }, { 179, 17 }, { 76, 112 }, { 117, 64 }, { 164, 174 }, { 2, 72 }, { 124, 151 }, { 41, 57 }, { 109, 13 }, { 65, 166 }, { 134, 110 }, { 158, 28 }, { 100, 70 }, { 25, 41 }, { 170, 21 }, { 0, 112 }, { 117, 73 }, { 175, 112 }, { 47, 182 }, { 169, 44 }, { 86, 82 }, { 183, 110 }, { 112, 197 }, { 85, 14 }, { 58, 100 }, { 16, 17 }, { 125, 132 }, { 75, 18 }, { 95, 80 }, { 77, 36 }, { 99, 174 }, { 60, 54 }, { 89, 7 }, { 183, 139 }, { 114, 106 }, { 162, 86 }, { 190, 6 }, { 81, 165 }, { 63, 106 }, { 125, 103 }, { 194, 59 }, { 100, 17 }, { 156, 171 }, { 84, 48 }, { 34, 86 }, { 91, 56 }, { 45, 13 }, { 102, 51 }, { 48, 149 }, { 188, 22 }, { 95, 82 }, { 31, 181 }, { 54, 116 }, { 126, 55 }, { 193, 100 }, { 145, 120 }, { 11, 114 }, { 34, 178 }, { 133, 47 }, { 157, 17 }, { 71, 67 }, { 146, 129 }, { 147, 193 }, { 154, 151 }, { 154, 16 }, { 34, 198 }, { 174, 178 }, { 73, 168 }, { 34, 62 }, { 33, 108 }, { 93, 21 }, { 139, 35 }, { 119, 97 }, { 71, 171 }, { 111, 33 }, { 13, 43 }, { 23, 74 }, { 99, 133 }, { 14, 24 }, { 3, 33 }, { 0, 122 }, { 151, 174 }, { 147, 123 }, { 180, 187 }, { 72, 28 }, { 49, 68 }, { 27, 158 }, { 98, 128 }, { 185, 190 }, { 149, 183 }, { 174, 10 }, { 64, 121 }, { 112, 111 }, { 53, 66 }, { 108, 149 }, { 44, 145 }, { 155, 58 }, { 131, 104 }, { 24, 83 }, { 124, 182 }, { 177, 26 }, { 155, 15 }, { 23, 176 }, { 154, 77 }, { 91, 99 }, { 60, 176 }, { 23, 91 }, { 154, 160 }, { 111, 103 }, { 13, 140 }, { 42, 77 }, { 105, 35 }, { 9, 198 }, { 105, 24 }, { 146, 135 }, { 117, 67 }, { 145, 140 }, { 124, 47 }, { 81, 37 }, { 154, 150 }, { 119, 48 }, { 191, 123 }, { 79, 165 }, { 118, 180 }, { 86, 39 }, { 92, 115 }, { 37, 195 }, { 52, 193 }, { 6, 98 }, { 77, 91 }, { 131, 151 }, { 76, 54 }, { 147, 143 }, { 95, 198 }, { 89, 134 }, { 104, 90 }, { 26, 197 }, { 42, 164 }, { 35, 113 }, { 187, 172 }, { 173, 168 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 96); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testGraph7() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 101, 127 }, { 65, 51 }, { 15, 137 }, { 166, 180 }, { 123, 77 }, { 55, 145 }, { 174, 183 }, { 1, 136 }, { 137, 59 }, { 60, 72 }, { 10, 109 }, { 80, 15 }, { 66, 55 }, { 165, 195 }, { 166, 37 }, { 166, 44 }, { 20, 18 }, { 56, 136 }, { 172, 189 }, { 181, 1 }, { 88, 109 }, { 191, 25 }, { 114, 25 }, { 11, 37 }, { 153, 141 }, { 156, 112 }, { 54, 71 }, { 129, 94 }, { 49, 184 }, { 68, 129 }, { 116, 142 }, { 64, 120 }, { 96, 157 }, { 78, 35 }, { 60, 61 }, { 148, 28 }, { 191, 167 }, { 123, 175 }, { 54, 90 }, { 187, 50 }, { 158, 34 }, { 85, 119 }, { 16, 24 }, { 172, 38 }, { 12, 180 }, { 97, 79 }, { 35, 46 }, { 194, 30 }, { 45, 53 }, { 63, 183 }, { 107, 119 }, { 105, 121 }, { 123, 135 }, { 30, 167 }, { 182, 36 }, { 109, 161 }, { 103, 6 }, { 178, 57 }, { 114, 163 }, { 183, 162 }, { 70, 24 }, { 72, 99 }, { 88, 155 }, { 105, 40 }, { 54, 157 }, { 126, 129 }, { 109, 197 }, { 39, 172 }, { 160, 7 }, { 141, 94 }, { 109, 20 }, { 69, 159 }, { 93, 43 }, { 25, 36 }, { 144, 189 }, { 61, 141 }, { 163, 22 }, { 101, 102 }, { 87, 176 }, { 16, 115 }, { 175, 169 }, { 72, 141 }, { 190, 148 }, { 50, 29 }, { 180, 128 }, { 41, 166 }, { 184, 73 }, { 158, 23 }, { 163, 122 }, { 96, 10 }, { 122, 173 }, { 144, 20 }, { 11, 199 }, { 93, 136 }, { 147, 180 }, { 189, 197 }, { 177, 54 }, { 178, 40 }, { 190, 181 }, { 14, 36 }, { 31, 80 }, { 157, 189 }, { 152, 49 }, { 134, 125 }, { 95, 63 }, { 85, 174 }, { 10, 141 }, { 48, 22 }, { 86, 168 }, { 60, 168 }, { 142, 45 }, { 155, 38 }, { 196, 9 }, { 100, 84 }, { 135, 98 }, { 176, 49 }, { 153, 154 }, { 164, 175 }, { 51, 133 }, { 96, 73 }, { 7, 152 }, { 66, 172 }, { 186, 177 }, { 112, 62 }, { 172, 141 }, { 145, 91 }, { 69, 180 }, { 102, 159 }, { 38, 57 }, { 138, 30 }, { 169, 133 }, { 150, 76 }, { 27, 102 }, { 196, 199 }, { 24, 56 }, { 48, 144 }, { 85, 1 }, { 12, 37 }, { 179, 106 }, { 15, 147 }, { 7, 167 }, { 61, 11 }, { 185, 181 }, { 179, 178 }, { 38, 128 }, { 41, 27 }, { 27, 97 }, { 4, 135 }, { 111, 15 }, { 71, 117 }, { 43, 13 }, { 181, 68 }, { 168, 121 }, { 182, 12 }, { 53, 181 }, { 148, 109 }, { 100, 118 }, { 176, 26 }, { 86, 65 }, { 102, 167 }, { 18, 142 }, { 148, 46 }, { 101, 9 }, { 138, 158 }, { 32, 161 }, { 172, 20 }, { 139, 31 }, { 145, 32 }, { 59, 108 }, { 131, 52 }, { 6, 184 }, { 123, 157 }, { 100, 37 }, { 56, 36 }, { 116, 50 }, { 172, 118 }, { 176, 28 }, { 107, 183 }, { 174, 30 }, { 177, 190 }, { 35, 33 }, { 175, 34 }, { 142, 46 }, { 138, 194 }, { 71, 160 }, { 96, 65 }, { 66, 32 }, { 175, 176 }, { 36, 88 }, { 4, 54 }, { 9, 120 }, { 53, 11 }, { 183, 31 }, { 140, 178 }, { 194, 193 }, { 0, 68 }, { 29, 7 }, { 89, 74 }, { 178, 125 }, { 176, 58 }, { 46, 164 }, { 185, 2 }, { 84, 160 }, { 182, 195 }, { 76, 171 }, { 41, 173 }, { 24, 168 }, { 117, 120 }, { 171, 156 }, { 106, 154 }, { 174, 63 }, { 43, 173 }, { 72, 41 }, { 37, 136 }, { 146, 95 }, { 199, 117 }, { 116, 100 }, { 1, 187 }, { 127, 52 }, { 106, 42 }, { 112, 116 }, { 114, 51 }, { 126, 117 }, { 8, 122 }, { 96, 160 }, { 1, 156 }, { 78, 19 }, { 14, 178 }, { 122, 170 }, { 32, 176 }, { 114, 48 }, { 115, 143 }, { 110, 60 }, { 28, 6 }, { 0, 25 }, { 88, 120 }, { 77, 142 }, { 19, 38 }, { 182, 108 }, { 122, 77 }, { 99, 126 }, { 157, 170 }, { 117, 138 }, { 45, 90 }, { 54, 141 }, { 13, 79 }, { 32, 110 }, { 112, 92 }, { 198, 184 }, { 79, 145 }, { 107, 67 }, { 133, 10 }, { 125, 108 }, { 9, 26 }, { 197, 193 }, { 183, 125 }, { 183, 193 }, { 4, 90 }, { 184, 80 }, { 171, 55 }, { 110, 74 }, { 9, 55 }, { 10, 132 }, { 77, 15 }, { 67, 197 }, { 195, 116 }, { 190, 20 }, { 191, 153 }, { 95, 143 }, { 58, 189 }, { 183, 120 }, { 115, 56 }, { 198, 63 }, { 132, 62 }, { 112, 74 }, { 84, 190 }, { 3, 116 }, { 13, 20 }, { 47, 137 }, { 19, 33 }, { 130, 137 }, { 16, 58 }, { 9, 130 }, { 17, 106 }, { 116, 30 }, { 177, 94 }, { 56, 44 }, { 55, 90 }, { 27, 56 }, { 156, 66 }, { 60, 27 }, { 91, 133 }, { 101, 3 }, { 173, 199 }, { 56, 167 }, { 13, 165 }, { 195, 55 }, { 182, 32 }, { 129, 136 }, { 78, 170 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 91); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testGraph8() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 165, 24 }, { 192, 81 }, { 78, 195 }, { 88, 12 }, { 172, 77 }, { 58, 166 }, { 197, 94 }, { 187, 43 }, { 191, 11 }, { 130, 44 }, { 150, 116 }, { 131, 41 }, { 83, 170 }, { 25, 129 }, { 168, 159 }, { 160, 65 }, { 15, 41 }, { 23, 87 }, { 139, 156 }, { 188, 49 }, { 198, 67 }, { 170, 79 }, { 97, 195 }, { 46, 10 }, { 82, 84 }, { 47, 175 }, { 8, 141 }, { 68, 180 }, { 34, 147 }, { 63, 54 }, { 45, 182 }, { 167, 29 }, { 188, 112 }, { 43, 124 }, { 26, 50 }, { 130, 48 }, { 195, 124 }, { 136, 141 }, { 0, 57 }, { 99, 40 }, { 17, 101 }, { 84, 188 }, { 125, 92 }, { 152, 4 }, { 29, 9 }, { 166, 10 }, { 111, 47 }, { 59, 162 }, { 111, 119 }, { 193, 46 }, { 191, 23 }, { 6, 62 }, { 46, 3 }, { 193, 115 }, { 175, 195 }, { 159, 145 }, { 184, 17 }, { 68, 23 }, { 83, 13 }, { 173, 188 }, { 2, 55 }, { 49, 56 }, { 59, 96 }, { 8, 116 }, { 147, 53 }, { 76, 183 }, { 23, 33 }, { 28, 13 }, { 149, 53 }, { 64, 70 }, { 193, 127 }, { 78, 97 }, { 164, 117 }, { 122, 139 }, { 54, 188 }, { 13, 176 }, { 76, 73 }, { 21, 69 }, { 29, 83 }, { 114, 79 }, { 134, 27 }, { 104, 3 }, { 141, 66 }, { 136, 27 }, { 91, 29 }, { 9, 106 }, { 123, 191 }, { 124, 52 }, { 63, 12 }, { 133, 141 }, { 49, 101 }, { 53, 189 }, { 95, 28 }, { 140, 100 }, { 152, 77 }, { 188, 135 }, { 123, 160 }, { 89, 79 }, { 182, 151 }, { 189, 83 }, { 148, 168 }, { 104, 170 }, { 24, 96 }, { 116, 47 }, { 94, 130 }, { 38, 9 }, { 9, 83 }, { 89, 69 }, { 159, 107 }, { 116, 122 }, { 8, 75 }, { 116, 57 }, { 5, 53 }, { 84, 55 }, { 70, 60 }, { 168, 145 }, { 156, 41 }, { 154, 75 }, { 77, 191 }, { 11, 77 }, { 117, 108 }, { 115, 42 }, { 114, 164 }, { 140, 6 }, { 112, 3 }, { 144, 91 }, { 42, 71 }, { 116, 64 }, { 26, 120 }, { 12, 71 }, { 0, 21 }, { 157, 17 }, { 95, 92 }, { 65, 81 }, { 133, 158 }, { 165, 137 }, { 177, 157 }, { 175, 37 }, { 134, 138 }, { 107, 106 }, { 198, 143 }, { 181, 42 }, { 42, 102 }, { 40, 32 }, { 37, 180 }, { 109, 194 }, { 137, 150 }, { 112, 152 }, { 193, 158 }, { 180, 79 }, { 189, 146 }, { 118, 66 }, { 84, 41 }, { 134, 69 }, { 196, 147 }, { 106, 39 }, { 29, 172 }, { 22, 141 }, { 123, 196 }, { 38, 189 }, { 98, 38 }, { 52, 157 }, { 132, 3 }, { 36, 48 }, { 70, 26 }, { 196, 10 }, { 33, 63 }, { 17, 41 }, { 171, 21 }, { 173, 0 }, { 46, 185 }, { 81, 189 }, { 199, 85 }, { 90, 93 }, { 72, 51 }, { 197, 193 }, { 171, 4 }, { 110, 7 }, { 150, 167 }, { 122, 133 }, { 159, 69 }, { 115, 104 }, { 36, 171 }, { 123, 68 }, { 119, 48 }, { 176, 113 }, { 24, 74 }, { 46, 158 }, { 92, 113 }, { 178, 164 }, { 180, 199 }, { 138, 122 }, { 104, 178 }, { 18, 40 }, { 66, 160 }, { 153, 138 }, { 0, 94 }, { 98, 51 }, { 137, 53 }, { 126, 147 }, { 136, 185 }, { 47, 31 }, { 118, 199 }, { 192, 52 }, { 18, 91 }, { 0, 167 }, { 84, 99 }, { 133, 99 }, { 5, 8 }, { 156, 175 }, { 55, 141 }, { 115, 191 }, { 120, 107 }, { 109, 113 }, { 170, 157 }, { 173, 40 }, { 119, 39 }, { 84, 133 }, { 123, 162 }, { 108, 24 }, { 111, 193 }, { 180, 149 }, { 26, 43 }, { 186, 5 }, { 42, 13 }, { 80, 192 }, { 184, 83 }, { 173, 156 }, { 89, 139 }, { 51, 173 }, { 89, 47 }, { 16, 33 }, { 195, 85 }, { 150, 70 }, { 67, 76 }, { 38, 91 }, { 108, 189 }, { 146, 88 }, { 61, 132 }, { 23, 90 }, { 142, 169 }, { 9, 55 }, { 72, 175 }, { 96, 74 }, { 99, 17 }, { 169, 4 }, { 17, 44 }, { 64, 168 }, { 103, 197 }, { 176, 56 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 86); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testGraph9() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 9, 158 }, { 114, 119 }, { 136, 45 }, { 119, 69 }, { 95, 67 }, { 93, 158 }, { 136, 137 }, { 62, 67 }, { 155, 70 }, { 136, 190 }, { 165, 104 }, { 136, 55 }, { 180, 125 }, { 18, 49 }, { 105, 157 }, { 187, 120 }, { 120, 53 }, { 183, 154 }, { 91, 187 }, { 166, 111 }, { 26, 177 }, { 186, 142 }, { 47, 160 }, { 124, 197 }, { 30, 91 }, { 196, 116 }, { 74, 76 }, { 142, 7 }, { 43, 23 }, { 121, 135 }, { 107, 73 }, { 180, 43 }, { 23, 156 }, { 34, 72 }, { 59, 10 }, { 188, 138 }, { 38, 27 }, { 165, 78 }, { 181, 22 }, { 193, 22 }, { 51, 192 }, { 142, 111 }, { 150, 155 }, { 165, 123 }, { 13, 94 }, { 178, 110 }, { 189, 109 }, { 158, 159 }, { 51, 149 }, { 198, 149 }, { 116, 142 }, { 124, 35 }, { 112, 197 }, { 83, 154 }, { 61, 5 }, { 41, 49 }, { 15, 194 }, { 37, 75 }, { 29, 65 }, { 7, 38 }, { 55, 79 }, { 151, 195 }, { 83, 5 }, { 157, 143 }, { 39, 77 }, { 40, 165 }, { 49, 28 }, { 10, 189 }, { 43, 195 }, { 32, 45 }, { 170, 139 }, { 128, 35 }, { 37, 116 }, { 131, 92 }, { 66, 59 }, { 42, 52 }, { 84, 110 }, { 188, 122 }, { 81, 13 }, { 53, 151 }, { 16, 191 }, { 35, 115 }, { 79, 94 }, { 130, 69 }, { 187, 88 }, { 7, 189 }, { 145, 123 }, { 42, 63 }, { 17, 60 }, { 92, 6 }, { 34, 67 }, { 0, 154 }, { 80, 47 }, { 38, 31 }, { 50, 42 }, { 170, 44 }, { 144, 192 }, { 60, 165 }, { 138, 170 }, { 80, 133 }, { 92, 57 }, { 61, 148 }, { 22, 33 }, { 11, 105 }, { 87, 92 }, { 37, 108 }, { 65, 143 }, { 110, 163 }, { 199, 189 }, { 81, 102 }, { 99, 126 }, { 136, 33 }, { 133, 20 }, { 198, 126 }, { 30, 170 }, { 8, 28 }, { 99, 89 }, { 149, 32 }, { 20, 41 }, { 183, 110 }, { 188, 88 }, { 42, 28 }, { 155, 58 }, { 193, 187 }, { 14, 181 }, { 0, 11 }, { 56, 199 }, { 11, 122 }, { 130, 102 }, { 102, 89 }, { 47, 156 }, { 54, 92 }, { 10, 102 }, { 108, 99 }, { 144, 47 }, { 122, 177 }, { 114, 45 }, { 126, 56 }, { 83, 8 }, { 100, 191 }, { 72, 18 }, { 127, 146 }, { 77, 168 }, { 56, 148 }, { 148, 139 }, { 15, 196 }, { 176, 147 }, { 110, 161 }, { 136, 41 }, { 86, 10 }, { 15, 8 }, { 136, 87 }, { 112, 95 }, { 165, 94 }, { 174, 13 }, { 18, 187 }, { 73, 146 }, { 75, 111 }, { 86, 109 }, { 161, 51 }, { 142, 103 }, { 110, 121 }, { 46, 155 }, { 100, 143 }, { 158, 65 }, { 165, 177 }, { 67, 6 }, { 62, 83 }, { 167, 42 }, { 21, 184 }, { 120, 21 }, { 57, 193 }, { 150, 86 }, { 88, 109 }, { 158, 10 }, { 107, 129 }, { 180, 126 }, { 86, 37 }, { 117, 89 }, { 116, 171 }, { 122, 64 }, { 176, 109 }, { 96, 71 }, { 30, 17 }, { 61, 1 }, { 191, 99 }, { 69, 173 }, { 59, 55 }, { 146, 37 }, { 129, 18 }, { 2, 179 }, { 194, 197 }, { 82, 131 }, { 124, 28 }, { 81, 103 }, { 114, 193 }, { 191, 139 }, { 49, 191 }, { 92, 38 }, { 101, 70 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 75); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } @Test public void testGraph10() { Graph graph = new SimpleGraph<>(DefaultEdge.class); int[][] edges = { { 50, 128 }, { 164, 132 }, { 185, 71 }, { 77, 85 }, { 0, 77 }, { 114, 172 }, { 114, 131 }, { 167, 34 }, { 143, 58 }, { 16, 0 }, { 86, 34 }, { 116, 180 }, { 147, 36 }, { 120, 7 }, { 100, 105 }, { 125, 114 }, { 85, 101 }, { 107, 50 }, { 171, 100 }, { 12, 47 }, { 134, 191 }, { 61, 4 }, { 95, 74 }, { 7, 140 }, { 73, 173 }, { 36, 106 }, { 20, 109 }, { 69, 18 }, { 76, 62 }, { 184, 154 }, { 40, 152 }, { 143, 95 }, { 190, 132 }, { 100, 125 }, { 109, 81 }, { 112, 174 }, { 98, 182 }, { 115, 70 }, { 108, 198 }, { 85, 9 }, { 91, 172 }, { 123, 58 }, { 2, 137 }, { 94, 160 }, { 173, 145 }, { 93, 103 }, { 78, 54 }, { 114, 49 }, { 154, 135 }, { 122, 7 }, { 88, 50 }, { 86, 152 }, { 58, 65 }, { 39, 156 }, { 108, 27 }, { 110, 149 }, { 65, 114 }, { 25, 171 }, { 52, 76 }, { 34, 83 }, { 28, 192 }, { 26, 147 }, { 9, 87 }, { 34, 4 }, { 179, 13 }, { 74, 164 }, { 187, 2 }, { 186, 104 }, { 113, 98 }, { 37, 171 }, { 43, 61 }, { 30, 85 }, { 95, 155 }, { 91, 2 }, { 199, 120 }, { 150, 109 }, { 36, 8 }, { 67, 97 }, { 62, 63 }, { 131, 69 }, { 199, 47 }, { 38, 130 }, { 95, 55 }, { 24, 162 }, { 34, 181 }, { 42, 46 }, { 54, 176 }, { 41, 19 }, { 161, 196 }, { 44, 19 }, { 191, 138 }, { 54, 148 }, { 168, 59 }, { 196, 7 }, { 176, 178 }, { 17, 110 }, { 49, 155 }, { 116, 51 }, { 35, 100 }, { 83, 114 }, { 91, 46 }, { 1, 2 }, { 97, 71 }, { 171, 109 }, { 59, 152 }, { 8, 177 }, { 111, 94 }, { 102, 26 }, { 174, 144 }, { 177, 54 }, { 52, 83 }, { 31, 181 }, { 44, 133 }, { 87, 59 }, { 73, 108 }, { 136, 4 }, { 15, 10 }, { 142, 179 }, { 151, 160 }, { 31, 166 }, { 113, 132 }, { 195, 41 }, { 156, 96 }, { 98, 165 }, { 17, 56 }, { 135, 165 }, { 54, 160 }, { 18, 165 }, { 86, 160 }, { 100, 24 }, { 109, 77 }, { 155, 92 }, { 73, 100 }, { 6, 124 }, { 93, 30 }, { 90, 194 }, { 199, 131 }, { 98, 134 }, { 49, 36 }, { 157, 0 }, { 189, 97 }, { 121, 30 }, { 121, 100 }, { 110, 194 }, { 178, 24 }, { 110, 84 }, { 92, 65 }, { 32, 143 }, { 79, 73 }, { 11, 146 } }; for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); SparseEdmondsMaximumCardinalityMatching matcher = new SparseEdmondsMaximumCardinalityMatching<>(graph); verifyMatching(graph, matcher.getMatching(), 66); assertTrue( SparseEdmondsMaximumCardinalityMatching .isOptimalMatching( graph, matcher.getMatching().getEdges(), matcher.getOddSetCover())); } private void verifyMatching(Graph g, Matching m, int cardinality) { Set matched = new HashSet<>(); double weight = 0; for (E e : m.getEdges()) { V source = g.getEdgeSource(e); V target = g.getEdgeTarget(e); if (matched.contains(source)) fail("vertex is incident to multiple matches in the matching"); matched.add(source); if (matched.contains(target)) fail("vertex is incident to multiple matches in the matching"); matched.add(target); weight += g.getEdgeWeight(e); } assertEquals(m.getWeight(), weight, 0.0000001); assertEquals(cardinality, m.getEdges().size()); assertEquals(m.getEdges().size() * 2, matched.size()); // Ensure that there are no // self-loops } private static int maxEdges(int n) { if (n % 2 == 0) { return Math.multiplyExact(n / 2, n - 1); } else { return Math.multiplyExact(n, (n - 1) / 2); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/000077500000000000000000000000001402514743400301335ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/000077500000000000000000000000001402514743400304655ustar00rootroot00000000000000BlossomVDebugger.java000066400000000000000000000202551402514743400344660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.util.*; import org.jheaps.*; import java.util.*; import static org.junit.Assert.fail; /** * This class contains auxiliary methods for testing {@link KolmogorovWeightedPerfectMatching} and * related classes * * @author Timofey Chudakov */ public class BlossomVDebugger { /** * Returns the mapping from original graph vertices to the internal nodes used in the algortihm * * @param state the state of the algorithm * @param graph vertex type * @param graph edge type * @return the mapping from original graph vertices to the internal nodes used in the algortihm */ public static Map getVertexMap(BlossomVState state) { Map vertexMap = CollectionUtil.newHashMapWithExpectedSize(state.nodeNum); for (int i = 0; i < state.nodeNum; i++) { vertexMap.put(state.graphVertices.get(i), state.nodes[i]); } return vertexMap; } /** * Returns the mapping from original graph edges to the internal edges used in the algorithm. * * @param state the state of the algorithm * @param graph vertex type * @param graph edge type * @return the mapping from original graph edges to the internal edges used in the algorithm. */ public static Map getEdgeMap(BlossomVState state) { Map edgeMap = CollectionUtil.newHashMapWithExpectedSize(state.edgeNum); for (int i = 0; i < state.edgeNum; i++) { edgeMap.put(state.graphEdges.get(i), state.edges[i]); } return edgeMap; } /** * Returns all edge incident to {@code node} * * @param node some node * @return all edge incident to {@code node} */ public static Set getEdgesOf(BlossomVNode node) { Set edges = new HashSet<>(); for (BlossomVNode.IncidentEdgeIterator iterator = node.incidentEdgesIterator(); iterator.hasNext();) { edges.add(iterator.next()); } return edges; } /** * Returns all tree edges incident to {@code tree} * * @param tree some alternating tree * @return all tree edges incident to {@code tree} */ public static Set getTreeEdgesOf(BlossomVTree tree) { Set result = new HashSet<>(); for (BlossomVTree.TreeEdgeIterator iterator = tree.treeEdgeIterator(); iterator.hasNext();) { result.add(iterator.next()); } return result; } /** * Returns the first tree edge between {@code from} and {@code to}. If there is no tree edge * between these two alternating trees, this method fails. * * @param from some alternating tree * @param to some alternating tree * @return the first tree edge between {@code from} and {@code to} */ public static BlossomVTreeEdge getTreeEdge(BlossomVTree from, BlossomVTree to) { BlossomVTreeEdge treeEdge = null; for (BlossomVTree.TreeEdgeIterator iterator = from.treeEdgeIterator(); iterator.hasNext();) { treeEdge = iterator.next(); if (treeEdge.head[iterator.getCurrentDirection()] == to) { return treeEdge; } } fail(); return treeEdge; } /** * Returns all tree edges between {@code from} and {@code to} * * @param from some alternating tree * @param to some alternating tree * @return all tree edges between {@code from} and {@code to} */ public static Set getTreeEdgesBetween(BlossomVTree from, BlossomVTree to) { Set result = new HashSet<>(); for (BlossomVTree.TreeEdgeIterator iterator = from.treeEdgeIterator(); iterator.hasNext();) { BlossomVTreeEdge treeEdge = iterator.next(); if (treeEdge.head[iterator.getCurrentDirection()] == to) { result.add(treeEdge); } } return result; } /** * Returns all tree children of the {@code node} * * @param node some node * @return all tree children of the {@code node} */ public static Set getChildrenOf(BlossomVNode node) { Set children = new HashSet<>(); for (BlossomVNode child = node.firstTreeChild; child != null; child = child.treeSiblingNext) { children.add(child); } return children; } /** * Returns all tree roots of the alternating trees stored in the {@code state} * * @param state the state of the algorithm * @param graph vertex type * @param graph edge type * @return all tree roots of the alternating trees stored in the {@code state} */ public static Set getTreeRoots(BlossomVState state) { Set treeRoots = new HashSet<>(); for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { treeRoots.add(root); } return treeRoots; } /** * Returns a set of all nodes of the {@code tree} * * @param tree an alternating tree * @return a set of all nodes of the {@code tree} */ public static Set getTreeNodes(BlossomVTree tree) { Set nodes = new HashSet<>(); for (BlossomVTree.TreeNodeIterator iterator = tree.treeNodeIterator(); iterator.hasNext();) { nodes.add(iterator.next()); } return nodes; } /** * Returns the direction from the {@code tree} to the opposite tree, that are connected via * {@code treeEdge}. More precisely, returns {@code dir} such that * {@code treeEdge.head[dir] != tree}. * * @param treeEdge tree edge incident to {@code tree} * @param tree an alternating tree * @return dir such that {@code treeEdge.head[dir] != tree} */ public static int getDirToOpposite(BlossomVTreeEdge treeEdge, BlossomVTree tree) { return treeEdge.head[0] == tree ? 1 : 0; } /** * Returns current heap of (+, -) cross-tree edges if {@code tree} is considered as the current * tree. * * @param treeEdge tree edge incident to the {@code tree} * @param tree some alternating tree * @return current heap of (+, -) cross-tree edges if {@code tree} is considered as the current * tree. */ public static MergeableAddressableHeap getPlusMinusHeap( BlossomVTreeEdge treeEdge, BlossomVTree tree) { return treeEdge.head[0] == tree ? treeEdge.getCurrentPlusMinusHeap(1) : treeEdge.getCurrentPlusMinusHeap(0); } /** * Returns current heap of (-, +) cross-tree edges if {@code tree} is considered as the current * tree. * * @param treeEdge tree edge incident to the {@code tree} * @param tree some alternating tree * @return current heap of (-, +) cross-tree edges if {@code tree} is considered as the current * tree. */ public static MergeableAddressableHeap getMinusPlusHeap( BlossomVTreeEdge treeEdge, BlossomVTree tree) { return treeEdge.head[0] == tree ? treeEdge.getCurrentMinusPlusHeap(1) : treeEdge.getCurrentMinusPlusHeap(0); } } BlossomVDualUpdaterTest.java000066400000000000000000000410701402514743400360120ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.DualUpdateStrategy.MULTIPLE_TREE_CONNECTED_COMPONENTS; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.DualUpdateStrategy.MULTIPLE_TREE_FIXED_DELTA; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.NONE; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.EPS; import static org.junit.Assert.*; /** * Unit tests for the {@link BlossomVDualUpdater} * * @author Timofey Chudakov */ public class BlossomVDualUpdaterTest { private BlossomVOptions noneOptions = new BlossomVOptions(NONE); @org.junit.Test public void testUpdateDuals1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 5); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 2); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); primalUpdater.augment(edge34); BlossomVDualUpdater dualUpdater = new BlossomVDualUpdater<>(state, primalUpdater); assertTrue(dualUpdater.updateDuals(MULTIPLE_TREE_FIXED_DELTA) > 0); for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { assertEquals(root.tree.eps, 2.5, EPS); } } @Test public void testUpdateDuals2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 6); Graphs.addEdgeWithVertices(graph, 1, 3, 7); Graphs.addEdgeWithVertices(graph, 2, 3, 10); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVEdge edge45 = edgeMap.get(e45); primalUpdater.augment(edge45); BlossomVDualUpdater dualUpdater = new BlossomVDualUpdater<>(state, primalUpdater); dualUpdater.updateDuals(MULTIPLE_TREE_FIXED_DELTA); for (BlossomVNode root = state.nodes[state.nodeNum].treeSiblingNext; root != null; root = root.treeSiblingNext) { assertEquals(root.tree.eps, 3, EPS); } } @Test public void testUpdateDualsSingle1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 5); Graphs.addEdgeWithVertices(graph, 2, 3, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVDualUpdater dualUpdater = new BlossomVDualUpdater<>(state, new BlossomVPrimalUpdater<>(state)); Map vertexMap = BlossomVDebugger.getVertexMap(state); BlossomVTree tree = vertexMap.get(1).tree; dualUpdater.updateDualsSingle(tree); assertEquals(5, tree.eps, EPS); } public void testUpdateDualsSingle2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 0); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 2); DefaultWeightedEdge e25 = Graphs.addEdgeWithVertices(graph, 2, 5, 2); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 2); DefaultWeightedEdge e35 = Graphs.addEdgeWithVertices(graph, 3, 5, 4); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); BlossomVDualUpdater dualUpdater = new BlossomVDualUpdater<>(state, primalUpdater); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge24 = edgeMap.get(e24); BlossomVEdge edge25 = edgeMap.get(e25); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge35 = edgeMap.get(e35); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge56 = edgeMap.get(e56); primalUpdater.augment(edge23); primalUpdater.augment(edge45); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, true, false); node1.tree.clearCurrentEdges(); node6.tree.setCurrentEdges(); primalUpdater.grow(edge56, true, false); node6.tree.clearCurrentEdges(); assertTrue(dualUpdater.updateDualsSingle(node1.tree)); assertEquals(2, node1.tree.eps, EPS); assertFalse(dualUpdater.updateDualsSingle(node6.tree)); assertEquals(0, node6.tree.eps, EPS); } /** * Tests updating duals with connected components for basic invariants */ @Test public void testUpdateDualsConnectedComponents1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 10); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 0); // tight (-, +) // cross-tree edge DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 8); // infinity edge DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 0); // matched free edge BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); BlossomVDualUpdater dualUpdater = new BlossomVDualUpdater<>(state, primalUpdater); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node4 = vertexMap.get(4); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge56 = edgeMap.get(e56); primalUpdater.augment(edge23); primalUpdater.augment(edge56); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); node1.tree.clearCurrentEdges(); double dualChange = dualUpdater.updateDuals(MULTIPLE_TREE_CONNECTED_COMPONENTS); assertEquals(10, dualChange, EPS); assertEquals(node1.tree.eps, node4.tree.eps, EPS); } /** * Tests updating duals with two connected components */ @Test public void testUpdateDualsConnectedComponents2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); // tree edges DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); DefaultWeightedEdge e78 = Graphs.addEdgeWithVertices(graph, 7, 8, 0); // cross-tree and infinity edges DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 10); // infinity edge DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); // free matched edge DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 10); // infinity edge DefaultWeightedEdge e26 = Graphs.addEdgeWithVertices(graph, 2, 6, 6); // (-, +) cross-tree // edge DefaultWeightedEdge e36 = Graphs.addEdgeWithVertices(graph, 3, 6, 7); // (+, +) cross-tree // edge DefaultWeightedEdge e37 = Graphs.addEdgeWithVertices(graph, 3, 7, 5); // (+, -) cross-tree // edge BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); BlossomVDualUpdater dualUpdater = new BlossomVDualUpdater<>(state, primalUpdater); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node8 = vertexMap.get(8); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge78 = edgeMap.get(e78); // setting up the test case structure primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge67); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); node1.tree.clearCurrentEdges(); node8.tree.setCurrentEdges(); primalUpdater.grow(edge78, false, false); node8.tree.clearCurrentEdges(); double dualChange = dualUpdater.updateDuals(MULTIPLE_TREE_CONNECTED_COMPONENTS); assertEquals(dualChange, 7, EPS); } /** * Tests updating duals with connected components on a big test case */ @Test public void testUpdateDualsConnectedComponents3() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 0); // tight (-, +) // cross-tree edge DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); DefaultWeightedEdge e68 = Graphs.addEdgeWithVertices(graph, 6, 8, 0); // tight (-, +) // cross-tree edge DefaultWeightedEdge e910 = Graphs.addEdgeWithVertices(graph, 9, 10, 0); // free matched edge DefaultWeightedEdge e39 = Graphs.addEdgeWithVertices(graph, 3, 9, 5); DefaultWeightedEdge e49 = Graphs.addEdgeWithVertices(graph, 4, 9, 5); DefaultWeightedEdge e710 = Graphs.addEdgeWithVertices(graph, 7, 10, 15); DefaultWeightedEdge e810 = Graphs.addEdgeWithVertices(graph, 8, 10, 15); DefaultWeightedEdge e46 = Graphs.addEdgeWithVertices(graph, 4, 6, 4); // not tight (-, +) // cross-tree edge DefaultWeightedEdge e28 = Graphs.addEdgeWithVertices(graph, 2, 8, 6); // not tight (-, +) // cross-tree edge BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); BlossomVDualUpdater dualUpdater = new BlossomVDualUpdater<>(state, primalUpdater); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVNode node9 = vertexMap.get(9); BlossomVNode node10 = vertexMap.get(10); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge24 = edgeMap.get(e24); BlossomVEdge edge56 = edgeMap.get(e56); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge68 = edgeMap.get(e68); BlossomVEdge edge910 = edgeMap.get(e910); BlossomVEdge edge39 = edgeMap.get(e39); BlossomVEdge edge49 = edgeMap.get(e49); BlossomVEdge edge710 = edgeMap.get(e710); BlossomVEdge edge810 = edgeMap.get(e810); BlossomVEdge edge46 = edgeMap.get(e46); BlossomVEdge edge28 = edgeMap.get(e28); primalUpdater.augment(edge23); primalUpdater.augment(edge67); primalUpdater.augment(edge910); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); node1.tree.clearCurrentEdges(); node5.tree.setCurrentEdges(); primalUpdater.grow(edge56, false, false); node5.tree.clearCurrentEdges(); double dualChange = dualUpdater.updateDuals(MULTIPLE_TREE_CONNECTED_COMPONENTS); assertTrue(dualChange > 0); assertEquals(node1.tree.eps, node4.tree.eps, EPS); assertEquals(node5.tree.eps, node8.tree.eps, EPS); } } BlossomVInitializerTest.java000066400000000000000000000412451402514743400360670ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.*; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.EPS; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatchingTest.checkMatchingAndDualSolution; import static org.jgrapht.alg.matching.blossom.v5.ObjectiveSense.MINIMIZE; import static org.junit.Assert.*; /** * Unit tests for the {@link BlossomVInitializer} * * @author Timofey Chudakov */ public class BlossomVInitializerTest { private BlossomVOptions fractionalOptions = new BlossomVOptions(FRACTIONAL); /** * Tests greedy initialization */ @Test public void testGreedyInitialization() { DefaultUndirectedWeightedGraph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 5); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(new BlossomVOptions(GREEDY)); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVEdge edge12 = edgeMap.get(e12); assertEquals(5, node1.dual + node2.dual, EPS); assertEquals(0, edge12.slack, EPS); assertTrue(node1.isOuter); assertTrue(node2.isOuter); assertFalse(node1.isTreeRoot); assertFalse(node2.isTreeRoot); assertEquals(4, state.nodeNum); assertEquals(2, state.edgeNum); assertEquals(0, state.treeNum); assertEquals(Set.of(), BlossomVDebugger.getTreeRoots(state)); assertEquals(Set.of(edge12), BlossomVDebugger.getEdgesOf(node1)); assertEquals(Set.of(edge12), BlossomVDebugger.getEdgesOf(node2)); assertEquals(edge12, node1.matched); assertEquals(edge12, node2.matched); } /** * Tests simple initialization */ @Test public void testSimpleInitialization() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 1); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 2); DefaultWeightedEdge e25 = Graphs.addEdgeWithVertices(graph, 2, 5, 3); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 4); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 5); DefaultWeightedEdge e89 = Graphs.addEdgeWithVertices(graph, 8, 9, 0); graph.addVertex(7); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(new BlossomVOptions(NONE)); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); assertEquals(9, state.nodeNum); assertEquals(9, state.treeNum); assertEquals(6, state.edgeNum); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVTree tree1 = node1.tree; BlossomVTree tree2 = node2.tree; BlossomVTree tree3 = node3.tree; BlossomVTree tree4 = node4.tree; BlossomVTree tree5 = node5.tree; BlossomVTree tree6 = node6.tree; BlossomVTree tree7 = node7.tree; BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge25 = edgeMap.get(e25); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge56 = edgeMap.get(e56); assertEquals(0, node1.dual, EPS); assertEquals(0, node2.dual, EPS); assertEquals(0, node3.dual, EPS); assertEquals(0, node4.dual, EPS); assertEquals(0, node5.dual, EPS); assertEquals(0, node6.dual, EPS); assertEquals(0, node7.dual, EPS); assertTrue(node1.isOuter); assertTrue(node2.isOuter); assertTrue(node3.isOuter); assertTrue(node4.isOuter); assertTrue(node5.isOuter); assertTrue(node6.isOuter); assertTrue(node7.isOuter); assertTrue(node1.isTreeRoot); assertTrue(node2.isTreeRoot); assertTrue(node3.isTreeRoot); assertTrue(node4.isTreeRoot); assertTrue(node5.isTreeRoot); assertTrue(node6.isTreeRoot); assertTrue(node7.isTreeRoot); assertEquals(1, edge12.slack, EPS); assertEquals(2, edge23.slack, EPS); assertEquals(3, edge25.slack, EPS); assertEquals(4, edge45.slack, EPS); assertEquals(5, edge56.slack, EPS); Set actualRoots = BlossomVDebugger.getTreeRoots(state); Collection expectedRoots = vertexMap.values(); assertEquals(expectedRoots.size(), actualRoots.size()); assertTrue(actualRoots.containsAll(expectedRoots)); assertEquals(Set.of(edge12), BlossomVDebugger.getEdgesOf(node1)); assertEquals(Set.of(edge12, edge23, edge25), BlossomVDebugger.getEdgesOf(node2)); assertEquals(Set.of(edge23), BlossomVDebugger.getEdgesOf(node3)); assertEquals(Set.of(edge45), BlossomVDebugger.getEdgesOf(node4)); assertEquals(Set.of(edge25, edge45, edge56), BlossomVDebugger.getEdgesOf(node5)); assertEquals(Set.of(edge56), BlossomVDebugger.getEdgesOf(node6)); assertEquals(Set.of(), BlossomVDebugger.getEdgesOf(node7)); assertEquals(1, BlossomVDebugger.getTreeEdgesOf(tree1).size()); assertEquals(3, BlossomVDebugger.getTreeEdgesOf(tree2).size()); assertEquals(1, BlossomVDebugger.getTreeEdgesOf(tree3).size()); assertEquals(1, BlossomVDebugger.getTreeEdgesOf(tree4).size()); assertEquals(3, BlossomVDebugger.getTreeEdgesOf(tree5).size()); assertEquals(1, BlossomVDebugger.getTreeEdgesOf(tree6).size()); assertEquals(0, BlossomVDebugger.getTreeEdgesOf(tree7).size()); } /** * Tests fractional matching initialization on a bipartite graph with $V = {0,1,2}\cup{4,5,6}$ */ @Test public void testFractionalInitialization1() { int[][] edges = { { 0, 3, 8 }, { 0, 4, 3 }, { 0, 5, 3 }, { 1, 3, 2 }, { 1, 4, 5 }, { 1, 5, 2 }, { 2, 3, 7 }, { 2, 4, 3 }, { 2, 5, 4 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, fractionalOptions); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); KolmogorovWeightedPerfectMatching.Statistics statistics = perfectMatching.getStatistics(); assertEquals(8, matching.getWeight(), EPS); assertEquals(0, statistics.growNum); assertEquals(0, statistics.shrinkNum); assertEquals(0, statistics.expandNum); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), MINIMIZE); } /** * Tests fractional matching initialization on a bipartite graph with $V = {0,1,2}\cup{4,5,6}$ */ @Test public void testFractionalInitialization2() { int[][] edges = new int[][] { { 0, 3, 4 }, { 0, 4, 4 }, { 0, 5, 4 }, { 1, 3, 5 }, { 1, 4, 8 }, { 1, 5, 10 }, { 2, 3, 4 }, { 2, 4, 6 }, { 2, 5, 5 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, fractionalOptions); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); KolmogorovWeightedPerfectMatching.Statistics statistics = perfectMatching.getStatistics(); assertEquals(14, matching.getWeight(), EPS); assertEquals(0, statistics.growNum); assertEquals(0, statistics.shrinkNum); assertEquals(0, statistics.expandNum); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), MINIMIZE); } /** * Tests fractional matching initialization on a bipartite graph with $V = * {0,1,2,3}\cup{4,5,6,7}$ */ @Test public void testFractionalInitialization3() { int[][] edges = new int[][] { { 0, 5, 6 }, { 0, 6, 8 }, { 1, 5, 5 }, { 1, 6, 5 }, { 1, 7, 3 }, { 2, 4, 2 }, { 2, 5, 1 }, { 2, 6, 8 }, { 3, 5, 5 }, { 3, 7, 9 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, fractionalOptions); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); KolmogorovWeightedPerfectMatching.Statistics statistics = perfectMatching.getStatistics(); assertEquals(18, matching.getWeight(), EPS); assertEquals(0, statistics.growNum); assertEquals(0, statistics.shrinkNum); assertEquals(0, statistics.expandNum); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), MINIMIZE); } /** * Tests fractional matching initialization on a bipartite graph with $V = * {0,1,2,3}\cup{4,5,6,7}$ */ @Test public void testFractionalInitialization4() { int[][] edges = new int[][] { { 0, 5, 2 }, { 0, 6, 2 }, { 0, 7, 1 }, { 1, 4, 6 }, { 1, 7, 10 }, { 2, 4, 7 }, { 2, 6, 8 }, { 2, 7, 10 }, { 3, 4, 5 }, { 3, 5, 9 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, fractionalOptions); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); KolmogorovWeightedPerfectMatching.Statistics statistics = perfectMatching.getStatistics(); assertEquals(24, matching.getWeight(), EPS); assertEquals(0, statistics.growNum); assertEquals(0, statistics.shrinkNum); assertEquals(0, statistics.expandNum); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), MINIMIZE); } /** * Tests fractional matching initialization on triangulation of 8 points */ @Test public void testFractionalInitialization5() { int[][] edges = new int[][] { { 1, 0, 2 }, { 1, 2, 5 }, { 0, 2, 4 }, { 1, 4, 5 }, { 2, 4, 2 }, { 1, 3, 2 }, { 1, 5, 4 }, { 3, 5, 3 }, { 4, 5, 5 }, { 3, 6, 4 }, { 5, 6, 2 }, { 5, 7, 3 }, { 6, 7, 4 }, { 4, 7, 4 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, fractionalOptions); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); assertEquals(11, matching.getWeight(), EPS); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), MINIMIZE); } /** * Tests fractional matching initialization on triangulation of 8 points */ @Test public void testFractionalInitialization6() { int[][] edges = new int[][] { { 0, 1, 5 }, { 0, 2, 9 }, { 1, 2, 6 }, { 2, 3, 4 }, { 2, 4, 5 }, { 3, 4, 3 }, { 1, 4, 8 }, { 1, 5, 8 }, { 0, 5, 11 }, { 4, 5, 7 }, { 4, 6, 3 }, { 5, 6, 5 }, { 6, 7, 3 }, { 5, 7, 6 }, { 4, 7, 6 }, { 3, 7, 9 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, fractionalOptions); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); assertEquals(18, matching.getWeight(), EPS); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), MINIMIZE); } /** * Tests fractional matching initialization on triangulation of 8 points */ @Test public void testFractionalInitialization7() { int[][] edges = new int[][] { { 0, 1, 2 }, { 0, 2, 8 }, { 1, 2, 7 }, { 0, 4, 8 }, { 1, 4, 7 }, { 2, 4, 6 }, { 2, 3, 9 }, { 2, 5, 6 }, { 3, 5, 6 }, { 2, 6, 6 }, { 5, 6, 5 }, { 4, 6, 2 }, { 5, 7, 9 }, { 6, 7, 7 }, { 3, 7, 14 }, { 4, 7, 7 }, { 0, 7, 15 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, fractionalOptions); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); assertEquals(21, matching.getWeight(), EPS); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), MINIMIZE); } /** * Tests fractional matching initialization on triangulation of 8 points */ @Test public void testFractionalInitialization8() { int[][] edges = new int[][] { { 0, 1, 7 }, { 0, 2, 8 }, { 0, 3, 8 }, { 1, 3, 4 }, { 1, 5, 9 }, { 1, 6, 13 }, { 2, 4, 6 }, { 2, 3, 11 }, { 3, 4, 10 }, { 3, 5, 6 }, { 4, 5, 8 }, { 4, 7, 7 }, { 5, 6, 4 }, { 5, 7, 4 }, { 6, 7, 1 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, fractionalOptions); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); assertEquals(20, matching.getWeight(), EPS); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), MINIMIZE); } /** * Tests fractional matching initialization on triangulation of 8 points */ @Test public void testFractionalInitialization9() { int[][] edges = new int[][] { { 0, 1, 4 }, { 0, 2, 4 }, { 0, 5, 14 }, { 1, 2, 3 }, { 1, 3, 1 }, { 1, 5, 11 }, { 2, 3, 4 }, { 2, 4, 4 }, { 2, 7, 11 }, { 3, 4, 1 }, { 3, 5, 10 }, { 4, 5, 10 }, { 4, 6, 10 }, { 4, 7, 9 }, { 5, 6, 3 }, { 6, 7, 8 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, fractionalOptions); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); assertEquals(17, matching.getWeight(), EPS); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), MINIMIZE); } } BlossomVNodeTest.java000066400000000000000000000476451402514743400345030ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVNode.Label.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.NONE; import static org.junit.Assert.*; /** * Unit tests for the {@link BlossomVNode} * * @author Timofey Chudakov */ public class BlossomVNodeTest { private BlossomVOptions noneOptions = new BlossomVOptions(NONE); @Test public void testLabels() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); BlossomVNode node = vertexMap.get(1); // position doesn't matter node.label = INFINITY; assertTrue(node.isInfinityNode()); node.label = PLUS; assertTrue(node.isPlusNode()); node.label = MINUS; assertTrue(node.isMinusNode()); } @Test public void testAncestors() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); primalUpdater.augment(edge23); primalUpdater.grow(edge12, false, false); assertEquals(node1, node2.getTreeParent()); assertEquals(node2, node3.getTreeParent()); assertEquals(node1, node3.getTreeGrandparent()); } /** * Tests correct edge addition and correct edge direction */ @Test public void testAddEdge() { BlossomVNode from = new BlossomVNode(-1); BlossomVNode to = new BlossomVNode(-1); BlossomVEdge nodeEdge = new BlossomVEdge(-1); nodeEdge.headOriginal[0] = to; nodeEdge.headOriginal[1] = from; from.addEdge(nodeEdge, 0); to.addEdge(nodeEdge, 1); assertSame(from.first[0], nodeEdge); assertSame(to.first[1], nodeEdge); assertNull(from.first[1]); assertNull(to.first[0]); assertSame(nodeEdge.head[0], to); assertSame(nodeEdge.head[1], from); for (BlossomVNode.IncidentEdgeIterator iterator = from.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); int dir = iterator.getDir(); assertSame(edge.head[dir], to); } for (BlossomVNode.IncidentEdgeIterator iterator = to.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); int dir = iterator.getDir(); assertSame(edge.head[dir], from); } } /** * Tests correct edge removal from linked lists of incidents edges */ @Test public void testRemoveEdge() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 5); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVEdge edge12 = edgeMap.get(e12); int dir = edge12.getDirFrom(node1); node1.removeEdge(edge12, dir); assertEquals(Collections.emptySet(), BlossomVDebugger.getEdgesOf(node1)); node2.removeEdge(edge12, 1 - dir); assertEquals(Collections.emptySet(), BlossomVDebugger.getEdgesOf(node2)); } /** * Tests iteration over all incident edges and correct edge direction */ @Test public void testIncidentEdgeIterator1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e14 = Graphs.addEdgeWithVertices(graph, 1, 4, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge14 = edgeMap.get(e14); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge24 = edgeMap.get(e24); BlossomVEdge edge34 = edgeMap.get(e34); testIncidentEdgeIteratorOf(node1, Set.of(edge12, edge14)); testIncidentEdgeIteratorOf(node2, Set.of(edge12, edge23, edge24)); testIncidentEdgeIteratorOf(node3, Set.of(edge23, edge34)); testIncidentEdgeIteratorOf(node4, Set.of(edge14, edge24, edge34)); } /** * Tests {@link BlossomVNode.IncidentEdgeIterator} for a particular node * * @param node node whose adjacent edge iterator is been tested * @param expectedIncidentEdges expected incident edges of the {@code node} */ private void testIncidentEdgeIteratorOf( BlossomVNode node, Set expectedIncidentEdges) { Set adj = new HashSet<>(); for (BlossomVNode.IncidentEdgeIterator iterator = node.incidentEdgesIterator(); iterator.hasNext();) { BlossomVEdge edge = iterator.next(); assertEquals(node, edge.head[1 - iterator.getDir()]); adj.add(edge); } assertEquals(adj, expectedIncidentEdges); } /** * Tests the proper removal of nodes from their child lists including removal of tree roots from * tree roots linked list */ @Test public void testRemoveFromChildList() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e14 = Graphs.addEdgeWithVertices(graph, 1, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e16 = Graphs.addEdgeWithVertices(graph, 1, 6, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge14 = edgeMap.get(e14); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge16 = edgeMap.get(e16); primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge14, false, false); Set empty = new HashSet<>(); assertEquals(Set.of(node3), BlossomVDebugger.getChildrenOf(node2)); node3.removeFromChildList(); assertEquals(empty, BlossomVDebugger.getChildrenOf(node2)); assertEquals(Set.of(node5), BlossomVDebugger.getChildrenOf(node4)); node5.removeFromChildList(); assertEquals(empty, BlossomVDebugger.getChildrenOf(node4)); assertEquals(Set.of(node2, node4), BlossomVDebugger.getChildrenOf(node1)); node4.removeFromChildList(); assertEquals(Set.of(node2), BlossomVDebugger.getChildrenOf(node1)); node2.removeFromChildList(); assertEquals(empty, BlossomVDebugger.getChildrenOf(node1)); assertEquals(Set.of(node1, node6), BlossomVDebugger.getTreeRoots(state)); node1.removeFromChildList(); assertEquals(Set.of(node6), BlossomVDebugger.getTreeRoots(state)); node6.removeFromChildList(); assertEquals(empty, BlossomVDebugger.getTreeRoots(state)); } /** * Tests proper moving of child lists */ @Test public void testMoveChildrenTo() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e14 = Graphs.addEdgeWithVertices(graph, 1, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); DefaultWeightedEdge e78 = Graphs.addEdgeWithVertices(graph, 7, 8, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge14 = edgeMap.get(e14); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge78 = edgeMap.get(e78); // building tree structures primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge78); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge14, false, false); node1.tree.clearCurrentEdges(); node6.tree.setCurrentEdges(); primalUpdater.grow(edge67, false, false); node6.tree.setCurrentEdges(); // node5 and node4 have no children node5.moveChildrenTo(node3); assertEquals(Set.of(), BlossomVDebugger.getChildrenOf(node3)); // moving child list of size 1 to empty list node2.moveChildrenTo(node4); assertEquals(Set.of(node3, node5), BlossomVDebugger.getChildrenOf(node4)); // moving child list of size 2 to empty list node4.moveChildrenTo(node2); assertEquals(Set.of(node3, node5), BlossomVDebugger.getChildrenOf(node2)); // moving child list to non-empty child list node1.moveChildrenTo(node6); assertEquals(Set.of(node2, node4, node7), BlossomVDebugger.getChildrenOf(node6)); } /** * Tests correct search of penultimate blossom */ @Test public void testGetPenultimateBlossom() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e15 = Graphs.addEdgeWithVertices(graph, 1, 5, 0); DefaultWeightedEdge e16 = Graphs.addEdgeWithVertices(graph, 1, 6, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge16 = edgeMap.get(e16); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge15 = edgeMap.get(e15); node1.tree.setCurrentEdges(); primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.grow(edge12, true, false); BlossomVNode blossom1 = primalUpdater.shrink(edge13, false); primalUpdater.shrink(edge15, false); assertEquals(blossom1, node1.getPenultimateBlossom()); assertEquals(blossom1, node2.getPenultimateBlossom()); assertEquals(blossom1, node3.getPenultimateBlossom()); assertEquals(node4, node4.getPenultimateBlossom()); assertEquals(node5, node5.getPenultimateBlossom()); } @Test public void testGetPenultimateBlossomAndFixBlossomGrandparent() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 0); DefaultWeightedEdge e15 = Graphs.addEdgeWithVertices(graph, 1, 5, 0); DefaultWeightedEdge e17 = Graphs.addEdgeWithVertices(graph, 1, 7, 0); DefaultWeightedEdge e18 = Graphs.addEdgeWithVertices(graph, 1, 8, 0); DefaultWeightedEdge e19 = Graphs.addEdgeWithVertices(graph, 1, 9, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge56 = edgeMap.get(e56); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge15 = edgeMap.get(e15); BlossomVEdge edge17 = edgeMap.get(e17); BlossomVEdge edge18 = edgeMap.get(e18); BlossomVEdge edge19 = edgeMap.get(e19); node1.tree.setCurrentEdges(); primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge67); primalUpdater.grow(edge12, true, false); BlossomVNode blossom1 = primalUpdater.shrink(edge13, false); BlossomVNode blossom2 = primalUpdater.shrink(edge15, false); BlossomVNode blossom3 = primalUpdater.shrink(edge17, false); blossom3.tree.clearCurrentEdges(); node8.tree.setCurrentEdges(); primalUpdater.augment(edge19); primalUpdater.grow(edge18, false, false); // let's assume the worst case: all blossomGrandparent references point to blossom3 node1.blossomGrandparent = blossom1.blossomGrandparent = blossom2.blossomGrandparent = blossom3; assertEquals(blossom2, node1.getPenultimateBlossomAndFixBlossomGrandparent()); assertNotEquals(blossom3, node1.blossomGrandparent); assertNotEquals(blossom3, blossom1.blossomGrandparent); } } BlossomVPrimalUpdaterTest.java000066400000000000000000002575521402514743400363670ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.NONE; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.EPS; import static org.junit.Assert.*; /** * Unit tests for the {@link BlossomVPrimalUpdater} * * @author Timofey Chudakov */ public class BlossomVPrimalUpdaterTest { private BlossomVOptions noneOptions = new BlossomVOptions(NONE); /** * Tests one grow operation */ @Test public void testGrow1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); primalUpdater.augment(edge23); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); node1.tree.clearCurrentEdges(); assertEquals(1, state.statistics.growNum); assertEquals(1, state.treeNum); assertEquals(node1.tree, node2.tree); assertEquals(node1.tree, node3.tree); assertTrue(node2.isMinusNode()); assertTrue(node3.isPlusNode()); assertEquals(node2.getTreeParent(), node1); assertEquals(node3.getTreeParent(), node2); assertEquals(node1.firstTreeChild, node2); assertEquals(node2.firstTreeChild, node3); } /** * Tests updating of the tree structure (tree parent, node's tree reference, node's label). Uses * recursive grow flag */ @Test public void testGrow2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge edge12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge edge23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge edge34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge edge45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge edge36 = Graphs.addEdgeWithVertices(graph, 3, 6, 0); DefaultWeightedEdge edge67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVTree tree = node1.tree; primalUpdater.augment(edgeMap.get(edge45)); primalUpdater.augment(edgeMap.get(edge23)); primalUpdater.augment(edgeMap.get(edge67)); node1.tree.setCurrentEdges(); primalUpdater.grow(edgeMap.get(edge12), true, false); node1.tree.clearCurrentEdges(); assertEquals(tree, node2.tree); assertEquals(tree, node3.tree); assertEquals(tree, node4.tree); assertEquals(tree, node5.tree); assertEquals(tree, node6.tree); assertEquals(tree, node7.tree); assertTrue(node2.isMinusNode()); assertTrue(node4.isMinusNode()); assertTrue(node6.isMinusNode()); assertTrue(node3.isPlusNode()); assertTrue(node5.isPlusNode()); assertTrue(node7.isPlusNode()); assertEquals(node1.firstTreeChild, node2); assertEquals(node2.firstTreeChild, node3); assertTrue(node3.firstTreeChild == node4 || node3.firstTreeChild == node6); assertEquals(node4.firstTreeChild, node5); assertEquals(node6.firstTreeChild, node7); assertEquals(node2.getTreeParent(), node1); assertEquals(node3.getTreeParent(), node2); assertEquals(node4.getTreeParent(), node3); assertEquals(node5.getTreeParent(), node4); assertEquals(node6.getTreeParent(), node3); assertEquals(node7.getTreeParent(), node6); } /** * Tests proper addition of new tree edges without duplicates, and size of heaps in the tree * edges */ @Test public void testGrow3() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); // tree edges DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); // DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 0); DefaultWeightedEdge e57 = Graphs.addEdgeWithVertices(graph, 5, 7, 0); // other edges DefaultWeightedEdge e26 = Graphs.addEdgeWithVertices(graph, 2, 6, 0); DefaultWeightedEdge e36 = Graphs.addEdgeWithVertices(graph, 3, 6, 0); DefaultWeightedEdge e46 = Graphs.addEdgeWithVertices(graph, 4, 6, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); primalUpdater.augment(edge23); primalUpdater.augment(edge45); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); node1.tree.clearCurrentEdges(); Set treeEdges1 = BlossomVDebugger.getTreeEdgesBetween(node1.tree, node6.tree); assertEquals(1, treeEdges1.size()); BlossomVTreeEdge treeEdge1 = treeEdges1.iterator().next(); assertEquals(1, treeEdge1.plusPlusEdges.size()); assertEquals(1, BlossomVDebugger.getMinusPlusHeap(treeEdge1, node1.tree).size()); node1.tree.setCurrentEdges(); primalUpdater.grow(edge34, false, false); node1.tree.clearCurrentEdges(); Set treeEdges2 = BlossomVDebugger.getTreeEdgesBetween(node1.tree, node6.tree); assertEquals(1, treeEdges2.size()); BlossomVTreeEdge treeEdge2 = treeEdges2.iterator().next(); assertEquals(treeEdge1, treeEdge2); assertEquals(2, treeEdge1.plusPlusEdges.size()); assertEquals(2, BlossomVDebugger.getMinusPlusHeap(treeEdge1, node1.tree).size()); Set treeEdges3 = BlossomVDebugger.getTreeEdgesBetween(node1.tree, node7.tree); assertEquals(1, treeEdges3.size()); BlossomVTreeEdge treeEdge3 = treeEdges3.iterator().next(); assertEquals(1, treeEdge3.plusPlusEdges.size()); } /** * Tests addition of new (-, +), (+,-) and (+, +) cross-tree edges to appropriate heaps and * addition of a new tree edge */ @Test public void testGrow4() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); // in-tree edges DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e46 = Graphs.addEdgeWithVertices(graph, 4, 6, 0); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 0); // neighbor tree DefaultWeightedEdge e68 = Graphs.addEdgeWithVertices(graph, 6, 8, 0); DefaultWeightedEdge e78 = Graphs.addEdgeWithVertices(graph, 7, 8, 0); // cross-tree and infinity edges DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 0); DefaultWeightedEdge e26 = Graphs.addEdgeWithVertices(graph, 2, 6, 0); DefaultWeightedEdge e35 = Graphs.addEdgeWithVertices(graph, 3, 5, 0); DefaultWeightedEdge e36 = Graphs.addEdgeWithVertices(graph, 3, 6, 0); DefaultWeightedEdge e37 = Graphs.addEdgeWithVertices(graph, 3, 7, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node4 = vertexMap.get(4); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge56 = edgeMap.get(e56); BlossomVEdge edge78 = edgeMap.get(e78); primalUpdater.augment(edge23); primalUpdater.augment(edge56); primalUpdater.augment(edge78); node4.tree.setCurrentEdges(); primalUpdater.grow(edge45, false, false); node4.tree.clearCurrentEdges(); assertEquals(4, node4.tree.plusInfinityEdges.size()); assertEquals(1, node4.tree.plusPlusEdges.size()); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); node1.tree.clearCurrentEdges(); assertEquals(1, node4.tree.plusInfinityEdges.size()); assertEquals(1, node4.tree.plusPlusEdges.size()); assertEquals(1, node1.tree.plusInfinityEdges.size()); assertEquals(1, node1.tree.plusPlusEdges.size()); BlossomVTreeEdge treeEdge = BlossomVDebugger.getTreeEdge(node1.tree, node4.tree); assertNotNull(treeEdge); int dir = BlossomVDebugger.getDirToOpposite(treeEdge, node1.tree); assertEquals(2, treeEdge.getCurrentMinusPlusHeap(dir).size()); assertEquals(1, treeEdge.getCurrentPlusMinusHeap(dir).size()); assertEquals(1, treeEdge.plusPlusEdges.size()); } /** * Tests updating of the slacks of the incident edges to "-" and "+" grow nodes and updating * their keys in corresponding heaps */ @Test public void testGrow5() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 4); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 2); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 4); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 2); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 5); DefaultWeightedEdge e26 = Graphs.addEdgeWithVertices(graph, 2, 6, 3); DefaultWeightedEdge e35 = Graphs.addEdgeWithVertices(graph, 3, 5, 3); DefaultWeightedEdge e36 = Graphs.addEdgeWithVertices(graph, 3, 6, 3); DefaultWeightedEdge e78 = Graphs.addEdgeWithVertices(graph, 7, 8, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge56 = edgeMap.get(e56); BlossomVEdge edge24 = edgeMap.get(e24); BlossomVEdge edge26 = edgeMap.get(e26); BlossomVEdge edge35 = edgeMap.get(e35); BlossomVEdge edge36 = edgeMap.get(e36); node2.tree.eps = 1; node3.tree.eps = 1; primalUpdater.augment(edge23); node5.tree.eps = 1; node6.tree.eps = 1; primalUpdater.augment(edge56); node4.tree.eps = 3; node4.tree.setCurrentEdges(); primalUpdater.grow(edge45, false, false); node4.tree.clearCurrentEdges(); assertEquals(4, node5.dual, EPS); assertEquals(-2, node6.dual, EPS); assertEquals(0, edge45.slack, EPS); assertEquals(0, edge56.slack, EPS); assertEquals(4, edge24.slack, EPS); assertEquals(4, edge26.slack, EPS); assertEquals(-2, edge35.slack, EPS); assertEquals(4, edge36.slack, EPS); // edge35 is (-, inf) edge, so it isn't present in any heap assertEquals(4, edge24.handle.getKey(), EPS); assertEquals(4, edge26.handle.getKey(), EPS); assertEquals(4, edge26.handle.getKey(), EPS); assertEquals(3, node4.tree.plusInfinityEdges.size()); node1.tree.eps = 3; node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); node1.tree.clearCurrentEdges(); assertEquals(4, node2.dual, EPS); assertEquals(-2, node3.dual, EPS); assertEquals(0, edge12.slack, EPS); assertEquals(0, edge23.slack, EPS); assertEquals(1, edge24.slack, EPS); assertEquals(1, edge26.slack, EPS); assertEquals(1, edge35.slack, EPS); assertEquals(7, edge36.slack, EPS); assertEquals(1, edge24.handle.getKey(), EPS); assertEquals(1, edge26.handle.getKey(), EPS); assertEquals(1, edge35.handle.getKey(), EPS); assertEquals(7, edge36.handle.getKey(), EPS); BlossomVTreeEdge treeEdge = BlossomVDebugger.getTreeEdge(node1.tree, node4.tree); assertNotNull(treeEdge); assertEquals(2, BlossomVDebugger.getMinusPlusHeap(treeEdge, node1.tree).size()); assertEquals(1, BlossomVDebugger.getPlusMinusHeap(treeEdge, node1.tree).size()); assertEquals(1, treeEdge.plusPlusEdges.size()); } /** * Tests addition of (+, +) in-tree edges, (+, inf) edges and "-" pseudonodes to appropriate * heaps and removal of former (+, inf) edges */ @Test public void testGrow6() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 0); DefaultWeightedEdge e57 = Graphs.addEdgeWithVertices(graph, 5, 7, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); DefaultWeightedEdge e71 = Graphs.addEdgeWithVertices(graph, 7, 1, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge24 = edgeMap.get(e24); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge71 = edgeMap.get(e71); primalUpdater.augment(edge34); node2.tree.setCurrentEdges(); primalUpdater.grow(edge23, false, false); BlossomVNode blossom = primalUpdater.shrink(edge24, false); blossom.tree.clearCurrentEdges(); primalUpdater.augment(edge45); primalUpdater.augment(edge67); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); assertEquals(3, node1.tree.plusInfinityEdges.size()); assertEquals(1, node1.tree.minusBlossoms.size()); assertEquals(0, node1.tree.plusPlusEdges.size()); primalUpdater.grow(edge71, false, false); assertEquals(1, node1.tree.minusBlossoms.size()); assertEquals(1, node1.tree.plusPlusEdges.size()); assertEquals(0, node1.tree.plusInfinityEdges.size()); } /** * Tests finding a blossom root */ @Test public void testFindBlossomRoot() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e16 = Graphs.addEdgeWithVertices(graph, 1, 6, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); DefaultWeightedEdge e57 = Graphs.addEdgeWithVertices(graph, 5, 7, 0); BlossomVState state = new BlossomVInitializer<>(graph).initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge16 = edgeMap.get(e16); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge57 = edgeMap.get(e57); primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge67); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge34, false, false); primalUpdater.grow(edge16, false, false); node1.tree.clearCurrentEdges(); BlossomVNode root = primalUpdater.findBlossomRoot(edge57); assertEquals(root, node1); } /** * Tests augment operation on a small test case. Checks updating of the matching, changing * labels, updating edge slack and nodes dual variables */ @Test public void testAugment1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 4); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); node1.tree.eps = 1; node2.tree.eps = 3; BlossomVEdge edge12 = edgeMap.get(e12); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); primalUpdater.augment(edge12); assertEquals(edge12, node1.matched); assertEquals(edge12, node2.matched); Assert.assertEquals(BlossomVNode.Label.INFINITY, node1.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node2.label); assertEquals(2, state.treeNum); assertEquals(0, edge12.slack, EPS); assertEquals(1, node1.dual, EPS); assertEquals(3, node2.dual, EPS); } /** * Tests augment operation. Checks labeling, updated edges' slacks, matching, and tree structure */ @Test public void testAugment2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 4); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 3); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 4); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 3); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 4); DefaultWeightedEdge e78 = Graphs.addEdgeWithVertices(graph, 7, 8, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge56 = edgeMap.get(e56); node2.tree.eps = 2; node3.tree.eps = 1; primalUpdater.augment(edge23); Assert.assertEquals(BlossomVNode.Label.INFINITY, node2.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node3.label); assertEquals(2, edge12.slack, EPS); assertEquals(0, edge23.slack, EPS); assertEquals(3, edge34.slack, EPS); assertEquals(1, node1.tree.plusInfinityEdges.size()); assertEquals(1, node4.tree.plusInfinityEdges.size()); assertTrue(BlossomVDebugger.getTreeEdgesOf(node1.tree).isEmpty()); node4.tree.eps = 1; node5.tree.eps = 2; primalUpdater.augment(edge45); Assert.assertEquals(BlossomVNode.Label.INFINITY, node4.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node5.label); assertEquals(2, edge34.slack, EPS); assertEquals(0, edge45.slack, EPS); assertEquals(2, edge56.slack, EPS); assertEquals(1, node6.tree.plusInfinityEdges.size()); assertTrue(BlossomVDebugger.getTreeEdgesOf(node6.tree).isEmpty()); node1.tree.eps = 2; node6.tree.eps = 2; node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); node1.tree.clearCurrentEdges(); assertEquals(node1.tree, node2.tree); assertEquals(node1.tree, node3.tree); node6.tree.setCurrentEdges(); primalUpdater.grow(edge56, false, false); node6.tree.clearCurrentEdges(); node1.tree.eps += 1; node1.tree.eps += 1; primalUpdater.augment(edge34); Assert.assertEquals(BlossomVNode.Label.INFINITY, node1.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node2.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node3.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node4.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node5.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node6.label); assertEquals(edge12, node1.matched); assertEquals(edge12, node2.matched); assertEquals(edge34, node3.matched); assertEquals(edge34, node4.matched); assertEquals(edge56, node5.matched); assertEquals(edge56, node6.matched); } /** * Tests augment operation on a big test case. Checks matching and labeling */ @Test public void testAugment3() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e36 = Graphs.addEdgeWithVertices(graph, 3, 6, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); DefaultWeightedEdge e18 = Graphs.addEdgeWithVertices(graph, 1, 8, 0); DefaultWeightedEdge e89 = Graphs.addEdgeWithVertices(graph, 8, 9, 0); DefaultWeightedEdge e710 = Graphs.addEdgeWithVertices(graph, 7, 10, 2); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVNode node9 = vertexMap.get(9); BlossomVNode node10 = vertexMap.get(10); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge36 = edgeMap.get(e36); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge18 = edgeMap.get(e18); BlossomVEdge edge89 = edgeMap.get(e89); BlossomVEdge edge710 = edgeMap.get(e710); primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge67); primalUpdater.augment(edge89); node1.tree.setCurrentEdges(); primalUpdater.grow(edge18, true, false); primalUpdater.grow(edge12, true, false); node1.tree.clearCurrentEdges(); node1.tree.eps = 2; primalUpdater.augment(edge710); Assert.assertEquals(BlossomVNode.Label.INFINITY, node1.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node2.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node3.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node4.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node5.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node6.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node7.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node8.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node9.label); Assert.assertEquals(BlossomVNode.Label.INFINITY, node10.label); assertEquals(edge12, node1.matched); assertEquals(edge12, node2.matched); assertEquals(edge36, node3.matched); assertEquals(edge36, node6.matched); assertEquals(edge45, node4.matched); assertEquals(edge45, node5.matched); assertEquals(edge89, node8.matched); assertEquals(edge89, node9.matched); assertEquals(0, edge710.slack, EPS); } /** * Tests tree edges */ @Test public void testAugment4() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 1, 4, 0); DefaultWeightedEdge e41 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVTree tree3 = vertexMap.get(3).tree; BlossomVTree tree4 = vertexMap.get(4).tree; BlossomVTreeEdge treeEdge34 = BlossomVDebugger.getTreeEdge(tree3, tree4); primalUpdater.augment(edge12); assertEquals(Set.of(treeEdge34), BlossomVDebugger.getTreeEdgesOf(tree3)); assertEquals(Set.of(treeEdge34), BlossomVDebugger.getTreeEdgesOf(tree4)); } /** * Test shrink on a small test case. Checks updates tree structure, marking, and other meta data */ @Test public void testShrink1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 0); DefaultWeightedEdge e14 = Graphs.addEdgeWithVertices(graph, 1, 4, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge14 = edgeMap.get(e14); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge24 = edgeMap.get(e24); primalUpdater.augment(edge23); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); BlossomVNode blossom = primalUpdater.shrink(edge13, false); node1.tree.clearCurrentEdges(); assertEquals(1, state.statistics.shrinkNum); assertEquals(1, state.blossomNum); assertFalse(node1.isTreeRoot); assertEquals(Set.of(edge12, edge13), BlossomVDebugger.getEdgesOf(node1)); assertEquals(Set.of(edge12, edge23), BlossomVDebugger.getEdgesOf(node2)); assertEquals(Set.of(edge14, edge24), BlossomVDebugger.getEdgesOf(blossom)); assertEquals(blossom, node1.blossomParent); assertEquals(blossom, node2.blossomParent); assertEquals(blossom, node3.blossomParent); assertEquals(blossom, node1.blossomGrandparent); assertEquals(blossom, node2.blossomGrandparent); assertEquals(blossom, node3.blossomGrandparent); assertEquals(node1, edge14.getCurrentOriginal(blossom)); assertEquals(node4, edge14.getCurrentOriginal(node4)); assertEquals(blossom, edge14.getOpposite(node4)); assertEquals(node4, edge14.getOpposite(blossom)); assertEquals(node4, edge24.getCurrentOriginal(node4)); assertEquals(node2, edge24.getCurrentOriginal(blossom)); assertEquals(blossom, edge24.getOpposite(node4)); assertEquals(node4, edge24.getOpposite(blossom)); assertEquals(blossom, node1.tree.root); assertTrue(blossom.isOuter); assertFalse(node1.isMarked); assertFalse(node2.isMarked); assertFalse(node3.isMarked); } /** * Tests updating of the slacks after blossom shrinking and updating of the edge.handle.getKey() */ @Test public void testShrink2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 4); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 4); DefaultWeightedEdge e14 = Graphs.addEdgeWithVertices(graph, 1, 4, 4); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 2); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 4); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVTree tree1 = node1.tree; BlossomVTree tree4 = node4.tree; BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge14 = edgeMap.get(e14); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge24 = edgeMap.get(e24); node2.tree.eps = 1; node3.tree.eps = 1; primalUpdater.augment(edge23); tree1.setCurrentEdges(); node1.tree.eps = 3; node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); BlossomVNode blossom = primalUpdater.shrink(edge13, false); node1.tree.clearCurrentEdges(); assertEquals(0, edge12.slack, EPS); assertEquals(0, edge13.slack, EPS); assertEquals(0, edge23.slack, EPS); assertEquals(4, edge14.slack, EPS); assertEquals(6, edge24.slack, EPS); assertEquals(4, edge14.handle.getKey(), EPS); assertEquals(6, edge24.handle.getKey(), EPS); BlossomVTreeEdge treeEdge = BlossomVDebugger.getTreeEdge(tree1, tree4); assertNotNull(treeEdge); assertEquals(2, treeEdge.plusPlusEdges.size()); assertEquals(-3, blossom.dual, EPS); } /** * Tests dual part of the shrink operation (updating edges' slacks, handle keys, etc.) */ @Test public void testShrink3() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); // blossom edges DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 5); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 2); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 6); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 4); DefaultWeightedEdge e51 = Graphs.addEdgeWithVertices(graph, 5, 1, 7); // neighbor tree edges DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 3); DefaultWeightedEdge e78 = Graphs.addEdgeWithVertices(graph, 7, 8, 2); // cross-tree edges DefaultWeightedEdge e16 = Graphs.addEdgeWithVertices(graph, 1, 6, 10); DefaultWeightedEdge e57 = Graphs.addEdgeWithVertices(graph, 5, 7, 8); DefaultWeightedEdge e58 = Graphs.addEdgeWithVertices(graph, 5, 8, 9); DefaultWeightedEdge e47 = Graphs.addEdgeWithVertices(graph, 4, 7, 7); DefaultWeightedEdge e910 = Graphs.addEdgeWithVertices(graph, 9, 10, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge51 = edgeMap.get(e51); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge78 = edgeMap.get(e78); BlossomVEdge edge16 = edgeMap.get(e16); BlossomVEdge edge57 = edgeMap.get(e57); BlossomVEdge edge58 = edgeMap.get(e58); BlossomVEdge edge47 = edgeMap.get(e47); node2.tree.eps = 1; node3.tree.eps = 1; node4.tree.eps = 1; node5.tree.eps = 3; primalUpdater.augment(edge23); primalUpdater.augment(edge45); node1.tree.setCurrentEdges(); node1.tree.eps = 4; node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge51, false, false); node1.tree.clearCurrentEdges(); node1.tree.eps += 2; node8.tree.eps = 2; primalUpdater.augment(edge78); node6.tree.setCurrentEdges(); node6.tree.eps = 3; node6.tree.setCurrentEdges(); primalUpdater.grow(edge67, false, false); node6.tree.clearCurrentEdges(); node1.tree.setCurrentEdges(); BlossomVNode blossom = primalUpdater.shrink(edge34, false); assertEquals(6, node1.dual, EPS); assertEquals(-1, node2.dual, EPS); assertEquals(3, node3.dual, EPS); assertEquals(3, node4.dual, EPS); assertEquals(1, node5.dual, EPS); assertEquals(0, edge12.slack, EPS); assertEquals(0, edge23.slack, EPS); assertEquals(0, edge34.slack, EPS); assertEquals(0, edge45.slack, EPS); assertEquals(0, edge51.slack, EPS); assertEquals(10, edge16.slack, EPS); assertEquals(10, edge57.slack, EPS); assertEquals(15, edge58.slack, EPS); assertEquals(7, edge47.slack, EPS); assertEquals(10, edge16.handle.getKey(), EPS); assertEquals(10, edge57.handle.getKey(), EPS); assertEquals(15, edge58.handle.getKey(), EPS); assertEquals(7, edge47.handle.getKey(), EPS); Set treeEdges = BlossomVDebugger.getTreeEdgesBetween(blossom.tree, node6.tree); assertEquals(1, treeEdges.size()); BlossomVTreeEdge treeEdge = treeEdges.iterator().next(); assertEquals(2, treeEdge.plusPlusEdges.size()); assertEquals(2, BlossomVDebugger.getPlusMinusHeap(treeEdge, blossom.tree).size()); assertEquals(0, BlossomVDebugger.getMinusPlusHeap(treeEdge, blossom.tree).size()); assertEquals(0, blossom.tree.plusPlusEdges.size()); } /** * Tests addition and removal of cross-tree edges after blossom shrinking and addition of new * (+, inf) edges ("-" nodes now become "+" nodes) */ @Test public void testShrink4() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 1); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 1); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e51 = Graphs.addEdgeWithVertices(graph, 5, 1, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); DefaultWeightedEdge e78 = Graphs.addEdgeWithVertices(graph, 7, 8, 0); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 0); DefaultWeightedEdge e57 = Graphs.addEdgeWithVertices(graph, 5, 7, 0); DefaultWeightedEdge e58 = Graphs.addEdgeWithVertices(graph, 5, 8, 0); DefaultWeightedEdge e47 = Graphs.addEdgeWithVertices(graph, 4, 7, 0); DefaultWeightedEdge e48 = Graphs.addEdgeWithVertices(graph, 4, 8, 0); DefaultWeightedEdge e29 = Graphs.addEdgeWithVertices(graph, 2, 9, 0); DefaultWeightedEdge e910 = Graphs.addEdgeWithVertices(graph, 9, 10, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVNode node9 = vertexMap.get(9); BlossomVNode node10 = vertexMap.get(10); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge51 = edgeMap.get(e51); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge78 = edgeMap.get(e78); BlossomVEdge edge56 = edgeMap.get(e56); BlossomVEdge edge57 = edgeMap.get(e57); BlossomVEdge edge58 = edgeMap.get(e58); BlossomVEdge edge47 = edgeMap.get(e47); BlossomVEdge edge48 = edgeMap.get(e48); BlossomVEdge edge29 = edgeMap.get(e29); BlossomVEdge edge910 = edgeMap.get(e910); primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge78); primalUpdater.augment(edge910); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge51, false, false); node1.tree.clearCurrentEdges(); node6.tree.setCurrentEdges(); primalUpdater.grow(edge67, false, false); node6.tree.clearCurrentEdges(); node1.tree.setCurrentEdges(); BlossomVNode blossom = primalUpdater.shrink(edge34, false); blossom.tree.clearCurrentEdges(); assertEquals(Set.of(edge12, edge13, edge51), BlossomVDebugger.getEdgesOf(node1)); assertEquals(Set.of(edge12, edge23), BlossomVDebugger.getEdgesOf(node2)); assertEquals(Set.of(edge13, edge23, edge34), BlossomVDebugger.getEdgesOf(node3)); assertEquals(Set.of(edge34, edge45), BlossomVDebugger.getEdgesOf(node4)); assertEquals(Set.of(edge45, edge51), BlossomVDebugger.getEdgesOf(node5)); assertEquals( Set.of(edge29, edge56, edge57, edge58, edge47, edge48), BlossomVDebugger.getEdgesOf(blossom)); BlossomVTreeEdge treeEdge = BlossomVDebugger.getTreeEdge(node1.tree, node6.tree); assertNotNull(treeEdge); assertTrue(blossom.isOuter); assertEquals(1, blossom.tree.plusInfinityEdges.size()); assertEquals(3, treeEdge.plusPlusEdges.size()); assertEquals(2, BlossomVDebugger.getPlusMinusHeap(treeEdge, blossom.tree).size()); assertEquals(0, BlossomVDebugger.getMinusPlusHeap(treeEdge, blossom.tree).size()); } /** * Tests blossomSibling, blossomParent and blossomGrandParent references */ @Test public void testShrink5() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e51 = Graphs.addEdgeWithVertices(graph, 5, 1, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge51 = edgeMap.get(e51); primalUpdater.augment(edge23); primalUpdater.augment(edge45); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge51, false, false); node1.tree.clearCurrentEdges(); BlossomVNode blossom = primalUpdater.shrink(edge34, false); assertEquals(blossom, node1.blossomParent); assertEquals(blossom, node2.blossomParent); assertEquals(blossom, node3.blossomParent); assertEquals(blossom, node4.blossomParent); assertEquals(blossom, node5.blossomParent); assertEquals(blossom, node1.blossomGrandparent); assertEquals(blossom, node2.blossomGrandparent); assertEquals(blossom, node3.blossomGrandparent); assertEquals(blossom, node4.blossomGrandparent); assertEquals(blossom, node5.blossomGrandparent); Set expectedBlossomNodes = Set.of(node1, node2, node3, node4, node5); Set actualBlossomNodes = new HashSet<>(Collections.singletonList(node1)); for (BlossomVNode current = node1.blossomSibling.getOpposite(node1); current != node1; current = current.blossomSibling.getOpposite(current)) { assertNotNull(current); actualBlossomNodes.add(current); } assertEquals(expectedBlossomNodes, actualBlossomNodes); } /** * Tests proper edge moving */ @Test public void testShrink6() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); // first tree edges DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e36 = Graphs.addEdgeWithVertices(graph, 3, 6, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); // neighbor tree edges DefaultWeightedEdge e89 = Graphs.addEdgeWithVertices(graph, 8, 9, 0); DefaultWeightedEdge e910 = Graphs.addEdgeWithVertices(graph, 9, 10, 0); // cross-tree edges DefaultWeightedEdge e18 = Graphs.addEdgeWithVertices(graph, 1, 8, 0); DefaultWeightedEdge e19 = Graphs.addEdgeWithVertices(graph, 1, 9, 0); DefaultWeightedEdge e28 = Graphs.addEdgeWithVertices(graph, 2, 8, 0); DefaultWeightedEdge e29 = Graphs.addEdgeWithVertices(graph, 2, 9, 0); DefaultWeightedEdge e39 = Graphs.addEdgeWithVertices(graph, 3, 9, 0); DefaultWeightedEdge e310 = Graphs.addEdgeWithVertices(graph, 3, 10, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVNode node9 = vertexMap.get(9); BlossomVNode node10 = vertexMap.get(10); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge36 = edgeMap.get(e36); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge89 = edgeMap.get(e89); BlossomVEdge edge910 = edgeMap.get(e910); BlossomVEdge edge18 = edgeMap.get(e18); BlossomVEdge edge19 = edgeMap.get(e19); BlossomVEdge edge28 = edgeMap.get(e28); BlossomVEdge edge29 = edgeMap.get(e29); BlossomVEdge edge39 = edgeMap.get(e39); BlossomVEdge edge310 = edgeMap.get(e310); // setting up the test case structure primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge67); primalUpdater.augment(edge910); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge34, false, false); primalUpdater.grow(edge36, false, false); primalUpdater.grow(edge89, false, false); BlossomVNode blossom = primalUpdater.shrink(edge13, false); blossom.tree.clearCurrentEdges(); // validating the tree structure assertEquals(blossom, node4.getTreeParent()); assertEquals(blossom, node6.getTreeParent()); assertEquals(Set.of(node4, node6), BlossomVDebugger.getChildrenOf(blossom)); // validating the edges endpoints assertEquals(blossom, edge18.getOpposite(node8)); assertEquals(blossom, edge19.getOpposite(node9)); assertEquals(blossom, edge28.getOpposite(node8)); assertEquals(blossom, edge29.getOpposite(node9)); assertEquals(blossom, edge39.getOpposite(node9)); assertEquals(blossom, edge310.getOpposite(node10)); } /** * Tests removal of the (-,+) and addition of the (+,+) cross-tree edges, updating their slacks * and updating heaps */ @Test public void testShrink7() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); // main tree edges DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 4); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 4); DefaultWeightedEdge e26 = Graphs.addEdgeWithVertices(graph, 2, 6, 3); // neighbor tree edges DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 4); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 4); // cross-tree edges DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 2); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 3); DefaultWeightedEdge e25 = Graphs.addEdgeWithVertices(graph, 2, 5, 3); DefaultWeightedEdge e78 = Graphs.addEdgeWithVertices(graph, 7, 8, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge24 = edgeMap.get(e24); BlossomVEdge edge25 = edgeMap.get(e25); BlossomVEdge edge26 = edgeMap.get(e26); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge56 = edgeMap.get(e56); node2.tree.eps = 1; node3.tree.eps = 1; primalUpdater.augment(edge23); node1.tree.eps = 3; node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); node1.tree.clearCurrentEdges(); node5.tree.eps = 2; node6.tree.eps = 2; primalUpdater.augment(edge56); node4.tree.eps = 2; node4.tree.setCurrentEdges(); primalUpdater.grow(edge45, false, false); node4.tree.clearCurrentEdges(); node1.tree.setCurrentEdges(); BlossomVNode blossom = primalUpdater.shrink(edge13, false); assertEquals(5, edge24.slack, EPS); assertEquals(1, edge25.slack, EPS); assertEquals(5, edge26.slack, EPS); BlossomVTreeEdge treeEdge = BlossomVDebugger.getTreeEdge(node1.tree, node4.tree); assertNotNull(treeEdge); assertEquals(0, BlossomVDebugger.getMinusPlusHeap(treeEdge, node1.tree).size()); assertEquals(1, BlossomVDebugger.getPlusMinusHeap(treeEdge, node1.tree).size()); assertEquals(2, treeEdge.plusPlusEdges.size()); assertEquals(5, edge24.handle.getKey(), EPS); assertEquals(1, edge25.handle.getKey(), EPS); assertEquals(5, edge26.handle.getKey(), EPS); } /** * Tests dual updates of the inner (+, +), (-, +) and (+, inf) edges */ @Test public void testShrink8() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 4); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 3); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 4); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 3); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 5); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 3); DefaultWeightedEdge e71 = Graphs.addEdgeWithVertices(graph, 7, 1, 4); DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 8); DefaultWeightedEdge e26 = Graphs.addEdgeWithVertices(graph, 2, 6, 8); DefaultWeightedEdge e36 = Graphs.addEdgeWithVertices(graph, 3, 6, 8); DefaultWeightedEdge e89 = Graphs.addEdgeWithVertices(graph, 8, 9, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge56 = edgeMap.get(e56); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge71 = edgeMap.get(e71); BlossomVEdge edge24 = edgeMap.get(e24); BlossomVEdge edge26 = edgeMap.get(e26); BlossomVEdge edge36 = edgeMap.get(e36); node2.tree.eps = 2; node4.tree.eps = 2; node7.tree.eps = 2; node3.tree.eps = 1; node5.tree.eps = 1; node6.tree.eps = 1; primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge67); node1.tree.eps = 2; node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge71, false, false); node1.tree.eps += 1; primalUpdater.grow(edge34, false, false); node1.tree.eps += 1; BlossomVNode blossom = primalUpdater.shrink(edge56, false); blossom.tree.clearCurrentEdges(); assertEquals(7, edge24.slack, EPS); assertEquals(5, edge26.slack, EPS); assertEquals(2, edge36.slack, EPS); assertEquals(0, blossom.tree.plusPlusEdges.size()); } /** * Tests updating of the tree structure on a small test case */ @Test public void testExpand1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e35 = Graphs.addEdgeWithVertices(graph, 3, 5, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge35 = edgeMap.get(e35); primalUpdater.augment(edge23); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); BlossomVNode blossom = primalUpdater.shrink(edge13, false); blossom.tree.clearCurrentEdges(); primalUpdater.augment(edge35); node4.tree.setCurrentEdges(); primalUpdater.grow(edge34, false, false); primalUpdater.expand(blossom, false); node4.tree.clearCurrentEdges(); assertEquals(1, state.statistics.expandNum); // checking tree structure assertEquals(node4.tree, node3.tree); assertEquals(node4, node3.getTreeParent()); assertEquals(node3, node5.getTreeParent()); assertEquals(Set.of(node3), BlossomVDebugger.getChildrenOf(node4)); assertEquals(Set.of(node5), BlossomVDebugger.getChildrenOf(node3)); assertEquals(Set.of(edge34, edge35, edge23, edge13), BlossomVDebugger.getEdgesOf(node3)); // checking edges new endpoints assertEquals(node3, edge34.getOpposite(node4)); assertEquals(node3, edge35.getOpposite(node5)); // checking the matching assertEquals(edge12, node1.matched); assertEquals(edge12, node2.matched); assertEquals(edge35, node3.matched); assertFalse(node1.isMarked); assertFalse(node2.isMarked); assertFalse(node3.isMarked); assertFalse(node4.isMarked); assertFalse(node5.isMarked); assertFalse(node1.isProcessed); assertFalse(node2.isProcessed); assertFalse(node3.isProcessed); assertFalse(node4.isProcessed); assertFalse(node5.isProcessed); // checking the labeling and isOuter flag assertTrue(node1.isInfinityNode()); assertTrue(node2.isInfinityNode()); assertTrue(node3.isMinusNode()); assertTrue(node1.isOuter); assertTrue(node2.isOuter); assertTrue(node3.isOuter); } /** * Test primal updates after blossom expanding */ @Test public void testExpand2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); // blossom nodes DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e51 = Graphs.addEdgeWithVertices(graph, 5, 1, 0); // blossom tree nodes DefaultWeightedEdge e62 = Graphs.addEdgeWithVertices(graph, 6, 2, 0); DefaultWeightedEdge e37 = Graphs.addEdgeWithVertices(graph, 3, 7, 0); // neighbor tree nodes DefaultWeightedEdge e89 = Graphs.addEdgeWithVertices(graph, 8, 9, 0); DefaultWeightedEdge e910 = Graphs.addEdgeWithVertices(graph, 9, 10, 0); // cross-tree edges DefaultWeightedEdge e18 = Graphs.addEdgeWithVertices(graph, 1, 8, 0); DefaultWeightedEdge e58 = Graphs.addEdgeWithVertices(graph, 5, 8, 0); DefaultWeightedEdge e48 = Graphs.addEdgeWithVertices(graph, 4, 8, 0); DefaultWeightedEdge e29 = Graphs.addEdgeWithVertices(graph, 2, 9, 0); DefaultWeightedEdge e39 = Graphs.addEdgeWithVertices(graph, 3, 9, 0); // infinity edge DefaultWeightedEdge e210 = Graphs.addEdgeWithVertices(graph, 2, 10, 0); DefaultWeightedEdge e310 = Graphs.addEdgeWithVertices(graph, 3, 10, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVNode node9 = vertexMap.get(9); BlossomVNode node10 = vertexMap.get(10); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge51 = edgeMap.get(e51); BlossomVEdge edge89 = edgeMap.get(e89); BlossomVEdge edge910 = edgeMap.get(e910); BlossomVEdge edge62 = edgeMap.get(e62); BlossomVEdge edge37 = edgeMap.get(e37); BlossomVEdge edge18 = edgeMap.get(e18); BlossomVEdge edge58 = edgeMap.get(e58); BlossomVEdge edge48 = edgeMap.get(e48); BlossomVEdge edge29 = edgeMap.get(e29); BlossomVEdge edge39 = edgeMap.get(e39); BlossomVEdge edge210 = edgeMap.get(e210); BlossomVEdge edge310 = edgeMap.get(e310); primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge910); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge51, false, false); BlossomVNode blossom = primalUpdater.shrink(edge34, false); blossom.tree.clearCurrentEdges(); node8.tree.setCurrentEdges(); primalUpdater.grow(edge89, false, false); node8.tree.clearCurrentEdges(); primalUpdater.augment(edge37); node6.tree.setCurrentEdges(); primalUpdater.grow(edge62, false, false); primalUpdater.expand(blossom, false); // testing edges endpoints assertEquals(node2, edge62.getOpposite(node6)); assertEquals(node3, edge37.getOpposite(node7)); assertEquals(node1, edge18.getOpposite(node8)); assertEquals(node5, edge58.getOpposite(node8)); assertEquals(node4, edge48.getOpposite(node8)); assertEquals(node2, edge29.getOpposite(node9)); assertEquals(node2, edge210.getOpposite(node10)); assertEquals(node3, edge39.getOpposite(node9)); assertEquals(node3, edge310.getOpposite(node10)); // testing the matching assertEquals(edge12, node2.matched); assertEquals(edge12, node1.matched); assertEquals(edge45, node5.matched); assertEquals(edge45, node4.matched); assertEquals(edge37, node3.matched); // testing the labeling assertTrue(node2.isMinusNode()); assertTrue(node1.isPlusNode()); assertTrue(node5.isMinusNode()); assertTrue(node4.isPlusNode()); assertTrue(node3.isMinusNode()); // testing isOuter assertTrue(node2.isOuter); assertTrue(node1.isOuter); assertTrue(node5.isOuter); assertTrue(node4.isOuter); assertTrue(node3.isOuter); // testing node.tree assertEquals(node6.tree, node2.tree); assertEquals(node6.tree, node1.tree); assertEquals(node6.tree, node5.tree); assertEquals(node6.tree, node4.tree); assertEquals(node6.tree, node3.tree); // testing tree structure assertEquals(node6, node2.getTreeParent()); assertEquals(node2, node1.getTreeParent()); assertEquals(node1, node5.getTreeParent()); assertEquals(node5, node4.getTreeParent()); assertEquals(node4, node3.getTreeParent()); assertEquals(node3, node7.getTreeParent()); assertEquals(Set.of(node2), BlossomVDebugger.getChildrenOf(node6)); assertEquals(Set.of(node1), BlossomVDebugger.getChildrenOf(node2)); assertEquals(Set.of(node5), BlossomVDebugger.getChildrenOf(node1)); assertEquals(Set.of(node4), BlossomVDebugger.getChildrenOf(node5)); assertEquals(Set.of(node3), BlossomVDebugger.getChildrenOf(node4)); assertEquals(Set.of(node7), BlossomVDebugger.getChildrenOf(node3)); assertEquals( Set.of(node6, node2, node1, node5, node4, node3, node7), BlossomVDebugger.getTreeNodes(node6.tree)); } /** * Tests dual part of the expand operation */ @Test public void testExpand3() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 4); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 4); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 2); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 5); DefaultWeightedEdge e35 = Graphs.addEdgeWithVertices(graph, 3, 5, 5); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge35 = edgeMap.get(e35); node2.tree.eps = 1; node3.tree.eps = 1; primalUpdater.augment(edge23); node1.tree.setCurrentEdges(); node1.tree.eps = 3; primalUpdater.grow(edge12, false, false); BlossomVNode blossom = primalUpdater.shrink(edge13, false); blossom.tree.clearCurrentEdges(); node5.tree.eps = 2; blossom.tree.eps += 2; primalUpdater.augment(edge35); node4.tree.eps = 2; node4.tree.setCurrentEdges(); primalUpdater.grow(edge34, false, false); primalUpdater.expand(blossom, false); node4.tree.clearCurrentEdges(); assertEquals(3, node1.dual, EPS); assertEquals(1, node2.dual, EPS); assertEquals(3, node3.dual, EPS); assertEquals(0, node4.dual, EPS); assertEquals(0, node5.dual, EPS); assertEquals(0, edge12.slack, EPS); assertEquals(-2, edge13.slack, EPS); assertEquals(-2, edge23.slack, EPS); assertEquals(0, edge34.slack, EPS); assertEquals(0, edge35.slack, EPS); } /** * Tests dual part of the expand operation on a bigger test case */ @Test public void testExpand4() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); // blossom edges DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 4); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 3); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 4); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 3); DefaultWeightedEdge e51 = Graphs.addEdgeWithVertices(graph, 5, 1, 4); // edges of the tree, that will contain blossom DefaultWeightedEdge e65 = Graphs.addEdgeWithVertices(graph, 6, 5, 4); DefaultWeightedEdge e37 = Graphs.addEdgeWithVertices(graph, 3, 7, 4); // edges of neighbor tree DefaultWeightedEdge e89 = Graphs.addEdgeWithVertices(graph, 8, 9, 0); DefaultWeightedEdge e910 = Graphs.addEdgeWithVertices(graph, 9, 10, 0); // edges between blossom and neighbor tree DefaultWeightedEdge e58 = Graphs.addEdgeWithVertices(graph, 5, 8, 8); DefaultWeightedEdge e59 = Graphs.addEdgeWithVertices(graph, 5, 9, 8); DefaultWeightedEdge e48 = Graphs.addEdgeWithVertices(graph, 4, 8, 8); DefaultWeightedEdge e49 = Graphs.addEdgeWithVertices(graph, 4, 9, 8); DefaultWeightedEdge e29 = Graphs.addEdgeWithVertices(graph, 2, 9, 8); DefaultWeightedEdge e210 = Graphs.addEdgeWithVertices(graph, 2, 10, 8); // inner blossom edges DefaultWeightedEdge e24 = Graphs.addEdgeWithVertices(graph, 2, 4, 8); DefaultWeightedEdge e25 = Graphs.addEdgeWithVertices(graph, 2, 5, 8); // edges between blossom nodes and node from the same tree DefaultWeightedEdge e27 = Graphs.addEdgeWithVertices(graph, 2, 7, 8); DefaultWeightedEdge e47 = Graphs.addEdgeWithVertices(graph, 4, 7, 8); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVNode node9 = vertexMap.get(9); BlossomVNode node10 = vertexMap.get(10); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge51 = edgeMap.get(e51); BlossomVEdge edge65 = edgeMap.get(e65); BlossomVEdge edge37 = edgeMap.get(e37); BlossomVEdge edge89 = edgeMap.get(e89); BlossomVEdge edge910 = edgeMap.get(e910); BlossomVEdge edge58 = edgeMap.get(e58); BlossomVEdge edge59 = edgeMap.get(e59); BlossomVEdge edge48 = edgeMap.get(e48); BlossomVEdge edge49 = edgeMap.get(e49); BlossomVEdge edge27 = edgeMap.get(e27); BlossomVEdge edge29 = edgeMap.get(e29); BlossomVEdge edge210 = edgeMap.get(e210); BlossomVEdge edge47 = edgeMap.get(e47); BlossomVEdge edge24 = edgeMap.get(e24); BlossomVEdge edge25 = edgeMap.get(e25); // setting up the blossom structure node2.tree.eps = 2; node3.tree.eps = 1; node4.tree.eps = 1; node5.tree.eps = 2; primalUpdater.augment(edge23); primalUpdater.augment(edge45); node1.tree.eps = 2; node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge51, false, false); node1.tree.eps += 1; BlossomVNode blossom = primalUpdater.shrink(edge34, false); blossom.tree.clearCurrentEdges(); // setting up the "-" blossom's tree structure node7.tree.eps = 1; blossom.tree.eps += 1; primalUpdater.augment(edge37); node6.tree.eps = 2; node6.tree.setCurrentEdges(); primalUpdater.grow(edge65, false, false); node6.tree.clearCurrentEdges(); // setting up the structure of the neighbor tree primalUpdater.augment(edge910); node8.tree.setCurrentEdges(); primalUpdater.grow(edge89, false, false); node8.tree.setCurrentEdges(); node6.tree.setCurrentEdges(); primalUpdater.expand(blossom, false); node6.tree.clearCurrentEdges(); BlossomVTreeEdge treeEdge = BlossomVDebugger.getTreeEdge(node6.tree, node8.tree); // validating blossom node duals node1.tree = node2.tree = null; assertEquals(3, node1.dual, EPS); assertEquals(1, node2.dual, EPS); assertEquals(4, node3.dual, EPS); // tree eps is 2, node3.label = "-" assertEquals(0, node4.dual, EPS); // tree eps is 2, node4.label = "+" assertEquals(3, node5.dual, EPS); // tree eps is 2, node5.label = "-" // validating slacks of the edges in the tree structure assertEquals(0, edge65.slack, EPS); assertEquals(0, edge45.slack, EPS); assertEquals(0, edge34.slack, EPS); assertEquals(0, edge37.slack, EPS); // validating the slacks of inner blossom edges assertEquals(7, edge24.slack, EPS); assertEquals(4, edge25.slack, EPS); // validating slacks of cross-tree edges // assertEquals(4, edge58.slack, EPS); assertEquals(4, edge59.slack, EPS); assertEquals(7, edge48.slack, EPS); assertEquals(7, edge49.slack, EPS); // validating slacks of the (+, inf) edges and a (-, inf) edge assertEquals(6, edge210.slack, EPS); assertEquals(7, edge27.slack, EPS); assertEquals(6, edge29.slack, EPS); // validating keys of the cross-tree and infinity edges in the heaps assertEquals(4, edge58.handle.getKey(), EPS); assertEquals(7, edge48.handle.getKey(), EPS); assertEquals(7, edge49.handle.getKey(), EPS); assertEquals(6, edge210.handle.getKey(), EPS); assertEquals(7, edge24.handle.getKey(), EPS); assertEquals(8, edge47.handle.getKey(), EPS); assertEquals(7, edge27.handle.getKey(), EPS); // validating slacks of the edges on the odd branch assertEquals(-2, edge51.slack, EPS); assertEquals(-2, edge23.slack, EPS); assertEquals(0, edge12.slack, EPS); // validating slack of the new (+, +) node assertEquals(8, edge47.slack, EPS); // validating tree edges amount assertNotNull(treeEdge); assertEquals(1, BlossomVDebugger.getTreeEdgesBetween(node6.tree, node8.tree).size()); // validating sizes of the heaps of the tree edge assertEquals(1, treeEdge.plusPlusEdges.size()); assertEquals(1, BlossomVDebugger.getMinusPlusHeap(treeEdge, node6.tree).size()); assertEquals(1, BlossomVDebugger.getPlusMinusHeap(treeEdge, node6.tree).size()); // validating sizes of tree heaps assertEquals(2, node6.tree.plusInfinityEdges.size()); assertEquals(1, node6.tree.plusPlusEdges.size()); assertEquals(0, node6.tree.minusBlossoms.size()); assertEquals(1, node8.tree.plusInfinityEdges.size()); assertEquals(0, node8.tree.plusPlusEdges.size()); assertEquals(0, node8.tree.minusBlossoms.size()); } /** * Tests preserving the state of the blossom, inner and infinity edges after shrink and expand * operations */ @Test public void testExpand5() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); // blossom edges DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 3); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 4); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 4); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 4); DefaultWeightedEdge e56 = Graphs.addEdgeWithVertices(graph, 5, 6, 6); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 4); DefaultWeightedEdge e71 = Graphs.addEdgeWithVertices(graph, 7, 1, 3); // tree edges DefaultWeightedEdge e78 = Graphs.addEdgeWithVertices(graph, 7, 8, 1); DefaultWeightedEdge e39 = Graphs.addEdgeWithVertices(graph, 3, 9, 3); // inner blossom edges DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 8); // (-, inf) edge DefaultWeightedEdge e26 = Graphs.addEdgeWithVertices(graph, 2, 6, 8); // (+, inf) edge DefaultWeightedEdge e35 = Graphs.addEdgeWithVertices(graph, 3, 5, 8); // (-, -) edge DefaultWeightedEdge e46 = Graphs.addEdgeWithVertices(graph, 4, 6, 8); // (+, +) edge DefaultWeightedEdge e47 = Graphs.addEdgeWithVertices(graph, 4, 7, 8); // (+, -) edge // matched edge DefaultWeightedEdge e1011 = Graphs.addEdgeWithVertices(graph, 10, 11, 0); // infinity edges DefaultWeightedEdge e510 = Graphs.addEdgeWithVertices(graph, 5, 10, 8); // (-, inf) edge DefaultWeightedEdge e610 = Graphs.addEdgeWithVertices(graph, 6, 10, 8); // (+, inf) edge DefaultWeightedEdge e211 = Graphs.addEdgeWithVertices(graph, 2, 11, 8); // (inf, inf) edge BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVNode node8 = vertexMap.get(8); BlossomVNode node9 = vertexMap.get(9); BlossomVNode node10 = vertexMap.get(10); BlossomVNode node11 = vertexMap.get(11); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge56 = edgeMap.get(e56); BlossomVEdge edge67 = edgeMap.get(e67); BlossomVEdge edge71 = edgeMap.get(e71); BlossomVEdge edge78 = edgeMap.get(e78); BlossomVEdge edge39 = edgeMap.get(e39); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge26 = edgeMap.get(e26); BlossomVEdge edge35 = edgeMap.get(e35); BlossomVEdge edge46 = edgeMap.get(e46); BlossomVEdge edge47 = edgeMap.get(e47); BlossomVEdge edge510 = edgeMap.get(e510); BlossomVEdge edge610 = edgeMap.get(e610); BlossomVEdge edge211 = edgeMap.get(e211); node1.tree.eps = 2; node2.tree.eps = 1; node3.tree.eps = 3; node4.tree.eps = 1; node5.tree.eps = 3; node6.tree.eps = 3; node7.tree.eps = 1; primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge67); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, false, false); primalUpdater.grow(edge34, false, false); primalUpdater.grow(edge71, false, false); BlossomVNode blossom = primalUpdater.shrink(edge56, false); blossom.tree.clearCurrentEdges(); primalUpdater.augment(edge39); node8.tree.setCurrentEdges(); primalUpdater.grow(edge78, false, false); primalUpdater.expand(blossom, false); assertEquals(node7, edge78.getOpposite(node8)); assertEquals(node3, edge39.getOpposite(node9)); assertEquals(node5, edge510.getOpposite(node10)); assertEquals(node6, edge610.getOpposite(node10)); assertEquals(node2, edge211.getOpposite(node11)); // tight edges assertEquals(0, edge12.slack, EPS); assertEquals(0, edge23.slack, EPS); assertEquals(0, edge34.slack, EPS); assertEquals(0, edge45.slack, EPS); assertEquals(0, edge56.slack, EPS); assertEquals(0, edge67.slack, EPS); assertEquals(0, edge71.slack, EPS); assertEquals(0, edge78.slack, EPS); assertEquals(0, edge39.slack, EPS); // inner edges assertEquals(3, edge13.slack, EPS); assertEquals(4, edge26.slack, EPS); assertEquals(2, edge35.slack, EPS); assertEquals(4, edge46.slack, EPS); assertEquals(6, edge47.slack, EPS); // boundary edges assertEquals(7, edge211.slack, EPS); assertEquals(5, edge510.slack, EPS); assertEquals(5, edge610.slack, EPS); } } BlossomVStateTest.java000066400000000000000000000065041402514743400346630ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.NONE; import static org.junit.Assert.assertEquals; /** * Unit tests for the {@link BlossomVState} * * @author Timofey Chudakov */ public class BlossomVStateTest { @Test public void testAddTreeEdge() { BlossomVTree tree1 = new BlossomVTree(new BlossomVNode(-1)); // positions doesn't matter // here BlossomVTree tree2 = new BlossomVTree(new BlossomVNode(-1)); BlossomVTreeEdge treeEdge = BlossomVTree.addTreeEdge(tree1, tree2); int currentDir = tree2.currentDirection; assertEquals(tree2, treeEdge.head[currentDir]); assertEquals(tree1, treeEdge.head[1 - currentDir]); } @Test public void testMoveEdge() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e13 = Graphs.addEdgeWithVertices(graph, 1, 3, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(new BlossomVOptions(NONE)); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge13 = edgeMap.get(e13); BlossomVEdge edge23 = edgeMap.get(e23); edge12.moveEdgeTail(node2, node3); assertEquals(node3, edge12.getOpposite(node1)); assertEquals(Set.of(edge12, edge13), BlossomVDebugger.getEdgesOf(node1)); assertEquals(Set.of(edge23), BlossomVDebugger.getEdgesOf(node2)); assertEquals(Set.of(edge12, edge13, edge23), BlossomVDebugger.getEdgesOf(node3)); edge23.moveEdgeTail(node2, node1); assertEquals(node1, edge13.getOpposite(node3)); assertEquals(Set.of(edge12, edge13, edge23), BlossomVDebugger.getEdgesOf(node1)); assertEquals(Set.of(), BlossomVDebugger.getEdgesOf(node2)); assertEquals(Set.of(edge12, edge13, edge23), BlossomVDebugger.getEdgesOf(node3)); } } BlossomVTreeEdgeTest.java000066400000000000000000000100741402514743400352640ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.NONE; import static org.junit.Assert.*; /** * Unit tests for the {@link BlossomVTreeEdge} * * @author Timofey Chudakov */ public class BlossomVTreeEdgeTest { @Test public void testGetCurrentPlusMinusHeap() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(new BlossomVOptions(NONE)); Map vertexMap = BlossomVDebugger.getVertexMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVTreeEdge treeEdge = BlossomVDebugger.getTreeEdge(node1.tree, node2.tree); assertNotSame(treeEdge.getCurrentMinusPlusHeap(0), treeEdge.getCurrentPlusMinusHeap(0)); assertNotSame(treeEdge.getCurrentMinusPlusHeap(1), treeEdge.getCurrentPlusMinusHeap(1)); assertSame(treeEdge.getCurrentPlusMinusHeap(0), treeEdge.getCurrentMinusPlusHeap(1)); assertSame(treeEdge.getCurrentMinusPlusHeap(0), treeEdge.getCurrentPlusMinusHeap(1)); } @Test public void testRemoveFromTreeEdgeList() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 0); Graphs.addEdgeWithVertices(graph, 1, 3, 0); Graphs.addEdgeWithVertices(graph, 2, 3, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(new BlossomVOptions(NONE)); Map vertexMap = BlossomVDebugger.getVertexMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVTree tree1 = node1.tree; BlossomVTree tree2 = node2.tree; BlossomVTree tree3 = node3.tree; BlossomVTreeEdge treeEdge12 = BlossomVDebugger.getTreeEdge(tree1, tree2); BlossomVTreeEdge treeEdge13 = BlossomVDebugger.getTreeEdge(tree1, tree3); BlossomVTreeEdge treeEdge23 = BlossomVDebugger.getTreeEdge(tree2, tree3); assertNotNull(treeEdge12); assertNotNull(treeEdge13); assertNotNull(treeEdge23); treeEdge12.removeFromTreeEdgeList(); assertEquals(Set.of(treeEdge13), BlossomVDebugger.getTreeEdgesOf(tree1)); assertEquals(Set.of(treeEdge23), BlossomVDebugger.getTreeEdgesOf(tree2)); treeEdge13.removeFromTreeEdgeList(); assertTrue(BlossomVDebugger.getTreeEdgesOf(tree1).isEmpty()); assertEquals(Set.of(treeEdge23), BlossomVDebugger.getTreeEdgesOf(tree2)); assertEquals(Set.of(treeEdge23), BlossomVDebugger.getTreeEdgesOf(tree3)); treeEdge23.removeFromTreeEdgeList(); assertTrue(BlossomVDebugger.getTreeEdgesOf(tree2).isEmpty()); assertTrue(BlossomVDebugger.getTreeEdgesOf(tree3).isEmpty()); } } BlossomVTreeTest.java000066400000000000000000000132461402514743400345030ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.BlossomVNode.Label.MINUS; import static org.jgrapht.alg.matching.blossom.v5.BlossomVOptions.InitializationType.NONE; import static org.junit.Assert.*; /** * Unit tests for the {@link BlossomVTree} * * @author Timofey Chudakov */ public class BlossomVTreeTest { private BlossomVOptions noneOptions = new BlossomVOptions(NONE); @Test public void testTreeNodeIterator() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge e12 = Graphs.addEdgeWithVertices(graph, 1, 2, 0); DefaultWeightedEdge e23 = Graphs.addEdgeWithVertices(graph, 2, 3, 0); DefaultWeightedEdge e34 = Graphs.addEdgeWithVertices(graph, 3, 4, 0); DefaultWeightedEdge e45 = Graphs.addEdgeWithVertices(graph, 4, 5, 0); DefaultWeightedEdge e36 = Graphs.addEdgeWithVertices(graph, 3, 6, 0); DefaultWeightedEdge e67 = Graphs.addEdgeWithVertices(graph, 6, 7, 0); BlossomVInitializer initializer = new BlossomVInitializer<>(graph); BlossomVState state = initializer.initialize(noneOptions); BlossomVPrimalUpdater primalUpdater = new BlossomVPrimalUpdater<>(state); Map vertexMap = BlossomVDebugger.getVertexMap(state); Map edgeMap = BlossomVDebugger.getEdgeMap(state); BlossomVNode node1 = vertexMap.get(1); BlossomVNode node2 = vertexMap.get(2); BlossomVNode node3 = vertexMap.get(3); BlossomVNode node4 = vertexMap.get(4); BlossomVNode node5 = vertexMap.get(5); BlossomVNode node6 = vertexMap.get(6); BlossomVNode node7 = vertexMap.get(7); BlossomVEdge edge12 = edgeMap.get(e12); BlossomVEdge edge23 = edgeMap.get(e23); BlossomVEdge edge34 = edgeMap.get(e34); BlossomVEdge edge45 = edgeMap.get(e45); BlossomVEdge edge36 = edgeMap.get(e36); BlossomVEdge edge67 = edgeMap.get(e67); primalUpdater.augment(edge23); primalUpdater.augment(edge45); primalUpdater.augment(edge67); node1.tree.setCurrentEdges(); primalUpdater.grow(edge12, true, false); int i = 0; Set actualNodes = new HashSet<>(); for (BlossomVTree.TreeNodeIterator iterator = node1.tree.treeNodeIterator(); iterator.hasNext();) { i++; actualNodes.add(iterator.next()); } assertEquals(7, i); assertEquals(Set.of(node1, node2, node3, node4, node5, node6, node7), actualNodes); } @Test public void testTreeEdgeIterator() { BlossomVNode node1 = new BlossomVNode(-1); // positions doesn't matter here BlossomVNode node2 = new BlossomVNode(-1); BlossomVNode node3 = new BlossomVNode(-1); BlossomVNode node4 = new BlossomVNode(-1); BlossomVNode node5 = new BlossomVNode(-1); BlossomVTree tree1 = new BlossomVTree(node1); BlossomVTree tree2 = new BlossomVTree(node2); BlossomVTree tree3 = new BlossomVTree(node3); BlossomVTree tree4 = new BlossomVTree(node4); BlossomVTree tree5 = new BlossomVTree(node5); BlossomVTreeEdge treeEdge1 = BlossomVTree.addTreeEdge(tree1, tree2); BlossomVTreeEdge treeEdge2 = BlossomVTree.addTreeEdge(tree1, tree3); BlossomVTreeEdge treeEdge3 = BlossomVTree.addTreeEdge(tree4, tree1); BlossomVTreeEdge treeEdge4 = BlossomVTree.addTreeEdge(tree5, tree1); Set expectedOutEdges = Set.of(treeEdge1, treeEdge2); Set expectedInEdges = Set.of(treeEdge3, treeEdge4); Set actualOutEdges = new HashSet<>(); Set actualInEdges = new HashSet<>(); for (BlossomVTree.TreeEdgeIterator iterator = tree1.treeEdgeIterator(); iterator.hasNext();) { BlossomVTreeEdge edge = iterator.next(); int currentDir = iterator.getCurrentDirection(); if (currentDir == 0) { actualOutEdges.add(edge); } else { actualInEdges.add(edge); } assertSame(tree1, edge.head[1 - currentDir]); } assertEquals(expectedOutEdges, actualOutEdges); assertEquals(expectedInEdges, actualInEdges); } @Test public void testAddMinusBlossom() { BlossomVNode root = new BlossomVNode(-1); BlossomVTree tree = new BlossomVTree(root); BlossomVNode blossom = new BlossomVNode(-1); blossom.label = MINUS; blossom.isOuter = true; blossom.isBlossom = true; tree.addMinusBlossom(blossom); assertNotNull(blossom.handle); assertSame(blossom.handle.getValue(), blossom); } } KolmogorovWeightedMatchingTest.java000066400000000000000000003752461402514743400374240ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.runner.*; import org.junit.runners.*; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.EPS; import static org.jgrapht.alg.matching.blossom.v5.ObjectiveSense.MAXIMIZE; import static org.jgrapht.alg.matching.blossom.v5.ObjectiveSense.MINIMIZE; /** * Unit tests for the {@link KolmogorovWeightedMatching} * * @author Timofey Chudakov */ @RunWith(Parameterized.class) public class KolmogorovWeightedMatchingTest { /** * Algorithm options */ private BlossomVOptions options; /** * Objective sense of the algorithm */ private ObjectiveSense objectiveSense; public KolmogorovWeightedMatchingTest(BlossomVOptions options, ObjectiveSense objectiveSense) { this.options = options; this.objectiveSense = objectiveSense; } /** * Generate all combinations of options and algorithm objective sense. * * @return all combinations of options and algorithm objective sense. */ @Parameterized.Parameters public static Object[][] params() { BlossomVOptions[] options = BlossomVOptions.ALL_OPTIONS; Object[][] params = new Object[2 * options.length][2]; for (int i = 0; i < options.length; i++) { params[2 * i][0] = options[i]; params[2 * i][1] = MAXIMIZE; params[2 * i + 1][0] = options[i]; params[2 * i + 1][1] = MINIMIZE; } return params; } @Test public void testGetMatching1() { int[][] edges = new int[][] { { 0, 1, -1 }, { 1, 2, 1 }, { 2, 3, -1 }, { 3, 0, 1 }, }; double maxWeight = 2; double minWeight = -2; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } @Test public void testGetMatching2() { int[][] edges = new int[][] { { 0, 1, 3 }, { 1, 2, 2 }, { 2, 3, 1 }, { 3, 0, 5 }, { 0, 2, 15 }, { 1, 3, -15 } }; double maxWeight = 15; double minWeight = -15; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Triangulation of 3 points with randomly negated edge weights */ @Test public void testGetMatching3() { int[][] edges = new int[][] { { 2, 0, -2 }, { 1, 2, 2 }, { 0, 1, 3 }, }; double maxWeight = 3; double minWeight = -2; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Triangulation of 10 points with randomly negated edge weights */ @Test public void testGetMatching4() { int[][] edges = new int[][] { { 9, 4, 4 }, { 7, 9, 4 }, { 4, 7, -4 }, { 6, 4, -2 }, { 9, 6, 2 }, { 8, 9, -2 }, { 6, 8, 1 }, { 4, 5, 3 }, { 6, 3, 3 }, { 1, 6, 6 }, { 3, 4, 1 }, { 4, 2, -5 }, { 3, 2, 5 }, { 0, 2, -1 }, { 8, 1, 7 }, { 0, 1, -6 }, { 3, 0, -5 }, { 1, 3, -3 }, { 5, 7, -2 }, { 2, 5, 4 }, { 7, 2, -6 }, }; double maxWeight = 19; double minWeight = -16; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Triangulation of 10 points with randomly negated edge weights */ @Test public void testGetMatching5() { int[][] edges = new int[][] { { 9, 5, 4 }, { 8, 9, -4 }, { 8, 5, -4 }, { 5, 7, 4 }, { 6, 7, 2 }, { 5, 6, -4 }, { 8, 4, 4 }, { 3, 8, -4 }, { 5, 2, 3 }, { 7, 9, 2 }, { 5, 0, -5 }, { 4, 5, -3 }, { 4, 0, 5 }, { 1, 0, -5 }, { 2, 6, 1 }, { 0, 2, -5 }, { 4, 1, 2 }, { 3, 4, -1 }, { 1, 3, 2 }, { 0, 6, 6 }, }; double maxWeight = 17; double minWeight = -14; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Triangulation of 50 points with randomly negated edge weights */ @Test public void testGetMatching6() { int[][] edges = new int[][] { { 48, 49, 30 }, { 48, 47, -30 }, { 48, 42, 17 }, { 48, 40, 17 }, { 45, 40, -19 }, { 43, 44, 8 }, { 47, 49, 2 }, { 34, 47, 27 }, { 49, 34, 29 }, { 41, 44, -20 }, { 46, 41, 13 }, { 44, 46, -14 }, { 45, 38, -14 }, { 41, 45, -9 }, { 38, 40, -11 }, { 41, 38, 7 }, { 36, 41, 8 }, { 42, 39, 7 }, { 40, 42, 6 }, { 39, 40, -2 }, { 48, 45, 17 }, { 46, 48, 20 }, { 45, 46, 5 }, { 35, 44, -16 }, { 44, 36, 20 }, { 35, 37, 18 }, { 43, 35, 13 }, { 37, 43, 15 }, { 33, 42, 13 }, { 47, 33, -27 }, { 42, 47, 26 }, { 39, 33, -17 }, { 28, 39, 20 }, { 33, 34, 27 }, { 31, 35, 13 }, { 33, 30, 30 }, { 37, 31, -12 }, { 39, 25, 21 }, { 38, 39, 10 }, { 25, 38, 20 }, { 35, 36, 13 }, { 32, 35, 3 }, { 30, 34, 8 }, { 36, 26, -16 }, { 32, 36, -12 }, { 32, 26, -22 }, { 31, 32, 12 }, { 29, 31, -6 }, { 33, 17, 30 }, { 28, 33, -10 }, { 32, 22, -23 }, { 29, 32, -10 }, { 38, 27, 17 }, { 36, 38, -11 }, { 27, 36, -16 }, { 31, 23, 12 }, { 21, 31, 16 }, { 26, 24, 6 }, { 22, 26, -10 }, { 28, 17, -26 }, { 17, 30, -20 }, { 37, 21, 25 }, { 23, 29, -10 }, { 25, 26, -10 }, { 27, 25, -9 }, { 26, 27, 1 }, { 25, 9, 27 }, { 21, 23, -7 }, { 23, 19, 16 }, { 14, 23, -20 }, { 29, 22, -21 }, { 19, 29, 17 }, { 28, 13, -20 }, { 22, 24, -4 }, { 21, 14, -25 }, { 6, 21, -28 }, { 22, 18, -4 }, { 19, 22, 7 }, { 18, 19, 8 }, { 24, 25, 12 }, { 18, 24, -6 }, { 25, 18, -18 }, { 16, 25, -18 }, { 28, 16, -16 }, { 25, 28, 16 }, { 19, 15, -5 }, { 14, 19, 5 }, { 16, 13, 11 }, { 20, 30, 14 }, { 30, 11, -28 }, { 15, 18, 5 }, { 6, 14, -34 }, { 10, 14, -13 }, { 15, 10, -12 }, { 14, 15, -1 }, { 17, 20, 6 }, { 20, 11, 14 }, { 8, 13, -17 }, { 17, 11, 13 }, { 12, 11, -18 }, { 12, 13, 4 }, { 17, 12, 13 }, { 13, 17, 13 }, { 16, 8, -16 }, { 37, 6, -53 }, { 2, 11, -29 }, { 16, 7, -24 }, { 9, 16, -24 }, { 9, 7, 4 }, { 5, 9, -11 }, { 9, 10, -8 }, { 18, 9, 19 }, { 10, 18, -13 }, { 10, 5, 18 }, { 10, 0, 37 }, { 6, 10, 37 }, { 8, 12, -15 }, { 2, 8, 16 }, { 12, 2, -24 }, { 8, 4, 16 }, { 7, 8, 17 }, { 1, 4, -12 }, { 4, 2, -19 }, { 5, 1, 18 }, { 0, 5, 38 }, { 0, 1, -53 }, { 4, 5, -6 }, { 7, 4, 11 }, { 5, 7, 8 }, { 6, 0, -16 }, { 3, 6, 5 }, { 0, 3, 12 }, { 1, 2, -10 }, }; double maxWeight = 417; double minWeight = -436; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Triangulation of 100 points with randomly negated edge weights */ @Test public void testGetMatching7() { int[][] edges = new int[][] { { 97, 99, 28 }, { 99, 96, -17 }, { 99, 93, -15 }, { 98, 99, -25 }, { 96, 94, -16 }, { 95, 97, -24 }, { 99, 92, -44 }, { 94, 99, 33 }, { 98, 86, 20 }, { 94, 92, 11 }, { 95, 90, -16 }, { 94, 91, 5 }, { 91, 92, 6 }, { 88, 94, 11 }, { 94, 89, 4 }, { 93, 96, 2 }, { 96, 88, 8 }, { 95, 87, -15 }, { 91, 79, -13 }, { 89, 91, 4 }, { 93, 88, -9 }, { 89, 79, -13 }, { 90, 97, 10 }, { 85, 97, -12 }, { 97, 98, 3 }, { 95, 80, 14 }, { 89, 77, -13 }, { 87, 90, 3 }, { 90, 85, 11 }, { 78, 93, 15 }, { 93, 84, 12 }, { 99, 78, -20 }, { 86, 99, -15 }, { 86, 78, -17 }, { 89, 69, 18 }, { 88, 89, -11 }, { 83, 85, 7 }, { 98, 83, -16 }, { 85, 98, 12 }, { 80, 87, 14 }, { 87, 81, 8 }, { 86, 75, 8 }, { 83, 86, 12 }, { 87, 85, -12 }, { 82, 87, 8 }, { 85, 82, 9 }, { 83, 75, -14 }, { 92, 22, 61 }, { 79, 92, -13 }, { 83, 64, -15 }, { 82, 83, -14 }, { 64, 82, -17 }, { 84, 88, -10 }, { 82, 63, 14 }, { 81, 82, 1 }, { 79, 22, -48 }, { 84, 76, 5 }, { 88, 69, -18 }, { 76, 88, 12 }, { 78, 84, -7 }, { 74, 78, 3 }, { 84, 74, -7 }, { 76, 69, -13 }, { 78, 72, 6 }, { 75, 78, 14 }, { 78, 71, 8 }, { 80, 81, 15 }, { 73, 80, -4 }, { 76, 67, 8 }, { 64, 75, 21 }, { 76, 68, -9 }, { 74, 76, 8 }, { 75, 71, -10 }, { 70, 75, -5 }, { 71, 72, 3 }, { 54, 80, 21 }, { 80, 65, 13 }, { 71, 68, -9 }, { 71, 57, -13 }, { 68, 72, -6 }, { 74, 68, 6 }, { 72, 74, -5 }, { 68, 67, 6 }, { 57, 68, 8 }, { 69, 77, 11 }, { 66, 69, -4 }, { 77, 66, 13 }, { 67, 69, 12 }, { 69, 62, -6 }, { 67, 62, -10 }, { 65, 73, -9 }, { 81, 63, 14 }, { 73, 81, 13 }, { 61, 73, 13 }, { 73, 63, 15 }, { 71, 48, -16 }, { 65, 61, -6 }, { 66, 60, 5 }, { 62, 66, 3 }, { 62, 60, -7 }, { 79, 46, 29 }, { 53, 63, 12 }, { 63, 64, 12 }, { 59, 63, -8 }, { 54, 65, -8 }, { 65, 58, -4 }, { 70, 47, 24 }, { 64, 70, 21 }, { 54, 58, 4 }, { 62, 52, -8 }, { 55, 62, -12 }, { 61, 63, 6 }, { 48, 57, -10 }, { 95, 54, 35 }, { 57, 67, 9 }, { 55, 57, 5 }, { 67, 55, 9 }, { 56, 58, 1 }, { 58, 61, -5 }, { 55, 52, -10 }, { 61, 49, 7 }, { 56, 61, -4 }, { 60, 46, 13 }, { 52, 60, 10 }, { 64, 47, -10 }, { 50, 64, -9 }, { 52, 46, -11 }, { 45, 53, -8 }, { 53, 50, 2 }, { 51, 48, 4 }, { 54, 56, 4 }, { 49, 54, 4 }, { 56, 49, 5 }, { 64, 53, -8 }, { 59, 64, 5 }, { 53, 59, -5 }, { 51, 70, -15 }, { 71, 51, 15 }, { 70, 71, -6 }, { 45, 50, 8 }, { 50, 47, 3 }, { 52, 44, 10 }, { 49, 63, -12 }, { 45, 49, -17 }, { 63, 45, 17 }, { 60, 77, 14 }, { 79, 60, 17 }, { 77, 79, -3 }, { 51, 43, -11 }, { 55, 36, -18 }, { 27, 49, 23 }, { 49, 42, -17 }, { 46, 22, -20 }, { 51, 41, 15 }, { 47, 51, 20 }, { 37, 44, 8 }, { 47, 41, 14 }, { 36, 52, -15 }, { 52, 37, -15 }, { 13, 54, -40 }, { 54, 27, -26 }, { 47, 39, -13 }, { 45, 47, 10 }, { 45, 39, 13 }, { 41, 43, 15 }, { 43, 34, 7 }, { 48, 55, -14 }, { 43, 48, 9 }, { 34, 40, -5 }, { 44, 46, -5 }, { 46, 26, 16 }, { 55, 40, 19 }, { 43, 55, 18 }, { 40, 43, -5 }, { 38, 44, -6 }, { 44, 26, -14 }, { 40, 36, -16 }, { 38, 26, -12 }, { 24, 41, -16 }, { 41, 34, 14 }, { 35, 40, -4 }, { 40, 31, -7 }, { 34, 35, -1 }, { 24, 34, 8 }, { 42, 45, -3 }, { 33, 42, 10 }, { 45, 33, -11 }, { 33, 39, -6 }, { 25, 33, -12 }, { 24, 35, 9 }, { 35, 31, 8 }, { 41, 19, -16 }, { 39, 41, 6 }, { 31, 36, 11 }, { 39, 19, -17 }, { 32, 39, 6 }, { 28, 36, -9 }, { 31, 28, -4 }, { 29, 36, -7 }, { 37, 29, -8 }, { 36, 37, 1 }, { 33, 32, 1 }, { 30, 33, 2 }, { 28, 29, -2 }, { 29, 20, -8 }, { 20, 37, 13 }, { 38, 20, -13 }, { 37, 38, -3 }, { 32, 19, 18 }, { 30, 32, 1 }, { 26, 22, 6 }, { 27, 23, 3 }, { 25, 27, 4 }, { 42, 25, 13 }, { 27, 42, -13 }, { 30, 19, -18 }, { 17, 28, 12 }, { 28, 20, -9 }, { 13, 27, 21 }, { 27, 21, -4 }, { 31, 17, -14 }, { 24, 31, 14 }, { 30, 14, -20 }, { 23, 25, 3 }, { 21, 23, -1 }, { 26, 18, -9 }, { 20, 26, -15 }, { 30, 11, -22 }, { 20, 18, -12 }, { 13, 21, 19 }, { 18, 22, -10 }, { 19, 24, -6 }, { 24, 17, 15 }, { 18, 8, -12 }, { 4, 18, -21 }, { 8, 4, 20 }, { 19, 17, -19 }, { 14, 19, -8 }, { 15, 6, -11 }, { 21, 25, -4 }, { 16, 21, -7 }, { 17, 20, -11 }, { 15, 17, 2 }, { 30, 10, -22 }, { 25, 30, -11 }, { 15, 20, -10 }, { 4, 15, 17 }, { 20, 4, 22 }, { 95, 13, -75 }, { 14, 17, -23 }, { 17, 6, 12 }, { 0, 13, -17 }, { 7, 0, 13 }, { 25, 10, -17 }, { 16, 25, 10 }, { 7, 13, 10 }, { 12, 7, -6 }, { 12, 13, 14 }, { 13, 16, -15 }, { 12, 16, -6 }, { 16, 10, -9 }, { 11, 14, 7 }, { 14, 6, 26 }, { 12, 10, -7 }, { 5, 12, 8 }, { 2, 11, -20 }, { 6, 2, 46 }, { 11, 6, -30 }, { 5, 10, -6 }, { 9, 5, -6 }, { 22, 1, 29 }, { 8, 22, 16 }, { 1, 8, 13 }, { 9, 10, -1 }, { 11, 9, -14 }, { 10, 11, 15 }, { 5, 7, 9 }, { 0, 5, 16 }, { 6, 4, -9 }, { 2, 5, 6 }, { 9, 2, 11 }, { 4, 3, 16 }, { 3, 8, -9 }, { 1, 3, 5 }, { 1, 0, -89 }, { 0, 2, 16 }, { 4, 1, 19 }, { 2, 4, 54 }, { 1, 2, 73 }, }; double maxWeight = 731; double minWeight = -745; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 4 vertices and 4 edges */ @Test public void testGetMatching8() { int[][] edges = new int[][] { { 0, 1, 7 }, { 3, 2, 9 }, { 3, 0, -8 }, { 1, 2, 7 }, }; double maxWeight = 16; double minWeight = -8; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 10 vertices and 20 edges */ @Test public void testGetMatching9() { int[][] edges = new int[][] { { 0, 7, 10 }, { 8, 1, 10 }, { 0, 9, 10 }, { 6, 3, 10 }, { 8, 5, 10 }, { 3, 1, 1 }, { 8, 6, -7 }, { 7, 6, -5 }, { 5, 3, 1 }, { 1, 2, -8 }, { 9, 6, -4 }, { 9, 8, 3 }, { 9, 5, -6 }, { 2, 7, 9 }, { 6, 1, 10 }, { 8, 3, -8 }, { 6, 2, 7 }, { 3, 2, 9 }, { 8, 7, -8 }, { 5, 1, 7 }, }; double maxWeight = 39; double minWeight = -27; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 15 vertices and 40 edges */ @Test public void testGetMatching10() { int[][] edges = new int[][] { { 3, 1, 0 }, { 8, 13, 10 }, { 10, 14, 8 }, { 5, 14, 6 }, { 8, 7, 3 }, { 11, 8, 9 }, { 10, 6, -8 }, { 5, 8, -2 }, { 1, 8, -10 }, { 10, 9, -8 }, { 3, 7, -4 }, { 4, 6, 6 }, { 9, 12, 6 }, { 10, 11, 0 }, { 2, 1, -8 }, { 5, 3, 10 }, { 2, 0, 10 }, { 13, 3, 10 }, { 9, 4, 5 }, { 5, 13, -7 }, { 14, 8, 10 }, { 4, 3, 10 }, { 10, 3, 10 }, { 1, 12, 10 }, { 4, 7, 10 }, { 8, 9, 1 }, { 8, 10, -7 }, { 12, 11, -5 }, { 13, 10, 1 }, { 6, 3, -8 }, { 14, 7, -4 }, { 11, 3, 3 }, { 1, 6, -6 }, { 13, 1, 9 }, { 11, 13, 10 }, { 13, 6, -8 }, { 1, 14, 7 }, { 6, 7, 9 }, { 2, 5, -8 }, { 0, 4, 7 }, }; double maxWeight = 64; double minWeight = -43; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 30 vertices and 100 edges */ @Test public void testGetMatching11() { int[][] edges = new int[][] { { 19, 24, 6 }, { 29, 23, 5 }, { 14, 27, 9 }, { 29, 14, 4 }, { 25, 12, 6 }, { 21, 22, 3 }, { 16, 23, 0 }, { 20, 9, -1 }, { 6, 17, -1 }, { 3, 19, 0 }, { 27, 5, -2 }, { 3, 6, -7 }, { 24, 17, -10 }, { 3, 23, 6 }, { 29, 15, -9 }, { 9, 13, 6 }, { 13, 15, -6 }, { 4, 15, -2 }, { 23, 26, -8 }, { 9, 14, -1 }, { 1, 27, 3 }, { 16, 13, -10 }, { 6, 1, 0 }, { 12, 15, 9 }, { 27, 29, 6 }, { 6, 0, -4 }, { 13, 25, -8 }, { 26, 28, 4 }, { 8, 6, 7 }, { 10, 15, 7 }, { 28, 5, 10 }, { 13, 14, -8 }, { 19, 9, -7 }, { 14, 28, -10 }, { 1, 19, 8 }, { 6, 21, -5 }, { 27, 3, -4 }, { 12, 20, -10 }, { 1, 25, 6 }, { 20, 27, 4 }, { 12, 8, -4 }, { 3, 15, -7 }, { 0, 25, -7 }, { 17, 5, 3 }, { 24, 22, -2 }, { 1, 16, -2 }, { 27, 26, -1 }, { 5, 18, 5 }, { 8, 18, 5 }, { 3, 28, 5 }, { 19, 8, -2 }, { 10, 2, 4 }, { 13, 18, 4 }, { 2, 23, 9 }, { 28, 6, -6 }, { 5, 20, 7 }, { 23, 1, -3 }, { 21, 16, -10 }, { 29, 0, 6 }, { 2, 15, 3 }, { 16, 26, 0 }, { 4, 10, 10 }, { 28, 7, 8 }, { 12, 2, 6 }, { 11, 29, 3 }, { 3, 24, 9 }, { 1, 2, -8 }, { 28, 0, -2 }, { 13, 17, -10 }, { 2, 19, -8 }, { 21, 10, -4 }, { 6, 4, 6 }, { 1, 13, 6 }, { 9, 24, 0 }, { 28, 21, -8 }, { 21, 18, 10 }, { 4, 27, 10 }, { 21, 9, 10 }, { 17, 20, 5 }, { 5, 15, -7 }, { 9, 2, 10 }, { 20, 24, 10 }, { 16, 28, 10 }, { 8, 4, 10 }, { 22, 13, 10 }, { 11, 28, 1 }, { 4, 16, -7 }, { 20, 18, -5 }, { 16, 12, 1 }, { 25, 14, -8 }, { 7, 19, -4 }, { 25, 4, 3 }, { 7, 11, -6 }, { 22, 19, 9 }, { 2, 6, 10 }, { 5, 14, -8 }, { 1, 11, 7 }, { 9, 18, 9 }, { 17, 3, -8 }, { 7, 3, 7 }, }; double maxWeight = 112; double minWeight = -98; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 50 vertices and 100 edges */ @Test public void testGetMatching12() { int[][] edges = new int[][] { { 7, 28, 6 }, { 6, 29, 5 }, { 32, 5, 9 }, { 4, 27, 4 }, { 23, 4, 6 }, { 25, 43, 3 }, { 36, 19, 0 }, { 37, 36, -1 }, { 33, 25, -1 }, { 37, 47, 0 }, { 46, 17, -2 }, { 30, 47, -7 }, { 20, 3, -10 }, { 15, 45, 6 }, { 30, 39, -9 }, { 26, 29, 6 }, { 16, 5, -6 }, { 9, 3, -2 }, { 30, 44, -8 }, { 36, 27, -1 }, { 28, 1, 3 }, { 40, 44, -10 }, { 24, 27, 0 }, { 30, 21, 9 }, { 7, 19, 6 }, { 0, 10, -4 }, { 5, 8, -8 }, { 6, 26, 4 }, { 28, 36, 7 }, { 12, 7, 7 }, { 13, 11, 10 }, { 2, 5, -8 }, { 11, 30, -7 }, { 47, 12, -10 }, { 20, 43, 8 }, { 3, 48, -5 }, { 42, 34, -4 }, { 24, 35, -10 }, { 31, 41, 6 }, { 6, 9, 4 }, { 0, 13, -4 }, { 35, 31, -7 }, { 14, 32, -7 }, { 35, 47, 3 }, { 37, 13, -2 }, { 18, 23, -2 }, { 1, 47, -1 }, { 11, 6, 5 }, { 15, 12, 5 }, { 33, 1, 5 }, { 33, 47, -2 }, { 42, 7, 4 }, { 47, 45, 4 }, { 14, 11, 9 }, { 27, 14, -6 }, { 33, 27, 7 }, { 47, 29, -3 }, { 6, 21, -10 }, { 22, 7, 6 }, { 48, 12, 3 }, { 32, 25, 0 }, { 41, 47, 10 }, { 9, 49, 8 }, { 47, 44, 6 }, { 4, 15, 3 }, { 38, 0, 9 }, { 43, 16, -8 }, { 13, 22, -2 }, { 40, 16, -10 }, { 26, 22, -8 }, { 31, 1, -4 }, { 38, 40, 6 }, { 8, 47, 6 }, { 34, 33, 0 }, { 11, 29, -8 }, { 48, 43, 10 }, { 27, 16, 10 }, { 41, 49, 10 }, { 45, 0, 5 }, { 38, 45, -7 }, { 5, 29, 10 }, { 34, 18, 10 }, { 14, 24, 10 }, { 16, 14, 10 }, { 13, 45, 10 }, { 18, 47, 1 }, { 22, 4, -7 }, { 16, 41, -5 }, { 22, 19, 1 }, { 11, 33, -8 }, { 3, 28, -4 }, { 18, 1, 3 }, { 40, 9, -6 }, { 7, 13, 9 }, { 4, 13, 10 }, { 43, 35, -8 }, { 46, 27, 7 }, { 8, 20, 9 }, { 31, 18, -8 }, { 12, 18, 7 }, }; double maxWeight = 146; double minWeight = -127; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 100 vertices and 100 edges */ @Test public void testGetMatching13() { int[][] edges = new int[][] { { 98, 6, 6 }, { 53, 82, 5 }, { 0, 7, 9 }, { 57, 91, 4 }, { 69, 41, 6 }, { 18, 15, 3 }, { 84, 38, 0 }, { 43, 57, -1 }, { 31, 81, -1 }, { 84, 88, 0 }, { 6, 74, -2 }, { 74, 36, -7 }, { 69, 1, -10 }, { 7, 69, 6 }, { 15, 67, -9 }, { 66, 70, 6 }, { 74, 43, -6 }, { 88, 97, -2 }, { 61, 78, -8 }, { 26, 96, -1 }, { 3, 41, 3 }, { 41, 89, -10 }, { 0, 40, 0 }, { 82, 12, 9 }, { 61, 62, 6 }, { 93, 60, -4 }, { 5, 4, -8 }, { 16, 23, 4 }, { 65, 30, 7 }, { 37, 3, 7 }, { 61, 75, 10 }, { 58, 42, -8 }, { 60, 8, -7 }, { 96, 1, -10 }, { 86, 30, 8 }, { 90, 52, -5 }, { 27, 83, -4 }, { 78, 24, -10 }, { 6, 34, 6 }, { 45, 14, 4 }, { 95, 39, -4 }, { 36, 27, -7 }, { 78, 79, -7 }, { 2, 89, 3 }, { 28, 59, -2 }, { 76, 5, -2 }, { 55, 78, -1 }, { 38, 93, 5 }, { 72, 35, 5 }, { 52, 78, 5 }, { 92, 76, -2 }, { 52, 81, 4 }, { 4, 81, 4 }, { 85, 51, 9 }, { 12, 37, -6 }, { 76, 32, 7 }, { 55, 47, -3 }, { 58, 9, -10 }, { 71, 67, 6 }, { 22, 56, 3 }, { 32, 85, 0 }, { 28, 11, 10 }, { 75, 37, 8 }, { 18, 34, 6 }, { 94, 21, 3 }, { 10, 42, 9 }, { 6, 0, -8 }, { 48, 20, -2 }, { 64, 48, -10 }, { 77, 62, -8 }, { 69, 62, -4 }, { 76, 72, 6 }, { 1, 20, 6 }, { 11, 25, 0 }, { 33, 28, -8 }, { 85, 76, 10 }, { 67, 39, 10 }, { 54, 37, 10 }, { 46, 0, 5 }, { 55, 71, -7 }, { 90, 41, 10 }, { 52, 48, 10 }, { 34, 45, 10 }, { 55, 39, 10 }, { 92, 40, 10 }, { 35, 33, 1 }, { 64, 92, -7 }, { 56, 7, -5 }, { 9, 37, 1 }, { 12, 71, -8 }, { 48, 19, -4 }, { 42, 1, 3 }, { 78, 98, -6 }, { 49, 15, 9 }, { 87, 70, 10 }, { 49, 79, -8 }, { 16, 8, 7 }, { 93, 36, 9 }, { 74, 8, -8 }, { 0, 39, 7 }, }; double maxWeight = 208; double minWeight = -152; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 100 vertices and 200 edges */ @Test public void testGetMatching14() { int[][] edges = new int[][] { { 27, 92, -22 }, { 23, 54, -33 }, { 27, 28, 46 }, { 7, 89, -98 }, { 48, 36, -23 }, { 87, 39, -6 }, { 40, 70, -21 }, { 34, 60, 14 }, { 1, 31, -69 }, { 82, 46, -74 }, { 72, 9, 93 }, { 49, 59, 87 }, { 74, 54, -29 }, { 87, 4, 56 }, { 97, 14, 19 }, { 6, 76, 6 }, { 97, 31, -30 }, { 15, 78, -90 }, { 24, 95, -19 }, { 41, 15, -85 }, { 44, 51, 6 }, { 64, 78, 14 }, { 70, 56, -59 }, { 82, 87, -24 }, { 84, 72, 12 }, { 35, 3, 51 }, { 63, 87, -22 }, { 50, 10, 52 }, { 49, 25, 36 }, { 71, 23, -43 }, { 59, 67, 39 }, { 1, 63, 84 }, { 99, 79, 47 }, { 54, 86, 10 }, { 41, 42, 19 }, { 70, 97, 17 }, { 33, 63, 59 }, { 16, 33, 66 }, { 73, 78, -87 }, { 94, 34, -30 }, { 43, 36, 98 }, { 76, 93, -5 }, { 24, 40, 56 }, { 51, 89, 23 }, { 34, 35, 91 }, { 86, 97, -50 }, { 98, 65, 70 }, { 11, 88, -61 }, { 76, 82, 82 }, { 77, 12, -30 }, { 59, 4, -57 }, { 5, 22, 86 }, { 75, 68, -98 }, { 26, 41, -52 }, { 7, 38, -40 }, { 65, 62, 63 }, { 16, 93, -34 }, { 50, 32, -49 }, { 15, 79, 98 }, { 80, 17, 68 }, { 81, 74, 62 }, { 18, 5, -49 }, { 1, 40, -54 }, { 31, 68, -70 }, { 93, 0, 53 }, { 39, 50, -73 }, { 25, 65, -69 }, { 70, 94, 9 }, { 44, 6, 16 }, { 38, 75, 92 }, { 20, 90, -15 }, { 15, 90, 79 }, { 6, 94, -18 }, { 98, 11, 40 }, { 6, 33, 88 }, { 55, 15, 1 }, { 31, 2, 64 }, { 18, 76, -49 }, { 90, 78, 65 }, { 95, 26, 51 }, { 72, 43, -97 }, { 80, 49, -56 }, { 21, 56, 62 }, { 13, 63, 17 }, { 46, 25, 76 }, { 92, 72, -32 }, { 34, 63, 15 }, { 33, 87, 92 }, { 57, 1, 55 }, { 57, 94, 0 }, { 32, 28, -9 }, { 61, 86, -77 }, { 41, 56, 29 }, { 97, 62, -68 }, { 69, 80, 42 }, { 50, 68, 31 }, { 79, 28, -100 }, { 70, 49, 36 }, { 98, 29, 41 }, { 85, 6, -45 }, { 83, 38, 62 }, { 45, 55, 51 }, { 64, 60, 85 }, { 46, 77, 42 }, { 27, 70, 59 }, { 9, 85, 29 }, { 3, 83, -2 }, { 98, 27, -11 }, { 95, 61, -8 }, { 39, 48, -2 }, { 36, 79, -18 }, { 18, 87, -63 }, { 97, 0, -93 }, { 61, 87, 59 }, { 29, 72, -90 }, { 93, 83, 53 }, { 17, 84, -58 }, { 49, 39, -24 }, { 98, 10, -75 }, { 84, 74, -12 }, { 52, 71, 33 }, { 45, 32, -94 }, { 35, 21, -2 }, { 15, 94, 90 }, { 15, 87, 53 }, { 47, 18, -37 }, { 92, 21, -75 }, { 35, 8, 39 }, { 57, 52, 65 }, { 25, 87, 65 }, { 92, 26, 99 }, { 51, 23, -81 }, { 83, 28, -71 }, { 83, 8, -91 }, { 20, 79, 75 }, { 72, 52, -45 }, { 62, 86, -37 }, { 86, 45, -94 }, { 42, 31, 60 }, { 23, 34, 41 }, { 45, 64, -40 }, { 74, 17, -66 }, { 19, 91, -66 }, { 71, 27, 31 }, { 76, 33, -16 }, { 56, 47, -22 }, { 6, 15, -5 }, { 95, 8, 49 }, { 40, 86, 48 }, { 57, 95, 52 }, { 16, 72, -20 }, { 66, 16, 36 }, { 31, 71, 36 }, { 66, 68, 87 }, { 56, 54, -58 }, { 43, 39, 70 }, { 94, 93, -28 }, { 12, 2, -93 }, { 47, 38, 60 }, { 17, 22, 31 }, { 8, 34, 1 }, { 47, 14, 92 }, { 91, 79, 76 }, { 22, 81, 59 }, { 70, 51, 28 }, { 67, 50, 84 }, { 51, 56, -78 }, { 40, 71, -16 }, { 4, 73, -100 }, { 50, 71, -72 }, { 9, 33, -41 }, { 37, 92, 60 }, { 10, 39, 60 }, { 49, 12, -3 }, { 27, 85, -78 }, { 81, 19, 92 }, { 18, 72, 97 }, { 60, 67, 95 }, { 56, 71, 45 }, { 12, 9, -69 }, { 99, 27, 94 }, { 94, 55, 93 }, { 47, 92, 100 }, { 49, 7, 92 }, { 84, 89, 99 }, { 32, 98, 9 }, { 84, 93, -63 }, { 94, 40, -45 }, { 32, 68, 9 }, { 28, 86, -81 }, { 88, 59, -39 }, { 25, 29, 27 }, { 81, 88, -56 }, { 16, 3, 83 }, { 6, 73, 94 }, { 67, 70, -75 }, { 36, 20, 67 }, { 79, 17, 82 }, { 42, 63, -73 }, { 35, 93, 63 }, }; double maxWeight = 2748; double minWeight = -2402; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 100 vertices and 400 edges */ @Test public void testGetMatching15() { int[][] edges = new int[][] { { 96, 57, 84 }, { 82, 47, 59 }, { 13, 22, -28 }, { 6, 17, 64 }, { 81, 24, 74 }, { 64, 41, 2 }, { 20, 28, -21 }, { 6, 42, 2 }, { 47, 75, -79 }, { 99, 28, -39 }, { 41, 92, 51 }, { 29, 8, -11 }, { 1, 19, -2 }, { 21, 67, -13 }, { 73, 41, 83 }, { 98, 90, -3 }, { 57, 17, -39 }, { 60, 67, 55 }, { 54, 37, -8 }, { 68, 85, 86 }, { 54, 40, -24 }, { 44, 7, -84 }, { 85, 47, 34 }, { 57, 64, 56 }, { 75, 23, 63 }, { 71, 2, 25 }, { 92, 34, -31 }, { 30, 96, -26 }, { 87, 46, -23 }, { 72, 57, -64 }, { 15, 11, -38 }, { 86, 81, 38 }, { 20, 80, 63 }, { 59, 36, -63 }, { 37, 68, 25 }, { 78, 36, 49 }, { 94, 17, -43 }, { 39, 4, -41 }, { 41, 14, 44 }, { 11, 68, 9 }, { 94, 84, -24 }, { 3, 41, -10 }, { 0, 50, -1 }, { 42, 87, 30 }, { 6, 98, -74 }, { 66, 43, 47 }, { 81, 37, -49 }, { 55, 32, 30 }, { 40, 49, 62 }, { 14, 3, -67 }, { 85, 39, 82 }, { 22, 46, -92 }, { 8, 80, -19 }, { 83, 41, -97 }, { 87, 50, -36 }, { 37, 6, 65 }, { 98, 63, -90 }, { 39, 47, -30 }, { 72, 93, 11 }, { 18, 29, -53 }, { 93, 40, -13 }, { 26, 74, -88 }, { 87, 25, 52 }, { 73, 17, 15 }, { 6, 78, 31 }, { 5, 73, 92 }, { 25, 57, 91 }, { 18, 34, 89 }, { 21, 60, -2 }, { 94, 43, -74 }, { 96, 27, -56 }, { 62, 37, -81 }, { 77, 57, -2 }, { 69, 3, -19 }, { 28, 81, -46 }, { 38, 95, -52 }, { 0, 97, -58 }, { 87, 55, -22 }, { 22, 90, 76 }, { 30, 56, 56 }, { 17, 90, -68 }, { 33, 76, -78 }, { 28, 90, -94 }, { 42, 88, -26 }, { 11, 34, -31 }, { 87, 78, 80 }, { 89, 60, 78 }, { 14, 67, -33 }, { 47, 74, 9 }, { 30, 38, -2 }, { 22, 12, -51 }, { 80, 85, -2 }, { 8, 1, -91 }, { 2, 90, 89 }, { 43, 59, -40 }, { 69, 52, 81 }, { 30, 69, 65 }, { 28, 71, -91 }, { 72, 64, 97 }, { 84, 33, -17 }, { 38, 41, 65 }, { 74, 30, -52 }, { 72, 49, 1 }, { 24, 97, -64 }, { 63, 66, -50 }, { 21, 9, -76 }, { 45, 10, -92 }, { 12, 88, -52 }, { 54, 48, 53 }, { 95, 42, -85 }, { 40, 46, -24 }, { 42, 12, -20 }, { 91, 30, -73 }, { 74, 22, 3 }, { 29, 96, -60 }, { 17, 2, -30 }, { 70, 60, -12 }, { 55, 24, 25 }, { 3, 53, 7 }, { 41, 71, 71 }, { 5, 11, 36 }, { 4, 82, -71 }, { 88, 63, -99 }, { 77, 78, 10 }, { 1, 32, 26 }, { 35, 11, 16 }, { 0, 73, -85 }, { 73, 37, 74 }, { 97, 6, 61 }, { 85, 21, -73 }, { 57, 20, -44 }, { 33, 39, -71 }, { 9, 8, -81 }, { 21, 65, -47 }, { 90, 18, 97 }, { 37, 25, -64 }, { 95, 94, -74 }, { 37, 80, 83 }, { 59, 79, 50 }, { 42, 91, -14 }, { 14, 54, 32 }, { 41, 34, 60 }, { 17, 26, -91 }, { 40, 90, -48 }, { 86, 31, 72 }, { 80, 35, -20 }, { 81, 6, -31 }, { 77, 91, -84 }, { 55, 68, 36 }, { 99, 69, 74 }, { 88, 92, -27 }, { 84, 69, 64 }, { 79, 97, 96 }, { 36, 55, 55 }, { 23, 47, -94 }, { 35, 68, -100 }, { 76, 43, 96 }, { 59, 49, 93 }, { 52, 61, 0 }, { 66, 51, -79 }, { 47, 73, 21 }, { 82, 98, -12 }, { 69, 89, 71 }, { 47, 97, -85 }, { 31, 46, -11 }, { 8, 5, 100 }, { 9, 72, 9 }, { 13, 89, 8 }, { 50, 79, -8 }, { 8, 6, 65 }, { 20, 34, 1 }, { 3, 32, -70 }, { 65, 63, 12 }, { 21, 61, 83 }, { 98, 56, 1 }, { 44, 60, -54 }, { 6, 16, 83 }, { 18, 56, -84 }, { 50, 39, 44 }, { 78, 57, -10 }, { 56, 89, 98 }, { 70, 83, 50 }, { 10, 14, 43 }, { 35, 52, 38 }, { 63, 9, 6 }, { 6, 33, 31 }, { 24, 79, -32 }, { 14, 16, -48 }, { 60, 6, -6 }, { 85, 25, 20 }, { 94, 66, -76 }, { 59, 78, -67 }, { 45, 78, 24 }, { 19, 39, 6 }, { 84, 40, 73 }, { 27, 78, -38 }, { 36, 50, -13 }, { 10, 19, 59 }, { 87, 47, 86 }, { 77, 94, -68 }, { 92, 64, -22 }, { 88, 16, -33 }, { 22, 19, 46 }, { 17, 77, -98 }, { 91, 47, -23 }, { 56, 25, -6 }, { 64, 61, -21 }, { 23, 59, 14 }, { 20, 7, -69 }, { 63, 26, -74 }, { 74, 70, 93 }, { 32, 79, 87 }, { 43, 79, -29 }, { 11, 29, 56 }, { 48, 27, 19 }, { 25, 58, 6 }, { 69, 92, -30 }, { 45, 73, -90 }, { 65, 26, -19 }, { 53, 32, -85 }, { 7, 80, 6 }, { 16, 84, 14 }, { 6, 22, -59 }, { 0, 30, -24 }, { 52, 71, 12 }, { 84, 30, 51 }, { 17, 99, -22 }, { 36, 75, 52 }, { 49, 29, 36 }, { 92, 7, -43 }, { 53, 70, 39 }, { 96, 62, 84 }, { 72, 60, 47 }, { 83, 24, 10 }, { 22, 26, 19 }, { 2, 55, 17 }, { 3, 66, 59 }, { 56, 64, 66 }, { 8, 68, -87 }, { 3, 12, -30 }, { 45, 7, 98 }, { 53, 90, -5 }, { 6, 73, 56 }, { 32, 10, 23 }, { 7, 19, 91 }, { 40, 44, -50 }, { 53, 23, 70 }, { 82, 10, -61 }, { 86, 36, 82 }, { 59, 67, -30 }, { 35, 40, -57 }, { 29, 1, 86 }, { 46, 96, -98 }, { 2, 21, -52 }, { 28, 41, -40 }, { 98, 48, 63 }, { 75, 67, -34 }, { 25, 91, -49 }, { 28, 30, 98 }, { 14, 5, 68 }, { 71, 9, 62 }, { 27, 70, -49 }, { 72, 42, -54 }, { 19, 69, -70 }, { 25, 86, 53 }, { 34, 33, -73 }, { 4, 36, -69 }, { 52, 48, 9 }, { 92, 40, 16 }, { 76, 29, 92 }, { 55, 21, -15 }, { 86, 16, 79 }, { 50, 30, -18 }, { 70, 79, 40 }, { 36, 74, 88 }, { 86, 39, 1 }, { 95, 10, 64 }, { 27, 94, -49 }, { 44, 0, 65 }, { 88, 3, 51 }, { 53, 85, -97 }, { 81, 70, -56 }, { 8, 19, 62 }, { 4, 7, 17 }, { 95, 68, 76 }, { 79, 99, -32 }, { 41, 13, 15 }, { 86, 3, 92 }, { 19, 87, 55 }, { 4, 92, 0 }, { 46, 94, -9 }, { 59, 58, -77 }, { 75, 13, 29 }, { 20, 19, -68 }, { 94, 1, 42 }, { 47, 64, 31 }, { 58, 40, -100 }, { 22, 42, 36 }, { 59, 37, 41 }, { 38, 82, -45 }, { 21, 32, 62 }, { 95, 61, 51 }, { 22, 70, 85 }, { 39, 21, 42 }, { 99, 20, 59 }, { 75, 51, 29 }, { 67, 93, -2 }, { 91, 5, -11 }, { 27, 72, -8 }, { 75, 12, -2 }, { 44, 31, -18 }, { 89, 5, -63 }, { 46, 50, -93 }, { 48, 93, 59 }, { 41, 60, -90 }, { 93, 80, 53 }, { 85, 15, -58 }, { 73, 27, -24 }, { 7, 65, -75 }, { 12, 84, -12 }, { 78, 33, 33 }, { 68, 49, -94 }, { 11, 32, -2 }, { 0, 10, 90 }, { 81, 10, 53 }, { 77, 92, -37 }, { 99, 91, -75 }, { 74, 60, 39 }, { 78, 49, 65 }, { 4, 96, 65 }, { 1, 25, 99 }, { 93, 36, -81 }, { 27, 17, -71 }, { 27, 59, -91 }, { 53, 55, 75 }, { 2, 36, -45 }, { 49, 96, -37 }, { 14, 35, -94 }, { 98, 27, 60 }, { 9, 15, 41 }, { 36, 34, -40 }, { 75, 18, -66 }, { 90, 88, -66 }, { 48, 63, 31 }, { 6, 57, -16 }, { 2, 95, -22 }, { 61, 15, -5 }, { 17, 55, 49 }, { 59, 14, 48 }, { 3, 25, 52 }, { 48, 26, -20 }, { 63, 97, 36 }, { 24, 47, 36 }, { 5, 41, 87 }, { 52, 49, -58 }, { 18, 91, 70 }, { 82, 57, -28 }, { 81, 94, -93 }, { 90, 91, 60 }, { 69, 15, 31 }, { 34, 69, 1 }, { 81, 47, 92 }, { 11, 24, 76 }, { 54, 78, 59 }, { 76, 57, 28 }, { 11, 41, 84 }, { 44, 54, -78 }, { 26, 14, -16 }, { 67, 34, -100 }, { 93, 51, -72 }, { 96, 26, -41 }, { 7, 10, 60 }, { 57, 66, 60 }, { 33, 44, -3 }, { 3, 87, -78 }, { 41, 53, 92 }, { 85, 86, 97 }, { 73, 54, 95 }, { 6, 43, 45 }, { 64, 70, -69 }, { 22, 67, 94 }, { 32, 76, 93 }, { 0, 9, 100 }, { 61, 14, 92 }, { 95, 62, 99 }, { 96, 48, 9 }, { 65, 44, -63 }, { 23, 90, -45 }, { 1, 40, 9 }, { 73, 89, -81 }, { 42, 39, -39 }, { 5, 51, 27 }, { 10, 41, -56 }, { 68, 0, 83 }, { 19, 52, 94 }, { 57, 55, -75 }, { 68, 19, 67 }, { 10, 84, 82 }, { 68, 62, -73 }, { 12, 51, 63 }, }; double maxWeight = 3267; double minWeight = -3239; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 200 vertices and 500 edges */ @Test public void testGetMatching16() { int[][] edges = new int[][] { { 120, 6, -58 }, { 133, 125, -8 }, { 32, 184, 94 }, { 160, 198, -53 }, { 89, 168, -44 }, { 104, 138, 16 }, { 194, 195, -31 }, { 26, 49, -2 }, { 24, 175, -80 }, { 144, 14, 46 }, { 141, 105, -51 }, { 44, 23, 86 }, { 191, 87, -47 }, { 161, 148, -95 }, { 109, 64, 53 }, { 158, 133, 61 }, { 176, 161, -64 }, { 3, 40, -48 }, { 174, 98, 99 }, { 193, 74, -83 }, { 164, 113, -78 }, { 13, 4, 2 }, { 157, 102, 57 }, { 85, 168, -15 }, { 94, 112, -16 }, { 182, 181, -36 }, { 188, 94, 31 }, { 172, 168, -41 }, { 152, 129, -35 }, { 123, 124, -77 }, { 105, 41, 67 }, { 78, 157, -56 }, { 114, 101, -67 }, { 190, 16, 42 }, { 141, 40, 60 }, { 165, 37, 21 }, { 176, 106, -92 }, { 171, 169, -48 }, { 87, 195, -10 }, { 144, 125, 19 }, { 114, 166, -74 }, { 139, 4, -18 }, { 33, 28, -68 }, { 82, 172, -49 }, { 45, 198, 42 }, { 163, 19, -78 }, { 88, 80, 36 }, { 2, 142, -12 }, { 199, 43, -29 }, { 20, 12, 96 }, { 78, 179, 46 }, { 124, 120, 81 }, { 52, 97, 17 }, { 99, 112, -63 }, { 33, 2, 27 }, { 148, 64, -14 }, { 157, 137, 29 }, { 137, 163, 85 }, { 171, 1, -82 }, { 191, 196, -38 }, { 54, 14, -5 }, { 145, 81, -13 }, { 138, 149, 60 }, { 28, 40, -55 }, { 174, 34, -52 }, { 143, 61, -66 }, { 159, 169, -71 }, { 53, 179, -55 }, { 0, 40, -12 }, { 137, 3, -61 }, { 177, 16, 78 }, { 166, 57, 69 }, { 182, 12, -61 }, { 186, 190, -54 }, { 52, 192, 80 }, { 11, 129, -6 }, { 85, 5, -97 }, { 95, 136, -40 }, { 155, 132, 8 }, { 191, 121, -59 }, { 157, 138, -5 }, { 154, 148, 17 }, { 106, 61, 56 }, { 141, 53, 25 }, { 177, 166, -22 }, { 106, 159, 10 }, { 15, 38, -97 }, { 6, 22, 76 }, { 67, 44, -52 }, { 31, 60, 88 }, { 118, 167, 99 }, { 189, 127, -30 }, { 151, 23, 90 }, { 96, 178, 7 }, { 150, 186, -60 }, { 82, 186, 63 }, { 15, 124, -72 }, { 159, 98, -24 }, { 41, 135, 9 }, { 2, 154, 29 }, { 198, 77, 84 }, { 169, 12, 59 }, { 150, 177, -28 }, { 76, 96, 64 }, { 7, 62, 74 }, { 184, 121, 2 }, { 75, 25, -21 }, { 134, 144, 2 }, { 4, 89, -79 }, { 86, 161, -39 }, { 192, 140, 51 }, { 60, 107, -11 }, { 196, 157, -2 }, { 51, 96, -13 }, { 127, 126, 83 }, { 192, 187, -3 }, { 37, 106, -39 }, { 128, 72, 55 }, { 36, 39, -8 }, { 144, 151, 86 }, { 177, 143, -24 }, { 133, 37, -84 }, { 76, 16, 34 }, { 43, 102, 56 }, { 171, 105, 63 }, { 40, 127, 25 }, { 150, 182, -31 }, { 155, 35, -26 }, { 32, 65, -23 }, { 69, 81, -64 }, { 103, 122, -38 }, { 126, 93, 38 }, { 38, 185, 63 }, { 154, 12, -63 }, { 104, 194, 25 }, { 85, 173, 49 }, { 128, 81, -43 }, { 140, 12, -41 }, { 71, 126, 44 }, { 134, 170, 9 }, { 117, 196, -24 }, { 27, 91, -10 }, { 163, 86, -1 }, { 83, 70, 30 }, { 184, 113, -74 }, { 42, 1, 47 }, { 59, 152, -49 }, { 15, 101, 30 }, { 27, 141, 62 }, { 21, 181, -67 }, { 167, 171, 82 }, { 38, 6, -92 }, { 47, 31, -19 }, { 168, 171, -97 }, { 92, 19, -36 }, { 171, 96, 65 }, { 96, 8, -90 }, { 68, 162, -30 }, { 21, 173, 11 }, { 10, 145, -53 }, { 41, 36, -13 }, { 79, 2, -88 }, { 0, 70, 52 }, { 33, 63, 15 }, { 162, 20, 31 }, { 9, 32, 92 }, { 78, 184, 91 }, { 147, 47, 89 }, { 22, 4, -2 }, { 102, 87, -74 }, { 125, 67, -56 }, { 119, 93, -81 }, { 103, 118, -2 }, { 49, 163, -19 }, { 151, 81, -46 }, { 167, 60, -52 }, { 157, 133, -58 }, { 92, 134, -22 }, { 196, 6, 76 }, { 23, 103, 56 }, { 148, 84, -68 }, { 170, 166, -78 }, { 127, 155, -94 }, { 74, 45, -26 }, { 163, 153, -31 }, { 106, 73, 80 }, { 47, 68, 78 }, { 16, 29, -33 }, { 114, 176, 9 }, { 108, 119, -2 }, { 79, 136, -51 }, { 6, 51, -2 }, { 142, 76, -91 }, { 167, 15, 89 }, { 175, 187, -40 }, { 147, 16, 81 }, { 40, 17, 65 }, { 124, 20, -91 }, { 89, 107, 97 }, { 58, 97, -17 }, { 125, 17, 65 }, { 98, 148, -52 }, { 12, 156, 1 }, { 180, 13, -64 }, { 150, 138, -50 }, { 82, 2, -76 }, { 10, 183, -92 }, { 133, 121, -52 }, { 152, 84, 53 }, { 89, 99, -85 }, { 148, 21, -24 }, { 21, 46, -20 }, { 99, 182, -73 }, { 179, 98, 3 }, { 82, 102, -60 }, { 174, 167, -30 }, { 13, 72, -12 }, { 197, 22, 25 }, { 110, 87, 7 }, { 50, 142, 71 }, { 175, 152, 36 }, { 184, 33, -71 }, { 192, 149, -99 }, { 5, 110, 10 }, { 156, 120, 26 }, { 51, 153, 16 }, { 25, 181, -85 }, { 37, 81, 74 }, { 135, 140, 61 }, { 40, 136, -73 }, { 155, 133, -44 }, { 199, 33, -71 }, { 11, 86, -81 }, { 124, 134, -47 }, { 68, 106, 97 }, { 131, 152, -64 }, { 83, 67, -74 }, { 1, 4, 83 }, { 9, 155, 50 }, { 20, 27, -14 }, { 146, 118, 32 }, { 37, 129, 60 }, { 55, 162, -91 }, { 139, 8, -48 }, { 2, 122, 72 }, { 2, 15, -20 }, { 194, 48, -31 }, { 156, 102, -84 }, { 119, 130, 36 }, { 29, 44, 74 }, { 56, 127, -27 }, { 165, 116, 64 }, { 59, 3, 96 }, { 138, 155, 55 }, { 148, 176, -94 }, { 124, 78, -100 }, { 66, 158, 96 }, { 29, 91, 93 }, { 190, 53, 0 }, { 144, 13, -79 }, { 13, 73, 21 }, { 25, 69, -12 }, { 55, 150, 71 }, { 13, 78, -85 }, { 57, 117, -11 }, { 91, 160, 100 }, { 40, 46, 9 }, { 146, 64, 8 }, { 110, 119, -8 }, { 173, 78, 65 }, { 170, 181, 1 }, { 109, 136, -70 }, { 179, 121, 12 }, { 69, 87, 83 }, { 117, 30, 1 }, { 115, 79, -54 }, { 31, 104, 83 }, { 61, 11, -84 }, { 100, 104, 44 }, { 114, 33, -10 }, { 32, 70, 98 }, { 83, 2, 50 }, { 197, 55, 43 }, { 42, 102, 38 }, { 158, 120, 6 }, { 137, 46, 31 }, { 35, 161, -32 }, { 173, 138, -48 }, { 102, 167, -6 }, { 60, 149, 20 }, { 62, 28, -76 }, { 197, 158, -67 }, { 178, 40, 24 }, { 159, 73, 6 }, { 23, 29, 73 }, { 84, 39, -38 }, { 37, 182, -13 }, { 71, 10, 59 }, { 167, 64, 86 }, { 35, 124, -68 }, { 49, 13, -22 }, { 94, 145, -33 }, { 7, 165, 46 }, { 30, 123, -98 }, { 107, 127, -23 }, { 35, 189, -6 }, { 183, 39, -21 }, { 5, 98, 14 }, { 52, 177, -69 }, { 62, 0, -74 }, { 26, 70, 93 }, { 58, 32, 87 }, { 152, 189, -29 }, { 86, 104, 56 }, { 198, 180, 19 }, { 15, 100, 6 }, { 113, 183, -30 }, { 101, 80, -90 }, { 54, 33, -19 }, { 40, 90, -85 }, { 114, 74, 6 }, { 152, 103, 14 }, { 107, 115, -59 }, { 182, 50, -24 }, { 42, 54, 12 }, { 75, 26, 51 }, { 149, 148, -22 }, { 177, 2, 52 }, { 184, 188, 36 }, { 169, 97, -43 }, { 22, 124, 39 }, { 100, 85, 84 }, { 77, 36, 47 }, { 79, 92, 10 }, { 33, 177, 19 }, { 35, 87, 17 }, { 101, 67, 59 }, { 188, 160, 66 }, { 143, 154, -87 }, { 8, 172, -30 }, { 164, 45, 98 }, { 42, 26, -5 }, { 148, 19, 56 }, { 32, 101, 23 }, { 86, 47, 91 }, { 1, 0, -50 }, { 134, 62, 70 }, { 147, 152, -61 }, { 166, 116, 82 }, { 123, 199, -30 }, { 27, 15, -57 }, { 198, 87, 86 }, { 98, 10, -98 }, { 8, 4, -52 }, { 170, 29, -40 }, { 130, 158, 63 }, { 155, 131, -34 }, { 105, 182, -49 }, { 191, 182, 98 }, { 20, 47, 68 }, { 71, 79, 62 }, { 150, 32, -49 }, { 81, 9, -54 }, { 77, 56, -70 }, { 113, 103, 53 }, { 131, 97, -73 }, { 13, 139, -69 }, { 18, 1, 9 }, { 53, 170, 16 }, { 148, 38, 92 }, { 19, 85, -15 }, { 27, 137, 79 }, { 71, 58, -18 }, { 175, 37, 40 }, { 94, 99, 88 }, { 70, 104, 1 }, { 148, 37, 64 }, { 72, 25, -49 }, { 198, 125, 65 }, { 68, 108, 51 }, { 85, 63, -97 }, { 180, 187, -56 }, { 128, 47, 62 }, { 64, 198, 17 }, { 41, 140, 76 }, { 18, 5, -32 }, { 98, 99, 15 }, { 173, 140, 92 }, { 97, 145, 55 }, { 99, 151, 0 }, { 114, 35, -9 }, { 126, 175, -77 }, { 51, 154, 29 }, { 31, 173, -68 }, { 70, 170, 42 }, { 14, 61, 31 }, { 114, 11, -100 }, { 89, 197, 36 }, { 19, 2, 41 }, { 160, 54, -45 }, { 59, 161, 62 }, { 96, 74, 51 }, { 0, 13, 85 }, { 22, 72, 42 }, { 176, 135, 59 }, { 94, 150, 29 }, { 122, 23, -2 }, { 20, 109, -11 }, { 14, 140, -8 }, { 91, 153, -2 }, { 24, 104, -18 }, { 132, 42, -63 }, { 193, 178, -93 }, { 177, 142, 59 }, { 46, 68, -90 }, { 114, 184, 53 }, { 4, 120, -58 }, { 167, 141, -24 }, { 188, 37, -75 }, { 183, 117, -12 }, { 116, 14, 33 }, { 184, 7, -94 }, { 134, 21, -2 }, { 104, 89, 90 }, { 51, 8, 53 }, { 164, 151, -37 }, { 158, 20, -75 }, { 8, 78, 39 }, { 131, 62, 65 }, { 113, 5, 65 }, { 86, 13, 99 }, { 133, 56, -81 }, { 183, 53, -71 }, { 142, 145, -91 }, { 152, 194, 75 }, { 117, 189, -45 }, { 25, 29, -37 }, { 143, 95, -94 }, { 50, 180, 60 }, { 150, 132, 41 }, { 16, 96, -40 }, { 42, 135, -66 }, { 148, 34, -66 }, { 10, 6, 31 }, { 112, 22, -16 }, { 5, 77, -22 }, { 91, 4, -5 }, { 166, 155, 49 }, { 31, 155, 48 }, { 183, 75, 52 }, { 112, 31, -20 }, { 44, 12, 36 }, { 144, 6, 36 }, { 20, 36, 87 }, { 81, 46, -58 }, { 27, 8, 70 }, { 162, 73, -28 }, { 187, 112, -93 }, { 109, 101, 60 }, { 108, 138, 31 }, { 16, 78, 1 }, { 145, 141, 92 }, { 98, 30, 76 }, { 45, 120, 59 }, { 41, 174, 28 }, { 14, 2, 84 }, { 97, 166, -78 }, { 120, 51, -16 }, { 39, 90, -100 }, { 149, 35, -72 }, { 142, 198, -41 }, { 35, 171, 60 }, { 65, 148, 60 }, { 38, 2, -3 }, { 139, 44, -78 }, { 72, 170, 92 }, { 75, 173, 97 }, { 110, 91, 95 }, { 27, 34, 45 }, { 116, 134, -69 }, { 171, 103, 94 }, { 147, 93, 93 }, { 97, 140, 100 }, { 130, 58, 92 }, { 76, 154, 99 }, { 91, 54, 9 }, { 97, 117, -63 }, { 183, 12, -45 }, { 37, 89, 9 }, { 148, 62, -81 }, { 116, 192, -39 }, { 5, 142, 27 }, { 56, 44, -56 }, { 178, 16, 83 }, { 54, 137, 94 }, { 178, 199, -75 }, { 71, 22, 67 }, { 185, 4, 82 }, { 57, 65, -73 }, { 160, 30, 63 }, }; double maxWeight = 5229; double minWeight = -5301; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 400 vertices and 1000 edges */ @Test public void testGetMatching17() { int[][] edges = new int[][] { { 226, 265, -38 }, { 346, 351, 23 }, { 247, 266, -32 }, { 296, 345, -49 }, { 204, 302, -87 }, { 12, 363, 67 }, { 48, 148, -98 }, { 135, 190, 69 }, { 99, 98, -13 }, { 248, 298, -17 }, { 395, 124, -59 }, { 150, 202, 33 }, { 282, 55, -87 }, { 265, 111, -30 }, { 127, 158, 42 }, { 207, 329, 54 }, { 187, 180, 33 }, { 44, 146, 33 }, { 310, 268, 7 }, { 306, 349, -8 }, { 241, 5, 95 }, { 243, 304, -10 }, { 206, 298, -76 }, { 307, 334, 22 }, { 221, 24, -78 }, { 370, 199, -77 }, { 57, 289, -35 }, { 72, 20, -53 }, { 51, 144, 30 }, { 83, 221, 42 }, { 255, 334, 18 }, { 62, 301, -59 }, { 76, 148, -95 }, { 323, 312, 89 }, { 168, 18, 56 }, { 3, 123, 27 }, { 21, 95, -99 }, { 36, 229, 36 }, { 309, 225, 87 }, { 331, 4, 30 }, { 188, 16, 90 }, { 299, 67, 29 }, { 23, 101, 80 }, { 30, 19, -15 }, { 344, 73, 97 }, { 130, 55, 9 }, { 276, 363, 83 }, { 55, 340, 37 }, { 207, 209, 85 }, { 374, 104, 15 }, { 39, 339, -83 }, { 372, 154, -82 }, { 127, 100, 19 }, { 344, 291, -43 }, { 366, 178, 77 }, { 74, 59, -63 }, { 393, 64, -98 }, { 222, 198, 53 }, { 364, 18, 72 }, { 368, 399, -46 }, { 207, 104, 61 }, { 1, 390, 84 }, { 7, 174, -34 }, { 225, 135, -52 }, { 123, 275, 51 }, { 238, 155, 48 }, { 90, 248, 9 }, { 188, 290, -62 }, { 105, 156, -43 }, { 23, 352, 22 }, { 34, 165, 45 }, { 86, 312, -32 }, { 394, 33, 8 }, { 193, 326, -70 }, { 274, 174, -57 }, { 293, 257, -5 }, { 353, 374, -19 }, { 53, 81, 45 }, { 145, 383, -3 }, { 37, 333, -65 }, { 264, 162, 62 }, { 290, 8, -93 }, { 233, 41, -51 }, { 84, 140, -6 }, { 170, 30, -57 }, { 8, 327, 57 }, { 151, 210, -39 }, { 376, 212, -36 }, { 216, 123, 95 }, { 44, 205, 54 }, { 256, 66, -11 }, { 198, 49, -8 }, { 295, 219, -100 }, { 361, 224, -15 }, { 137, 390, -6 }, { 378, 142, -8 }, { 174, 53, -46 }, { 347, 151, -100 }, { 331, 206, 31 }, { 52, 175, 84 }, { 266, 196, 97 }, { 371, 365, -23 }, { 7, 193, 43 }, { 279, 264, 21 }, { 220, 102, -54 }, { 344, 163, -99 }, { 30, 38, 2 }, { 170, 325, 35 }, { 312, 47, -10 }, { 99, 332, 56 }, { 246, 218, -55 }, { 329, 3, -31 }, { 325, 69, 58 }, { 0, 320, 69 }, { 330, 15, -39 }, { 371, 227, 34 }, { 338, 196, 84 }, { 169, 41, -55 }, { 252, 172, 87 }, { 93, 10, -49 }, { 109, 302, 81 }, { 68, 353, -87 }, { 218, 199, -2 }, { 379, 169, 39 }, { 219, 124, 23 }, { 82, 308, 35 }, { 26, 179, -89 }, { 389, 202, -42 }, { 218, 92, -5 }, { 157, 260, 35 }, { 346, 288, -17 }, { 360, 375, -52 }, { 298, 315, 45 }, { 325, 243, 92 }, { 110, 262, -47 }, { 290, 254, 28 }, { 386, 344, 52 }, { 186, 269, 89 }, { 109, 110, 94 }, { 74, 67, 4 }, { 346, 168, -76 }, { 372, 221, 75 }, { 196, 230, -82 }, { 7, 44, -9 }, { 336, 226, -74 }, { 124, 84, 5 }, { 240, 15, -3 }, { 277, 116, -12 }, { 214, 290, -48 }, { 242, 135, -44 }, { 294, 128, 5 }, { 101, 315, -70 }, { 272, 185, -87 }, { 88, 352, -47 }, { 268, 163, -87 }, { 204, 355, -82 }, { 280, 41, 40 }, { 292, 171, -15 }, { 223, 212, -8 }, { 113, 259, 56 }, { 203, 155, -85 }, { 288, 88, 22 }, { 174, 391, 36 }, { 161, 37, -79 }, { 140, 298, 37 }, { 170, 141, -35 }, { 331, 321, 50 }, { 77, 41, 6 }, { 366, 295, -34 }, { 22, 195, 94 }, { 175, 220, -51 }, { 377, 354, 44 }, { 92, 166, 45 }, { 233, 358, 64 }, { 148, 310, 3 }, { 44, 246, -19 }, { 275, 157, 99 }, { 223, 56, 31 }, { 187, 193, 26 }, { 21, 160, 6 }, { 356, 314, -35 }, { 308, 205, -36 }, { 310, 290, -41 }, { 338, 48, -87 }, { 277, 28, 6 }, { 220, 78, -94 }, { 237, 275, 27 }, { 264, 88, 28 }, { 31, 343, -86 }, { 237, 104, 40 }, { 139, 67, 27 }, { 337, 78, 39 }, { 175, 194, 50 }, { 354, 60, -6 }, { 384, 228, 95 }, { 327, 111, 20 }, { 279, 362, 56 }, { 40, 190, -82 }, { 207, 10, -21 }, { 268, 328, -15 }, { 284, 15, -14 }, { 67, 265, -65 }, { 15, 80, -2 }, { 339, 316, -68 }, { 184, 52, -2 }, { 359, 247, -38 }, { 284, 324, 54 }, { 390, 291, -15 }, { 376, 98, -91 }, { 353, 155, -96 }, { 388, 296, 17 }, { 353, 248, 19 }, { 301, 243, 32 }, { 125, 385, -48 }, { 51, 168, -35 }, { 44, 297, 64 }, { 321, 171, -35 }, { 396, 250, 76 }, { 305, 136, 70 }, { 112, 150, 82 }, { 272, 316, -25 }, { 29, 80, -79 }, { 5, 397, 82 }, { 85, 19, 17 }, { 341, 255, -60 }, { 49, 293, -31 }, { 233, 3, 55 }, { 317, 172, 46 }, { 382, 247, 80 }, { 335, 297, 97 }, { 221, 160, 6 }, { 399, 301, 65 }, { 93, 361, 100 }, { 299, 210, -47 }, { 351, 4, -5 }, { 94, 191, 24 }, { 313, 367, -85 }, { 244, 340, -42 }, { 319, 47, 31 }, { 388, 222, -50 }, { 204, 276, 50 }, { 73, 346, 17 }, { 304, 269, -97 }, { 337, 110, -23 }, { 314, 253, -4 }, { 104, 115, -55 }, { 150, 303, -53 }, { 294, 216, 18 }, { 137, 85, 42 }, { 23, 211, -76 }, { 44, 295, 76 }, { 362, 136, -4 }, { 230, 73, 46 }, { 109, 303, -14 }, { 269, 222, 19 }, { 216, 241, -62 }, { 351, 364, -66 }, { 103, 212, -25 }, { 205, 207, -54 }, { 365, 214, 29 }, { 9, 194, -50 }, { 102, 286, 71 }, { 176, 267, -33 }, { 284, 321, -69 }, { 57, 55, 0 }, { 110, 275, 97 }, { 207, 213, 6 }, { 396, 239, -8 }, { 365, 119, 27 }, { 327, 40, -26 }, { 232, 54, -9 }, { 55, 48, -62 }, { 210, 37, -99 }, { 61, 8, 34 }, { 109, 265, -82 }, { 27, 89, 77 }, { 269, 55, -74 }, { 178, 114, 12 }, { 50, 142, -88 }, { 27, 115, -94 }, { 133, 230, 48 }, { 360, 345, -66 }, { 97, 297, 35 }, { 29, 171, 100 }, { 229, 384, 77 }, { 26, 69, -75 }, { 280, 241, -38 }, { 27, 99, -65 }, { 60, 136, -63 }, { 178, 79, 33 }, { 375, 67, 65 }, { 132, 317, 40 }, { 383, 242, -52 }, { 274, 375, 8 }, { 165, 223, 48 }, { 266, 149, 33 }, { 372, 234, -76 }, { 142, 367, 40 }, { 314, 392, -38 }, { 211, 326, -40 }, { 86, 44, 11 }, { 269, 319, 97 }, { 292, 86, 13 }, { 362, 305, -17 }, { 102, 396, -74 }, { 308, 118, 89 }, { 230, 397, 20 }, { 335, 276, 10 }, { 352, 390, 13 }, { 224, 158, -16 }, { 270, 284, 95 }, { 182, 187, -46 }, { 129, 92, -67 }, { 7, 182, 48 }, { 330, 68, 90 }, { 282, 381, -90 }, { 389, 238, -22 }, { 142, 195, 84 }, { 296, 217, 76 }, { 249, 197, 91 }, { 348, 265, 23 }, { 18, 59, -32 }, { 379, 288, -59 }, { 338, 130, -2 }, { 252, 332, 73 }, { 375, 231, -61 }, { 285, 322, -61 }, { 313, 358, -26 }, { 255, 332, -1 }, { 66, 4, -79 }, { 39, 255, -66 }, { 392, 89, -67 }, { 194, 224, -42 }, { 334, 385, 97 }, { 89, 391, 64 }, { 363, 149, -97 }, { 278, 68, -28 }, { 393, 37, -14 }, { 390, 241, 65 }, { 257, 89, -42 }, { 113, 280, 3 }, { 374, 312, 26 }, { 390, 359, -64 }, { 64, 168, 32 }, { 44, 96, 24 }, { 283, 343, -17 }, { 53, 229, 82 }, { 255, 9, 5 }, { 180, 84, 72 }, { 114, 211, -20 }, { 257, 239, -45 }, { 156, 279, -88 }, { 123, 31, -21 }, { 170, 129, 61 }, { 46, 282, -62 }, { 102, 305, 67 }, { 22, 146, 24 }, { 357, 329, -22 }, { 260, 160, 43 }, { 130, 41, -66 }, { 190, 177, -85 }, { 137, 105, -74 }, { 301, 47, 82 }, { 295, 247, -84 }, { 146, 129, 1 }, { 297, 8, 65 }, { 356, 148, -51 }, { 178, 3, -14 }, { 300, 150, -13 }, { 188, 163, -9 }, { 375, 97, -5 }, { 307, 387, 60 }, { 380, 98, 29 }, { 317, 23, 30 }, { 102, 337, -72 }, { 128, 214, 95 }, { 358, 48, 88 }, { 102, 324, 4 }, { 137, 377, -16 }, { 389, 339, 32 }, { 374, 115, -61 }, { 265, 43, -70 }, { 123, 84, 41 }, { 116, 279, 45 }, { 153, 366, -54 }, { 183, 226, 64 }, { 286, 163, -87 }, { 243, 154, 64 }, { 345, 371, -42 }, { 310, 119, -81 }, { 168, 87, 34 }, { 121, 149, 4 }, { 170, 268, 87 }, { 299, 39, -86 }, { 20, 270, 75 }, { 390, 128, -92 }, { 104, 383, 7 }, { 249, 319, 37 }, { 280, 367, 91 }, { 152, 17, -89 }, { 370, 93, -37 }, { 56, 375, -6 }, { 130, 186, -36 }, { 353, 125, 0 }, { 377, 388, 50 }, { 223, 60, 43 }, { 186, 240, -54 }, { 334, 288, 96 }, { 102, 251, 93 }, { 72, 11, -67 }, { 138, 353, -38 }, { 287, 92, -2 }, { 327, 189, 19 }, { 231, 208, -95 }, { 49, 225, 0 }, { 375, 71, 78 }, { 331, 88, -34 }, { 16, 59, -52 }, { 209, 235, -51 }, { 173, 395, -64 }, { 359, 16, 3 }, { 284, 238, 15 }, { 161, 105, -2 }, { 188, 215, 61 }, { 35, 27, 22 }, { 249, 259, 72 }, { 42, 222, -6 }, { 37, 266, 24 }, { 43, 165, 87 }, { 152, 373, 22 }, { 108, 302, -47 }, { 123, 285, 81 }, { 337, 316, -20 }, { 304, 388, -4 }, { 296, 194, -49 }, { 324, 328, 0 }, { 247, 307, 38 }, { 248, 372, 49 }, { 102, 280, -15 }, { 316, 377, -94 }, { 248, 257, 3 }, { 30, 157, -61 }, { 142, 159, -87 }, { 275, 162, 40 }, { 223, 288, 44 }, { 117, 299, -33 }, { 289, 45, -29 }, { 188, 250, 79 }, { 124, 49, -23 }, { 320, 381, 81 }, { 255, 282, -81 }, { 254, 57, 43 }, { 195, 4, -43 }, { 186, 391, 56 }, { 49, 20, -98 }, { 21, 398, -1 }, { 190, 259, -100 }, { 32, 322, 31 }, { 95, 235, 0 }, { 273, 331, -79 }, { 185, 70, -54 }, { 195, 168, 44 }, { 40, 85, 70 }, { 323, 54, -73 }, { 292, 126, -42 }, { 105, 91, 36 }, { 399, 362, -75 }, { 101, 248, -33 }, { 258, 189, -35 }, { 43, 302, -48 }, { 58, 6, 54 }, { 336, 95, -81 }, { 46, 362, 17 }, { 182, 184, 60 }, { 3, 348, -97 }, { 198, 350, 83 }, { 19, 159, 92 }, { 140, 246, 77 }, { 89, 79, 77 }, { 104, 313, -93 }, { 305, 92, 54 }, { 35, 56, 98 }, { 158, 57, -70 }, { 191, 28, -27 }, { 157, 276, 27 }, { 370, 289, -21 }, { 265, 166, -40 }, { 181, 115, 36 }, { 399, 61, -66 }, { 313, 121, 25 }, { 355, 8, -17 }, { 138, 193, -2 }, { 275, 371, -20 }, { 72, 141, -54 }, { 348, 131, -62 }, { 162, 236, 4 }, { 363, 163, -55 }, { 133, 59, 9 }, { 278, 240, 21 }, { 308, 317, 93 }, { 42, 50, -58 }, { 328, 216, -8 }, { 390, 348, 94 }, { 299, 99, -53 }, { 360, 22, -44 }, { 32, 252, 16 }, { 120, 152, -31 }, { 6, 30, -2 }, { 307, 361, -80 }, { 254, 35, 46 }, { 345, 169, -51 }, { 90, 294, 86 }, { 330, 155, -47 }, { 391, 124, -95 }, { 349, 39, 53 }, { 29, 132, 61 }, { 130, 335, -64 }, { 117, 124, -48 }, { 201, 192, 99 }, { 394, 181, -83 }, { 211, 347, -78 }, { 20, 42, 2 }, { 216, 132, 57 }, { 242, 125, -15 }, { 330, 186, -16 }, { 23, 194, -36 }, { 25, 136, 31 }, { 199, 255, -41 }, { 166, 188, -35 }, { 147, 356, -77 }, { 317, 166, 67 }, { 17, 184, -56 }, { 205, 355, -67 }, { 176, 100, 42 }, { 251, 249, 60 }, { 60, 291, 21 }, { 231, 341, -92 }, { 333, 234, -48 }, { 77, 387, -10 }, { 159, 238, 19 }, { 59, 225, -74 }, { 242, 202, -18 }, { 162, 112, -68 }, { 136, 285, -49 }, { 238, 170, 42 }, { 140, 388, -78 }, { 297, 177, 36 }, { 51, 37, -12 }, { 322, 9, -29 }, { 370, 64, 96 }, { 342, 357, 46 }, { 364, 380, 81 }, { 66, 84, 17 }, { 286, 79, -63 }, { 280, 109, 27 }, { 380, 317, -14 }, { 177, 217, 29 }, { 174, 181, 85 }, { 307, 288, -82 }, { 198, 8, -38 }, { 51, 327, -5 }, { 324, 59, -13 }, { 285, 280, 60 }, { 381, 382, -55 }, { 242, 257, -52 }, { 376, 334, -66 }, { 387, 129, -71 }, { 186, 8, -55 }, { 188, 289, -12 }, { 274, 286, -61 }, { 211, 367, 78 }, { 136, 319, 69 }, { 337, 1, -61 }, { 275, 82, -54 }, { 321, 199, 80 }, { 313, 51, -6 }, { 8, 65, -97 }, { 60, 385, -40 }, { 148, 19, 8 }, { 273, 358, -59 }, { 233, 166, -5 }, { 131, 136, 17 }, { 120, 289, 56 }, { 73, 272, 25 }, { 210, 296, -22 }, { 291, 9, 10 }, { 29, 51, -97 }, { 43, 284, 76 }, { 340, 385, -52 }, { 187, 329, 88 }, { 212, 126, 99 }, { 106, 247, -30 }, { 5, 34, 90 }, { 220, 223, 7 }, { 183, 221, -60 }, { 377, 101, 63 }, { 30, 304, -72 }, { 285, 377, -24 }, { 368, 381, 9 }, { 33, 158, 29 }, { 349, 257, 84 }, { 264, 232, 59 }, { 158, 64, -28 }, { 77, 347, 64 }, { 258, 257, 74 }, { 60, 33, 2 }, { 150, 108, -21 }, { 367, 300, 2 }, { 115, 130, -79 }, { 124, 189, -39 }, { 141, 299, 51 }, { 235, 19, -11 }, { 346, 309, -2 }, { 396, 344, -13 }, { 273, 75, 83 }, { 74, 152, -3 }, { 196, 276, -39 }, { 105, 195, 55 }, { 47, 270, -8 }, { 118, 53, 86 }, { 306, 218, -24 }, { 336, 260, -84 }, { 156, 345, 34 }, { 354, 281, 56 }, { 282, 23, 63 }, { 308, 178, 25 }, { 367, 71, -31 }, { 225, 24, -26 }, { 197, 175, -23 }, { 378, 108, -64 }, { 307, 37, -38 }, { 268, 247, 38 }, { 352, 330, 63 }, { 110, 289, -63 }, { 200, 230, 25 }, { 207, 114, 49 }, { 173, 243, -43 }, { 30, 360, -41 }, { 81, 270, 44 }, { 154, 168, 9 }, { 193, 299, -24 }, { 92, 312, -10 }, { 261, 40, -1 }, { 322, 27, 30 }, { 342, 197, -74 }, { 158, 334, 47 }, { 63, 194, -49 }, { 319, 67, 30 }, { 88, 10, 62 }, { 255, 28, -67 }, { 112, 366, 82 }, { 254, 346, -92 }, { 218, 389, -19 }, { 46, 70, -97 }, { 363, 338, -36 }, { 255, 271, 65 }, { 273, 21, -90 }, { 76, 293, -30 }, { 246, 279, 11 }, { 102, 213, -53 }, { 245, 256, -13 }, { 144, 134, -88 }, { 93, 235, 52 }, { 331, 211, 15 }, { 85, 316, 31 }, { 164, 74, 92 }, { 304, 128, 91 }, { 300, 132, 89 }, { 114, 22, -2 }, { 231, 150, -74 }, { 98, 237, -56 }, { 344, 71, -81 }, { 289, 30, -2 }, { 12, 82, -19 }, { 170, 283, -46 }, { 4, 270, -52 }, { 316, 39, -58 }, { 328, 44, -22 }, { 36, 343, 76 }, { 41, 113, 56 }, { 63, 329, -68 }, { 181, 180, -78 }, { 319, 228, -94 }, { 18, 330, -26 }, { 269, 288, -31 }, { 387, 175, 80 }, { 207, 280, 78 }, { 207, 88, -33 }, { 250, 109, 9 }, { 196, 55, -2 }, { 106, 44, -51 }, { 102, 254, -2 }, { 32, 82, -91 }, { 318, 215, 89 }, { 61, 30, -40 }, { 351, 339, 81 }, { 349, 72, 65 }, { 27, 18, -91 }, { 390, 305, 97 }, { 246, 130, -17 }, { 34, 43, 65 }, { 329, 43, -52 }, { 234, 9, 1 }, { 27, 41, -64 }, { 4, 90, -50 }, { 350, 50, -76 }, { 35, 125, -92 }, { 316, 330, -52 }, { 234, 257, 53 }, { 255, 201, -85 }, { 327, 66, -24 }, { 248, 49, -20 }, { 76, 11, -73 }, { 77, 191, 3 }, { 289, 325, -60 }, { 265, 254, -30 }, { 353, 304, -12 }, { 314, 362, 25 }, { 356, 292, 7 }, { 38, 382, 71 }, { 189, 168, 36 }, { 189, 214, -71 }, { 307, 210, -99 }, { 262, 5, 10 }, { 84, 128, 26 }, { 99, 158, 16 }, { 135, 102, -85 }, { 199, 173, 74 }, { 49, 105, 61 }, { 59, 150, -73 }, { 220, 315, -44 }, { 332, 0, -71 }, { 340, 295, -81 }, { 71, 371, -47 }, { 210, 31, 97 }, { 216, 335, -64 }, { 173, 228, -74 }, { 32, 15, 83 }, { 286, 342, 50 }, { 38, 78, -14 }, { 52, 141, 32 }, { 65, 231, 60 }, { 377, 327, -91 }, { 387, 371, -48 }, { 83, 162, 72 }, { 344, 207, -20 }, { 238, 299, -31 }, { 336, 108, -84 }, { 216, 104, 36 }, { 91, 395, 74 }, { 301, 291, -27 }, { 243, 183, 64 }, { 325, 216, 96 }, { 383, 21, 55 }, { 109, 193, -94 }, { 389, 26, -100 }, { 178, 64, 96 }, { 80, 44, 93 }, { 179, 220, 0 }, { 3, 11, -79 }, { 296, 399, 21 }, { 30, 113, -12 }, { 46, 15, 71 }, { 160, 343, -85 }, { 326, 43, -11 }, { 11, 122, 100 }, { 14, 72, 9 }, { 95, 334, 8 }, { 158, 76, -8 }, { 65, 1, 65 }, { 342, 267, 1 }, { 363, 243, -70 }, { 278, 48, 12 }, { 328, 180, 83 }, { 175, 270, 1 }, { 144, 110, -54 }, { 26, 88, 83 }, { 2, 180, -84 }, { 278, 237, 44 }, { 321, 123, -10 }, { 198, 39, 98 }, { 130, 136, 50 }, { 123, 278, 43 }, { 323, 43, 38 }, { 95, 72, 6 }, { 126, 314, 31 }, { 386, 172, -32 }, { 319, 64, -48 }, { 121, 167, -6 }, { 355, 23, 20 }, { 324, 198, -76 }, { 114, 259, -67 }, { 287, 116, 24 }, { 239, 17, 6 }, { 333, 142, 73 }, { 318, 304, -38 }, { 305, 310, -13 }, { 70, 382, 59 }, { 109, 148, 86 }, { 88, 342, -68 }, { 111, 52, -22 }, { 143, 216, -33 }, { 73, 219, 46 }, { 154, 169, -98 }, { 321, 11, -23 }, { 390, 77, -6 }, { 90, 361, -21 }, { 128, 98, 14 }, { 113, 397, -69 }, { 106, 95, -74 }, { 43, 42, 93 }, { 349, 156, 87 }, { 205, 222, -29 }, { 207, 163, 56 }, { 317, 60, 19 }, { 53, 7, 6 }, { 232, 87, -30 }, { 44, 324, -90 }, { 36, 257, -19 }, { 307, 87, -85 }, { 280, 229, 6 }, { 28, 383, 14 }, { 265, 322, -59 }, { 3, 244, -24 }, { 87, 57, 12 }, { 302, 342, 51 }, { 104, 89, -22 }, { 358, 361, 52 }, { 340, 200, 36 }, { 278, 15, -43 }, { 321, 260, 39 }, { 259, 327, 84 }, { 97, 266, 47 }, { 284, 117, 10 }, { 201, 7, 19 }, { 323, 17, 17 }, { 276, 317, 59 }, { 314, 4, 66 }, { 73, 280, -87 }, { 34, 287, -30 }, { 105, 113, 98 }, { 337, 286, -5 }, { 380, 66, 56 }, { 227, 78, 23 }, { 396, 284, 91 }, { 179, 299, -50 }, { 131, 229, 70 }, { 122, 269, -61 }, { 162, 165, 82 }, { 8, 67, -30 }, { 2, 50, -57 }, { 257, 79, 86 }, { 175, 27, -98 }, { 155, 164, -52 }, { 242, 66, -40 }, { 22, 270, 63 }, { 46, 301, -34 }, { 155, 48, -49 }, { 367, 398, 98 }, { 313, 212, 68 }, { 47, 369, 62 }, { 189, 174, -49 }, { 153, 23, -54 }, { 193, 145, -70 }, { 194, 80, 53 }, { 151, 342, -73 }, { 229, 317, -69 }, { 134, 330, 9 }, { 287, 230, 16 }, { 245, 120, 92 }, { 60, 185, -15 }, { 374, 342, 79 }, { 208, 245, -18 }, { 54, 244, 40 }, { 57, 246, 88 }, { 114, 145, 1 }, { 244, 337, 64 }, { 253, 288, -49 }, { 61, 161, 65 }, { 368, 44, 51 }, { 251, 227, -97 }, { 306, 7, -56 }, { 12, 399, 62 }, { 58, 9, 17 }, { 142, 120, 76 }, { 304, 87, -32 }, { 383, 213, 15 }, { 139, 65, 92 }, { 134, 384, 55 }, { 119, 48, 0 }, { 178, 368, -9 }, { 102, 239, -77 }, { 322, 290, 29 }, { 202, 58, -68 }, { 262, 205, 42 }, { 353, 133, 31 }, { 100, 5, -100 }, { 224, 317, 36 }, { 110, 191, 41 }, { 36, 132, -45 }, { 83, 39, 62 }, { 245, 9, 51 }, { 67, 94, 85 }, { 108, 246, 42 }, { 216, 248, 59 }, { 264, 334, 29 }, { 322, 164, -2 }, { 221, 198, -11 }, { 3, 13, -8 }, { 157, 355, -2 }, { 123, 81, -18 }, { 153, 324, -63 }, { 325, 239, -93 }, { 346, 193, 59 }, { 147, 198, -90 }, { 348, 196, 53 }, { 134, 284, -58 }, { 332, 22, -24 }, { 65, 172, -75 }, { 243, 269, -12 }, { 399, 19, 33 }, { 193, 101, -94 }, { 114, 204, -2 }, { 158, 173, 90 }, { 350, 250, 53 }, { 397, 51, -37 }, { 118, 22, -75 }, { 355, 209, 39 }, { 327, 137, 65 }, { 104, 282, 65 }, { 146, 220, 99 }, { 155, 304, -81 }, { 271, 175, -71 }, { 60, 231, -91 }, { 63, 166, 75 }, { 181, 31, -45 }, { 305, 298, -37 }, { 266, 77, -94 }, { 376, 190, 60 }, { 305, 124, 41 }, { 315, 193, -40 }, { 195, 242, -66 }, { 14, 196, -66 }, { 149, 389, 31 }, { 320, 59, -16 }, { 17, 192, -22 }, { 133, 265, -5 }, { 217, 240, 49 }, { 118, 279, 48 }, { 142, 259, 52 }, { 246, 261, -20 }, { 268, 307, 36 }, { 375, 373, 36 }, { 297, 319, 87 }, { 397, 67, -58 }, { 31, 59, 70 }, { 194, 121, -28 }, { 311, 121, -93 }, { 347, 274, 60 }, { 199, 279, 31 }, { 137, 253, 1 }, { 302, 59, 92 }, { 259, 309, 76 }, { 174, 268, 59 }, { 115, 222, 28 }, { 185, 67, 84 }, { 367, 81, -78 }, { 256, 81, -16 }, { 297, 251, -100 }, { 233, 210, -72 }, { 234, 179, -41 }, { 144, 163, 60 }, { 235, 203, 60 }, { 96, 175, -3 }, { 181, 266, -78 }, { 210, 336, 92 }, { 90, 55, 97 }, { 326, 116, 95 }, { 116, 263, 45 }, { 368, 61, -69 }, { 382, 314, 94 }, { 86, 270, 93 }, { 272, 18, 100 }, { 136, 386, 92 }, { 112, 294, 99 }, { 368, 63, 9 }, { 37, 38, -63 }, { 91, 331, -45 }, { 302, 159, 9 }, { 359, 253, -81 }, { 84, 146, -39 }, { 398, 263, 27 }, { 44, 16, -56 }, { 50, 322, 83 }, { 320, 312, 94 }, { 122, 170, -75 }, { 223, 84, 67 }, { 333, 380, 82 }, { 211, 336, -73 }, { 109, 29, 63 }, }; double maxWeight = 10613; double minWeight = -10726; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 500 vertices and 1500 edges */ @Test public void testGetMatching18() { int[][] edges = new int[][] { { 183, 286, 85 }, { 469, 315, -39 }, { 347, 142, 24 }, { 374, 143, -82 }, { 375, 437, 28 }, { 463, 13, -61 }, { 131, 93, 42 }, { 150, 328, 80 }, { 498, 104, 98 }, { 402, 231, -98 }, { 22, 295, 82 }, { 121, 181, -12 }, { 314, 40, -22 }, { 82, 169, -90 }, { 219, 82, -98 }, { 286, 182, -52 }, { 372, 208, -62 }, { 109, 269, -86 }, { 342, 247, -26 }, { 209, 291, 79 }, { 295, 82, 56 }, { 465, 198, -58 }, { 82, 307, -37 }, { 371, 207, -74 }, { 222, 467, -30 }, { 381, 75, -40 }, { 414, 408, 16 }, { 262, 222, 20 }, { 97, 105, -44 }, { 431, 447, -63 }, { 210, 255, 91 }, { 239, 244, 12 }, { 495, 187, -82 }, { 97, 440, 47 }, { 105, 475, -74 }, { 125, 191, 49 }, { 371, 305, -50 }, { 255, 126, -80 }, { 267, 469, -35 }, { 58, 360, 83 }, { 385, 198, -14 }, { 193, 203, -6 }, { 294, 272, -34 }, { 246, 243, 35 }, { 195, 396, -64 }, { 492, 215, 81 }, { 340, 24, 72 }, { 237, 361, -59 }, { 123, 384, -92 }, { 303, 460, -65 }, { 161, 25, -34 }, { 336, 271, -33 }, { 294, 249, -21 }, { 424, 169, 4 }, { 387, 163, 7 }, { 367, 91, -89 }, { 32, 424, 100 }, { 7, 394, -28 }, { 14, 232, 74 }, { 161, 100, -65 }, { 387, 482, -16 }, { 440, 397, -80 }, { 46, 244, -72 }, { 48, 393, 64 }, { 462, 36, -27 }, { 477, 445, 53 }, { 7, 311, 70 }, { 477, 414, -7 }, { 324, 202, -25 }, { 477, 249, -17 }, { 170, 91, -87 }, { 6, 202, -43 }, { 125, 355, -19 }, { 341, 58, 100 }, { 490, 296, 81 }, { 267, 101, 42 }, { 38, 492, -1 }, { 286, 391, 8 }, { 414, 260, 43 }, { 391, 233, 98 }, { 106, 42, -43 }, { 90, 328, -77 }, { 286, 479, -89 }, { 355, 69, 80 }, { 211, 107, -82 }, { 482, 472, -34 }, { 323, 101, -65 }, { 401, 282, 33 }, { 77, 461, 60 }, { 151, 165, -65 }, { 11, 129, -26 }, { 380, 479, -90 }, { 231, 493, -34 }, { 296, 369, -11 }, { 35, 362, -84 }, { 492, 121, -31 }, { 135, 388, 35 }, { 480, 40, 71 }, { 2, 62, -87 }, { 157, 105, 12 }, { 405, 230, -95 }, { 290, 279, 69 }, { 363, 321, -60 }, { 373, 311, -95 }, { 97, 181, -18 }, { 6, 354, 30 }, { 431, 113, 77 }, { 356, 243, -7 }, { 320, 469, -37 }, { 487, 471, -34 }, { 322, 158, 62 }, { 286, 199, -41 }, { 13, 455, -16 }, { 34, 299, -40 }, { 465, 469, -89 }, { 366, 111, 88 }, { 290, 460, -90 }, { 124, 271, -94 }, { 76, 231, -57 }, { 67, 162, -74 }, { 31, 182, 31 }, { 114, 113, -20 }, { 252, 413, 47 }, { 41, 292, -65 }, { 488, 139, 46 }, { 362, 150, 3 }, { 135, 340, 50 }, { 394, 5, 57 }, { 73, 395, -7 }, { 245, 365, 72 }, { 205, 424, 84 }, { 491, 101, 97 }, { 119, 385, -81 }, { 384, 19, 87 }, { 431, 3, -10 }, { 383, 105, 72 }, { 162, 63, -95 }, { 54, 474, -88 }, { 492, 117, -21 }, { 372, 490, 48 }, { 286, 496, -21 }, { 218, 437, 46 }, { 50, 487, -70 }, { 300, 307, 87 }, { 441, 60, -78 }, { 316, 273, -90 }, { 428, 111, -67 }, { 100, 253, 15 }, { 206, 329, -26 }, { 147, 211, 24 }, { 112, 356, -65 }, { 316, 335, -67 }, { 497, 465, 33 }, { 314, 246, -72 }, { 353, 432, -88 }, { 244, 130, -79 }, { 64, 232, 91 }, { 338, 372, -84 }, { 128, 265, 68 }, { 170, 496, 90 }, { 346, 73, 44 }, { 288, 139, -59 }, { 328, 225, -20 }, { 480, 206, -17 }, { 468, 387, -23 }, { 93, 43, 28 }, { 252, 435, -30 }, { 315, 12, 13 }, { 218, 496, 90 }, { 291, 353, -59 }, { 17, 384, -66 }, { 375, 163, -46 }, { 37, 311, 71 }, { 107, 260, 75 }, { 271, 232, -20 }, { 117, 64, 71 }, { 144, 123, 68 }, { 312, 424, -2 }, { 479, 29, 45 }, { 386, 392, -76 }, { 77, 60, 98 }, { 233, 468, -11 }, { 387, 457, -28 }, { 68, 0, 7 }, { 289, 457, -33 }, { 350, 419, 0 }, { 241, 237, 49 }, { 217, 165, 39 }, { 344, 15, -13 }, { 445, 109, 8 }, { 353, 495, 99 }, { 241, 466, -44 }, { 236, 410, -54 }, { 202, 181, 27 }, { 306, 144, 37 }, { 328, 313, -92 }, { 353, 339, 37 }, { 297, 149, -62 }, { 87, 92, -16 }, { 483, 72, -71 }, { 431, 70, 48 }, { 343, 428, -59 }, { 20, 266, 90 }, { 455, 131, -76 }, { 348, 139, 96 }, { 461, 266, -62 }, { 457, 59, 29 }, { 342, 415, -2 }, { 171, 312, -56 }, { 277, 354, -91 }, { 142, 483, -87 }, { 249, 470, -28 }, { 158, 480, -3 }, { 100, 32, 23 }, { 219, 90, -10 }, { 458, 485, -94 }, { 324, 257, 70 }, { 168, 9, 26 }, { 390, 249, -89 }, { 313, 212, 11 }, { 170, 460, 22 }, { 394, 183, -58 }, { 74, 0, -74 }, { 121, 66, 69 }, { 139, 74, -17 }, { 356, 365, -3 }, { 404, 413, 17 }, { 385, 172, 73 }, { 378, 247, -92 }, { 341, 443, -76 }, { 436, 348, -20 }, { 205, 65, -97 }, { 309, 484, 86 }, { 302, 375, 16 }, { 103, 327, -81 }, { 397, 357, 86 }, { 14, 138, 11 }, { 477, 85, 16 }, { 457, 253, 20 }, { 351, 292, 54 }, { 411, 295, 57 }, { 149, 493, 97 }, { 170, 282, -47 }, { 188, 442, 79 }, { 224, 347, -96 }, { 392, 181, 46 }, { 79, 423, 81 }, { 89, 222, 32 }, { 78, 142, -63 }, { 278, 423, 25 }, { 71, 144, 14 }, { 96, 442, -84 }, { 431, 59, 18 }, { 57, 465, 33 }, { 57, 128, -1 }, { 290, 276, -51 }, { 403, 161, 17 }, { 323, 446, 17 }, { 115, 333, 60 }, { 481, 80, 48 }, { 88, 377, 51 }, { 112, 474, 69 }, { 282, 375, -60 }, { 186, 414, 15 }, { 75, 281, -45 }, { 194, 440, 81 }, { 235, 116, -82 }, { 206, 423, 0 }, { 459, 457, -66 }, { 43, 481, -55 }, { 497, 471, 91 }, { 494, 282, 61 }, { 51, 242, -43 }, { 136, 360, -4 }, { 14, 119, -97 }, { 497, 162, 6 }, { 381, 481, -11 }, { 117, 442, 98 }, { 354, 35, 57 }, { 139, 177, 23 }, { 460, 495, -18 }, { 438, 374, 73 }, { 437, 394, -94 }, { 393, 69, -100 }, { 313, 225, -40 }, { 294, 288, 98 }, { 431, 63, -69 }, { 498, 350, 76 }, { 239, 499, -72 }, { 300, 32, 63 }, { 441, 160, -51 }, { 375, 345, 17 }, { 393, 263, 66 }, { 84, 438, 40 }, { 327, 437, -96 }, { 36, 54, 86 }, { 73, 269, -66 }, { 350, 405, 12 }, { 14, 368, -35 }, { 272, 423, 70 }, { 193, 106, -81 }, { 267, 399, -89 }, { 110, 456, -63 }, { 211, 333, -71 }, { 32, 208, -65 }, { 125, 221, -1 }, { 103, 212, 35 }, { 396, 365, -62 }, { 467, 374, -8 }, { 213, 437, -1 }, { 406, 115, -10 }, { 106, 205, 36 }, { 238, 243, -48 }, { 132, 106, -73 }, { 243, 160, -62 }, { 57, 341, -78 }, { 479, 198, 48 }, { 138, 289, -82 }, { 386, 437, 57 }, { 181, 126, 7 }, { 244, 300, 6 }, { 136, 347, -36 }, { 272, 266, -84 }, { 15, 464, 58 }, { 55, 124, -95 }, { 192, 416, 65 }, { 89, 378, -12 }, { 485, 66, 50 }, { 228, 140, 28 }, { 443, 476, -19 }, { 158, 167, 100 }, { 373, 492, -60 }, { 303, 251, 28 }, { 142, 94, -69 }, { 175, 32, -71 }, { 327, 206, 18 }, { 372, 61, 68 }, { 424, 259, 77 }, { 284, 356, 12 }, { 293, 468, 3 }, { 433, 403, -6 }, { 499, 206, 98 }, { 478, 234, -14 }, { 180, 350, 73 }, { 29, 38, 76 }, { 265, 480, -67 }, { 165, 253, 85 }, { 245, 334, 54 }, { 4, 414, -55 }, { 493, 267, 67 }, { 281, 324, 93 }, { 397, 393, -12 }, { 62, 232, 38 }, { 75, 176, 34 }, { 465, 244, -34 }, { 218, 260, -35 }, { 409, 371, -54 }, { 235, 463, 90 }, { 376, 116, 99 }, { 346, 167, 58 }, { 303, 204, -79 }, { 498, 249, -57 }, { 436, 454, 17 }, { 297, 403, -25 }, { 275, 10, 39 }, { 456, 407, 51 }, { 280, 312, -99 }, { 477, 321, 62 }, { 421, 239, 81 }, { 181, 499, -21 }, { 408, 472, 30 }, { 348, 327, 54 }, { 448, 367, 77 }, { 162, 312, -22 }, { 397, 468, -7 }, { 100, 112, 78 }, { 320, 238, 57 }, { 227, 297, -14 }, { 167, 126, -1 }, { 8, 70, 65 }, { 460, 69, -86 }, { 476, 452, -61 }, { 236, 46, -65 }, { 26, 489, -46 }, { 159, 142, 27 }, { 239, 29, -15 }, { 406, 413, 95 }, { 408, 123, -34 }, { 169, 115, -70 }, { 86, 302, -97 }, { 206, 54, 33 }, { 157, 41, -12 }, { 105, 294, 1 }, { 251, 235, 41 }, { 201, 250, 89 }, { 300, 262, 37 }, { 390, 440, 11 }, { 334, 174, -21 }, { 164, 447, 62 }, { 402, 393, 48 }, { 156, 307, -54 }, { 411, 242, -28 }, { 114, 66, 75 }, { 466, 254, 38 }, { 147, 426, -38 }, { 203, 421, -12 }, { 407, 67, -77 }, { 335, 45, -52 }, { 433, 278, 84 }, { 70, 372, 51 }, { 397, 250, -47 }, { 82, 29, -92 }, { 422, 201, 91 }, { 372, 44, -48 }, { 25, 14, 33 }, { 379, 149, 17 }, { 389, 185, 23 }, { 498, 81, -70 }, { 133, 280, 47 }, { 271, 246, -31 }, { 214, 344, 23 }, { 273, 393, 4 }, { 49, 47, -94 }, { 433, 70, 48 }, { 141, 28, -85 }, { 247, 324, 34 }, { 60, 399, 74 }, { 15, 73, -21 }, { 131, 58, 69 }, { 71, 308, -3 }, { 151, 24, 96 }, { 284, 28, 73 }, { 286, 497, -90 }, { 365, 495, 6 }, { 166, 481, -27 }, { 321, 212, 5 }, { 264, 38, 67 }, { 191, 214, -82 }, { 16, 40, 70 }, { 466, 374, 39 }, { 88, 75, 97 }, { 216, 139, -73 }, { 98, 475, 55 }, { 169, 183, -62 }, { 154, 179, -29 }, { 172, 176, 95 }, { 280, 331, 52 }, { 52, 119, 87 }, { 102, 318, -18 }, { 242, 205, 55 }, { 326, 339, -2 }, { 66, 116, 26 }, { 443, 407, -58 }, { 383, 116, 53 }, { 403, 444, -76 }, { 400, 142, -27 }, { 391, 380, 11 }, { 472, 368, -10 }, { 326, 109, 68 }, { 334, 267, -19 }, { 187, 390, -34 }, { 49, 365, -88 }, { 63, 337, -9 }, { 409, 130, -78 }, { 82, 224, 73 }, { 391, 360, -79 }, { 400, 370, 54 }, { 400, 310, -57 }, { 423, 475, 91 }, { 372, 30, 99 }, { 143, 394, 67 }, { 146, 410, 5 }, { 190, 26, -61 }, { 343, 372, 44 }, { 468, 434, -30 }, { 281, 352, 9 }, { 377, 485, -62 }, { 180, 363, 30 }, { 18, 485, 52 }, { 258, 425, 9 }, { 29, 148, 97 }, { 438, 423, 28 }, { 49, 400, 43 }, { 437, 265, -4 }, { 365, 231, 93 }, { 283, 197, 29 }, { 111, 411, -57 }, { 100, 334, 88 }, { 148, 365, 69 }, { 258, 140, -77 }, { 122, 168, 50 }, { 497, 356, -37 }, { 201, 380, -29 }, { 300, 89, -47 }, { 342, 32, 72 }, { 219, 220, 74 }, { 468, 253, -46 }, { 381, 285, 8 }, { 341, 35, 99 }, { 272, 307, 17 }, { 24, 358, -38 }, { 338, 289, 23 }, { 56, 253, -32 }, { 374, 189, -49 }, { 453, 373, -87 }, { 475, 234, 67 }, { 325, 338, -98 }, { 116, 45, 69 }, { 122, 489, -13 }, { 84, 273, -17 }, { 370, 313, -59 }, { 360, 314, 33 }, { 251, 363, -87 }, { 243, 377, -30 }, { 483, 191, 42 }, { 181, 146, 54 }, { 378, 164, 33 }, { 58, 250, 33 }, { 134, 30, 7 }, { 492, 337, -8 }, { 87, 455, 95 }, { 319, 454, -10 }, { 224, 16, -76 }, { 93, 272, 22 }, { 397, 318, -78 }, { 202, 73, -77 }, { 79, 172, -35 }, { 465, 432, -53 }, { 217, 498, 30 }, { 183, 332, 42 }, { 323, 94, 18 }, { 388, 79, -59 }, { 192, 458, -95 }, { 331, 296, 89 }, { 176, 126, 56 }, { 266, 24, 27 }, { 283, 478, -99 }, { 482, 302, 36 }, { 137, 319, 87 }, { 492, 81, 30 }, { 306, 138, 90 }, { 225, 422, 29 }, { 352, 34, 80 }, { 302, 114, -15 }, { 498, 71, 97 }, { 391, 54, 9 }, { 155, 18, 83 }, { 404, 57, 37 }, { 492, 403, 85 }, { 378, 306, 15 }, { 118, 235, -83 }, { 152, 155, -82 }, { 36, 203, 19 }, { 489, 48, -43 }, { 292, 442, 77 }, { 129, 296, -63 }, { 384, 114, -98 }, { 21, 256, 53 }, { 214, 134, 72 }, { 320, 288, -46 }, { 483, 225, 61 }, { 190, 183, 84 }, { 372, 314, -34 }, { 351, 386, -52 }, { 362, 163, 51 }, { 213, 469, 48 }, { 305, 136, 9 }, { 91, 450, -62 }, { 164, 353, -43 }, { 194, 469, 22 }, { 22, 125, 45 }, { 36, 432, -32 }, { 459, 337, 8 }, { 58, 210, -70 }, { 96, 20, -57 }, { 449, 2, -5 }, { 352, 497, -19 }, { 236, 363, 45 }, { 281, 111, -3 }, { 404, 220, -65 }, { 105, 26, 62 }, { 490, 209, -93 }, { 489, 110, -51 }, { 237, 248, -6 }, { 439, 44, -57 }, { 251, 41, 57 }, { 335, 385, -39 }, { 461, 322, -36 }, { 152, 327, 95 }, { 136, 230, 54 }, { 71, 248, -11 }, { 70, 191, -8 }, { 308, 117, -100 }, { 249, 205, -15 }, { 92, 116, -6 }, { 116, 337, -8 }, { 354, 477, -46 }, { 191, 150, -100 }, { 97, 291, 31 }, { 463, 279, 84 }, { 462, 356, 97 }, { 214, 12, -23 }, { 146, 353, 43 }, { 100, 454, 21 }, { 35, 32, -54 }, { 387, 190, -99 }, { 351, 223, 2 }, { 280, 341, 35 }, { 44, 479, -10 }, { 49, 58, 56 }, { 157, 313, -55 }, { 57, 167, -31 }, { 229, 463, 58 }, { 366, 188, 69 }, { 394, 353, -39 }, { 90, 157, 34 }, { 89, 30, 84 }, { 437, 134, -55 }, { 12, 401, 87 }, { 97, 400, -49 }, { 43, 365, 81 }, { 43, 471, -87 }, { 323, 124, -2 }, { 389, 126, 39 }, { 79, 439, 23 }, { 288, 42, 35 }, { 237, 498, -89 }, { 248, 458, -42 }, { 141, 325, -5 }, { 393, 490, 35 }, { 88, 483, -17 }, { 152, 47, -52 }, { 467, 1, 45 }, { 102, 126, 92 }, { 360, 250, -47 }, { 312, 120, 28 }, { 65, 361, 52 }, { 436, 225, 89 }, { 390, 75, 94 }, { 422, 243, 4 }, { 450, 13, -76 }, { 464, 48, 75 }, { 157, 348, -82 }, { 258, 61, -9 }, { 22, 390, -74 }, { 26, 14, 5 }, { 403, 37, -3 }, { 197, 97, -12 }, { 495, 214, -48 }, { 331, 163, -44 }, { 126, 426, 5 }, { 316, 20, -70 }, { 426, 304, -87 }, { 61, 362, -47 }, { 296, 32, -87 }, { 105, 337, -82 }, { 352, 63, 40 }, { 145, 382, -15 }, { 201, 360, -8 }, { 42, 173, 56 }, { 296, 294, -85 }, { 196, 220, 22 }, { 41, 334, 36 }, { 10, 147, -79 }, { 499, 88, 37 }, { 400, 448, -35 }, { 25, 17, 50 }, { 233, 39, 6 }, { 10, 99, -34 }, { 303, 314, 94 }, { 262, 264, -51 }, { 183, 361, 44 }, { 408, 251, 45 }, { 497, 196, 64 }, { 314, 333, 3 }, { 206, 285, -19 }, { 127, 311, 99 }, { 495, 24, 31 }, { 484, 464, 26 }, { 323, 334, 6 }, { 294, 481, -35 }, { 220, 226, -36 }, { 286, 446, -41 }, { 312, 237, -87 }, { 246, 472, 6 }, { 225, 68, -94 }, { 195, 393, 27 }, { 5, 345, 28 }, { 368, 179, -86 }, { 339, 267, 40 }, { 345, 300, 27 }, { 261, 447, 39 }, { 491, 284, 50 }, { 227, 127, -6 }, { 363, 373, 95 }, { 111, 306, 20 }, { 428, 324, 56 }, { 83, 160, -82 }, { 134, 117, -21 }, { 72, 166, -15 }, { 64, 481, -14 }, { 148, 61, -65 }, { 221, 157, -2 }, { 442, 55, -68 }, { 78, 413, -2 }, { 436, 182, -38 }, { 91, 73, 54 }, { 468, 177, -15 }, { 232, 295, -91 }, { 299, 4, -96 }, { 26, 94, 17 }, { 375, 139, 19 }, { 281, 55, 32 }, { 420, 262, -48 }, { 472, 358, -35 }, { 289, 39, 64 }, { 418, 263, -35 }, { 372, 308, 76 }, { 143, 381, 70 }, { 355, 378, 82 }, { 415, 490, -25 }, { 423, 73, -79 }, { 99, 156, 82 }, { 118, 151, 17 }, { 490, 462, -60 }, { 64, 443, -31 }, { 268, 47, 55 }, { 139, 338, 46 }, { 297, 112, 80 }, { 108, 16, 97 }, { 187, 277, 6 }, { 265, 227, 65 }, { 170, 385, 100 }, { 300, 405, -47 }, { 249, 45, -5 }, { 163, 167, 24 }, { 428, 485, -85 }, { 345, 117, -42 }, { 96, 368, 31 }, { 289, 203, -50 }, { 5, 107, 50 }, { 13, 348, 17 }, { 103, 4, -97 }, { 450, 335, -23 }, { 56, 172, -4 }, { 358, 59, -55 }, { 385, 441, -53 }, { 89, 334, 18 }, { 206, 189, 42 }, { 476, 493, -76 }, { 43, 480, 76 }, { 275, 201, -4 }, { 424, 176, 46 }, { 386, 301, -14 }, { 64, 418, 19 }, { 85, 403, -62 }, { 349, 52, -66 }, { 279, 328, -25 }, { 490, 88, -54 }, { 113, 256, 29 }, { 398, 379, -50 }, { 327, 266, 71 }, { 117, 467, -33 }, { 344, 357, -69 }, { 284, 481, 0 }, { 121, 486, 97 }, { 209, 140, 6 }, { 426, 188, -8 }, { 346, 109, 27 }, { 137, 425, -26 }, { 126, 403, -9 }, { 99, 210, -62 }, { 230, 125, -99 }, { 134, 142, 34 }, { 141, 81, -82 }, { 223, 123, 77 }, { 67, 417, -74 }, { 42, 94, 12 }, { 418, 359, -88 }, { 436, 219, -94 }, { 491, 307, 48 }, { 95, 151, -66 }, { 415, 95, 35 }, { 73, 228, 100 }, { 204, 69, 77 }, { 98, 102, -75 }, { 426, 232, -38 }, { 215, 245, -65 }, { 348, 356, -63 }, { 25, 23, 33 }, { 181, 345, 65 }, { 133, 419, 40 }, { 104, 401, -52 }, { 208, 273, 8 }, { 238, 9, 48 }, { 261, 4, 33 }, { 247, 249, -76 }, { 397, 25, 40 }, { 45, 38, -38 }, { 464, 422, -40 }, { 337, 76, 11 }, { 142, 219, 97 }, { 56, 244, 13 }, { 376, 127, -17 }, { 76, 311, -74 }, { 4, 85, 89 }, { 484, 133, 20 }, { 254, 288, 10 }, { 415, 25, 13 }, { 132, 117, -16 }, { 44, 161, 95 }, { 472, 65, -46 }, { 35, 91, -67 }, { 168, 462, 48 }, { 431, 13, 90 }, { 138, 493, -90 }, { 185, 118, -22 }, { 317, 12, 84 }, { 55, 358, 76 }, { 247, 64, 91 }, { 80, 296, 23 }, { 68, 418, -32 }, { 339, 317, -59 }, { 219, 339, -2 }, { 474, 375, 73 }, { 68, 250, -61 }, { 216, 410, -61 }, { 281, 304, -26 }, { 477, 391, -1 }, { 492, 357, -79 }, { 390, 183, -66 }, { 124, 130, -67 }, { 252, 491, -42 }, { 275, 457, 97 }, { 182, 345, 64 }, { 283, 33, -97 }, { 362, 313, -28 }, { 353, 226, -14 }, { 286, 352, 65 }, { 487, 303, -42 }, { 83, 348, 3 }, { 137, 338, 26 }, { 386, 242, -64 }, { 155, 432, 32 }, { 220, 136, 24 }, { 288, 187, -17 }, { 148, 102, 82 }, { 9, 143, 5 }, { 410, 154, 72 }, { 146, 415, -20 }, { 138, 402, -45 }, { 465, 364, -88 }, { 252, 175, -21 }, { 366, 255, 61 }, { 82, 120, -62 }, { 239, 377, 67 }, { 380, 308, 24 }, { 373, 95, -22 }, { 98, 491, 43 }, { 86, 474, -66 }, { 316, 389, -85 }, { 420, 471, -74 }, { 186, 172, 82 }, { 370, 114, -84 }, { 95, 108, 1 }, { 365, 217, 65 }, { 112, 273, -51 }, { 333, 123, -14 }, { 220, 125, -13 }, { 24, 299, -9 }, { 161, 47, -5 }, { 483, 451, 60 }, { 135, 122, 29 }, { 216, 313, 30 }, { 417, 382, -72 }, { 92, 228, 95 }, { 78, 147, 88 }, { 125, 273, 4 }, { 433, 150, -16 }, { 342, 211, 32 }, { 343, 89, -61 }, { 74, 80, -70 }, { 240, 358, 41 }, { 378, 34, 45 }, { 493, 181, -54 }, { 419, 194, 64 }, { 177, 218, -87 }, { 446, 43, 64 }, { 382, 128, -42 }, { 477, 290, -81 }, { 317, 490, 34 }, { 478, 381, 4 }, { 452, 178, 87 }, { 47, 469, -86 }, { 299, 180, 75 }, { 50, 100, -92 }, { 254, 337, 7 }, { 57, 459, 37 }, { 142, 445, 91 }, { 152, 248, -89 }, { 470, 93, -37 }, { 14, 86, -6 }, { 264, 105, -36 }, { 185, 169, 0 }, { 288, 200, 50 }, { 145, 444, 43 }, { 259, 245, -54 }, { 43, 281, 96 }, { 219, 218, 93 }, { 5, 480, -67 }, { 110, 26, -38 }, { 178, 343, -2 }, { 29, 292, 19 }, { 472, 158, -95 }, { 351, 137, 0 }, { 269, 345, 78 }, { 45, 118, -34 }, { 413, 205, -52 }, { 240, 495, -51 }, { 475, 467, -64 }, { 353, 147, 3 }, { 80, 198, 15 }, { 420, 229, -2 }, { 141, 241, 61 }, { 223, 274, 22 }, { 156, 305, 72 }, { 441, 72, -6 }, { 129, 196, 24 }, { 198, 165, 87 }, { 416, 25, 22 }, { 149, 196, -47 }, { 290, 333, 81 }, { 13, 480, -20 }, { 397, 286, -4 }, { 171, 334, -49 }, { 385, 275, 0 }, { 408, 49, 38 }, { 317, 57, 49 }, { 422, 111, -15 }, { 348, 0, -94 }, { 409, 55, 3 }, { 368, 488, -61 }, { 315, 285, -87 }, { 403, 341, 40 }, { 394, 174, 44 }, { 96, 185, -33 }, { 38, 464, -29 }, { 341, 417, 79 }, { 234, 175, -23 }, { 386, 182, 81 }, { 39, 184, -81 }, { 42, 124, 43 }, { 351, 258, -43 }, { 264, 68, 56 }, { 339, 447, -98 }, { 337, 190, -1 }, { 319, 310, -100 }, { 380, 350, 31 }, { 474, 22, 0 }, { 453, 124, -79 }, { 314, 1, -54 }, { 389, 109, 44 }, { 428, 210, 70 }, { 338, 228, -73 }, { 400, 398, -42 }, { 439, 484, 36 }, { 75, 194, -75 }, { 295, 207, -33 }, { 445, 57, -35 }, { 6, 323, -48 }, { 225, 413, 54 }, { 268, 408, -81 }, { 72, 58, 17 }, { 105, 27, 60 }, { 59, 84, -97 }, { 252, 335, 83 }, { 50, 277, 92 }, { 253, 481, 77 }, { 453, 290, 77 }, { 303, 155, -93 }, { 257, 132, 54 }, { 191, 132, 98 }, { 52, 411, -70 }, { 250, 4, -27 }, { 499, 494, 27 }, { 122, 1, -21 }, { 288, 177, -40 }, { 79, 336, 36 }, { 465, 324, -66 }, { 112, 396, 25 }, { 488, 458, -17 }, { 106, 99, -2 }, { 5, 54, -20 }, { 355, 287, -54 }, { 113, 360, -62 }, { 299, 112, 4 }, { 447, 110, -55 }, { 51, 190, 9 }, { 476, 284, 21 }, { 462, 157, 93 }, { 106, 298, -58 }, { 212, 394, -8 }, { 374, 449, 94 }, { 409, 17, -53 }, { 255, 137, -44 }, { 115, 309, 16 }, { 301, 373, -31 }, { 299, 455, -2 }, { 322, 239, -80 }, { 332, 450, 46 }, { 38, 85, -51 }, { 112, 478, 86 }, { 294, 215, -47 }, { 463, 107, -95 }, { 256, 303, 53 }, { 342, 313, 61 }, { 368, 226, -64 }, { 433, 93, -48 }, { 257, 337, 99 }, { 192, 486, -83 }, { 45, 64, -78 }, { 450, 369, 2 }, { 82, 153, 57 }, { 426, 333, -15 }, { 210, 52, -16 }, { 459, 147, -36 }, { 241, 77, 31 }, { 262, 465, -41 }, { 186, 250, -35 }, { 23, 13, -77 }, { 103, 406, 67 }, { 480, 236, -56 }, { 160, 387, -67 }, { 436, 486, 42 }, { 426, 416, 60 }, { 363, 440, 21 }, { 44, 485, -92 }, { 169, 497, -48 }, { 170, 86, -10 }, { 345, 366, 19 }, { 53, 454, -74 }, { 496, 210, -18 }, { 186, 340, -68 }, { 138, 484, -49 }, { 418, 136, 42 }, { 239, 190, -78 }, { 22, 196, 36 }, { 33, 461, -12 }, { 71, 395, -29 }, { 89, 313, 96 }, { 160, 386, 46 }, { 228, 311, 81 }, { 135, 325, 17 }, { 236, 269, -63 }, { 465, 85, 27 }, { 279, 458, -14 }, { 70, 483, 29 }, { 383, 323, 85 }, { 468, 75, -82 }, { 458, 221, -38 }, { 230, 67, -5 }, { 67, 471, -13 }, { 4, 430, 60 }, { 61, 180, -55 }, { 440, 470, -52 }, { 264, 215, -66 }, { 154, 499, -71 }, { 343, 283, -55 }, { 135, 113, -12 }, { 321, 25, -61 }, { 52, 249, 78 }, { 462, 190, 69 }, { 312, 58, -61 }, { 100, 409, -54 }, { 239, 326, 80 }, { 200, 321, -6 }, { 251, 380, -97 }, { 334, 66, -40 }, { 349, 145, 8 }, { 461, 482, -59 }, { 21, 401, -5 }, { 66, 192, 17 }, { 145, 176, 56 }, { 99, 175, 25 }, { 30, 77, -22 }, { 451, 211, 10 }, { 52, 383, -97 }, { 496, 4, 76 }, { 387, 34, -52 }, { 189, 167, 88 }, { 402, 224, 99 }, { 77, 414, -30 }, { 144, 488, 90 }, { 87, 255, 7 }, { 388, 87, -60 }, { 315, 351, 63 }, { 312, 241, -72 }, { 149, 140, -24 }, { 465, 249, 9 }, { 244, 57, 29 }, { 70, 328, 84 }, { 25, 105, 59 }, { 499, 203, -28 }, { 421, 99, 64 }, { 232, 332, 74 }, { 393, 75, 2 }, { 256, 244, -21 }, { 384, 25, 2 }, { 96, 309, -79 }, { 429, 371, -39 }, { 237, 349, 51 }, { 9, 211, -11 }, { 390, 240, -2 }, { 14, 132, -13 }, { 490, 102, 83 }, { 81, 120, -3 }, { 39, 101, -39 }, { 213, 94, 55 }, { 321, 494, -8 }, { 383, 80, 86 }, { 453, 84, -24 }, { 298, 239, -84 }, { 302, 453, 34 }, { 292, 337, 56 }, { 217, 469, 63 }, { 94, 447, 25 }, { 84, 420, -31 }, { 292, 115, -26 }, { 188, 135, -23 }, { 256, 203, -64 }, { 188, 489, -38 }, { 377, 359, 38 }, { 209, 323, 63 }, { 346, 408, -63 }, { 187, 218, 25 }, { 360, 62, 49 }, { 391, 495, -43 }, { 228, 151, -41 }, { 158, 297, 44 }, { 405, 393, 9 }, { 61, 170, -24 }, { 65, 70, -10 }, { 251, 246, -1 }, { 253, 181, 30 }, { 432, 129, -74 }, { 23, 79, 47 }, { 135, 211, -49 }, { 216, 74, 30 }, { 310, 249, 62 }, { 150, 1, -67 }, { 187, 377, 82 }, { 131, 146, -92 }, { 350, 370, -19 }, { 162, 144, -97 }, { 170, 38, -36 }, { 473, 477, 65 }, { 33, 240, -90 }, { 128, 195, -30 }, { 375, 93, 11 }, { 237, 220, -53 }, { 337, 490, -13 }, { 212, 147, -88 }, { 498, 338, 52 }, { 344, 145, 15 }, { 374, 361, 31 }, { 442, 197, 92 }, { 362, 161, 91 }, { 381, 355, 89 }, { 291, 404, -2 }, { 182, 359, -74 }, { 326, 254, -56 }, { 430, 64, -81 }, { 60, 8, -2 }, { 166, 283, -19 }, { 203, 269, -46 }, { 34, 433, -52 }, { 108, 111, -58 }, { 281, 33, -22 }, { 86, 220, 76 }, { 183, 303, 56 }, { 226, 495, -68 }, { 368, 91, -78 }, { 160, 266, -94 }, { 276, 66, -26 }, { 198, 489, -31 }, { 431, 169, 80 }, { 255, 332, 78 }, { 227, 448, -33 }, { 363, 389, 9 }, { 91, 298, -2 }, { 117, 160, -51 }, { 154, 105, -2 }, { 241, 347, -91 }, { 59, 327, 89 }, { 114, 285, -40 }, { 143, 438, 81 }, { 446, 217, 65 }, { 177, 249, -91 }, { 436, 466, 97 }, { 398, 239, -17 }, { 42, 446, 65 }, { 385, 153, -52 }, { 261, 473, 1 }, { 227, 39, -64 }, { 150, 67, -50 }, { 487, 171, -76 }, { 177, 432, -92 }, { 283, 174, -52 }, { 487, 250, 53 }, { 207, 394, -85 }, { 207, 235, -24 }, { 79, 252, -20 }, { 405, 293, -73 }, { 218, 5, 3 }, { 187, 83, -60 }, { 345, 258, -30 }, { 356, 290, -12 }, { 388, 426, 25 }, { 287, 420, 7 }, { 280, 406, 71 }, { 151, 351, 36 }, { 198, 108, -71 }, { 317, 60, -99 }, { 131, 432, 10 }, { 354, 33, 26 }, { 447, 66, 16 }, { 361, 356, -85 }, { 295, 374, 74 }, { 464, 342, 61 }, { 401, 164, -73 }, { 49, 285, -44 }, { 363, 323, -71 }, { 428, 161, -81 }, { 80, 71, -47 }, { 418, 461, 97 }, { 181, 433, -64 }, { 161, 11, -74 }, { 204, 47, 83 }, { 142, 333, 50 }, { 316, 257, -14 }, { 447, 335, 32 }, { 141, 447, 60 }, { 247, 476, -91 }, { 347, 248, -48 }, { 42, 485, 72 }, { 204, 266, -20 }, { 65, 210, -31 }, { 214, 58, -84 }, { 337, 432, 36 }, { 469, 177, 74 }, { 145, 232, -27 }, { 174, 160, 64 }, { 226, 446, 96 }, { 281, 129, 55 }, { 134, 349, -94 }, { 444, 215, -100 }, { 322, 456, 96 }, { 408, 341, 93 }, { 177, 465, 0 }, { 471, 473, -79 }, { 316, 233, 21 }, { 6, 122, -12 }, { 419, 116, 71 }, { 344, 366, -85 }, { 419, 402, -11 }, { 359, 68, 100 }, { 65, 378, 9 }, { 23, 478, 8 }, { 383, 281, -8 }, { 73, 302, 65 }, { 181, 486, 1 }, { 298, 156, -70 }, { 326, 429, 12 }, { 271, 323, 83 }, { 372, 45, 1 }, { 119, 235, -54 }, { 485, 294, 83 }, { 214, 245, -84 }, { 204, 71, 44 }, { 180, 380, -10 }, { 303, 31, 98 }, { 191, 36, 50 }, { 254, 354, 43 }, { 368, 401, 38 }, { 227, 342, 6 }, { 56, 446, 31 }, { 79, 432, -32 }, { 414, 118, -48 }, { 100, 408, -6 }, { 52, 474, 20 }, { 156, 176, -76 }, { 459, 140, -67 }, { 42, 304, 24 }, { 338, 188, 6 }, { 420, 149, 73 }, { 62, 244, -38 }, { 105, 208, -13 }, { 199, 413, 59 }, { 391, 197, 86 }, { 137, 291, -68 }, { 147, 308, -22 }, { 271, 481, -33 }, { 263, 222, 46 }, { 275, 226, -98 }, { 272, 284, -23 }, { 170, 74, -6 }, { 296, 164, -21 }, { 107, 77, 14 }, { 467, 60, -69 }, { 485, 469, -74 }, { 206, 276, 93 }, { 118, 62, 87 }, { 268, 16, -29 }, { 488, 498, 56 }, { 28, 217, 19 }, { 170, 401, 6 }, { 18, 92, -30 }, { 356, 294, -90 }, { 309, 400, -19 }, { 315, 455, -85 }, { 297, 334, 6 }, { 145, 443, 14 }, { 469, 363, -59 }, { 395, 126, -24 }, { 299, 321, 12 }, { 59, 76, 51 }, { 307, 383, -22 }, { 352, 217, 52 }, { 189, 199, 36 }, { 459, 328, -43 }, { 395, 308, 39 }, { 292, 103, 84 }, { 241, 300, 47 }, { 53, 10, 10 }, { 202, 54, 19 }, { 181, 171, 17 }, { 351, 268, 59 }, { 61, 101, 66 }, { 187, 436, -87 }, { 139, 450, -30 }, { 67, 8, 98 }, { 368, 39, -5 }, { 446, 251, 56 }, { 56, 19, 23 }, { 91, 465, 91 }, { 146, 175, -50 }, { 393, 406, 70 }, { 273, 352, -61 }, { 8, 166, 82 }, { 164, 25, -30 }, { 334, 389, -57 }, { 5, 88, 86 }, { 171, 489, -98 }, { 334, 339, -52 }, { 441, 92, -40 }, { 104, 335, 63 }, { 126, 467, -34 }, { 248, 352, -49 }, { 452, 203, 98 }, { 166, 394, 68 }, { 223, 415, 62 }, { 383, 83, -49 }, { 265, 489, -54 }, { 370, 30, -70 }, { 34, 386, 53 }, { 95, 435, -73 }, { 236, 348, -69 }, { 332, 79, 9 }, { 481, 330, 16 }, { 480, 419, 92 }, { 59, 81, -15 }, { 232, 183, 79 }, { 17, 476, -18 }, { 135, 17, 40 }, { 24, 107, 88 }, { 138, 355, 1 }, { 89, 76, 64 }, { 100, 332, -49 }, { 228, 351, 65 }, { 252, 63, 51 }, { 23, 350, -97 }, { 312, 108, -56 }, { 313, 491, 62 }, { 456, 332, 17 }, { 296, 317, 76 }, { 115, 103, -32 }, { 279, 6, 15 }, { 452, 287, 92 }, { 411, 7, 55 }, { 65, 232, 0 }, { 307, 143, -9 }, { 340, 34, -77 }, { 448, 209, 29 }, { 295, 401, -68 }, { 305, 328, 42 }, { 337, 304, 31 }, { 8, 76, -100 }, { 153, 415, 36 }, { 180, 351, 41 }, { 122, 191, -45 }, { 193, 372, 62 }, { 419, 160, 51 }, { 10, 472, 85 }, { 177, 255, 42 }, { 165, 190, 59 }, { 406, 237, 29 }, { 353, 304, -2 }, { 124, 218, -11 }, { 120, 196, -8 }, { 367, 312, -2 }, { 494, 102, -18 }, { 31, 189, -63 }, { 9, 80, -93 }, { 17, 429, 59 }, { 290, 342, -90 }, { 22, 351, 53 }, { 200, 111, -58 }, { 404, 93, -24 }, { 38, 401, -75 }, { 266, 119, -12 }, { 463, 87, 33 }, { 91, 10, -94 }, { 49, 357, -2 }, { 327, 281, 90 }, { 414, 387, 53 }, { 476, 51, -37 }, { 399, 135, -75 }, { 380, 273, 39 }, { 301, 0, 65 }, { 135, 495, 65 }, { 270, 159, 99 }, { 369, 362, -81 }, { 171, 345, -71 }, { 183, 376, -91 }, { 323, 60, 75 }, { 258, 227, -45 }, { 208, 222, -37 }, { 150, 494, -94 }, { 228, 418, 60 }, { 427, 213, 41 }, { 198, 22, -40 }, { 381, 111, -66 }, { 301, 394, -66 }, { 186, 270, 31 }, { 489, 174, -16 }, { 109, 422, -22 }, { 121, 35, -5 }, { 481, 158, 49 }, { 221, 46, 48 }, { 198, 19, 52 }, { 429, 457, -20 }, { 179, 350, 36 }, { 401, 241, 36 }, { 484, 136, 87 }, { 361, 112, -58 }, { 194, 359, 70 }, { 327, 303, -28 }, { 480, 488, -93 }, { 120, 0, 60 }, { 464, 206, 31 }, { 165, 134, 1 }, { 334, 65, 92 }, { 55, 20, 76 }, { 236, 28, 59 }, { 216, 114, 28 }, { 459, 467, 84 }, { 363, 449, -78 }, { 268, 309, -16 }, { 261, 294, -100 }, { 22, 157, -72 }, { 97, 142, -41 }, { 306, 487, 60 }, { 218, 364, 60 }, { 196, 377, -3 }, { 379, 276, -78 }, { 410, 498, 92 }, { 444, 70, 97 }, { 306, 475, 95 }, { 119, 473, 45 }, { 420, 416, -69 }, { 85, 414, 94 }, { 376, 204, 93 }, { 273, 435, 100 }, { 481, 116, 92 }, { 300, 152, 99 }, { 326, 209, 9 }, { 151, 292, -63 }, { 429, 181, -45 }, { 43, 497, 9 }, { 321, 247, -81 }, { 83, 430, -39 }, { 471, 450, 27 }, { 234, 372, -56 }, { 389, 207, 83 }, { 38, 67, 94 }, { 307, 313, -75 }, { 11, 165, 67 }, { 355, 450, 82 }, { 412, 359, -73 }, { 221, 99, 63 }, }; double maxWeight = 14655; double minWeight = -14892; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Random graph with 1500 vertices and 1500 edges */ @Test public void testGetMatching19() { int[][] edges = new int[][] { { 678, 385, 85 }, { 786, 1109, -39 }, { 1164, 937, 24 }, { 40, 365, -82 }, { 892, 879, 28 }, { 1284, 231, -61 }, { 812, 246, 42 }, { 1337, 415, 80 }, { 1391, 369, 98 }, { 18, 1423, -98 }, { 241, 648, 82 }, { 635, 1165, -12 }, { 384, 17, -22 }, { 1357, 849, -90 }, { 1111, 309, -98 }, { 1233, 695, -52 }, { 189, 31, -62 }, { 232, 1138, -86 }, { 1200, 1141, -26 }, { 702, 714, 79 }, { 753, 1313, 56 }, { 1024, 1414, -58 }, { 706, 151, -37 }, { 1331, 1404, -74 }, { 1091, 1177, -30 }, { 296, 441, -40 }, { 886, 818, 16 }, { 1476, 1316, 20 }, { 762, 880, -44 }, { 836, 1436, -63 }, { 541, 763, 91 }, { 233, 62, 12 }, { 732, 874, -82 }, { 1192, 1475, 47 }, { 314, 224, -74 }, { 1017, 737, 49 }, { 241, 1194, -50 }, { 1296, 1267, -80 }, { 1141, 349, -35 }, { 660, 1207, 83 }, { 194, 1202, -14 }, { 1459, 1251, -6 }, { 1269, 1060, -34 }, { 146, 1019, 35 }, { 1189, 893, -64 }, { 494, 993, 81 }, { 23, 1415, 72 }, { 1435, 1113, -59 }, { 1254, 306, -92 }, { 1060, 240, -65 }, { 936, 247, -34 }, { 1356, 1164, -33 }, { 446, 1054, -21 }, { 515, 617, 4 }, { 1093, 792, 7 }, { 1351, 314, -89 }, { 1478, 422, 100 }, { 519, 929, -28 }, { 151, 598, 74 }, { 672, 532, -65 }, { 1167, 293, -16 }, { 676, 687, -80 }, { 519, 1460, -72 }, { 140, 186, 64 }, { 861, 430, -27 }, { 395, 37, 53 }, { 228, 257, 70 }, { 867, 285, -7 }, { 81, 1404, -25 }, { 1293, 742, -17 }, { 738, 493, -87 }, { 512, 710, -43 }, { 1301, 300, -19 }, { 881, 571, 100 }, { 599, 375, 81 }, { 91, 216, 42 }, { 814, 1259, -1 }, { 957, 172, 8 }, { 723, 655, 43 }, { 460, 752, 98 }, { 1471, 223, -43 }, { 482, 1150, -77 }, { 823, 899, -89 }, { 1046, 994, 80 }, { 442, 325, -82 }, { 635, 1401, -34 }, { 756, 184, -65 }, { 27, 319, 33 }, { 449, 94, 60 }, { 1144, 685, -65 }, { 1115, 396, -26 }, { 321, 995, -90 }, { 174, 1112, -34 }, { 1073, 1279, -11 }, { 532, 1279, -84 }, { 85, 615, -31 }, { 661, 1068, 35 }, { 235, 43, 71 }, { 1125, 37, -87 }, { 898, 514, 12 }, { 1481, 840, -95 }, { 1239, 34, 69 }, { 1062, 924, -60 }, { 1361, 58, -95 }, { 1019, 545, -18 }, { 1105, 495, 30 }, { 237, 652, 77 }, { 1024, 1358, -7 }, { 1073, 1311, -37 }, { 689, 860, -34 }, { 1382, 637, 62 }, { 217, 1375, -41 }, { 1359, 990, -16 }, { 797, 1468, -40 }, { 42, 1290, -89 }, { 43, 1133, 88 }, { 1234, 1346, -90 }, { 1236, 1101, -94 }, { 345, 530, -57 }, { 253, 1281, -74 }, { 770, 1228, 31 }, { 1182, 941, -20 }, { 707, 196, 47 }, { 172, 695, -65 }, { 526, 1457, 46 }, { 895, 143, 3 }, { 1176, 57, 50 }, { 1171, 1036, 57 }, { 1345, 420, -7 }, { 936, 589, 72 }, { 682, 1107, 84 }, { 339, 507, 97 }, { 892, 914, -81 }, { 1447, 1438, 87 }, { 1261, 1073, -10 }, { 1255, 1069, 72 }, { 273, 895, -95 }, { 798, 179, -88 }, { 1035, 836, -21 }, { 851, 1160, 48 }, { 1364, 790, -21 }, { 1214, 912, 46 }, { 1092, 854, -70 }, { 1004, 1398, 87 }, { 286, 1163, -78 }, { 385, 287, -90 }, { 656, 191, -67 }, { 1264, 1124, 15 }, { 196, 1489, -26 }, { 1358, 401, 24 }, { 1202, 918, -65 }, { 1000, 175, -67 }, { 23, 708, 33 }, { 1262, 1082, -72 }, { 701, 1135, -88 }, { 1355, 1307, -79 }, { 808, 493, 91 }, { 859, 95, -84 }, { 272, 1441, 68 }, { 119, 557, 90 }, { 1471, 735, 44 }, { 71, 888, -59 }, { 617, 735, -20 }, { 33, 1304, -17 }, { 1346, 1190, -23 }, { 380, 562, 28 }, { 1064, 1185, -30 }, { 895, 146, 13 }, { 101, 1465, 90 }, { 1048, 1200, -59 }, { 1191, 217, -66 }, { 128, 722, -46 }, { 1198, 355, 71 }, { 708, 605, 75 }, { 97, 913, -20 }, { 464, 281, 71 }, { 273, 219, 68 }, { 1314, 973, -2 }, { 1149, 990, 45 }, { 711, 1431, -76 }, { 590, 1242, 98 }, { 621, 673, -11 }, { 746, 785, -28 }, { 428, 1129, 7 }, { 1208, 606, -33 }, { 1091, 607, 0 }, { 1259, 196, 49 }, { 852, 355, 39 }, { 994, 839, -13 }, { 1332, 1300, 8 }, { 951, 1231, 99 }, { 1031, 366, -44 }, { 753, 1144, -54 }, { 863, 35, 27 }, { 340, 233, 37 }, { 1404, 489, -92 }, { 564, 81, 37 }, { 1118, 971, -62 }, { 1118, 1386, -16 }, { 864, 838, -71 }, { 161, 980, 48 }, { 755, 517, -59 }, { 863, 741, 90 }, { 793, 224, -76 }, { 1007, 782, 96 }, { 463, 1257, -62 }, { 722, 306, 29 }, { 393, 708, -2 }, { 1490, 1445, -56 }, { 697, 85, -91 }, { 818, 496, -87 }, { 34, 331, -28 }, { 23, 746, -3 }, { 1298, 870, 23 }, { 782, 717, -10 }, { 278, 1352, -94 }, { 1439, 574, 70 }, { 833, 400, 26 }, { 693, 1283, -89 }, { 26, 1480, 11 }, { 939, 1428, 22 }, { 1298, 804, -58 }, { 1497, 1111, -74 }, { 397, 36, 69 }, { 519, 663, -17 }, { 420, 1278, -3 }, { 874, 334, 17 }, { 565, 835, 73 }, { 1344, 1418, -92 }, { 1090, 1271, -76 }, { 115, 1226, -20 }, { 826, 1047, -97 }, { 796, 1286, 86 }, { 776, 171, 16 }, { 1482, 1004, -81 }, { 404, 303, 86 }, { 72, 196, 11 }, { 1424, 146, 16 }, { 906, 373, 20 }, { 1203, 176, 54 }, { 191, 49, 57 }, { 601, 524, 97 }, { 110, 595, -47 }, { 1003, 18, 79 }, { 256, 720, -96 }, { 1044, 291, 46 }, { 1497, 1420, 81 }, { 440, 703, 32 }, { 1182, 149, -63 }, { 1464, 1461, 25 }, { 661, 112, 14 }, { 126, 241, -84 }, { 847, 378, 18 }, { 1125, 273, 33 }, { 543, 114, -1 }, { 1052, 521, -51 }, { 479, 1192, 17 }, { 699, 1178, 17 }, { 824, 616, 60 }, { 206, 961, 48 }, { 140, 613, 51 }, { 142, 992, 69 }, { 1471, 832, -60 }, { 1365, 1092, 15 }, { 395, 877, -45 }, { 9, 120, 81 }, { 668, 644, -82 }, { 1132, 1138, 0 }, { 788, 407, -66 }, { 846, 1127, -55 }, { 1009, 607, 91 }, { 191, 564, 61 }, { 11, 356, -43 }, { 906, 580, -4 }, { 1089, 61, -97 }, { 538, 1074, 6 }, { 1431, 36, -11 }, { 1096, 424, 98 }, { 426, 482, 57 }, { 924, 658, 23 }, { 88, 18, -18 }, { 380, 1443, 73 }, { 1441, 587, -94 }, { 1247, 510, -100 }, { 574, 891, -40 }, { 262, 791, 98 }, { 639, 1422, -69 }, { 1216, 128, 76 }, { 231, 712, -72 }, { 539, 1249, 63 }, { 1212, 82, -51 }, { 1173, 871, 17 }, { 1252, 1498, 66 }, { 1240, 1046, 40 }, { 415, 449, -96 }, { 182, 141, 86 }, { 1032, 332, -66 }, { 1276, 442, 12 }, { 657, 1259, -35 }, { 533, 597, 70 }, { 703, 1041, -81 }, { 941, 904, -89 }, { 7, 383, -63 }, { 1000, 636, -71 }, { 1471, 804, -65 }, { 1144, 1423, -1 }, { 739, 85, 35 }, { 637, 159, -62 }, { 1042, 683, -8 }, { 608, 337, -1 }, { 528, 337, -10 }, { 586, 845, 36 }, { 121, 1449, -48 }, { 58, 77, -73 }, { 1228, 961, -62 }, { 136, 1311, -78 }, { 93, 282, 48 }, { 112, 1020, -82 }, { 419, 837, 57 }, { 262, 1078, 7 }, { 1450, 1122, 6 }, { 164, 1053, -36 }, { 180, 41, -84 }, { 1194, 1149, 58 }, { 1130, 1218, -95 }, { 427, 1191, 65 }, { 825, 1007, -12 }, { 1092, 710, 50 }, { 865, 1144, 28 }, { 13, 292, -19 }, { 802, 523, 100 }, { 780, 128, -60 }, { 481, 1141, 28 }, { 466, 1138, -69 }, { 225, 1157, -71 }, { 694, 541, 18 }, { 473, 987, 68 }, { 732, 307, 77 }, { 88, 1172, 12 }, { 1454, 851, 3 }, { 964, 1299, -6 }, { 586, 483, 98 }, { 604, 75, -14 }, { 468, 1472, 73 }, { 130, 1095, 76 }, { 1125, 1247, -67 }, { 531, 1333, 85 }, { 696, 1089, 54 }, { 991, 1287, -55 }, { 123, 1242, 67 }, { 935, 507, 93 }, { 1141, 176, -12 }, { 733, 170, 38 }, { 1414, 838, 34 }, { 483, 1226, -34 }, { 75, 1336, -35 }, { 1076, 836, -54 }, { 668, 1073, 90 }, { 932, 1484, 99 }, { 295, 236, 58 }, { 933, 659, -79 }, { 538, 1014, -57 }, { 570, 499, 17 }, { 928, 832, -25 }, { 1134, 332, 39 }, { 363, 32, 51 }, { 1102, 806, -99 }, { 1417, 773, 62 }, { 539, 1077, 81 }, { 933, 940, -21 }, { 621, 477, 30 }, { 256, 1312, 54 }, { 189, 1106, 77 }, { 417, 965, -22 }, { 844, 399, -7 }, { 696, 668, 78 }, { 1381, 1027, 57 }, { 1093, 1453, -14 }, { 544, 1456, -1 }, { 368, 831, 65 }, { 8, 991, -86 }, { 663, 1156, -61 }, { 1049, 266, -65 }, { 457, 1147, -46 }, { 790, 1488, 27 }, { 245, 1430, -15 }, { 262, 1486, 95 }, { 278, 283, -34 }, { 309, 1300, -70 }, { 541, 451, -97 }, { 113, 43, 33 }, { 339, 770, -12 }, { 429, 603, 1 }, { 1289, 1301, 41 }, { 581, 128, 89 }, { 149, 1156, 37 }, { 95, 888, 11 }, { 426, 978, -21 }, { 1266, 320, 62 }, { 698, 1482, 48 }, { 1037, 896, -54 }, { 931, 572, -28 }, { 1401, 808, 75 }, { 196, 853, 38 }, { 362, 476, -38 }, { 641, 735, -12 }, { 283, 397, -77 }, { 241, 140, -52 }, { 1456, 897, 84 }, { 822, 80, 51 }, { 45, 732, -47 }, { 286, 945, -92 }, { 232, 1093, 91 }, { 485, 810, -48 }, { 547, 764, 33 }, { 698, 794, 17 }, { 183, 861, 23 }, { 1266, 311, -70 }, { 771, 284, 47 }, { 1146, 1226, -31 }, { 361, 1138, 23 }, { 244, 237, 4 }, { 1097, 1196, -94 }, { 193, 212, 48 }, { 159, 1323, -85 }, { 817, 1460, 34 }, { 304, 1240, 74 }, { 267, 700, -21 }, { 1375, 239, 69 }, { 331, 448, -3 }, { 157, 809, 96 }, { 1484, 1432, 73 }, { 753, 386, -90 }, { 723, 609, 6 }, { 108, 1285, -27 }, { 40, 170, 5 }, { 474, 1163, 67 }, { 1111, 361, -82 }, { 609, 1005, 70 }, { 31, 1249, 39 }, { 123, 473, 97 }, { 825, 1127, -73 }, { 461, 647, 55 }, { 197, 1491, -62 }, { 368, 177, -29 }, { 420, 368, 95 }, { 364, 1032, 52 }, { 201, 74, 87 }, { 1391, 883, -18 }, { 432, 844, 55 }, { 384, 1469, -2 }, { 639, 1053, 26 }, { 684, 1067, -58 }, { 1124, 192, 53 }, { 33, 340, -76 }, { 904, 614, -27 }, { 1281, 1312, 11 }, { 558, 183, -10 }, { 589, 242, 68 }, { 686, 449, -19 }, { 906, 1124, -34 }, { 100, 799, -88 }, { 1136, 496, -9 }, { 267, 1001, -78 }, { 325, 638, 73 }, { 1223, 374, -79 }, { 75, 216, 54 }, { 1210, 76, -57 }, { 901, 773, 91 }, { 866, 296, 99 }, { 394, 44, 67 }, { 539, 737, 5 }, { 960, 443, -61 }, { 693, 978, 44 }, { 800, 1068, -30 }, { 1465, 573, 9 }, { 271, 979, -62 }, { 952, 1147, 30 }, { 980, 1022, 52 }, { 184, 57, 9 }, { 1474, 340, 97 }, { 851, 741, 28 }, { 124, 3, 43 }, { 1333, 1269, -4 }, { 615, 1114, 93 }, { 610, 1459, 29 }, { 1148, 486, -57 }, { 172, 1145, 88 }, { 102, 916, 69 }, { 333, 546, -77 }, { 674, 694, 50 }, { 231, 465, -37 }, { 1433, 972, -29 }, { 679, 1037, -47 }, { 641, 490, 72 }, { 1496, 455, 74 }, { 40, 324, -46 }, { 233, 236, 8 }, { 249, 758, 99 }, { 1437, 1497, 17 }, { 171, 1293, -38 }, { 794, 411, 23 }, { 59, 753, -32 }, { 155, 1466, -49 }, { 737, 112, -87 }, { 474, 402, 67 }, { 527, 247, -98 }, { 1070, 1252, 69 }, { 1181, 1047, -13 }, { 949, 78, -17 }, { 332, 1035, -59 }, { 1289, 201, 33 }, { 145, 96, -87 }, { 212, 599, -30 }, { 1132, 1236, 42 }, { 957, 725, 54 }, { 301, 959, 33 }, { 281, 334, 33 }, { 822, 262, 7 }, { 81, 856, -8 }, { 547, 593, 95 }, { 309, 1241, -10 }, { 1410, 1076, -76 }, { 1073, 466, 22 }, { 1423, 790, -78 }, { 359, 521, -77 }, { 767, 50, -35 }, { 1197, 1441, -53 }, { 998, 536, 30 }, { 864, 1017, 42 }, { 962, 182, 18 }, { 641, 979, -59 }, { 625, 1363, -95 }, { 631, 122, 89 }, { 1407, 1184, 56 }, { 389, 643, 27 }, { 33, 1095, -99 }, { 1493, 745, 36 }, { 149, 821, 87 }, { 377, 935, 30 }, { 418, 1386, 90 }, { 896, 673, 29 }, { 166, 218, 80 }, { 700, 1345, -15 }, { 231, 433, 97 }, { 1390, 896, 9 }, { 162, 131, 83 }, { 818, 925, 37 }, { 346, 166, 85 }, { 100, 1425, 15 }, { 284, 948, -83 }, { 64, 415, -82 }, { 654, 226, 19 }, { 74, 1309, -43 }, { 534, 913, 77 }, { 1001, 1319, -63 }, { 852, 1469, -98 }, { 780, 1488, 53 }, { 719, 747, 72 }, { 1284, 1455, -46 }, { 23, 1215, 61 }, { 685, 1355, 84 }, { 1162, 144, -34 }, { 591, 486, -52 }, { 790, 203, 51 }, { 801, 1034, 48 }, { 126, 542, 9 }, { 1132, 1128, -62 }, { 116, 732, -43 }, { 1028, 1045, 22 }, { 1094, 1040, 45 }, { 401, 379, -32 }, { 723, 811, 8 }, { 1392, 901, -70 }, { 152, 139, -57 }, { 1019, 642, -5 }, { 1444, 717, -19 }, { 341, 962, 45 }, { 1356, 305, -3 }, { 144, 489, -65 }, { 667, 1184, 62 }, { 382, 1260, -93 }, { 645, 941, -51 }, { 936, 838, -6 }, { 1349, 272, -57 }, { 1296, 477, 57 }, { 629, 416, -39 }, { 453, 205, -36 }, { 1420, 556, 95 }, { 1038, 63, 54 }, { 859, 344, -11 }, { 784, 645, -8 }, { 1216, 916, -100 }, { 842, 1037, -15 }, { 1414, 685, -6 }, { 1206, 1060, -8 }, { 1274, 954, -46 }, { 809, 597, -100 }, { 788, 914, 31 }, { 1357, 1166, 84 }, { 1334, 408, 97 }, { 1441, 917, -23 }, { 406, 774, 43 }, { 1238, 1490, 21 }, { 683, 1022, -54 }, { 1189, 808, -99 }, { 306, 1446, 2 }, { 236, 1466, 35 }, { 612, 1463, -10 }, { 637, 1044, 56 }, { 1470, 184, -55 }, { 1280, 922, -31 }, { 831, 1271, 58 }, { 1320, 752, 69 }, { 937, 252, -39 }, { 629, 830, 34 }, { 439, 845, 84 }, { 1028, 554, -55 }, { 699, 377, 87 }, { 1487, 991, -49 }, { 1140, 672, 81 }, { 6, 451, -87 }, { 393, 1214, -2 }, { 110, 1008, 39 }, { 596, 615, 23 }, { 1101, 1468, 35 }, { 664, 459, -89 }, { 218, 631, -42 }, { 605, 1335, -5 }, { 42, 649, 35 }, { 1046, 4, -17 }, { 491, 615, -52 }, { 920, 926, 45 }, { 244, 94, 92 }, { 1246, 1275, -47 }, { 1136, 62, 28 }, { 270, 372, 52 }, { 1119, 792, 89 }, { 294, 1208, 94 }, { 954, 739, 4 }, { 448, 304, -76 }, { 538, 239, 75 }, { 374, 154, -82 }, { 1148, 1075, -9 }, { 290, 914, -74 }, { 1264, 1160, 5 }, { 1004, 509, -3 }, { 995, 1406, -12 }, { 117, 903, -48 }, { 473, 462, -44 }, { 328, 1008, 5 }, { 274, 625, -70 }, { 674, 863, -87 }, { 11, 43, -47 }, { 148, 547, -87 }, { 825, 959, -82 }, { 1335, 601, 40 }, { 645, 702, -15 }, { 978, 1240, -8 }, { 779, 10, 56 }, { 892, 218, -85 }, { 1005, 373, 22 }, { 971, 132, 36 }, { 506, 1185, -79 }, { 489, 438, 37 }, { 1295, 494, -35 }, { 657, 909, 50 }, { 1460, 1466, 6 }, { 1073, 560, -34 }, { 1435, 436, 94 }, { 366, 1339, -51 }, { 125, 800, 44 }, { 778, 986, 45 }, { 1081, 990, 64 }, { 594, 1410, 3 }, { 362, 720, -19 }, { 344, 370, 99 }, { 409, 628, 31 }, { 343, 1492, 26 }, { 199, 2, 6 }, { 385, 561, -35 }, { 951, 279, -36 }, { 252, 334, -41 }, { 370, 207, -87 }, { 804, 520, 6 }, { 729, 1080, -94 }, { 597, 486, 27 }, { 805, 994, 28 }, { 1186, 580, -86 }, { 971, 145, 40 }, { 1072, 183, 27 }, { 1194, 125, 39 }, { 894, 860, 50 }, { 1352, 1142, -6 }, { 751, 502, 95 }, { 1294, 1004, 20 }, { 273, 1248, 56 }, { 840, 351, -82 }, { 67, 930, -21 }, { 689, 1030, -15 }, { 62, 1438, -14 }, { 1467, 137, -65 }, { 1457, 641, -2 }, { 122, 1337, -68 }, { 48, 911, -2 }, { 525, 429, -38 }, { 500, 68, 54 }, { 1382, 1477, -15 }, { 445, 588, -91 }, { 335, 417, -96 }, { 65, 98, 17 }, { 324, 402, 19 }, { 1473, 1312, 32 }, { 1362, 1464, -48 }, { 1324, 1334, -35 }, { 948, 953, 64 }, { 694, 419, -35 }, { 917, 1416, 76 }, { 1065, 755, 70 }, { 1382, 1494, 82 }, { 1015, 378, -25 }, { 1493, 1084, -79 }, { 8, 97, 82 }, { 346, 57, 17 }, { 987, 864, -60 }, { 589, 638, -31 }, { 691, 203, 55 }, { 1363, 278, 46 }, { 389, 8, 80 }, { 774, 549, 97 }, { 1252, 741, 6 }, { 1455, 1359, 65 }, { 1363, 206, 100 }, { 1238, 978, -47 }, { 815, 1030, -5 }, { 664, 423, 24 }, { 854, 826, -85 }, { 710, 768, -42 }, { 887, 325, 31 }, { 782, 815, -50 }, { 394, 1365, 50 }, { 1238, 1206, 17 }, { 702, 1070, -97 }, { 570, 43, -23 }, { 384, 734, -4 }, { 1295, 57, -55 }, { 1279, 68, -53 }, { 830, 1196, 18 }, { 1122, 1452, 42 }, { 18, 293, -76 }, { 297, 962, 76 }, { 664, 1221, -4 }, { 648, 1434, 46 }, { 245, 656, -14 }, { 1236, 101, 19 }, { 235, 732, -62 }, { 163, 718, -66 }, { 1103, 699, -25 }, { 1209, 1438, -54 }, { 606, 1405, 29 }, { 836, 755, -50 }, { 573, 354, 71 }, { 1373, 790, -33 }, { 738, 1244, -69 }, { 911, 1449, 0 }, { 77, 355, 97 }, { 1108, 1072, 6 }, { 1355, 281, -8 }, { 138, 1363, 27 }, { 825, 1103, -26 }, { 356, 28, -9 }, { 1024, 1389, -62 }, { 1142, 648, -99 }, { 431, 219, 34 }, { 830, 1072, -82 }, { 485, 1440, 77 }, { 811, 553, -74 }, { 684, 519, 12 }, { 969, 444, -88 }, { 922, 651, -94 }, { 1254, 1396, 48 }, { 592, 1334, -66 }, { 848, 600, 35 }, { 1484, 325, 100 }, { 1416, 341, 77 }, { 1142, 1183, -75 }, { 819, 3, -38 }, { 781, 196, -65 }, { 67, 562, -63 }, { 898, 44, 33 }, { 879, 527, 65 }, { 994, 324, 40 }, { 462, 1176, -52 }, { 633, 1116, 8 }, { 240, 250, 48 }, { 1403, 861, 33 }, { 63, 633, -76 }, { 1470, 1084, 40 }, { 1085, 149, -38 }, { 415, 947, -40 }, { 730, 39, 11 }, { 745, 415, 97 }, { 1433, 1409, 13 }, { 183, 667, -17 }, { 757, 1234, -74 }, { 877, 1136, 89 }, { 1213, 116, 20 }, { 770, 529, 10 }, { 560, 884, 13 }, { 1084, 389, -16 }, { 1363, 1291, 95 }, { 280, 418, -46 }, { 369, 910, -67 }, { 1047, 1243, 48 }, { 427, 544, 90 }, { 1485, 570, -90 }, { 144, 269, -22 }, { 949, 1303, 84 }, { 473, 191, 76 }, { 753, 645, 91 }, { 1189, 884, 23 }, { 399, 974, -32 }, { 120, 1175, -59 }, { 455, 1395, -2 }, { 999, 1039, 73 }, { 431, 602, -61 }, { 1015, 604, -61 }, { 197, 966, -26 }, { 57, 612, -1 }, { 1283, 97, -79 }, { 181, 1096, -66 }, { 681, 1413, -67 }, { 841, 485, -42 }, { 926, 1031, 97 }, { 988, 1048, 64 }, { 487, 37, -97 }, { 424, 1069, -28 }, { 1156, 717, -14 }, { 1464, 63, 65 }, { 508, 1095, -42 }, { 789, 452, 3 }, { 696, 895, 26 }, { 79, 52, -64 }, { 610, 445, 32 }, { 631, 1002, 24 }, { 1407, 181, -17 }, { 660, 982, 82 }, { 963, 834, 5 }, { 930, 1044, 72 }, { 812, 803, -20 }, { 763, 705, -45 }, { 534, 121, -88 }, { 676, 1021, -21 }, { 1072, 475, 61 }, { 505, 1201, -62 }, { 962, 1131, 67 }, { 1063, 1033, 24 }, { 594, 1196, -22 }, { 974, 1488, 43 }, { 1484, 700, -66 }, { 1215, 318, -85 }, { 3, 443, -74 }, { 1412, 1329, 82 }, { 139, 1484, -84 }, { 67, 449, 1 }, { 440, 199, 65 }, { 1316, 1178, -51 }, { 12, 1004, -14 }, { 1209, 415, -13 }, { 1232, 339, -9 }, { 1078, 228, -5 }, { 631, 572, 60 }, { 805, 1103, 29 }, { 1305, 845, 30 }, { 1087, 287, -72 }, { 166, 101, 95 }, { 53, 600, 88 }, { 1117, 851, 4 }, { 989, 1058, -16 }, { 330, 115, 32 }, { 1064, 1106, -61 }, { 33, 1087, -70 }, { 635, 1470, 41 }, { 160, 590, 45 }, { 749, 764, -54 }, { 112, 617, 64 }, { 952, 1077, -87 }, { 1138, 1355, 64 }, { 887, 1157, -42 }, { 134, 666, -81 }, { 1311, 118, 34 }, { 512, 121, 4 }, { 276, 1333, 87 }, { 1227, 553, -86 }, { 411, 1381, 75 }, { 1396, 847, -92 }, { 85, 331, 7 }, { 780, 16, 37 }, { 1250, 1378, 91 }, { 1219, 919, -89 }, { 1298, 1487, -37 }, { 150, 797, -6 }, { 936, 971, -36 }, { 455, 1246, 0 }, { 33, 63, 50 }, { 1348, 663, 43 }, { 506, 1172, -54 }, { 423, 833, 96 }, { 334, 1177, 93 }, { 1236, 877, -67 }, { 737, 265, -38 }, { 347, 1328, -2 }, { 1182, 45, 19 }, { 1027, 212, -95 }, { 655, 1489, 0 }, { 1283, 327, 78 }, { 53, 949, -34 }, { 484, 119, -52 }, { 35, 1391, -51 }, { 527, 311, -64 }, { 354, 367, 3 }, { 1412, 361, 15 }, { 804, 398, -2 }, { 378, 1372, 61 }, { 329, 163, 22 }, { 940, 707, 72 }, { 1229, 1435, -6 }, { 937, 342, 24 }, { 490, 1112, 87 }, { 784, 1424, 22 }, { 1352, 880, -47 }, { 451, 1286, 81 }, { 26, 850, -20 }, { 641, 235, -4 }, { 532, 572, -49 }, { 681, 1016, 0 }, { 779, 447, 38 }, { 412, 426, 49 }, { 1037, 36, -15 }, { 669, 973, -94 }, { 1218, 1428, 3 }, { 636, 767, -61 }, { 149, 1442, -87 }, { 961, 469, 40 }, { 302, 1035, 44 }, { 791, 1081, -33 }, { 1039, 686, -29 }, { 1404, 1296, 79 }, { 349, 565, -23 }, { 1345, 1050, 81 }, { 1174, 311, -81 }, { 985, 859, 43 }, { 1428, 163, -43 }, { 438, 1048, 56 }, { 1344, 581, -98 }, { 597, 706, -1 }, { 1344, 587, -100 }, { 1011, 838, 31 }, { 998, 291, 0 }, { 331, 1157, -79 }, { 994, 1458, -54 }, { 910, 57, 44 }, { 225, 532, 70 }, { 632, 725, -73 }, { 542, 146, -42 }, { 249, 1414, 36 }, { 1212, 660, -75 }, { 623, 203, -33 }, { 969, 980, -35 }, { 166, 93, -48 }, { 67, 322, 54 }, { 309, 238, -81 }, { 137, 861, 17 }, { 1414, 110, 60 }, { 279, 1081, -97 }, { 1200, 686, 83 }, { 330, 667, 92 }, { 239, 714, 77 }, { 849, 834, 77 }, { 441, 42, -93 }, { 744, 1250, 54 }, { 468, 628, 98 }, { 171, 1194, -70 }, { 1020, 1475, -27 }, { 1226, 1002, 27 }, { 1306, 379, -21 }, { 1012, 534, -40 }, { 1265, 1068, 36 }, { 411, 377, -66 }, { 655, 760, 25 }, { 1002, 1365, -17 }, { 567, 1133, -2 }, { 1425, 619, -20 }, { 1498, 1127, -54 }, { 625, 31, -62 }, { 643, 741, 4 }, { 449, 209, -55 }, { 1474, 977, 9 }, { 507, 1246, 21 }, { 984, 1205, 93 }, { 142, 740, -58 }, { 428, 205, -8 }, { 295, 1322, 94 }, { 934, 994, -53 }, { 1103, 136, -44 }, { 708, 1145, 16 }, { 1373, 291, -31 }, { 1477, 976, -2 }, { 517, 381, -80 }, { 405, 283, 46 }, { 535, 587, -51 }, { 396, 491, 86 }, { 1267, 726, -47 }, { 472, 923, -95 }, { 448, 475, 53 }, { 525, 520, 61 }, { 1247, 1382, -64 }, { 1063, 1318, -48 }, { 1466, 9, 99 }, { 630, 447, -83 }, { 269, 314, -78 }, { 241, 639, 2 }, { 1141, 458, 57 }, { 1248, 1225, -15 }, { 460, 959, -16 }, { 1038, 960, -36 }, { 228, 1491, 31 }, { 406, 1453, -41 }, { 1163, 950, -35 }, { 593, 184, -77 }, { 1493, 494, 67 }, { 1309, 76, -56 }, { 955, 989, -67 }, { 1136, 952, 42 }, { 844, 480, 60 }, { 448, 191, 21 }, { 49, 494, -92 }, { 1080, 1264, -48 }, { 1254, 294, -10 }, { 818, 1486, 19 }, { 1267, 1, -74 }, { 704, 845, -18 }, { 660, 1255, -68 }, { 1189, 457, -49 }, { 1256, 633, 42 }, { 397, 654, -78 }, { 344, 242, 36 }, { 1007, 1201, -12 }, { 571, 261, -29 }, { 1034, 18, 96 }, { 1484, 143, 46 }, { 57, 518, 81 }, { 1007, 133, 17 }, { 251, 522, -63 }, { 1432, 985, 27 }, { 363, 1469, -14 }, { 230, 1260, 29 }, { 104, 1074, 85 }, { 755, 1379, -82 }, { 535, 1365, -38 }, { 520, 434, -5 }, { 337, 1447, -13 }, { 203, 1416, 60 }, { 568, 251, -55 }, { 750, 806, -52 }, { 841, 680, -66 }, { 1179, 1125, -71 }, { 907, 892, -55 }, { 754, 862, -12 }, { 938, 171, -61 }, { 631, 468, 78 }, { 905, 980, 69 }, { 332, 1200, -61 }, { 901, 1267, -54 }, { 172, 457, 80 }, { 318, 636, -6 }, { 509, 189, -97 }, { 1364, 158, -40 }, { 279, 1200, 8 }, { 1322, 1415, -59 }, { 234, 639, -5 }, { 1266, 246, 17 }, { 1225, 1151, 56 }, { 1025, 74, 25 }, { 1328, 1186, -22 }, { 1287, 110, 10 }, { 42, 246, -97 }, { 978, 857, 76 }, { 1143, 888, -52 }, { 651, 1028, 88 }, { 345, 540, 99 }, { 722, 1061, -30 }, { 1436, 1107, 90 }, { 786, 1294, 7 }, { 748, 1373, -60 }, { 1075, 1247, 63 }, { 917, 416, -72 }, { 979, 44, -24 }, { 422, 932, 9 }, { 1251, 618, 29 }, { 1115, 516, 84 }, { 1340, 1491, 59 }, { 94, 304, -28 }, { 997, 1339, 64 }, { 1117, 569, 74 }, { 642, 1105, 2 }, { 904, 1234, -21 }, { 705, 570, 2 }, { 1365, 1036, -79 }, { 1376, 117, -39 }, { 557, 935, 51 }, { 1247, 100, -11 }, { 1048, 202, -2 }, { 83, 350, -13 }, { 501, 705, 83 }, { 166, 217, -3 }, { 855, 905, -39 }, { 600, 1182, 55 }, { 1487, 1425, -8 }, { 851, 694, 86 }, { 1063, 126, -24 }, { 1055, 1302, -84 }, { 22, 1439, 34 }, { 309, 82, 56 }, { 248, 1499, 63 }, { 869, 1102, 25 }, { 1356, 1180, -31 }, { 765, 730, -26 }, { 763, 1433, -23 }, { 103, 388, -64 }, { 1272, 461, -38 }, { 1001, 1164, 38 }, { 178, 349, 63 }, { 696, 1230, -63 }, { 121, 1143, 25 }, { 1043, 1326, 49 }, { 909, 816, -43 }, { 497, 31, -41 }, { 303, 509, 44 }, { 1349, 957, 9 }, { 853, 661, -24 }, { 857, 405, -10 }, { 99, 385, -1 }, { 905, 1154, 30 }, { 1282, 1488, -74 }, { 765, 866, 47 }, { 741, 521, -49 }, { 229, 777, 30 }, { 1295, 411, 62 }, { 523, 669, -67 }, { 1168, 554, 82 }, { 1317, 434, -92 }, { 1435, 71, -19 }, { 505, 761, -97 }, { 1158, 657, -36 }, { 490, 279, 65 }, { 1371, 1057, -90 }, { 664, 398, -30 }, { 1175, 1429, 11 }, { 219, 754, -53 }, { 981, 1083, -13 }, { 684, 405, -88 }, { 1117, 156, 52 }, { 132, 513, 15 }, { 573, 1367, 31 }, { 65, 479, 92 }, { 35, 976, 91 }, { 301, 1088, 89 }, { 576, 730, -2 }, { 577, 834, -74 }, { 201, 54, -56 }, { 671, 1340, -81 }, { 847, 1168, -2 }, { 1043, 437, -19 }, { 476, 138, -46 }, { 72, 284, -52 }, { 1051, 1148, -58 }, { 1172, 143, -22 }, { 952, 235, 76 }, { 1308, 1125, 56 }, { 1001, 134, -68 }, { 38, 122, -78 }, { 368, 818, -94 }, { 596, 1152, -26 }, { 585, 1005, -31 }, { 607, 115, 80 }, { 278, 193, 78 }, { 376, 156, -33 }, { 909, 1077, 9 }, { 1405, 327, -2 }, { 165, 168, -51 }, { 371, 254, -2 }, { 1341, 1213, -91 }, { 238, 402, 89 }, { 925, 578, -40 }, { 1164, 0, 81 }, { 883, 1232, 65 }, { 1087, 1243, -91 }, { 545, 678, 97 }, { 685, 1124, -17 }, { 638, 17, 65 }, { 183, 649, -52 }, { 1278, 25, 1 }, { 669, 915, -64 }, { 341, 45, -50 }, { 1091, 318, -76 }, { 608, 896, -92 }, { 604, 582, -52 }, { 819, 1486, 53 }, { 317, 216, -85 }, { 1062, 1100, -24 }, { 153, 53, -20 }, { 179, 160, -73 }, { 783, 970, 3 }, { 718, 1248, -60 }, { 1061, 801, -30 }, { 1227, 562, -12 }, { 319, 1462, 25 }, { 21, 1360, 7 }, { 1159, 1333, 71 }, { 511, 1240, 36 }, { 635, 647, -71 }, { 707, 526, -99 }, { 535, 247, 10 }, { 1037, 316, 26 }, { 1237, 571, 16 }, { 49, 670, -85 }, { 38, 838, 74 }, { 968, 487, 61 }, { 91, 1379, -73 }, { 509, 208, -44 }, { 759, 79, -71 }, { 232, 1271, -81 }, { 461, 764, -47 }, { 1098, 462, 97 }, { 1497, 495, -64 }, { 1442, 433, -74 }, { 503, 498, 83 }, { 335, 481, 50 }, { 1009, 61, -14 }, { 703, 423, 32 }, { 559, 933, 60 }, { 1115, 1483, -91 }, { 1127, 1202, -48 }, { 1452, 225, 72 }, { 473, 263, -20 }, { 273, 529, -31 }, { 1395, 686, -84 }, { 421, 989, 36 }, { 1249, 782, 74 }, { 375, 388, -27 }, { 915, 1047, 64 }, { 1011, 1109, 96 }, { 1319, 772, 55 }, { 564, 1014, -94 }, { 1414, 1168, -100 }, { 1108, 470, 96 }, { 734, 1220, 93 }, { 956, 992, 0 }, { 309, 1129, -79 }, { 1145, 257, 21 }, { 1483, 360, -12 }, { 637, 439, 71 }, { 1165, 1451, -85 }, { 1145, 163, -11 }, { 945, 619, 100 }, { 1146, 254, 9 }, { 190, 1006, 8 }, { 946, 1319, -8 }, { 169, 1315, 65 }, { 542, 864, 1 }, { 948, 311, -70 }, { 702, 725, 12 }, { 910, 63, 83 }, { 1040, 905, 1 }, { 979, 363, -54 }, { 756, 643, 83 }, { 1209, 1164, -84 }, { 430, 459, 44 }, { 738, 32, -10 }, { 645, 1418, 98 }, { 1083, 169, 50 }, { 980, 895, 43 }, { 373, 685, 38 }, { 363, 596, 6 }, { 535, 461, 31 }, { 849, 1296, -32 }, { 589, 800, -48 }, { 747, 401, -6 }, { 396, 809, 20 }, { 1266, 721, -76 }, { 1149, 820, -67 }, { 577, 1084, 24 }, { 325, 77, 6 }, { 252, 865, 73 }, { 933, 655, -38 }, { 1013, 1018, -13 }, { 1050, 467, 59 }, { 717, 940, 86 }, { 364, 262, -68 }, { 962, 307, -22 }, { 861, 1488, -33 }, { 1068, 97, 46 }, { 892, 1310, -98 }, { 1164, 82, -23 }, { 730, 1488, -6 }, { 1112, 964, -21 }, { 1071, 944, 14 }, { 483, 1384, -69 }, { 473, 257, -74 }, { 1439, 1456, 93 }, { 310, 1410, 87 }, { 229, 866, -29 }, { 1199, 337, 56 }, { 1361, 1076, 19 }, { 608, 483, 6 }, { 99, 765, -30 }, { 1481, 626, -90 }, { 1288, 116, -19 }, { 726, 1314, -85 }, { 1224, 224, 6 }, { 1311, 1147, 14 }, { 448, 183, -59 }, { 114, 779, -24 }, { 508, 1094, 12 }, { 1256, 3, 51 }, { 63, 1325, -22 }, { 242, 1187, 52 }, { 357, 543, 36 }, { 541, 1247, -43 }, { 259, 944, 39 }, { 92, 691, 84 }, { 481, 654, 47 }, { 697, 318, 10 }, { 833, 1421, 19 }, { 733, 1418, 17 }, { 589, 1262, 59 }, { 764, 1478, 66 }, { 1491, 1182, -87 }, { 1385, 701, -30 }, { 1302, 799, 98 }, { 135, 1170, -5 }, { 1467, 819, 56 }, { 1448, 241, 23 }, { 616, 1232, 91 }, { 552, 149, -50 }, { 1157, 511, 70 }, { 1080, 561, -61 }, { 937, 1065, 82 }, { 1237, 1260, -30 }, { 1263, 371, -57 }, { 907, 259, 86 }, { 233, 1432, -98 }, { 647, 925, -52 }, { 620, 1324, -40 }, { 877, 1186, 63 }, { 1370, 1062, -34 }, { 837, 760, -49 }, { 428, 1311, 98 }, { 888, 428, 68 }, { 1311, 293, 62 }, { 332, 1479, -49 }, { 337, 770, -54 }, { 221, 103, -70 }, { 1147, 1231, 53 }, { 219, 791, -73 }, { 142, 118, -69 }, { 248, 609, 9 }, { 825, 683, 16 }, { 1407, 1380, 92 }, { 489, 1426, -15 }, { 52, 43, 79 }, { 1104, 820, -18 }, { 446, 256, 40 }, { 530, 464, 88 }, { 870, 1399, 1 }, { 134, 496, 64 }, { 982, 85, -49 }, { 356, 969, 65 }, { 1134, 924, 51 }, { 1411, 23, -97 }, { 1497, 273, -56 }, { 37, 572, 62 }, { 566, 1305, 17 }, { 81, 103, 76 }, { 1457, 452, -32 }, { 1252, 435, 15 }, { 992, 1410, 92 }, { 711, 1417, 55 }, { 243, 90, 0 }, { 557, 199, -9 }, { 747, 140, -77 }, { 1397, 1479, 29 }, { 1457, 1067, -68 }, { 934, 343, 42 }, { 787, 696, 31 }, { 684, 1258, -100 }, { 546, 477, 36 }, { 449, 930, 41 }, { 24, 13, -45 }, { 373, 651, 62 }, { 635, 463, 51 }, { 1267, 1216, 85 }, { 53, 760, 42 }, { 325, 1068, 59 }, { 970, 910, 29 }, { 1297, 460, -2 }, { 1182, 538, -11 }, { 1264, 790, -8 }, { 1446, 973, -2 }, { 98, 993, -18 }, { 460, 191, -63 }, { 508, 87, -93 }, { 843, 427, 59 }, { 686, 383, -90 }, { 55, 735, 53 }, { 425, 964, -58 }, { 443, 1186, -24 }, { 428, 961, -75 }, { 1133, 1166, -12 }, { 928, 397, 33 }, { 766, 348, -94 }, { 1041, 714, -2 }, { 1152, 473, 90 }, { 1251, 1154, 53 }, { 795, 1300, -37 }, { 1346, 1202, -75 }, { 210, 513, 39 }, { 505, 875, 65 }, { 718, 1327, 65 }, { 792, 409, 99 }, { 873, 69, -81 }, { 1080, 817, -71 }, { 345, 1109, -91 }, { 867, 885, 75 }, { 1250, 262, -45 }, { 89, 474, -37 }, { 460, 1448, -94 }, { 782, 1454, 60 }, { 140, 1497, 41 }, { 1297, 899, -40 }, { 550, 294, -66 }, { 1408, 1315, -66 }, { 1404, 98, 31 }, { 1492, 821, -16 }, { 970, 1134, -22 }, { 743, 280, -5 }, { 536, 717, 49 }, { 803, 1127, 48 }, { 427, 437, 52 }, { 1418, 460, -20 }, { 124, 518, 36 }, { 32, 1432, 36 }, { 814, 1006, 87 }, { 1320, 1195, -58 }, { 1478, 190, 70 }, { 1159, 528, -28 }, { 647, 349, -93 }, { 521, 967, 60 }, { 491, 250, 31 }, { 1101, 802, 1 }, { 588, 446, 92 }, { 90, 525, 76 }, { 383, 173, 59 }, { 1446, 717, 28 }, { 11, 1320, 84 }, { 1184, 1219, -78 }, { 625, 1037, -16 }, { 601, 390, -100 }, { 783, 535, -72 }, { 155, 746, -41 }, { 1042, 501, 60 }, { 981, 185, 60 }, { 252, 143, -3 }, { 1157, 1051, -78 }, { 426, 533, 92 }, { 351, 870, 97 }, { 226, 884, 95 }, { 1319, 1196, 45 }, { 1327, 303, -69 }, { 177, 820, 94 }, { 77, 1396, 93 }, { 1040, 646, 100 }, { 135, 219, 92 }, { 1223, 1030, 99 }, { 1215, 957, 9 }, { 255, 902, -63 }, { 289, 1172, -45 }, { 259, 924, 9 }, { 427, 1002, -81 }, { 523, 715, -39 }, { 513, 1184, 27 }, { 1348, 1180, -56 }, { 695, 16, 83 }, { 1184, 368, 94 }, { 1330, 302, -75 }, { 320, 1272, 67 }, { 263, 480, 82 }, { 295, 820, -73 }, { 577, 27, 63 }, }; double maxWeight = 23875; double minWeight = -24426; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } private void test(int[][] edges, double result, ObjectiveSense objectiveSense) { DefaultUndirectedGraph graph = (DefaultUndirectedGraph) TestUtil.createUndirected(edges); int maxVertex = 0; for (int[] edge : edges) { maxVertex = Math.max(Math.max(maxVertex, edge[0]), edge[1]); } graph.setVertexSupplier(SupplierUtil.createIntegerSupplier(maxVertex + 1)); KolmogorovWeightedMatching weightedMatching = new KolmogorovWeightedMatching<>(graph, options, objectiveSense); MatchingAlgorithm.Matching matching = weightedMatching.getMatching(); assertEquals(matching.getWeight(), result, EPS); assertTrue(weightedMatching.testOptimality()); assertTrue(weightedMatching.getError() < EPS); } } KolmogorovWeightedPerfectMatchingTest.java000066400000000000000000005457421402514743400407350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.runner.*; import org.junit.runners.*; import java.util.*; import static org.jgrapht.alg.matching.blossom.v5.KolmogorovWeightedPerfectMatching.EPS; import static org.jgrapht.alg.matching.blossom.v5.ObjectiveSense.MAXIMIZE; import static org.jgrapht.alg.matching.blossom.v5.ObjectiveSense.MINIMIZE; import static org.junit.Assert.*; /** * Unit tests for the {@link KolmogorovWeightedPerfectMatching} * * @author Timofey Chudakov */ @RunWith(Parameterized.class) public class KolmogorovWeightedPerfectMatchingTest { /** * Algorithm options */ private BlossomVOptions options; /** * The objective sense of the algorithm */ private ObjectiveSense objectiveSense; public KolmogorovWeightedPerfectMatchingTest( BlossomVOptions options, ObjectiveSense objectiveSense) { this.options = options; this.objectiveSense = objectiveSense; } @Parameterized.Parameters public static Object[][] params() { BlossomVOptions[] options = BlossomVOptions.ALL_OPTIONS; Object[][] params = new Object[2 * options.length][2]; for (int i = 0; i < options.length; i++) { params[2 * i][0] = options[i]; params[2 * i][1] = MAXIMIZE; params[2 * i + 1][0] = options[i]; params[2 * i + 1][1] = MINIMIZE; } return params; } /** * Checks complementary slackness conditions * * @param matching a matching * @param dualSolution solution to a dual linear program * @param objectiveSense objective sense of the algorithm * @param graph vertex type * @param graph edge type */ static void checkMatchingAndDualSolution( MatchingAlgorithm.Matching matching, KolmogorovWeightedPerfectMatching.DualSolution dualSolution, ObjectiveSense objectiveSense) { Graph graph = dualSolution.getGraph(); assertEquals(graph.vertexSet().size(), 2 * matching.getEdges().size()); Set matchedEdges = matching.getEdges(); Set vertices = new HashSet<>(); Map slacks = new HashMap<>(); for (E edge : matchedEdges) { V source = graph.getEdgeSource(edge); V target = graph.getEdgeTarget(edge); if (source != target) { assertFalse(vertices.contains(source)); assertFalse(vertices.contains(target)); vertices.add(source); vertices.add(target); slacks.put(edge, graph.getEdgeWeight(edge)); } else { fail(); } } for (E edge : graph.edgeSet()) { if (!matchedEdges.contains(edge)) { V source = graph.getEdgeSource(edge); V target = graph.getEdgeTarget(edge); if (source != target) { slacks.put(edge, graph.getEdgeWeight(edge)); } } } assertEquals(graph.vertexSet(), vertices); Map, Double> dualMap = dualSolution.getDualVariables(); for (Map.Entry, Double> entry : dualMap.entrySet()) { double dualVariable = entry.getValue(); if (entry.getKey().size() > 1) { if (objectiveSense == MAXIMIZE) { // the dual variable of a pseudonode can't be greater than EPS // for maximization problem assertTrue(dualVariable - EPS <= 0); } else { // the dual variable of a pseudonode can't be less than -EPS // for minimization problem assertTrue(dualVariable + EPS >= 0); } } for (V vertex : entry.getKey()) { for (E edge : graph.edgesOf(vertex)) { if (!entry.getKey().contains(Graphs.getOppositeVertex(graph, edge, vertex))) { // checking // whether // the // edge // is // boundary slacks.put(edge, slacks.get(edge) - dualVariable); } } } } for (Map.Entry entry : slacks.entrySet()) { E edge = entry.getKey(); double edgeSlack = entry.getValue(); if (matchedEdges.contains(edge)) { // matched edge must have 0 slack assertTrue(Math.abs(edgeSlack) < EPS); } else if (objectiveSense == MAXIMIZE) { // in the optimal solution to the maximization problem edge slacks must be // non-positive assertTrue(edgeSlack - EPS <= 0); } else { // in the optimal solution to the minimization problem edge slacks must be // non-negative assertTrue(edgeSlack + EPS >= 0); } } } @Test public void testInvalidDualSolution() { int[][] edges = { { 1, 2, 7 }, { 2, 3, 4 }, { 3, 4, 3 }, { 4, 1, 4 }, }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching matching = new KolmogorovWeightedPerfectMatching<>(graph); matching.getMatching(); Map vertexMap = BlossomVDebugger.getVertexMap(matching.state); BlossomVNode node1 = vertexMap.get(1); node1.dual += 1; assertFalse(matching.testOptimality()); } /** * Test on a triangulation of 8 points Points: (2, 10), (9, 11), (10, 4), (11, 15), (12, 5), * (12, 6), (13, 12), (14, 11) */ @Test public void testGetMatching0() { int[][] edges = new int[][] { { 0, 1, 8 }, { 0, 2, 10 }, { 1, 2, 8 }, { 0, 3, 11 }, { 1, 3, 5 }, { 2, 5, 3 }, { 1, 5, 6 }, { 2, 4, 3 }, { 4, 5, 1 }, { 1, 6, 5 }, { 3, 6, 4 }, { 3, 7, 5 }, { 6, 7, 2 }, { 5, 7, 6 }, { 4, 7, 7 }, { 1, 7, 5 } }; double maxWeight = 27; double minWeight = 18; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on an empty graph */ @Test public void testGetMatching1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); assertEquals(0, matching.getWeight(), EPS); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), objectiveSense); } /** * Smoke test */ @Test public void testGetMatching2() { int[][] edges = new int[][] { { 1, 2, 5 } }; double maxWeight = 5; double minWeight = 5; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a graph with an odd cycle */ @Test public void testGetMatching3() { int[][] edges = new int[][] { { 1, 2, 8 }, { 2, 3, 8 }, { 3, 4, 8 }, { 4, 1, 8 }, { 2, 4, 2 } }; double maxWeight = 16; double minWeight = 16; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{2,2}$ */ @Test public void testGetMatching4() { int[][] edges = new int[][] { { 1, 3, 11 }, { 1, 4, 8 }, { 2, 3, 8 }, { 2, 4, 2 } }; double maxWeight = 16; double minWeight = 13; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{3,3}$ */ @Test public void testGetMatching5() { int[][] edges = new int[][] { { 0, 3, 7 }, { 0, 4, 5 }, { 0, 5, 2 }, { 1, 3, 1 }, { 1, 4, 3 }, { 1, 5, 4 }, { 2, 3, 7 }, { 2, 4, 10 }, { 2, 5, 7 } }; double maxWeight = 21; double minWeight = 12; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{3,3}$ */ @Test public void testGetMatching6() { int[][] edges = new int[][] { { 0, 3, 7 }, { 0, 4, 3 }, { 0, 5, 9 }, { 1, 3, 8 }, { 1, 4, 2 }, { 1, 5, 9 }, { 2, 3, 6 }, { 2, 4, 1 }, { 2, 5, 10 } }; double maxWeight = 21; double minWeight = 17; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{4}$ */ @Test public void testGetMatching7() { int[][] edges = new int[][] { { 0, 1, 2 }, { 0, 2, 5 }, { 0, 3, 1 }, { 1, 2, 5 }, { 1, 3, 2 }, { 2, 3, 1 } }; double maxWeight = 7; double minWeight = 3; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{5}$ with a dummy edge (2,5) */ @Test public void testGetMatching8() { int[][] edges = new int[][] { { 0, 1, 1 }, { 0, 2, 4 }, { 0, 3, 7 }, { 0, 4, 10 }, { 1, 2, 5 }, { 1, 3, 7 }, { 1, 4, 10 }, { 2, 3, 10 }, { 2, 4, 2 }, { 3, 4, 3 }, { 2, 5, 8 } }; double maxWeight = 25; double minWeight = 12; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{5}$ with a dummy edge (0,5) */ @Test public void testGetMatching9() { int[][] edges = new int[][] { { 0, 1, 1 }, { 0, 2, 6 }, { 0, 3, 1 }, { 0, 4, 1 }, { 1, 2, 6 }, { 1, 3, 6 }, { 1, 4, 5 }, { 2, 3, 7 }, { 2, 4, 8 }, { 3, 4, 8 }, { 0, 5, 8 } }; double maxWeight = 22; double minWeight = 20; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{5}$ with a dummy edge (0,5) */ @Test public void testGetMatching10() { int[][] edges = new int[][] { { 0, 1, 4 }, { 0, 2, 4 }, { 0, 3, 6 }, { 0, 4, 8 }, { 1, 2, 8 }, { 1, 3, 10 }, { 1, 4, 8 }, { 2, 3, 4 }, { 2, 4, 9 }, { 3, 4, 4 }, { 0, 5, 9 } }; double maxWeight = 28; double minWeight = 21; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{5}$ with a dummy edge (0,5) */ @Test public void testGetMatching11() { int[][] edges = new int[][] { { 0, 1, 5 }, { 0, 2, 1 }, { 0, 3, 8 }, { 0, 4, 1 }, { 1, 2, 2 }, { 1, 3, 8 }, { 1, 4, 1 }, { 2, 3, 8 }, { 2, 4, 5 }, { 3, 4, 10 }, { 0, 5, 8 } }; double maxWeight = 21; double minWeight = 17; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{5}$ with a dummy edge (0,5) */ @Test public void testGetMatching12() { int[][] edges = new int[][] { { 0, 1, 2 }, { 0, 2, 6 }, { 0, 3, 3 }, { 0, 4, 2 }, { 1, 2, 7 }, { 1, 3, 7 }, { 1, 4, 7 }, { 2, 3, 4 }, { 2, 4, 4 }, { 3, 4, 2 }, { 0, 5, 2 } }; double maxWeight = 13; double minWeight = 11; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{5}$ with a dummy edge (4,3) */ @Test public void testGetMatching13() { int[][] edges = new int[][] { { 2, 0, 5 }, { 2, 1, 9 }, { 3, 0, 2 }, { 3, 1, 6 }, { 3, 2, 7 }, { 4, 0, 3 }, { 4, 1, 5 }, { 4, 2, 5 }, { 4, 3, 7 }, { 5, 4, 6 } }; double maxWeight = 17; double minWeight = 17; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{5}$ with a dummy edge (4,0) */ @Test public void testGetMatching14() { int[][] edges = new int[][] { { 1, 0, 9 }, { 2, 0, 8 }, { 2, 1, 3 }, { 3, 0, 5 }, { 3, 1, 4 }, { 3, 2, 10 }, { 4, 0, 3 }, { 4, 1, 2 }, { 4, 2, 4 }, { 4, 3, 8 }, { 5, 1, 4 } }; double maxWeight = 20; double minWeight = 13; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{5}$ with a dummy edge (4,0) */ @Test public void testGetMatching15() { int[][] edges = new int[][] { { 1, 0, 8 }, { 2, 0, 7 }, { 2, 1, 10 }, { 3, 0, 8 }, { 3, 1, 5 }, { 3, 2, 3 }, { 4, 0, 9 }, { 4, 1, 5 }, { 4, 2, 4 }, { 4, 3, 10 }, { 5, 1, 4 } }; double maxWeight = 21; double minWeight = 16; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a $K_{6}$ with edges with zero weight */ @Test public void testGetMatching16() { int[][] edges = new int[][] { { 0, 1, 3 }, { 0, 2, 2 }, { 0, 3, 8 }, { 0, 4, 10 }, { 0, 5, 8 }, { 1, 2, 1 }, { 1, 3, 4 }, { 1, 4, 8 }, { 1, 5, 0 }, { 2, 3, 8 }, { 2, 4, 5 }, { 2, 5, 0 }, { 3, 4, 7 }, { 3, 5, 0 }, { 4, 5, 0 } }; double maxWeight = 24; double minWeight = 6; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 8 points */ @Test public void testGetMatching17() { int[][] edges = new int[][] { { 1, 0, 7 }, { 1, 2, 3 }, { 0, 2, 7 }, { 0, 3, 9 }, { 0, 4, 9 }, { 3, 4, 2 }, { 0, 5, 9 }, { 4, 5, 8 }, { 2, 5, 6 }, { 5, 6, 5 }, { 2, 6, 6 }, { 1, 6, 9 }, { 3, 7, 8 }, { 4, 7, 6 }, { 5, 7, 5 }, { 6, 7, 9 } }; double maxWeight = 32; double minWeight = 20; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points */ @Test public void testGetMatching18() { int[][] edges = new int[][] { { 0, 1, 10 }, { 0, 2, 7 }, { 1, 2, 4 }, { 2, 3, 1 }, { 1, 3, 3 }, { 3, 4, 1 }, { 1, 4, 2 }, { 3, 5, 1 }, { 4, 5, 2 }, { 2, 5, 2 }, { 4, 6, 3 }, { 5, 6, 3 }, { 1, 6, 3 }, { 2, 7, 5 }, { 0, 7, 6 }, { 5, 8, 2 }, { 6, 8, 3 }, { 2, 8, 4 }, { 7, 8, 3 }, { 7, 9, 3 }, { 0, 9, 7 }, { 8, 9, 8 } }; double maxWeight = 27; double minWeight = 16; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points Points: (1, 2), (1, 8), (3, 5), (3, 11), (3, 12), (5, * 3), (10, 6), (10, 11), (14, 8), (15, 1) */ @Test public void testGetMatching19() { int[][] edges = new int[][] { { 0, 1, 6 }, { 0, 2, 4 }, { 1, 2, 4 }, { 2, 3, 6 }, { 1, 3, 4 }, { 1, 4, 5 }, { 3, 4, 1 }, { 0, 5, 5 }, { 2, 5, 3 }, { 2, 6, 8 }, { 3, 6, 9 }, { 5, 6, 6 }, { 3, 7, 7 }, { 4, 7, 8 }, { 6, 7, 5 }, { 6, 8, 5 }, { 7, 8, 5 }, { 6, 9, 8 }, { 5, 9, 11 }, { 8, 9, 9 }, { 0, 9, 15 } }; double maxWeight = 37; double minWeight = 23; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points Points: (1, 7), (1, 11), (4, 5), (6, 5), (7, 8), (9, 1), * (11, 7), (13, 7), (13, 10), (15, 9) */ @Test public void testGetMatching20() { int[][] edges = new int[][] { { 2, 3, 2 }, { 2, 4, 5 }, { 3, 4, 4 }, { 0, 2, 4 }, { 0, 4, 7 }, { 0, 1, 4 }, { 1, 4, 7 }, { 2, 5, 7 }, { 3, 5, 5 }, { 0, 5, 10 }, { 3, 6, 6 }, { 5, 6, 7 }, { 4, 6, 5 }, { 5, 7, 8 }, { 6, 7, 2 }, { 4, 8, 7 }, { 1, 8, 13 }, { 6, 8, 4 }, { 7, 8, 3 }, { 7, 9, 3 }, { 8, 9, 3 }, { 5, 9, 10 } }; double maxWeight = 37; double minWeight = 19; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points Points: (4, 5), (5, 14), (6, 12), (7, 11), (9, 1), (9, * 8), (10, 15), (12, 6), (13, 12), (14, 6) */ @Test public void testGetMatching21() { int[][] edges = new int[][] { { 0, 1, 10 }, { 0, 2, 8 }, { 1, 2, 3 }, { 0, 3, 7 }, { 2, 3, 2 }, { 0, 4, 7 }, { 0, 5, 6 }, { 4, 5, 7 }, { 3, 5, 4 }, { 2, 6, 5 }, { 3, 6, 5 }, { 1, 6, 6 }, { 5, 7, 4 }, { 4, 7, 6 }, { 5, 8, 6 }, { 7, 8, 7 }, { 3, 8, 7 }, { 6, 8, 5 }, { 8, 9, 7 }, { 7, 9, 2 }, { 4, 9, 8 } }; double maxWeight = 34; double minWeight = 21; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points Points: (3, 3), (3, 10), (4, 12), (5, 13), (8, 4), (10, * 3), (11, 5), (11, 9), (11, 13), (14, 11) */ @Test public void testGetMatching22() { int[][] edges = new int[][] { { 1, 0, 7 }, { 1, 4, 8 }, { 0, 4, 6 }, { 0, 5, 7 }, { 4, 5, 3 }, { 4, 6, 4 }, { 5, 6, 3 }, { 4, 7, 6 }, { 6, 7, 4 }, { 2, 3, 2 }, { 2, 8, 8 }, { 3, 7, 8 }, { 1, 2, 3 }, { 1, 7, 9 }, { 3, 8, 7 }, { 7, 8, 5 }, { 7, 9, 4 }, { 8, 9, 5 }, { 6, 9, 7 } }; double maxWeight = 38; double minWeight = 21; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points Points: (1, 15), (4, 2), (7, 13), (8, 15), (9, 5), (10, * 7), (11, 11), (12, 4), (12, 9), (13, 11) */ @Test public void testGetMatching23() { int[][] edges = new int[][] { { 1, 0, 14 }, { 1, 2, 12 }, { 0, 2, 7 }, { 0, 3, 7 }, { 2, 3, 3 }, { 2, 4, 9 }, { 1, 4, 6 }, { 2, 5, 7 }, { 4, 5, 3 }, { 2, 6, 5 }, { 3, 6, 5 }, { 5, 6, 5 }, { 4, 7, 4 }, { 1, 7, 9 }, { 5, 7, 4 }, { 5, 8, 3 }, { 7, 8, 5 }, { 6, 8, 3 }, { 3, 9, 7 }, { 6, 9, 2 }, { 8, 9, 3 }, { 7, 9, 8 } }; double maxWeight = 40; double minWeight = 25; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points Points: (1, 6), (2, 14), (5, 5), (7, 10), (9, 8), (9, * 10), (12, 4), (13, 8), (13, 12), (14, 10) */ @Test public void testGetMatching24() { int[][] edges = new int[][] { { 0, 2, 5 }, { 0, 3, 8 }, { 2, 3, 6 }, { 0, 1, 9 }, { 1, 3, 7 }, { 2, 4, 5 }, { 3, 4, 3 }, { 3, 5, 2 }, { 4, 5, 2 }, { 1, 5, 9 }, { 4, 6, 5 }, { 2, 6, 8 }, { 4, 7, 4 }, { 5, 7, 5 }, { 6, 7, 5 }, { 5, 8, 5 }, { 1, 8, 12 }, { 7, 8, 4 }, { 7, 9, 3 }, { 8, 9, 3 }, { 6, 9, 7 } }; double maxWeight = 37; double minWeight = 22; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points Points: (1,4), (5, 10), (5, 13), (8, 7), (9, 8), (10, * 6), (11, 2), (11, 13), (14, 3), (15, 13) */ @Test public void testGetMatching25() { int[][] edges = new int[][] { { 0, 1, 8 }, { 0, 2, 10 }, { 1, 2, 3 }, { 0, 3, 8 }, { 1, 3, 5 }, { 1, 4, 5 }, { 3, 4, 2 }, { 4, 5, 3 }, { 3, 5, 3 }, { 3, 6, 6 }, { 0, 6, 11 }, { 5, 6, 5 }, { 1, 7, 7 }, { 4, 7, 6 }, { 2, 7, 6 }, { 5, 8, 5 }, { 6, 8, 4 }, { 5, 9, 8 }, { 8, 9, 10 }, { 4, 9, 8 }, { 7, 9, 5 } }; double maxWeight = 36; double minWeight = 23; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points */ @Test public void testGetMatching26() { int[][] edges = new int[][] { { 0, 1, 5 }, { 0, 2, 5 }, { 1, 2, 3 }, { 1, 3, 4 }, { 2, 3, 3 }, { 2, 4, 5 }, { 3, 4, 6 }, { 0, 4, 5 }, { 3, 5, 5 }, { 4, 5, 3 }, { 3, 6, 6 }, { 5, 6, 5 }, { 1, 6, 10 }, { 5, 7, 9 }, { 4, 7, 8 }, { 0, 7, 12 }, { 5, 8, 5 }, { 7, 8, 11 }, { 6, 8, 2 }, { 7, 9, 13 }, { 8, 9, 5 }, { 6, 9, 5 } }; double maxWeight = 39; double minWeight = 26; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 10 points Points: (2, 9), (4, 13), (6, 5), (6, 12), (8, 4), (8, * 9), (8, 14), (10, 15), (14, 10), (15, 4) */ @Test public void testGetMatching27() { int[][] edges = new int[][] { { 0, 1, 5 }, { 0, 3, 5 }, { 1, 3, 3 }, { 2, 4, 3 }, { 2, 5, 5 }, { 4, 5, 5 }, { 0, 2, 6 }, { 0, 5, 6 }, { 3, 5, 4 }, { 5, 6, 5 }, { 3, 6, 3 }, { 1, 6, 5 }, { 5, 7, 7 }, { 6, 7, 3 }, { 1, 7, 7 }, { 5, 8, 7 }, { 7, 8, 7 }, { 4, 8, 9 }, { 8, 9, 7 }, { 4, 9, 7 } }; double maxWeight = 30; double minWeight = 22; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a triangulation of 20 points Points: (2, 24), (4, 8), (5, 21), (5, 24), (6, 12), (10, * 4), (15, 3), (15, 5), (17, 5), (19, 27), (20, 16), (23, 1), (23, 8), (23, 12), (24, 14), (25, * 21), (27, 3), (27, 11), (30, 23), (30, 28) */ @Test public void testGetMatching28() { int[][] edges = new int[][] { { 0, 2, 5 }, { 0, 3, 3 }, { 2, 3, 3 }, { 0, 1, 17 }, { 0, 4, 13 }, { 1, 4, 5 }, { 2, 4, 10 }, { 4, 5, 9 }, { 1, 5, 8 }, { 5, 7, 6 }, { 4, 7, 12 }, { 5, 6, 6 }, { 6, 7, 2 }, { 6, 8, 3 }, { 7, 8, 2 }, { 2, 9, 16 }, { 3, 9, 15 }, { 0, 8, 18 }, { 2, 10, 16 }, { 4, 10, 15 }, { 9, 10, 12 }, { 7, 10, 13 }, { 8, 10, 12 }, { 5, 11, 14 }, { 6, 11, 9 }, { 8, 11, 8 }, { 8, 12, 7 }, { 11, 12, 7 }, { 8, 13, 10 }, { 10, 13, 5 }, { 12, 13, 4 }, { 10, 14, 5 }, { 13, 14, 2 }, { 10, 15, 8 }, { 14, 15, 9 }, { 9, 15, 9 }, { 12, 16, 7 }, { 11, 16, 5 }, { 14, 17, 4 }, { 15, 17, 11 }, { 12, 17, 5 }, { 16, 17, 8 }, { 13, 17, 5 }, { 17, 18, 13 }, { 15, 18, 6 }, { 16, 18, 21 }, { 15, 19, 9 }, { 9, 19, 12 }, { 18, 19, 5 } }; double maxWeight = 117; double minWeight = 57; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on triangulation of 50 points */ @Test public void testGetMatching29() { int[][] edges = new int[][] { { 0, 2, 44 }, { 0, 3, 50 }, { 2, 3, 6 }, { 2, 4, 6 }, { 3, 4, 7 }, { 3, 5, 6 }, { 4, 5, 5 }, { 0, 1, 16 }, { 0, 6, 15 }, { 1, 6, 15 }, { 0, 7, 21 }, { 6, 7, 22 }, { 2, 7, 30 }, { 4, 7, 30 }, { 6, 8, 5 }, { 1, 8, 16 }, { 4, 9, 16 }, { 5, 9, 13 }, { 3, 9, 18 }, { 6, 10, 7 }, { 7, 10, 21 }, { 8, 10, 6 }, { 7, 12, 22 }, { 10, 12, 9 }, { 4, 13, 25 }, { 7, 13, 23 }, { 9, 13, 26 }, { 10, 15, 11 }, { 12, 15, 6 }, { 8, 15, 14 }, { 12, 16, 5 }, { 15, 16, 3 }, { 11, 14, 3 }, { 11, 17, 10 }, { 14, 17, 9 }, { 9, 11, 8 }, { 9, 17, 15 }, { 13, 17, 19 }, { 17, 18, 2 }, { 14, 18, 8 }, { 8, 19, 21 }, { 15, 19, 14 }, { 1, 19, 33 }, { 17, 20, 10 }, { 13, 20, 14 }, { 18, 20, 10 }, { 11, 21, 28 }, { 14, 21, 26 }, { 9, 21, 34 }, { 3, 21, 50 }, { 7, 22, 30 }, { 12, 22, 29 }, { 13, 22, 16 }, { 13, 23, 14 }, { 20, 23, 9 }, { 22, 23, 10 }, { 15, 24, 15 }, { 16, 24, 14 }, { 19, 24, 13 }, { 16, 25, 15 }, { 24, 25, 12 }, { 12, 25, 19 }, { 22, 25, 25 }, { 20, 26, 9 }, { 23, 26, 10 }, { 18, 26, 16 }, { 23, 27, 8 }, { 22, 27, 9 }, { 26, 27, 13 }, { 18, 28, 24 }, { 26, 28, 22 }, { 14, 28, 27 }, { 21, 28, 23 }, { 22, 29, 18 }, { 25, 29, 24 }, { 22, 30, 18 }, { 27, 30, 14 }, { 29, 30, 3 }, { 24, 31, 20 }, { 19, 31, 29 }, { 25, 31, 24 }, { 26, 32, 18 }, { 28, 32, 18 }, { 27, 32, 24 }, { 30, 32, 26 }, { 28, 33, 14 }, { 21, 33, 27 }, { 30, 34, 14 }, { 29, 34, 12 }, { 25, 34, 27 }, { 31, 34, 30 }, { 30, 36, 18 }, { 34, 36, 14 }, { 32, 36, 26 }, { 32, 35, 9 }, { 35, 36, 23 }, { 32, 37, 20 }, { 35, 37, 17 }, { 28, 37, 22 }, { 33, 37, 14 }, { 34, 38, 13 }, { 36, 38, 20 }, { 31, 38, 27 }, { 21, 39, 42 }, { 33, 39, 17 }, { 33, 40, 18 }, { 37, 40, 10 }, { 39, 40, 7 }, { 37, 41, 10 }, { 35, 41, 17 }, { 40, 41, 14 }, { 38, 42, 11 }, { 31, 42, 28 }, { 40, 43, 18 }, { 41, 43, 8 }, { 35, 43, 22 }, { 40, 44, 14 }, { 39, 44, 11 }, { 21, 44, 52 }, { 42, 45, 14 }, { 31, 45, 32 }, { 19, 45, 60 }, { 42, 46, 19 }, { 45, 46, 29 }, { 38, 46, 21 }, { 36, 46, 24 }, { 36, 47, 35 }, { 46, 47, 37 }, { 35, 47, 26 }, { 43, 47, 6 }, { 46, 48, 63 }, { 47, 48, 26 }, { 43, 48, 24 }, { 40, 48, 18 }, { 44, 48, 8 }, { 44, 49, 8 }, { 48, 49, 1 } }; double maxWeight = 605; double minWeight = 279; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on triangulation of 50 points */ @Test public void testGetMatching30() { int[][] edges = new int[][] { { 0, 1, 34 }, { 0, 2, 146 }, { 1, 2, 113 }, { 1, 3, 34 }, { 2, 3, 80 }, { 1, 5, 18 }, { 3, 5, 36 }, { 0, 5, 39 }, { 0, 4, 34 }, { 0, 7, 29 }, { 4, 7, 23 }, { 5, 7, 47 }, { 2, 8, 43 }, { 3, 8, 65 }, { 2, 6, 24 }, { 6, 8, 40 }, { 4, 9, 33 }, { 7, 9, 25 }, { 3, 10, 46 }, { 5, 10, 37 }, { 3, 11, 71 }, { 10, 11, 63 }, { 8, 11, 21 }, { 5, 12, 42 }, { 7, 12, 65 }, { 10, 12, 16 }, { 7, 13, 40 }, { 9, 13, 20 }, { 8, 15, 38 }, { 6, 15, 59 }, { 11, 15, 25 }, { 11, 14, 20 }, { 14, 15, 8 }, { 7, 16, 68 }, { 12, 16, 17 }, { 13, 16, 60 }, { 11, 17, 20 }, { 14, 17, 13 }, { 12, 18, 39 }, { 16, 18, 39 }, { 10, 18, 39 }, { 11, 18, 53 }, { 11, 19, 45 }, { 17, 19, 32 }, { 18, 19, 15 }, { 14, 20, 20 }, { 17, 20, 24 }, { 15, 20, 20 }, { 18, 21, 13 }, { 19, 21, 24 }, { 16, 21, 34 }, { 17, 22, 25 }, { 19, 22, 36 }, { 20, 22, 14 }, { 15, 23, 32 }, { 6, 23, 82 }, { 20, 23, 22 }, { 9, 24, 65 }, { 13, 24, 46 }, { 4, 24, 97 }, { 13, 25, 47 }, { 16, 25, 63 }, { 24, 25, 8 }, { 20, 26, 26 }, { 22, 26, 23 }, { 23, 26, 23 }, { 19, 27, 36 }, { 21, 27, 34 }, { 22, 27, 48 }, { 23, 28, 32 }, { 26, 28, 15 }, { 23, 29, 37 }, { 28, 29, 21 }, { 6, 29, 117 }, { 27, 30, 20 }, { 27, 31, 32 }, { 30, 31, 20 }, { 22, 31, 49 }, { 26, 31, 42 }, { 26, 32, 30 }, { 28, 32, 21 }, { 31, 32, 21 }, { 31, 33, 6 }, { 30, 33, 15 }, { 25, 34, 43 }, { 24, 34, 45 }, { 27, 35, 47 }, { 30, 35, 41 }, { 21, 35, 64 }, { 25, 36, 64 }, { 34, 36, 37 }, { 16, 36, 85 }, { 21, 36, 77 }, { 35, 36, 23 }, { 34, 37, 12 }, { 36, 37, 29 }, { 6, 38, 139 }, { 29, 38, 23 }, { 35, 39, 13 }, { 36, 39, 11 }, { 35, 40, 7 }, { 39, 40, 13 }, { 35, 41, 11 }, { 30, 41, 40 }, { 40, 41, 7 }, { 31, 42, 21 }, { 32, 42, 30 }, { 33, 42, 20 }, { 30, 42, 30 }, { 41, 42, 53 }, { 28, 43, 37 }, { 32, 43, 37 }, { 29, 43, 30 }, { 38, 43, 14 }, { 38, 44, 11 }, { 43, 44, 8 }, { 42, 45, 65 }, { 41, 45, 16 }, { 40, 45, 14 }, { 39, 45, 16 }, { 36, 45, 23 }, { 36, 46, 27 }, { 45, 46, 37 }, { 37, 46, 18 }, { 42, 47, 14 }, { 45, 47, 75 }, { 43, 47, 42 }, { 44, 47, 49 }, { 32, 47, 32 }, { 45, 48, 62 }, { 47, 48, 136 }, { 46, 48, 27 }, { 37, 48, 32 }, { 34, 48, 36 }, { 47, 49, 140 }, { 48, 49, 5 }, { 24, 49, 79 }, { 4, 49, 175 }, { 34, 49, 38 } }; double maxWeight = 1426; double minWeight = 496; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on triangulation of 100 points */ @Test public void testGetMatching31() { int[][] edges = new int[][] { { 0, 1, 118 }, { 0, 2, 125 }, { 1, 2, 7 }, { 0, 3, 66 }, { 1, 3, 54 }, { 0, 4, 41 }, { 3, 4, 27 }, { 0, 5, 19 }, { 4, 5, 30 }, { 5, 6, 16 }, { 0, 6, 19 }, { 1, 7, 13 }, { 3, 7, 44 }, { 5, 8, 29 }, { 4, 8, 6 }, { 4, 9, 11 }, { 3, 9, 19 }, { 8, 9, 10 }, { 1, 10, 15 }, { 7, 10, 11 }, { 2, 10, 17 }, { 2, 11, 30 }, { 2, 12, 35 }, { 11, 12, 6 }, { 9, 13, 16 }, { 8, 13, 11 }, { 2, 14, 22 }, { 10, 14, 10 }, { 8, 15, 16 }, { 5, 15, 27 }, { 13, 15, 7 }, { 7, 16, 22 }, { 10, 16, 25 }, { 3, 16, 34 }, { 11, 17, 8 }, { 12, 17, 10 }, { 2, 17, 31 }, { 14, 17, 25 }, { 6, 18, 25 }, { 6, 19, 25 }, { 18, 19, 3 }, { 6, 20, 34 }, { 0, 20, 52 }, { 18, 20, 11 }, { 19, 20, 13 }, { 14, 21, 24 }, { 17, 21, 13 }, { 14, 22, 16 }, { 14, 23, 16 }, { 22, 23, 4 }, { 21, 23, 18 }, { 17, 24, 16 }, { 12, 24, 19 }, { 21, 24, 12 }, { 14, 25, 18 }, { 22, 25, 4 }, { 10, 25, 25 }, { 16, 25, 30 }, { 23, 25, 8 }, { 19, 26, 12 }, { 20, 26, 19 }, { 23, 27, 13 }, { 25, 27, 7 }, { 5, 28, 41 }, { 15, 28, 32 }, { 19, 28, 29 }, { 26, 28, 21 }, { 6, 28, 41 }, { 9, 29, 42 }, { 13, 29, 43 }, { 3, 29, 43 }, { 16, 29, 34 }, { 15, 29, 46 }, { 25, 30, 10 }, { 16, 30, 31 }, { 27, 30, 4 }, { 16, 31, 32 }, { 29, 31, 33 }, { 30, 31, 13 }, { 15, 32, 38 }, { 29, 32, 50 }, { 28, 32, 20 }, { 27, 33, 23 }, { 30, 33, 24 }, { 21, 33, 27 }, { 24, 33, 32 }, { 23, 33, 25 }, { 28, 34, 23 }, { 32, 34, 13 }, { 26, 34, 37 }, { 24, 35, 32 }, { 33, 35, 20 }, { 24, 36, 33 }, { 12, 36, 51 }, { 35, 36, 8 }, { 30, 37, 26 }, { 33, 37, 14 }, { 31, 37, 28 }, { 29, 38, 28 }, { 32, 38, 38 }, { 29, 39, 33 }, { 31, 39, 33 }, { 38, 39, 22 }, { 33, 40, 20 }, { 35, 40, 18 }, { 37, 40, 17 }, { 38, 41, 21 }, { 39, 41, 21 }, { 31, 42, 41 }, { 37, 42, 42 }, { 39, 42, 17 }, { 38, 43, 24 }, { 41, 43, 17 }, { 32, 43, 42 }, { 39, 44, 17 }, { 41, 44, 12 }, { 42, 44, 13 }, { 35, 45, 28 }, { 40, 45, 18 }, { 36, 45, 29 }, { 26, 46, 56 }, { 34, 46, 33 }, { 20, 46, 69 }, { 36, 48, 31 }, { 45, 48, 5 }, { 46, 49, 4 }, { 46, 50, 13 }, { 49, 50, 13 }, { 34, 50, 32 }, { 32, 51, 42 }, { 34, 51, 38 }, { 43, 51, 21 }, { 42, 52, 11 }, { 44, 52, 13 }, { 42, 47, 9 }, { 47, 52, 9 }, { 34, 53, 35 }, { 50, 53, 8 }, { 51, 53, 17 }, { 37, 54, 38 }, { 40, 54, 40 }, { 42, 54, 22 }, { 47, 54, 14 }, { 50, 55, 14 }, { 53, 55, 8 }, { 51, 55, 17 }, { 51, 57, 19 }, { 55, 57, 30 }, { 43, 57, 20 }, { 44, 58, 20 }, { 52, 58, 19 }, { 41, 58, 21 }, { 43, 58, 28 }, { 57, 58, 23 }, { 40, 59, 39 }, { 45, 59, 31 }, { 54, 59, 19 }, { 48, 59, 32 }, { 48, 56, 13 }, { 56, 59, 34 }, { 48, 60, 29 }, { 36, 60, 51 }, { 56, 60, 18 }, { 12, 60, 100 }, { 52, 61, 20 }, { 58, 61, 21 }, { 47, 61, 23 }, { 54, 61, 26 }, { 46, 62, 31 }, { 49, 62, 28 }, { 20, 62, 90 }, { 54, 63, 25 }, { 61, 63, 8 }, { 49, 64, 28 }, { 62, 64, 14 }, { 50, 64, 32 }, { 55, 64, 31 }, { 58, 65, 20 }, { 61, 65, 30 }, { 57, 65, 20 }, { 56, 66, 27 }, { 59, 66, 22 }, { 60, 66, 35 }, { 61, 67, 13 }, { 63, 67, 7 }, { 59, 68, 20 }, { 66, 68, 23 }, { 63, 68, 23 }, { 67, 68, 20 }, { 54, 68, 30 }, { 57, 69, 27 }, { 55, 69, 40 }, { 65, 69, 17 }, { 66, 70, 9 }, { 68, 70, 22 }, { 64, 71, 17 }, { 62, 71, 20 }, { 55, 72, 31 }, { 64, 72, 29 }, { 69, 72, 29 }, { 66, 73, 28 }, { 70, 73, 25 }, { 60, 73, 28 }, { 70, 74, 25 }, { 73, 74, 3 }, { 68, 75, 27 }, { 67, 75, 19 }, { 65, 76, 25 }, { 69, 76, 24 }, { 67, 76, 37 }, { 75, 76, 31 }, { 61, 76, 38 }, { 64, 77, 26 }, { 72, 77, 25 }, { 71, 77, 16 }, { 77, 78, 17 }, { 71, 78, 15 }, { 62, 78, 33 }, { 69, 79, 25 }, { 72, 79, 18 }, { 76, 80, 10 }, { 75, 80, 26 }, { 68, 81, 31 }, { 70, 81, 42 }, { 75, 81, 10 }, { 73, 82, 15 }, { 74, 82, 14 }, { 60, 82, 41 }, { 72, 83, 21 }, { 77, 83, 17 }, { 79, 83, 19 }, { 75, 85, 13 }, { 81, 85, 9 }, { 80, 85, 21 }, { 80, 84, 4 }, { 84, 85, 18 }, { 77, 86, 23 }, { 78, 86, 13 }, { 81, 87, 14 }, { 85, 87, 7 }, { 84, 87, 18 }, { 70, 88, 33 }, { 81, 88, 47 }, { 74, 88, 27 }, { 82, 88, 23 }, { 79, 89, 16 }, { 83, 89, 17 }, { 79, 90, 21 }, { 89, 90, 15 }, { 69, 90, 36 }, { 76, 90, 36 }, { 80, 91, 37 }, { 84, 91, 37 }, { 76, 91, 37 }, { 90, 91, 4 }, { 82, 93, 30 }, { 88, 93, 9 }, { 83, 94, 25 }, { 89, 94, 30 }, { 77, 94, 27 }, { 86, 94, 20 }, { 86, 92, 10 }, { 92, 94, 14 }, { 81, 95, 23 }, { 87, 95, 24 }, { 88, 95, 36 }, { 93, 95, 32 }, { 92, 96, 10 }, { 86, 96, 15 }, { 78, 96, 28 }, { 62, 96, 61 }, { 20, 96, 150 }, { 92, 97, 12 }, { 94, 97, 22 }, { 96, 97, 3 }, { 94, 98, 34 }, { 97, 98, 54 }, { 89, 98, 16 }, { 90, 98, 15 }, { 91, 98, 15 }, { 93, 99, 41 }, { 95, 99, 12 }, { 97, 99, 128 }, { 98, 99, 74 }, { 91, 99, 63 }, { 87, 99, 22 }, { 84, 99, 37 } }; double maxWeight = 1781; double minWeight = 693; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on triangulation of 100 points */ @Test public void testGetMatching32() { int[][] edges = new int[][] { { 0, 1, 48 }, { 0, 3, 23 }, { 1, 3, 27 }, { 1, 6, 14 }, { 3, 6, 16 }, { 0, 2, 80 }, { 0, 7, 53 }, { 2, 7, 28 }, { 3, 8, 13 }, { 6, 8, 5 }, { 0, 9, 43 }, { 7, 9, 13 }, { 6, 10, 10 }, { 8, 10, 11 }, { 1, 10, 17 }, { 1, 4, 15 }, { 4, 10, 21 }, { 0, 11, 25 }, { 9, 11, 23 }, { 2, 12, 26 }, { 7, 12, 40 }, { 2, 5, 20 }, { 5, 12, 21 }, { 5, 13, 27 }, { 12, 13, 29 }, { 10, 14, 16 }, { 4, 14, 25 }, { 7, 15, 25 }, { 9, 15, 19 }, { 0, 16, 35 }, { 3, 16, 37 }, { 11, 16, 23 }, { 7, 17, 26 }, { 15, 17, 4 }, { 11, 18, 22 }, { 16, 18, 5 }, { 12, 19, 9 }, { 13, 19, 30 }, { 3, 20, 31 }, { 16, 20, 15 }, { 11, 21, 25 }, { 18, 21, 25 }, { 9, 21, 26 }, { 15, 21, 14 }, { 10, 22, 32 }, { 14, 22, 33 }, { 8, 22, 31 }, { 3, 22, 33 }, { 20, 22, 9 }, { 12, 23, 16 }, { 19, 23, 8 }, { 15, 24, 13 }, { 17, 24, 13 }, { 21, 24, 9 }, { 21, 25, 7 }, { 18, 25, 28 }, { 24, 25, 6 }, { 17, 26, 16 }, { 24, 26, 20 }, { 17, 27, 20 }, { 7, 27, 39 }, { 26, 27, 6 }, { 12, 27, 33 }, { 23, 27, 21 }, { 22, 29, 17 }, { 14, 29, 28 }, { 22, 30, 17 }, { 29, 30, 9 }, { 26, 31, 11 }, { 27, 31, 13 }, { 24, 31, 20 }, { 25, 31, 24 }, { 19, 32, 29 }, { 23, 32, 31 }, { 13, 32, 29 }, { 13, 28, 21 }, { 28, 32, 15 }, { 29, 33, 22 }, { 14, 33, 29 }, { 30, 33, 27 }, { 16, 34, 29 }, { 18, 34, 30 }, { 20, 34, 23 }, { 22, 34, 22 }, { 30, 34, 14 }, { 23, 35, 24 }, { 32, 35, 31 }, { 27, 35, 25 }, { 31, 35, 28 }, { 32, 36, 15 }, { 28, 36, 24 }, { 35, 36, 34 }, { 25, 37, 31 }, { 31, 37, 35 }, { 18, 37, 42 }, { 34, 37, 38 }, { 14, 38, 52 }, { 4, 38, 70 }, { 33, 38, 30 }, { 31, 39, 28 }, { 35, 39, 16 }, { 30, 40, 23 }, { 33, 40, 35 }, { 34, 40, 21 }, { 35, 42, 25 }, { 36, 42, 32 }, { 39, 42, 19 }, { 39, 41, 7 }, { 41, 42, 16 }, { 42, 43, 26 }, { 36, 43, 17 }, { 28, 43, 40 }, { 34, 44, 40 }, { 37, 44, 19 }, { 40, 45, 14 }, { 40, 46, 16 }, { 45, 46, 5 }, { 34, 46, 32 }, { 44, 46, 29 }, { 41, 47, 15 }, { 42, 47, 26 }, { 39, 47, 19 }, { 31, 47, 38 }, { 37, 47, 43 }, { 44, 48, 22 }, { 46, 48, 13 }, { 42, 49, 18 }, { 43, 49, 18 }, { 49, 50, 9 }, { 43, 50, 17 }, { 40, 51, 34 }, { 45, 51, 25 }, { 33, 51, 49 }, { 38, 51, 48 }, { 37, 52, 40 }, { 44, 52, 35 }, { 47, 52, 23 }, { 44, 54, 25 }, { 48, 54, 16 }, { 51, 53, 6 }, { 51, 55, 21 }, { 53, 55, 22 }, { 45, 55, 23 }, { 46, 55, 23 }, { 48, 55, 20 }, { 49, 56, 14 }, { 50, 56, 13 }, { 44, 57, 33 }, { 52, 57, 14 }, { 48, 58, 19 }, { 54, 58, 10 }, { 44, 59, 28 }, { 54, 59, 17 }, { 57, 59, 14 }, { 48, 60, 20 }, { 55, 60, 9 }, { 58, 60, 8 }, { 51, 62, 23 }, { 53, 62, 19 }, { 38, 62, 51 }, { 58, 63, 7 }, { 60, 63, 12 }, { 54, 63, 13 }, { 56, 64, 15 }, { 50, 64, 21 }, { 56, 61, 8 }, { 61, 64, 10 }, { 43, 64, 37 }, { 47, 65, 35 }, { 52, 65, 36 }, { 42, 65, 39 }, { 49, 65, 34 }, { 56, 65, 26 }, { 55, 66, 13 }, { 53, 66, 24 }, { 60, 66, 12 }, { 56, 67, 21 }, { 65, 67, 7 }, { 54, 68, 15 }, { 59, 68, 16 }, { 63, 68, 9 }, { 60, 69, 11 }, { 63, 69, 10 }, { 66, 69, 12 }, { 56, 70, 19 }, { 61, 70, 16 }, { 67, 70, 7 }, { 65, 70, 13 }, { 52, 71, 25 }, { 57, 71, 27 }, { 65, 71, 22 }, { 62, 72, 17 }, { 38, 72, 61 }, { 66, 73, 17 }, { 69, 73, 22 }, { 62, 73, 32 }, { 72, 73, 38 }, { 53, 73, 31 }, { 69, 74, 18 }, { 73, 74, 29 }, { 63, 74, 20 }, { 68, 74, 18 }, { 61, 75, 27 }, { 64, 75, 24 }, { 70, 75, 25 }, { 64, 76, 29 }, { 75, 76, 13 }, { 43, 76, 65 }, { 28, 76, 104 }, { 57, 77, 40 }, { 71, 77, 33 }, { 59, 77, 41 }, { 68, 77, 41 }, { 68, 78, 32 }, { 74, 78, 19 }, { 77, 78, 24 }, { 74, 79, 19 }, { 78, 79, 8 }, { 72, 81, 47 }, { 73, 81, 26 }, { 72, 80, 28 }, { 80, 81, 43 }, { 71, 82, 37 }, { 77, 82, 33 }, { 65, 82, 43 }, { 70, 82, 44 }, { 70, 83, 40 }, { 75, 83, 27 }, { 82, 83, 24 }, { 78, 84, 14 }, { 77, 84, 31 }, { 79, 84, 11 }, { 77, 85, 16 }, { 82, 85, 27 }, { 84, 85, 31 }, { 82, 86, 13 }, { 83, 86, 13 }, { 79, 87, 26 }, { 84, 87, 28 }, { 74, 87, 35 }, { 73, 87, 33 }, { 81, 87, 12 }, { 72, 88, 39 }, { 38, 88, 96 }, { 80, 88, 12 }, { 86, 89, 19 }, { 83, 89, 9 }, { 75, 89, 29 }, { 76, 89, 31 }, { 84, 90, 9 }, { 87, 90, 26 }, { 87, 91, 14 }, { 90, 91, 27 }, { 81, 91, 22 }, { 86, 92, 15 }, { 89, 92, 26 }, { 82, 92, 20 }, { 82, 93, 23 }, { 85, 93, 30 }, { 92, 93, 9 }, { 81, 94, 28 }, { 91, 94, 25 }, { 80, 94, 37 }, { 88, 94, 36 }, { 94, 95, 29 }, { 88, 95, 17 }, { 90, 96, 15 }, { 91, 96, 22 }, { 91, 97, 22 }, { 96, 97, 4 }, { 85, 98, 29 }, { 93, 98, 40 }, { 96, 98, 25 }, { 97, 98, 25 }, { 90, 98, 25 }, { 84, 98, 29 }, { 94, 99, 8 }, { 95, 99, 29 }, { 91, 99, 27 }, { 97, 99, 44 }, { 98, 99, 69 } }; double maxWeight = 1576; double minWeight = 728; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on triangulation of 200 points */ @Test public void testGetMatching33() { int[][] edges = new int[][] { { 1, 2, 47 }, { 1, 3, 33 }, { 2, 3, 15 }, { 1, 5, 18 }, { 3, 5, 16 }, { 1, 4, 26 }, { 1, 6, 68 }, { 4, 6, 43 }, { 1, 0, 129 }, { 0, 6, 63 }, { 4, 7, 11 }, { 6, 7, 32 }, { 0, 9, 16 }, { 0, 10, 11 }, { 9, 10, 9 }, { 3, 11, 9 }, { 5, 11, 11 }, { 3, 12, 10 }, { 11, 12, 13 }, { 2, 12, 11 }, { 2, 8, 8 }, { 8, 12, 13 }, { 1, 13, 23 }, { 4, 13, 8 }, { 4, 14, 14 }, { 7, 14, 7 }, { 13, 14, 18 }, { 1, 15, 14 }, { 5, 15, 12 }, { 0, 16, 25 }, { 6, 16, 42 }, { 10, 16, 28 }, { 13, 17, 3 }, { 14, 17, 20 }, { 1, 17, 22 }, { 1, 18, 17 }, { 17, 18, 11 }, { 1, 20, 16 }, { 15, 20, 8 }, { 18, 20, 11 }, { 6, 21, 26 }, { 16, 21, 19 }, { 7, 22, 18 }, { 14, 22, 13 }, { 6, 22, 22 }, { 6, 19, 13 }, { 19, 22, 12 }, { 6, 23, 20 }, { 19, 23, 25 }, { 21, 23, 8 }, { 15, 24, 8 }, { 20, 24, 5 }, { 15, 25, 13 }, { 24, 25, 11 }, { 5, 25, 18 }, { 11, 25, 16 }, { 9, 26, 16 }, { 10, 26, 14 }, { 16, 27, 13 }, { 21, 27, 13 }, { 20, 28, 14 }, { 18, 28, 9 }, { 24, 28, 17 }, { 17, 28, 13 }, { 11, 29, 14 }, { 12, 29, 16 }, { 21, 30, 10 }, { 27, 30, 5 }, { 11, 31, 17 }, { 25, 31, 3 }, { 29, 31, 14 }, { 9, 32, 20 }, { 26, 32, 5 }, { 12, 33, 19 }, { 29, 33, 16 }, { 8, 33, 21 }, { 26, 34, 8 }, { 32, 34, 7 }, { 10, 34, 20 }, { 23, 35, 13 }, { 19, 35, 26 }, { 21, 35, 17 }, { 30, 35, 19 }, { 29, 36, 18 }, { 33, 36, 4 }, { 8, 36, 24 }, { 24, 37, 14 }, { 28, 37, 25 }, { 25, 37, 10 }, { 31, 37, 9 }, { 34, 38, 5 }, { 34, 39, 7 }, { 38, 39, 3 }, { 10, 39, 24 }, { 16, 39, 28 }, { 19, 40, 17 }, { 22, 40, 18 }, { 31, 41, 9 }, { 29, 41, 18 }, { 37, 41, 4 }, { 27, 42, 19 }, { 30, 42, 22 }, { 16, 42, 22 }, { 39, 42, 13 }, { 22, 43, 18 }, { 14, 43, 24 }, { 40, 43, 12 }, { 39, 44, 4 }, { 42, 44, 13 }, { 38, 44, 5 }, { 17, 45, 25 }, { 28, 45, 15 }, { 19, 46, 24 }, { 35, 46, 16 }, { 40, 46, 12 }, { 40, 47, 9 }, { 46, 47, 4 }, { 37, 48, 10 }, { 41, 48, 7 }, { 29, 48, 20 }, { 14, 49, 28 }, { 43, 49, 7 }, { 8, 50, 33 }, { 36, 50, 11 }, { 47, 51, 4 }, { 46, 51, 7 }, { 43, 51, 17 }, { 49, 51, 20 }, { 40, 51, 10 }, { 28, 52, 19 }, { 37, 52, 24 }, { 45, 52, 8 }, { 14, 53, 33 }, { 49, 53, 18 }, { 17, 53, 30 }, { 45, 53, 13 }, { 38, 54, 18 }, { 44, 54, 18 }, { 34, 54, 18 }, { 32, 54, 17 }, { 9, 54, 36 }, { 37, 55, 22 }, { 48, 55, 23 }, { 52, 55, 9 }, { 46, 56, 13 }, { 51, 56, 16 }, { 35, 56, 19 }, { 49, 57, 14 }, { 53, 57, 10 }, { 36, 58, 19 }, { 50, 58, 15 }, { 29, 58, 27 }, { 48, 58, 25 }, { 52, 59, 9 }, { 55, 59, 8 }, { 45, 59, 13 }, { 9, 60, 44 }, { 54, 60, 9 }, { 54, 61, 9 }, { 44, 61, 21 }, { 60, 61, 3 }, { 44, 62, 25 }, { 42, 62, 20 }, { 42, 63, 23 }, { 30, 63, 29 }, { 62, 63, 6 }, { 45, 64, 15 }, { 53, 64, 15 }, { 59, 64, 6 }, { 58, 65, 5 }, { 50, 65, 17 }, { 58, 66, 12 }, { 48, 66, 20 }, { 65, 66, 13 }, { 35, 67, 32 }, { 56, 67, 27 }, { 30, 67, 31 }, { 63, 67, 4 }, { 51, 68, 15 }, { 56, 68, 15 }, { 49, 68, 26 }, { 48, 69, 23 }, { 55, 69, 12 }, { 44, 70, 24 }, { 62, 70, 10 }, { 63, 70, 14 }, { 48, 71, 22 }, { 66, 71, 24 }, { 69, 71, 4 }, { 44, 72, 24 }, { 61, 72, 15 }, { 70, 72, 14 }, { 53, 73, 18 }, { 57, 73, 15 }, { 64, 73, 11 }, { 50, 74, 24 }, { 65, 74, 9 }, { 66, 74, 14 }, { 57, 75, 14 }, { 73, 75, 6 }, { 69, 76, 9 }, { 71, 76, 11 }, { 59, 76, 16 }, { 64, 76, 17 }, { 55, 76, 16 }, { 70, 77, 12 }, { 72, 77, 7 }, { 70, 78, 8 }, { 77, 78, 6 }, { 56, 79, 19 }, { 67, 79, 20 }, { 68, 79, 24 }, { 49, 80, 27 }, { 68, 80, 18 }, { 57, 81, 23 }, { 75, 81, 21 }, { 49, 81, 27 }, { 80, 81, 1 }, { 75, 82, 6 }, { 81, 82, 20 }, { 73, 82, 10 }, { 72, 83, 12 }, { 77, 83, 13 }, { 61, 83, 17 }, { 60, 83, 18 }, { 68, 84, 14 }, { 80, 84, 8 }, { 83, 85, 3 }, { 60, 85, 19 }, { 70, 86, 13 }, { 78, 86, 10 }, { 63, 86, 19 }, { 67, 86, 19 }, { 64, 87, 19 }, { 73, 87, 18 }, { 76, 87, 12 }, { 73, 88, 14 }, { 82, 88, 11 }, { 87, 88, 11 }, { 80, 90, 11 }, { 84, 90, 8 }, { 71, 91, 19 }, { 76, 91, 20 }, { 66, 91, 28 }, { 82, 92, 10 }, { 88, 92, 6 }, { 85, 93, 9 }, { 60, 93, 26 }, { 85, 89, 5 }, { 89, 93, 5 }, { 89, 94, 6 }, { 93, 94, 8 }, { 85, 94, 9 }, { 83, 94, 11 }, { 77, 94, 17 }, { 78, 94, 21 }, { 86, 94, 26 }, { 80, 95, 12 }, { 81, 95, 13 }, { 90, 95, 7 }, { 66, 96, 26 }, { 91, 96, 10 }, { 74, 96, 26 }, { 81, 97, 14 }, { 82, 97, 20 }, { 95, 97, 5 }, { 90, 98, 12 }, { 95, 98, 17 }, { 84, 98, 15 }, { 68, 98, 25 }, { 79, 98, 25 }, { 76, 99, 19 }, { 87, 99, 18 }, { 91, 99, 10 }, { 96, 101, 22 }, { 74, 101, 22 }, { 96, 100, 5 }, { 100, 101, 19 }, { 50, 101, 43 }, { 67, 102, 29 }, { 79, 102, 23 }, { 86, 102, 20 }, { 91, 103, 9 }, { 99, 103, 5 }, { 96, 103, 14 }, { 100, 103, 16 }, { 86, 104, 17 }, { 102, 104, 9 }, { 82, 105, 20 }, { 92, 105, 15 }, { 97, 105, 11 }, { 99, 106, 6 }, { 103, 106, 3 }, { 79, 107, 24 }, { 98, 107, 24 }, { 102, 107, 13 }, { 92, 108, 14 }, { 105, 108, 10 }, { 87, 109, 22 }, { 99, 109, 29 }, { 88, 109, 17 }, { 92, 109, 14 }, { 108, 109, 5 }, { 98, 110, 20 }, { 107, 110, 13 }, { 99, 111, 16 }, { 106, 111, 12 }, { 109, 111, 25 }, { 50, 112, 57 }, { 101, 112, 15 }, { 94, 113, 22 }, { 94, 114, 26 }, { 113, 114, 7 }, { 86, 114, 31 }, { 104, 114, 23 }, { 101, 115, 18 }, { 100, 115, 23 }, { 112, 115, 6 }, { 104, 116, 17 }, { 114, 116, 26 }, { 102, 116, 17 }, { 107, 116, 17 }, { 100, 117, 18 }, { 115, 117, 16 }, { 106, 117, 23 }, { 111, 117, 23 }, { 103, 117, 23 }, { 107, 118, 14 }, { 110, 118, 10 }, { 116, 118, 14 }, { 110, 119, 15 }, { 118, 119, 22 }, { 98, 119, 23 }, { 94, 120, 27 }, { 113, 120, 13 }, { 93, 120, 28 }, { 95, 121, 27 }, { 97, 121, 27 }, { 98, 121, 26 }, { 119, 121, 10 }, { 105, 122, 22 }, { 108, 122, 25 }, { 97, 122, 26 }, { 121, 122, 11 }, { 111, 123, 14 }, { 117, 123, 17 }, { 113, 124, 14 }, { 114, 124, 10 }, { 120, 124, 21 }, { 116, 125, 10 }, { 118, 125, 11 }, { 108, 126, 27 }, { 122, 126, 3 }, { 121, 126, 11 }, { 121, 127, 7 }, { 126, 127, 12 }, { 119, 127, 12 }, { 114, 128, 20 }, { 116, 128, 17 }, { 124, 128, 12 }, { 116, 129, 16 }, { 125, 129, 13 }, { 128, 129, 6 }, { 125, 130, 6 }, { 129, 130, 11 }, { 118, 130, 16 }, { 117, 131, 17 }, { 123, 131, 14 }, { 124, 132, 9 }, { 128, 132, 13 }, { 120, 132, 24 }, { 111, 133, 23 }, { 109, 133, 35 }, { 123, 133, 15 }, { 126, 134, 10 }, { 127, 134, 14 }, { 123, 135, 14 }, { 133, 135, 4 }, { 123, 136, 14 }, { 135, 136, 13 }, { 131, 136, 8 }, { 129, 137, 9 }, { 130, 137, 18 }, { 128, 137, 9 }, { 132, 137, 12 }, { 136, 138, 5 }, { 131, 138, 8 }, { 115, 139, 25 }, { 112, 139, 28 }, { 131, 139, 23 }, { 138, 139, 23 }, { 117, 139, 29 }, { 132, 140, 9 }, { 120, 140, 26 }, { 132, 141, 9 }, { 137, 141, 7 }, { 140, 141, 9 }, { 126, 142, 26 }, { 134, 142, 21 }, { 109, 142, 37 }, { 133, 142, 26 }, { 108, 142, 37 }, { 136, 143, 8 }, { 135, 143, 10 }, { 136, 144, 6 }, { 138, 144, 3 }, { 143, 144, 9 }, { 112, 145, 30 }, { 139, 145, 3 }, { 130, 146, 15 }, { 137, 146, 26 }, { 118, 146, 26 }, { 119, 147, 28 }, { 127, 147, 27 }, { 118, 147, 28 }, { 146, 147, 9 }, { 134, 148, 14 }, { 142, 148, 29 }, { 127, 148, 19 }, { 147, 148, 23 }, { 133, 149, 13 }, { 142, 149, 31 }, { 135, 149, 11 }, { 143, 149, 5 }, { 140, 150, 7 }, { 120, 150, 28 }, { 140, 151, 6 }, { 150, 151, 2 }, { 147, 152, 21 }, { 148, 152, 4 }, { 143, 153, 12 }, { 144, 153, 7 }, { 149, 153, 14 }, { 139, 153, 25 }, { 145, 153, 25 }, { 138, 153, 8 }, { 151, 154, 5 }, { 150, 154, 7 }, { 140, 154, 9 }, { 141, 154, 11 }, { 153, 155, 26 }, { 145, 155, 9 }, { 147, 156, 17 }, { 152, 156, 8 }, { 145, 157, 11 }, { 112, 157, 40 }, { 155, 157, 4 }, { 152, 158, 7 }, { 156, 158, 3 }, { 147, 159, 15 }, { 146, 159, 15 }, { 156, 159, 22 }, { 156, 160, 20 }, { 158, 160, 22 }, { 159, 160, 9 }, { 141, 161, 32 }, { 154, 161, 32 }, { 137, 161, 31 }, { 146, 161, 25 }, { 159, 161, 15 }, { 154, 162, 19 }, { 161, 162, 24 }, { 159, 163, 10 }, { 161, 163, 13 }, { 160, 163, 6 }, { 155, 164, 23 }, { 153, 164, 22 }, { 149, 165, 29 }, { 142, 165, 31 }, { 149, 166, 27 }, { 153, 166, 33 }, { 165, 166, 4 }, { 155, 167, 22 }, { 164, 167, 7 }, { 157, 167, 22 }, { 157, 168, 19 }, { 167, 168, 17 }, { 112, 168, 58 }, { 50, 168, 114 }, { 150, 169, 29 }, { 120, 169, 51 }, { 154, 169, 30 }, { 162, 169, 25 }, { 93, 169, 78 }, { 60, 169, 103 }, { 152, 170, 26 }, { 158, 170, 24 }, { 148, 170, 29 }, { 142, 170, 37 }, { 158, 171, 23 }, { 160, 171, 30 }, { 170, 171, 5 }, { 161, 172, 15 }, { 163, 172, 11 }, { 160, 172, 16 }, { 165, 173, 9 }, { 166, 173, 10 }, { 160, 174, 30 }, { 171, 174, 7 }, { 171, 175, 8 }, { 170, 175, 7 }, { 174, 175, 7 }, { 170, 176, 16 }, { 175, 176, 14 }, { 165, 176, 23 }, { 173, 176, 20 }, { 142, 176, 38 }, { 166, 177, 20 }, { 173, 177, 23 }, { 153, 177, 33 }, { 164, 177, 22 }, { 173, 178, 7 }, { 177, 178, 24 }, { 176, 178, 19 }, { 164, 179, 23 }, { 167, 179, 24 }, { 177, 179, 12 }, { 162, 180, 29 }, { 169, 180, 18 }, { 177, 181, 11 }, { 179, 181, 3 }, { 160, 182, 26 }, { 172, 182, 14 }, { 174, 182, 28 }, { 162, 183, 24 }, { 180, 183, 23 }, { 161, 183, 33 }, { 172, 183, 33 }, { 174, 184, 19 }, { 175, 184, 24 }, { 182, 184, 18 }, { 167, 185, 31 }, { 179, 185, 34 }, { 168, 185, 25 }, { 179, 186, 11 }, { 181, 186, 9 }, { 177, 186, 18 }, { 178, 186, 30 }, { 168, 187, 27 }, { 50, 187, 140 }, { 185, 187, 2 }, { 185, 188, 4 }, { 187, 188, 3 }, { 179, 188, 34 }, { 180, 189, 12 }, { 183, 189, 22 }, { 183, 190, 13 }, { 189, 190, 18 }, { 178, 191, 35 }, { 186, 191, 13 }, { 179, 192, 31 }, { 188, 192, 15 }, { 182, 193, 29 }, { 184, 193, 40 }, { 183, 193, 25 }, { 190, 193, 20 }, { 172, 193, 35 }, { 190, 194, 15 }, { 189, 194, 13 }, { 186, 195, 18 }, { 191, 195, 11 }, { 179, 195, 26 }, { 192, 195, 20 }, { 190, 196, 13 }, { 193, 196, 19 }, { 194, 196, 13 }, { 193, 197, 20 }, { 196, 197, 1 }, { 194, 197, 14 }, { 191, 198, 30 }, { 195, 198, 39 }, { 178, 198, 34 }, { 195, 199, 42 }, { 198, 199, 4 }, { 193, 199, 80 }, { 197, 199, 98 }, { 184, 199, 48 }, { 175, 199, 46 }, { 176, 199, 40 }, { 178, 199, 35 } }; double maxWeight = 2461; double minWeight = 974; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on triangulation of 300 points */ @Test public void testGetMatching34() { int[][] edges = new int[][] { { 0, 1, 14 }, { 0, 6, 13 }, { 1, 6, 3 }, { 3, 4, 16 }, { 3, 7, 13 }, { 4, 7, 5 }, { 7, 8, 2 }, { 4, 8, 3 }, { 0, 10, 10 }, { 6, 10, 4 }, { 0, 9, 4 }, { 9, 10, 6 }, { 6, 11, 2 }, { 10, 11, 2 }, { 2, 3, 2 }, { 2, 12, 3 }, { 3, 12, 2 }, { 7, 13, 1 }, { 8, 13, 3 }, { 8, 14, 4 }, { 4, 14, 3 }, { 4, 15, 5 }, { 14, 15, 3 }, { 10, 16, 1 }, { 11, 16, 3 }, { 1, 17, 4 }, { 6, 17, 5 }, { 2, 1, 1 }, { 2, 17, 4 }, { 12, 17, 2 }, { 3, 18, 5 }, { 12, 18, 5 }, { 3, 19, 6 }, { 7, 19, 8 }, { 18, 19, 1 }, { 7, 20, 3 }, { 13, 20, 2 }, { 19, 20, 6 }, { 8, 21, 3 }, { 14, 21, 2 }, { 0, 22, 5 }, { 9, 22, 2 }, { 12, 23, 3 }, { 17, 23, 4 }, { 18, 23, 3 }, { 8, 24, 4 }, { 13, 24, 4 }, { 21, 24, 2 }, { 15, 25, 3 }, { 15, 26, 3 }, { 25, 26, 1 }, { 9, 27, 4 }, { 22, 27, 2 }, { 9, 28, 5 }, { 27, 28, 3 }, { 10, 28, 4 }, { 16, 28, 3 }, { 6, 29, 5 }, { 11, 29, 5 }, { 17, 29, 3 }, { 18, 30, 3 }, { 19, 30, 2 }, { 23, 30, 4 }, { 19, 31, 4 }, { 20, 31, 4 }, { 30, 31, 3 }, { 20, 32, 3 }, { 31, 32, 2 }, { 20, 33, 3 }, { 32, 33, 3 }, { 13, 33, 4 }, { 24, 33, 3 }, { 14, 34, 4 }, { 21, 34, 4 }, { 15, 34, 4 }, { 25, 34, 3 }, { 25, 35, 2 }, { 34, 35, 3 }, { 26, 35, 1 }, { 22, 37, 3 }, { 27, 37, 4 }, { 0, 37, 7 }, { 5, 0, 6 }, { 5, 37, 8 }, { 5, 36, 5 }, { 36, 37, 6 }, { 16, 38, 4 }, { 28, 38, 2 }, { 27, 38, 5 }, { 16, 39, 3 }, { 38, 39, 1 }, { 16, 40, 4 }, { 11, 40, 5 }, { 39, 40, 1 }, { 11, 41, 5 }, { 29, 41, 3 }, { 40, 41, 2 }, { 29, 42, 2 }, { 41, 42, 1 }, { 17, 43, 5 }, { 23, 43, 2 }, { 23, 44, 3 }, { 30, 44, 3 }, { 43, 44, 1 }, { 30, 45, 2 }, { 44, 45, 1 }, { 24, 46, 3 }, { 33, 46, 1 }, { 32, 46, 4 }, { 24, 47, 3 }, { 46, 47, 3 }, { 21, 47, 3 }, { 34, 47, 4 }, { 34, 48, 4 }, { 35, 48, 1 }, { 17, 49, 5 }, { 29, 49, 5 }, { 43, 49, 2 }, { 44, 49, 3 }, { 30, 50, 5 }, { 31, 50, 3 }, { 32, 50, 3 }, { 46, 50, 5 }, { 46, 51, 2 }, { 47, 51, 3 }, { 46, 52, 5 }, { 50, 52, 1 }, { 27, 54, 5 }, { 37, 54, 4 }, { 39, 55, 4 }, { 40, 55, 4 }, { 38, 55, 3 }, { 27, 55, 6 }, { 41, 56, 4 }, { 42, 56, 3 }, { 42, 57, 4 }, { 56, 57, 2 }, { 29, 57, 5 }, { 49, 57, 4 }, { 49, 58, 2 }, { 57, 58, 3 }, { 44, 58, 4 }, { 50, 59, 5 }, { 52, 59, 5 }, { 30, 59, 4 }, { 45, 59, 4 }, { 44, 59, 4 }, { 47, 60, 4 }, { 51, 60, 5 }, { 34, 60, 5 }, { 48, 60, 5 }, { 60, 61, 4 }, { 48, 61, 3 }, { 37, 62, 7 }, { 54, 62, 7 }, { 36, 62, 5 }, { 36, 53, 3 }, { 53, 62, 2 }, { 27, 63, 6 }, { 54, 63, 6 }, { 55, 63, 2 }, { 55, 64, 4 }, { 63, 64, 4 }, { 40, 64, 5 }, { 41, 64, 5 }, { 56, 64, 3 }, { 44, 65, 5 }, { 58, 65, 4 }, { 59, 65, 2 }, { 59, 66, 2 }, { 52, 66, 4 }, { 65, 66, 2 }, { 51, 67, 4 }, { 60, 67, 7 }, { 46, 67, 5 }, { 52, 67, 4 }, { 53, 68, 3 }, { 62, 68, 1 }, { 62, 69, 6 }, { 54, 69, 3 }, { 68, 69, 5 }, { 56, 70, 2 }, { 57, 70, 3 }, { 64, 70, 3 }, { 58, 71, 3 }, { 65, 71, 2 }, { 65, 72, 1 }, { 66, 72, 3 }, { 71, 72, 1 }, { 60, 73, 3 }, { 61, 73, 3 }, { 54, 74, 3 }, { 69, 74, 2 }, { 64, 75, 2 }, { 63, 75, 5 }, { 70, 75, 3 }, { 58, 76, 3 }, { 71, 76, 3 }, { 57, 76, 5 }, { 71, 77, 1 }, { 72, 77, 2 }, { 76, 77, 2 }, { 72, 78, 2 }, { 66, 78, 3 }, { 77, 78, 2 }, { 52, 79, 5 }, { 66, 79, 6 }, { 67, 79, 3 }, { 69, 80, 5 }, { 68, 80, 3 }, { 69, 81, 3 }, { 74, 81, 1 }, { 74, 82, 2 }, { 81, 82, 1 }, { 74, 83, 4 }, { 82, 83, 2 }, { 54, 83, 5 }, { 63, 83, 4 }, { 57, 84, 5 }, { 70, 84, 4 }, { 76, 84, 3 }, { 76, 85, 1 }, { 77, 85, 3 }, { 84, 85, 2 }, { 66, 86, 4 }, { 78, 86, 3 }, { 79, 86, 5 }, { 67, 87, 3 }, { 79, 87, 2 }, { 67, 88, 5 }, { 60, 88, 5 }, { 87, 88, 4 }, { 60, 89, 5 }, { 88, 89, 1 }, { 73, 89, 4 }, { 63, 91, 5 }, { 75, 91, 3 }, { 78, 92, 4 }, { 86, 92, 6 }, { 77, 92, 3 }, { 85, 92, 2 }, { 89, 93, 4 }, { 73, 93, 3 }, { 61, 93, 6 }, { 80, 90, 1 }, { 80, 94, 4 }, { 90, 94, 4 }, { 69, 94, 5 }, { 81, 94, 3 }, { 81, 95, 3 }, { 82, 95, 3 }, { 94, 95, 1 }, { 82, 96, 3 }, { 83, 96, 2 }, { 95, 96, 4 }, { 83, 97, 3 }, { 96, 97, 2 }, { 63, 97, 5 }, { 63, 98, 6 }, { 91, 98, 3 }, { 97, 98, 1 }, { 91, 99, 2 }, { 98, 99, 3 }, { 75, 99, 3 }, { 70, 99, 5 }, { 84, 99, 6 }, { 86, 101, 3 }, { 79, 101, 4 }, { 86, 100, 3 }, { 100, 101, 1 }, { 79, 102, 4 }, { 87, 102, 3 }, { 101, 102, 1 }, { 87, 103, 4 }, { 88, 103, 3 }, { 89, 103, 3 }, { 89, 104, 4 }, { 103, 104, 5 }, { 93, 104, 1 }, { 61, 104, 7 }, { 68, 105, 6 }, { 53, 105, 7 }, { 80, 105, 4 }, { 90, 105, 3 }, { 95, 106, 5 }, { 96, 106, 1 }, { 96, 107, 2 }, { 97, 107, 2 }, { 106, 107, 1 }, { 98, 108, 1 }, { 99, 108, 4 }, { 97, 108, 2 }, { 107, 108, 2 }, { 85, 109, 3 }, { 84, 109, 4 }, { 92, 109, 3 }, { 87, 110, 3 }, { 102, 110, 3 }, { 87, 111, 4 }, { 103, 111, 3 }, { 110, 111, 1 }, { 90, 112, 3 }, { 105, 112, 3 }, { 90, 113, 4 }, { 94, 113, 3 }, { 112, 113, 1 }, { 84, 114, 5 }, { 99, 114, 4 }, { 84, 115, 5 }, { 109, 115, 4 }, { 114, 115, 1 }, { 95, 116, 3 }, { 106, 116, 5 }, { 94, 116, 4 }, { 113, 116, 4 }, { 99, 117, 4 }, { 108, 117, 3 }, { 114, 117, 5 }, { 109, 118, 4 }, { 115, 118, 1 }, { 114, 118, 2 }, { 92, 119, 5 }, { 109, 119, 5 }, { 86, 119, 6 }, { 100, 119, 5 }, { 100, 120, 4 }, { 119, 120, 1 }, { 102, 121, 4 }, { 101, 121, 3 }, { 110, 121, 4 }, { 100, 121, 4 }, { 120, 121, 3 }, { 103, 122, 5 }, { 104, 122, 4 }, { 113, 123, 2 }, { 112, 123, 3 }, { 113, 124, 3 }, { 116, 124, 3 }, { 123, 124, 1 }, { 116, 125, 2 }, { 124, 125, 3 }, { 106, 125, 5 }, { 106, 126, 4 }, { 125, 126, 2 }, { 107, 127, 3 }, { 108, 127, 4 }, { 106, 127, 4 }, { 126, 127, 2 }, { 108, 128, 4 }, { 117, 128, 2 }, { 127, 128, 3 }, { 117, 129, 1 }, { 128, 129, 1 }, { 109, 130, 4 }, { 119, 130, 3 }, { 120, 131, 4 }, { 121, 131, 1 }, { 110, 131, 5 }, { 125, 132, 2 }, { 126, 132, 2 }, { 127, 132, 4 }, { 114, 133, 4 }, { 118, 133, 4 }, { 117, 133, 3 }, { 129, 133, 3 }, { 109, 134, 4 }, { 118, 134, 4 }, { 130, 134, 3 }, { 110, 135, 5 }, { 111, 135, 5 }, { 131, 135, 7 }, { 103, 135, 5 }, { 122, 135, 4 }, { 135, 136, 2 }, { 122, 136, 3 }, { 122, 137, 2 }, { 136, 137, 1 }, { 122, 138, 3 }, { 104, 138, 5 }, { 137, 138, 2 }, { 61, 138, 12 }, { 118, 139, 4 }, { 133, 139, 3 }, { 118, 140, 4 }, { 134, 140, 3 }, { 139, 140, 2 }, { 130, 141, 3 }, { 134, 141, 2 }, { 130, 142, 2 }, { 141, 142, 1 }, { 120, 143, 4 }, { 131, 143, 5 }, { 119, 143, 3 }, { 130, 143, 3 }, { 142, 143, 2 }, { 136, 144, 1 }, { 137, 144, 2 }, { 135, 144, 3 }, { 112, 145, 6 }, { 123, 145, 5 }, { 105, 145, 6 }, { 123, 146, 4 }, { 124, 146, 3 }, { 145, 146, 4 }, { 125, 147, 4 }, { 132, 147, 4 }, { 124, 147, 4 }, { 146, 147, 1 }, { 133, 148, 3 }, { 139, 148, 4 }, { 129, 148, 4 }, { 128, 148, 4 }, { 139, 149, 2 }, { 140, 149, 2 }, { 140, 150, 2 }, { 149, 150, 2 }, { 134, 150, 3 }, { 141, 150, 3 }, { 131, 151, 4 }, { 143, 151, 4 }, { 137, 152, 3 }, { 144, 152, 3 }, { 138, 152, 3 }, { 127, 153, 5 }, { 132, 153, 6 }, { 128, 153, 5 }, { 148, 153, 4 }, { 142, 154, 2 }, { 143, 154, 3 }, { 141, 154, 3 }, { 150, 154, 4 }, { 131, 155, 6 }, { 135, 155, 4 }, { 151, 155, 6 }, { 144, 156, 2 }, { 152, 156, 3 }, { 135, 156, 4 }, { 147, 157, 2 }, { 146, 157, 3 }, { 148, 158, 3 }, { 153, 158, 3 }, { 148, 159, 3 }, { 158, 159, 3 }, { 139, 159, 4 }, { 149, 159, 3 }, { 149, 160, 2 }, { 159, 160, 2 }, { 150, 161, 3 }, { 154, 161, 5 }, { 149, 161, 3 }, { 160, 161, 1 }, { 154, 162, 2 }, { 161, 162, 5 }, { 143, 162, 4 }, { 143, 163, 3 }, { 151, 163, 4 }, { 162, 163, 1 }, { 151, 164, 3 }, { 155, 164, 5 }, { 155, 165, 1 }, { 164, 165, 4 }, { 155, 166, 3 }, { 165, 166, 2 }, { 135, 166, 4 }, { 156, 166, 3 }, { 147, 167, 4 }, { 132, 167, 6 }, { 157, 167, 2 }, { 132, 168, 6 }, { 153, 168, 3 }, { 167, 168, 5 }, { 158, 169, 2 }, { 159, 169, 3 }, { 151, 170, 4 }, { 163, 170, 3 }, { 164, 170, 3 }, { 162, 170, 4 }, { 156, 171, 3 }, { 152, 171, 3 }, { 146, 172, 5 }, { 145, 172, 5 }, { 146, 173, 5 }, { 157, 173, 3 }, { 172, 173, 2 }, { 157, 174, 2 }, { 167, 174, 2 }, { 173, 174, 2 }, { 158, 175, 3 }, { 169, 175, 4 }, { 153, 175, 3 }, { 168, 175, 3 }, { 159, 176, 3 }, { 160, 176, 4 }, { 169, 176, 2 }, { 166, 177, 2 }, { 165, 177, 3 }, { 156, 177, 4 }, { 171, 177, 5 }, { 152, 178, 5 }, { 138, 178, 7 }, { 171, 178, 4 }, { 61, 178, 17 }, { 172, 179, 1 }, { 173, 179, 3 }, { 173, 180, 2 }, { 174, 180, 2 }, { 179, 180, 3 }, { 174, 181, 2 }, { 180, 181, 2 }, { 167, 181, 2 }, { 168, 181, 6 }, { 168, 182, 2 }, { 175, 182, 3 }, { 181, 182, 5 }, { 169, 183, 2 }, { 175, 183, 4 }, { 176, 183, 2 }, { 176, 184, 1 }, { 183, 184, 1 }, { 160, 184, 5 }, { 165, 185, 4 }, { 164, 185, 4 }, { 177, 185, 5 }, { 175, 186, 3 }, { 182, 186, 4 }, { 183, 186, 3 }, { 161, 187, 5 }, { 162, 187, 6 }, { 160, 187, 5 }, { 184, 187, 6 }, { 162, 188, 6 }, { 170, 188, 4 }, { 187, 188, 8 }, { 164, 188, 5 }, { 185, 188, 4 }, { 177, 189, 2 }, { 185, 189, 5 }, { 177, 190, 3 }, { 171, 190, 4 }, { 189, 190, 2 }, { 171, 191, 4 }, { 190, 191, 3 }, { 178, 191, 3 }, { 180, 192, 5 }, { 179, 192, 3 }, { 172, 192, 4 }, { 145, 192, 7 }, { 182, 193, 2 }, { 181, 193, 6 }, { 182, 194, 3 }, { 186, 194, 2 }, { 193, 194, 2 }, { 187, 195, 1 }, { 188, 195, 9 }, { 185, 196, 3 }, { 188, 196, 2 }, { 185, 197, 3 }, { 196, 197, 1 }, { 185, 198, 4 }, { 189, 198, 2 }, { 189, 199, 3 }, { 190, 199, 1 }, { 190, 200, 3 }, { 199, 200, 2 }, { 191, 200, 2 }, { 193, 201, 3 }, { 194, 201, 1 }, { 186, 201, 3 }, { 186, 202, 5 }, { 201, 202, 5 }, { 183, 202, 4 }, { 184, 202, 4 }, { 187, 203, 3 }, { 195, 203, 3 }, { 184, 203, 5 }, { 202, 203, 2 }, { 185, 204, 4 }, { 197, 204, 3 }, { 198, 204, 3 }, { 198, 205, 1 }, { 204, 205, 2 }, { 198, 206, 3 }, { 205, 206, 2 }, { 189, 206, 3 }, { 199, 206, 2 }, { 191, 207, 3 }, { 200, 207, 4 }, { 178, 207, 4 }, { 181, 208, 5 }, { 193, 208, 5 }, { 180, 208, 5 }, { 193, 210, 3 }, { 201, 210, 2 }, { 193, 209, 2 }, { 209, 210, 1 }, { 202, 211, 1 }, { 203, 211, 3 }, { 197, 212, 3 }, { 196, 212, 3 }, { 204, 212, 2 }, { 200, 213, 3 }, { 207, 213, 2 }, { 180, 214, 6 }, { 208, 214, 6 }, { 192, 214, 4 }, { 193, 215, 4 }, { 208, 215, 3 }, { 209, 215, 3 }, { 209, 216, 2 }, { 210, 216, 1 }, { 202, 217, 5 }, { 201, 217, 3 }, { 211, 217, 5 }, { 210, 217, 3 }, { 216, 217, 2 }, { 203, 218, 3 }, { 211, 218, 4 }, { 195, 218, 4 }, { 196, 219, 3 }, { 212, 219, 3 }, { 205, 220, 4 }, { 206, 220, 3 }, { 199, 220, 3 }, { 200, 220, 4 }, { 208, 221, 4 }, { 214, 221, 3 }, { 209, 222, 2 }, { 215, 222, 3 }, { 216, 222, 2 }, { 195, 223, 6 }, { 218, 223, 6 }, { 188, 223, 7 }, { 188, 224, 6 }, { 223, 224, 3 }, { 196, 224, 5 }, { 219, 224, 3 }, { 205, 225, 4 }, { 220, 225, 5 }, { 204, 225, 4 }, { 212, 225, 3 }, { 225, 226, 4 }, { 220, 226, 1 }, { 220, 227, 3 }, { 226, 227, 2 }, { 200, 227, 4 }, { 213, 227, 3 }, { 213, 228, 3 }, { 227, 228, 3 }, { 207, 228, 3 }, { 214, 229, 3 }, { 192, 229, 6 }, { 221, 229, 4 }, { 221, 230, 1 }, { 229, 230, 3 }, { 215, 231, 2 }, { 222, 231, 3 }, { 208, 231, 4 }, { 216, 232, 4 }, { 217, 232, 3 }, { 222, 232, 5 }, { 211, 232, 5 }, { 211, 233, 3 }, { 218, 233, 4 }, { 232, 233, 3 }, { 225, 234, 1 }, { 226, 234, 5 }, { 227, 235, 3 }, { 228, 235, 2 }, { 208, 236, 4 }, { 231, 236, 3 }, { 221, 236, 4 }, { 230, 236, 4 }, { 231, 237, 2 }, { 236, 237, 3 }, { 222, 237, 3 }, { 232, 238, 3 }, { 233, 238, 2 }, { 218, 239, 5 }, { 223, 239, 3 }, { 223, 240, 3 }, { 224, 240, 3 }, { 239, 240, 2 }, { 219, 241, 4 }, { 224, 241, 5 }, { 225, 241, 3 }, { 234, 241, 3 }, { 212, 241, 4 }, { 227, 242, 3 }, { 226, 242, 5 }, { 235, 242, 1 }, { 228, 242, 3 }, { 230, 243, 4 }, { 229, 243, 2 }, { 230, 244, 3 }, { 236, 244, 2 }, { 243, 244, 5 }, { 232, 245, 2 }, { 238, 245, 3 }, { 243, 246, 1 }, { 244, 246, 6 }, { 244, 247, 1 }, { 246, 247, 5 }, { 236, 247, 3 }, { 237, 247, 5 }, { 222, 248, 5 }, { 237, 248, 5 }, { 232, 248, 4 }, { 245, 248, 2 }, { 234, 249, 4 }, { 241, 249, 3 }, { 234, 250, 3 }, { 249, 250, 1 }, { 226, 251, 5 }, { 242, 251, 7 }, { 234, 251, 4 }, { 250, 251, 2 }, { 245, 252, 2 }, { 248, 252, 2 }, { 245, 253, 3 }, { 238, 253, 3 }, { 252, 253, 2 }, { 218, 254, 6 }, { 233, 254, 5 }, { 239, 254, 5 }, { 238, 254, 5 }, { 253, 254, 4 }, { 239, 255, 4 }, { 240, 255, 3 }, { 240, 256, 4 }, { 255, 256, 2 }, { 224, 256, 5 }, { 241, 256, 5 }, { 228, 257, 5 }, { 242, 257, 4 }, { 246, 259, 3 }, { 247, 259, 5 }, { 246, 258, 2 }, { 258, 259, 1 }, { 247, 260, 3 }, { 259, 260, 3 }, { 237, 261, 4 }, { 247, 261, 5 }, { 248, 261, 5 }, { 248, 262, 3 }, { 261, 262, 3 }, { 248, 263, 2 }, { 252, 263, 2 }, { 262, 263, 1 }, { 239, 264, 5 }, { 254, 264, 4 }, { 255, 264, 4 }, { 249, 265, 3 }, { 250, 265, 4 }, { 241, 265, 5 }, { 256, 265, 4 }, { 242, 266, 5 }, { 251, 266, 5 }, { 257, 266, 4 }, { 259, 267, 3 }, { 258, 267, 2 }, { 246, 267, 4 }, { 243, 267, 5 }, { 229, 267, 7 }, { 192, 267, 11 }, { 252, 268, 3 }, { 253, 268, 3 }, { 263, 268, 3 }, { 253, 269, 3 }, { 254, 269, 3 }, { 268, 269, 3 }, { 254, 270, 2 }, { 269, 270, 2 }, { 254, 271, 3 }, { 264, 271, 3 }, { 270, 271, 1 }, { 247, 272, 5 }, { 260, 272, 4 }, { 261, 272, 3 }, { 261, 273, 2 }, { 262, 273, 4 }, { 272, 273, 2 }, { 262, 274, 3 }, { 273, 274, 4 }, { 263, 274, 2 }, { 268, 274, 3 }, { 268, 275, 1 }, { 269, 275, 4 }, { 274, 275, 2 }, { 269, 276, 2 }, { 270, 276, 2 }, { 275, 276, 4 }, { 255, 277, 4 }, { 264, 277, 3 }, { 255, 278, 3 }, { 256, 278, 4 }, { 277, 278, 1 }, { 256, 279, 4 }, { 265, 279, 3 }, { 278, 279, 3 }, { 265, 280, 2 }, { 279, 280, 2 }, { 266, 281, 3 }, { 257, 281, 4 }, { 257, 282, 3 }, { 281, 282, 2 }, { 274, 283, 1 }, { 275, 283, 3 }, { 270, 284, 3 }, { 271, 284, 2 }, { 276, 284, 3 }, { 264, 284, 4 }, { 277, 284, 5 }, { 279, 285, 3 }, { 280, 285, 1 }, { 280, 286, 2 }, { 285, 286, 1 }, { 265, 286, 4 }, { 250, 286, 6 }, { 259, 287, 5 }, { 267, 287, 3 }, { 260, 288, 5 }, { 272, 288, 5 }, { 259, 288, 5 }, { 287, 288, 4 }, { 272, 289, 3 }, { 273, 289, 3 }, { 273, 290, 3 }, { 289, 290, 2 }, { 274, 290, 4 }, { 283, 290, 4 }, { 250, 291, 7 }, { 251, 291, 7 }, { 286, 291, 2 }, { 287, 292, 2 }, { 288, 292, 4 }, { 289, 293, 3 }, { 290, 293, 5 }, { 288, 293, 4 }, { 292, 293, 6 }, { 272, 293, 4 }, { 290, 294, 5 }, { 293, 294, 8 }, { 283, 294, 3 }, { 276, 294, 6 }, { 284, 294, 8 }, { 275, 294, 4 }, { 284, 295, 10 }, { 294, 295, 16 }, { 279, 295, 4 }, { 278, 295, 5 }, { 285, 295, 3 }, { 277, 295, 6 }, { 285, 296, 2 }, { 295, 296, 1 }, { 286, 296, 3 }, { 291, 296, 3 }, { 291, 297, 1 }, { 296, 297, 2 }, { 291, 298, 4 }, { 297, 298, 3 }, { 251, 298, 7 }, { 266, 298, 7 }, { 281, 299, 4 }, { 282, 299, 5 }, { 266, 299, 6 }, { 298, 299, 3 } }; double maxWeight = 670; double minWeight = 316; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on triangulation of 400 points */ @Test public void testGetMatching35() { int[][] edges = new int[][] { { 3, 4, 8 }, { 3, 8, 8 }, { 4, 8, 2 }, { 4, 5, 4 }, { 4, 9, 3 }, { 5, 9, 3 }, { 5, 6, 2 }, { 5, 10, 2 }, { 6, 10, 2 }, { 9, 10, 3 }, { 6, 11, 2 }, { 10, 11, 2 }, { 6, 12, 8 }, { 11, 12, 6 }, { 6, 7, 24 }, { 7, 12, 18 }, { 12, 13, 10 }, { 7, 13, 8 }, { 13, 14, 2 }, { 7, 14, 6 }, { 14, 15, 5 }, { 7, 15, 1 }, { 0, 1, 2 }, { 0, 17, 2 }, { 1, 17, 3 }, { 0, 16, 3 }, { 16, 17, 1 }, { 2, 3, 1 }, { 2, 18, 2 }, { 3, 18, 3 }, { 1, 2, 1 }, { 1, 18, 3 }, { 17, 18, 3 }, { 3, 19, 3 }, { 8, 19, 6 }, { 18, 19, 3 }, { 4, 20, 3 }, { 8, 20, 3 }, { 9, 20, 2 }, { 10, 21, 2 }, { 11, 21, 2 }, { 11, 22, 2 }, { 12, 22, 6 }, { 21, 22, 2 }, { 7, 23, 3 }, { 15, 23, 2 }, { 16, 24, 1 }, { 17, 24, 2 }, { 18, 25, 3 }, { 19, 25, 2 }, { 19, 26, 1 }, { 25, 26, 1 }, { 19, 27, 4 }, { 8, 27, 3 }, { 26, 27, 3 }, { 10, 28, 3 }, { 21, 28, 4 }, { 9, 28, 3 }, { 20, 28, 3 }, { 21, 29, 1 }, { 22, 29, 3 }, { 28, 29, 3 }, { 12, 31, 3 }, { 13, 31, 9 }, { 12, 30, 3 }, { 30, 31, 1 }, { 15, 33, 3 }, { 14, 33, 4 }, { 15, 34, 2 }, { 33, 34, 2 }, { 23, 34, 2 }, { 17, 35, 3 }, { 18, 35, 3 }, { 24, 35, 3 }, { 25, 36, 2 }, { 26, 36, 3 }, { 18, 36, 3 }, { 35, 36, 3 }, { 20, 37, 3 }, { 28, 37, 4 }, { 8, 37, 4 }, { 27, 37, 4 }, { 13, 38, 6 }, { 31, 38, 4 }, { 13, 32, 3 }, { 13, 39, 5 }, { 32, 39, 4 }, { 38, 39, 1 }, { 14, 40, 3 }, { 33, 40, 4 }, { 13, 40, 4 }, { 32, 40, 4 }, { 35, 41, 3 }, { 36, 41, 2 }, { 28, 42, 3 }, { 37, 42, 3 }, { 28, 43, 3 }, { 29, 43, 3 }, { 42, 43, 2 }, { 31, 44, 3 }, { 30, 44, 3 }, { 12, 44, 4 }, { 22, 44, 6 }, { 31, 45, 3 }, { 38, 45, 3 }, { 44, 45, 3 }, { 38, 46, 1 }, { 39, 46, 2 }, { 45, 46, 2 }, { 39, 47, 2 }, { 46, 47, 2 }, { 32, 47, 3 }, { 32, 48, 3 }, { 47, 48, 1 }, { 24, 49, 4 }, { 35, 49, 3 }, { 41, 49, 4 }, { 36, 50, 3 }, { 41, 50, 3 }, { 26, 50, 4 }, { 26, 51, 3 }, { 50, 51, 1 }, { 27, 52, 4 }, { 37, 52, 5 }, { 26, 52, 4 }, { 51, 52, 2 }, { 37, 53, 2 }, { 52, 53, 4 }, { 37, 54, 3 }, { 42, 54, 2 }, { 53, 54, 1 }, { 42, 55, 1 }, { 43, 55, 3 }, { 54, 55, 1 }, { 44, 56, 3 }, { 45, 56, 2 }, { 46, 57, 1 }, { 47, 57, 3 }, { 45, 57, 3 }, { 56, 57, 3 }, { 32, 58, 4 }, { 48, 58, 3 }, { 40, 58, 3 }, { 40, 59, 3 }, { 58, 59, 1 }, { 40, 60, 3 }, { 33, 60, 4 }, { 59, 60, 2 }, { 33, 61, 4 }, { 34, 61, 3 }, { 23, 61, 5 }, { 41, 62, 3 }, { 49, 62, 2 }, { 50, 63, 1 }, { 51, 63, 2 }, { 41, 63, 3 }, { 43, 64, 3 }, { 55, 64, 2 }, { 54, 64, 3 }, { 43, 65, 2 }, { 64, 65, 1 }, { 44, 66, 3 }, { 56, 66, 5 }, { 22, 66, 6 }, { 29, 66, 7 }, { 47, 67, 2 }, { 48, 67, 3 }, { 57, 67, 3 }, { 58, 68, 2 }, { 59, 68, 3 }, { 48, 68, 3 }, { 67, 68, 2 }, { 59, 69, 3 }, { 60, 69, 1 }, { 60, 70, 3 }, { 69, 70, 2 }, { 33, 70, 4 }, { 61, 70, 3 }, { 70, 71, 2 }, { 61, 71, 1 }, { 49, 72, 2 }, { 62, 72, 2 }, { 24, 72, 6 }, { 62, 73, 1 }, { 72, 73, 1 }, { 62, 74, 3 }, { 73, 74, 2 }, { 41, 74, 3 }, { 63, 74, 3 }, { 51, 75, 2 }, { 52, 75, 3 }, { 63, 75, 2 }, { 57, 76, 3 }, { 56, 76, 3 }, { 57, 77, 2 }, { 67, 77, 3 }, { 76, 77, 2 }, { 59, 78, 3 }, { 68, 78, 4 }, { 69, 78, 2 }, { 72, 79, 1 }, { 73, 79, 2 }, { 24, 79, 7 }, { 73, 80, 1 }, { 74, 80, 3 }, { 79, 80, 1 }, { 74, 81, 2 }, { 80, 81, 3 }, { 63, 81, 3 }, { 75, 81, 3 }, { 52, 82, 3 }, { 53, 82, 5 }, { 75, 82, 3 }, { 64, 83, 3 }, { 65, 83, 3 }, { 65, 84, 4 }, { 83, 84, 2 }, { 29, 84, 7 }, { 66, 84, 5 }, { 43, 84, 5 }, { 66, 85, 3 }, { 84, 85, 5 }, { 56, 85, 5 }, { 76, 85, 5 }, { 68, 86, 3 }, { 78, 86, 3 }, { 67, 86, 4 }, { 80, 87, 1 }, { 81, 87, 4 }, { 79, 87, 2 }, { 75, 88, 2 }, { 81, 88, 3 }, { 82, 88, 3 }, { 53, 89, 4 }, { 82, 89, 5 }, { 54, 89, 5 }, { 64, 89, 5 }, { 64, 90, 3 }, { 83, 90, 3 }, { 89, 90, 3 }, { 76, 91, 3 }, { 77, 91, 3 }, { 67, 92, 5 }, { 86, 92, 1 }, { 78, 92, 3 }, { 78, 93, 3 }, { 92, 93, 4 }, { 69, 93, 4 }, { 70, 93, 4 }, { 70, 94, 4 }, { 71, 94, 3 }, { 93, 94, 3 }, { 71, 95, 4 }, { 94, 95, 1 }, { 61, 95, 5 }, { 23, 95, 8 }, { 81, 96, 3 }, { 87, 96, 3 }, { 81, 97, 2 }, { 88, 97, 3 }, { 96, 97, 1 }, { 82, 98, 3 }, { 88, 98, 2 }, { 83, 99, 3 }, { 84, 99, 3 }, { 90, 99, 4 }, { 84, 100, 2 }, { 99, 100, 1 }, { 84, 101, 3 }, { 85, 101, 5 }, { 100, 101, 1 }, { 85, 102, 3 }, { 101, 102, 3 }, { 85, 103, 3 }, { 102, 103, 2 }, { 76, 103, 5 }, { 91, 103, 5 }, { 87, 105, 3 }, { 79, 105, 3 }, { 96, 105, 4 }, { 88, 106, 2 }, { 97, 106, 3 }, { 98, 106, 2 }, { 98, 107, 2 }, { 106, 107, 2 }, { 82, 107, 3 }, { 89, 107, 5 }, { 92, 104, 1 }, { 92, 108, 3 }, { 104, 108, 3 }, { 77, 108, 5 }, { 91, 108, 5 }, { 67, 108, 6 }, { 92, 109, 4 }, { 93, 109, 3 }, { 104, 109, 4 }, { 96, 110, 2 }, { 105, 110, 4 }, { 97, 110, 3 }, { 97, 111, 2 }, { 106, 111, 3 }, { 110, 111, 1 }, { 106, 112, 1 }, { 107, 112, 3 }, { 111, 112, 2 }, { 103, 113, 2 }, { 102, 113, 3 }, { 103, 114, 3 }, { 113, 114, 1 }, { 103, 115, 4 }, { 91, 115, 4 }, { 114, 115, 2 }, { 91, 116, 5 }, { 108, 116, 2 }, { 115, 116, 4 }, { 108, 117, 3 }, { 116, 117, 3 }, { 104, 117, 2 }, { 109, 117, 4 }, { 93, 118, 4 }, { 94, 118, 4 }, { 109, 118, 3 }, { 94, 119, 4 }, { 95, 119, 3 }, { 118, 119, 3 }, { 79, 120, 6 }, { 24, 120, 11 }, { 105, 120, 3 }, { 110, 121, 1 }, { 111, 121, 2 }, { 105, 121, 4 }, { 120, 121, 4 }, { 111, 122, 1 }, { 112, 122, 3 }, { 121, 122, 1 }, { 112, 123, 1 }, { 122, 123, 2 }, { 107, 123, 3 }, { 89, 124, 5 }, { 90, 124, 5 }, { 99, 125, 3 }, { 100, 125, 4 }, { 90, 125, 5 }, { 124, 125, 5 }, { 100, 126, 3 }, { 101, 126, 4 }, { 125, 126, 1 }, { 116, 127, 1 }, { 117, 127, 4 }, { 115, 127, 5 }, { 109, 128, 3 }, { 117, 128, 2 }, { 109, 129, 3 }, { 118, 129, 2 }, { 128, 129, 3 }, { 107, 130, 5 }, { 123, 130, 6 }, { 89, 130, 6 }, { 124, 130, 3 }, { 102, 131, 5 }, { 113, 131, 4 }, { 101, 131, 5 }, { 126, 131, 4 }, { 113, 132, 2 }, { 114, 132, 3 }, { 131, 132, 3 }, { 114, 133, 3 }, { 115, 133, 2 }, { 132, 133, 3 }, { 117, 134, 3 }, { 127, 134, 2 }, { 117, 135, 3 }, { 128, 135, 3 }, { 134, 135, 1 }, { 118, 136, 2 }, { 129, 136, 2 }, { 118, 137, 3 }, { 136, 137, 1 }, { 119, 137, 3 }, { 119, 138, 3 }, { 137, 138, 1 }, { 121, 139, 5 }, { 120, 139, 2 }, { 122, 140, 3 }, { 123, 140, 3 }, { 121, 140, 3 }, { 124, 141, 3 }, { 130, 141, 1 }, { 124, 142, 4 }, { 125, 142, 3 }, { 115, 143, 4 }, { 127, 143, 3 }, { 133, 143, 3 }, { 127, 144, 3 }, { 134, 144, 3 }, { 143, 144, 1 }, { 128, 145, 3 }, { 135, 145, 4 }, { 129, 145, 3 }, { 136, 145, 4 }, { 137, 146, 2 }, { 138, 146, 3 }, { 136, 146, 1 }, { 145, 146, 3 }, { 123, 147, 4 }, { 140, 147, 3 }, { 130, 147, 5 }, { 141, 147, 5 }, { 141, 148, 1 }, { 147, 148, 4 }, { 141, 149, 3 }, { 148, 149, 2 }, { 124, 149, 3 }, { 142, 149, 4 }, { 125, 150, 3 }, { 126, 150, 4 }, { 142, 150, 3 }, { 126, 151, 4 }, { 131, 151, 3 }, { 150, 151, 3 }, { 131, 152, 4 }, { 132, 152, 2 }, { 151, 152, 4 }, { 133, 153, 3 }, { 143, 153, 4 }, { 132, 153, 3 }, { 152, 153, 2 }, { 145, 154, 2 }, { 146, 154, 3 }, { 139, 155, 2 }, { 139, 156, 3 }, { 155, 156, 2 }, { 121, 156, 5 }, { 140, 156, 5 }, { 140, 157, 2 }, { 147, 157, 3 }, { 156, 157, 4 }, { 148, 158, 3 }, { 149, 158, 1 }, { 149, 159, 3 }, { 142, 159, 3 }, { 158, 159, 2 }, { 142, 160, 3 }, { 150, 160, 2 }, { 159, 160, 2 }, { 150, 161, 1 }, { 151, 161, 4 }, { 160, 161, 1 }, { 145, 162, 3 }, { 154, 162, 3 }, { 135, 162, 4 }, { 146, 163, 4 }, { 138, 163, 4 }, { 119, 163, 5 }, { 156, 164, 1 }, { 157, 164, 5 }, { 155, 164, 3 }, { 147, 165, 2 }, { 157, 165, 3 }, { 147, 166, 3 }, { 148, 166, 4 }, { 165, 166, 1 }, { 148, 167, 3 }, { 158, 167, 2 }, { 166, 167, 4 }, { 158, 168, 1 }, { 159, 168, 3 }, { 167, 168, 1 }, { 159, 169, 2 }, { 160, 169, 2 }, { 160, 170, 1 }, { 161, 170, 2 }, { 169, 170, 1 }, { 152, 171, 2 }, { 153, 171, 3 }, { 151, 171, 5 }, { 143, 172, 4 }, { 153, 172, 3 }, { 144, 172, 4 }, { 134, 173, 4 }, { 144, 173, 4 }, { 135, 173, 5 }, { 162, 173, 4 }, { 146, 174, 4 }, { 163, 174, 2 }, { 167, 175, 2 }, { 168, 175, 1 }, { 168, 176, 2 }, { 175, 176, 1 }, { 159, 176, 3 }, { 169, 176, 3 }, { 161, 177, 3 }, { 151, 177, 4 }, { 170, 177, 3 }, { 151, 178, 4 }, { 171, 178, 4 }, { 177, 178, 3 }, { 153, 179, 4 }, { 171, 179, 2 }, { 172, 179, 4 }, { 144, 180, 5 }, { 172, 180, 4 }, { 173, 180, 2 }, { 146, 181, 5 }, { 154, 181, 5 }, { 174, 181, 2 }, { 164, 182, 3 }, { 155, 182, 4 }, { 164, 183, 2 }, { 182, 183, 1 }, { 164, 184, 4 }, { 157, 184, 4 }, { 183, 184, 3 }, { 157, 185, 4 }, { 165, 185, 3 }, { 184, 185, 2 }, { 165, 186, 3 }, { 166, 186, 2 }, { 185, 186, 2 }, { 166, 187, 3 }, { 167, 187, 3 }, { 186, 187, 2 }, { 167, 188, 3 }, { 175, 188, 3 }, { 187, 188, 1 }, { 170, 189, 3 }, { 177, 189, 4 }, { 169, 189, 2 }, { 176, 189, 3 }, { 177, 190, 4 }, { 178, 190, 1 }, { 172, 191, 3 }, { 179, 191, 2 }, { 172, 192, 3 }, { 180, 192, 3 }, { 191, 192, 3 }, { 180, 193, 2 }, { 192, 193, 3 }, { 173, 193, 2 }, { 162, 193, 5 }, { 162, 194, 4 }, { 193, 194, 4 }, { 154, 194, 5 }, { 154, 195, 5 }, { 194, 195, 3 }, { 181, 195, 2 }, { 181, 196, 1 }, { 195, 196, 1 }, { 174, 196, 3 }, { 182, 197, 1 }, { 183, 197, 2 }, { 155, 197, 5 }, { 183, 198, 2 }, { 184, 198, 3 }, { 197, 198, 2 }, { 184, 199, 1 }, { 185, 199, 3 }, { 198, 199, 2 }, { 185, 200, 3 }, { 186, 200, 1 }, { 186, 201, 2 }, { 187, 201, 2 }, { 200, 201, 1 }, { 187, 202, 1 }, { 188, 202, 2 }, { 201, 202, 1 }, { 188, 203, 2 }, { 202, 203, 2 }, { 175, 203, 3 }, { 176, 203, 3 }, { 177, 204, 4 }, { 189, 204, 1 }, { 176, 204, 3 }, { 177, 205, 3 }, { 190, 205, 2 }, { 179, 206, 3 }, { 191, 206, 4 }, { 171, 206, 4 }, { 178, 206, 3 }, { 190, 206, 3 }, { 191, 207, 3 }, { 192, 207, 2 }, { 194, 209, 2 }, { 195, 209, 3 }, { 194, 208, 1 }, { 208, 209, 1 }, { 155, 210, 6 }, { 197, 210, 1 }, { 197, 211, 2 }, { 198, 211, 2 }, { 210, 211, 1 }, { 185, 212, 3 }, { 199, 212, 1 }, { 198, 212, 3 }, { 202, 213, 1 }, { 203, 213, 3 }, { 201, 213, 2 }, { 200, 213, 3 }, { 176, 214, 4 }, { 203, 214, 4 }, { 204, 214, 2 }, { 190, 215, 3 }, { 205, 215, 3 }, { 206, 215, 2 }, { 206, 216, 2 }, { 215, 216, 2 }, { 191, 216, 3 }, { 191, 217, 2 }, { 207, 217, 3 }, { 216, 217, 2 }, { 194, 218, 3 }, { 193, 218, 3 }, { 208, 218, 3 }, { 208, 219, 2 }, { 218, 219, 3 }, { 209, 219, 1 }, { 195, 219, 3 }, { 174, 220, 5 }, { 163, 220, 5 }, { 196, 220, 3 }, { 198, 221, 3 }, { 211, 221, 3 }, { 212, 221, 2 }, { 212, 222, 2 }, { 221, 222, 2 }, { 185, 222, 4 }, { 200, 222, 4 }, { 203, 223, 3 }, { 213, 223, 1 }, { 200, 223, 3 }, { 222, 223, 5 }, { 204, 224, 3 }, { 214, 224, 4 }, { 177, 224, 5 }, { 205, 224, 4 }, { 205, 225, 2 }, { 224, 225, 3 }, { 215, 226, 2 }, { 216, 226, 4 }, { 205, 226, 3 }, { 225, 226, 1 }, { 207, 227, 3 }, { 217, 227, 4 }, { 192, 227, 3 }, { 193, 227, 5 }, { 218, 228, 4 }, { 219, 228, 1 }, { 219, 229, 4 }, { 228, 229, 3 }, { 196, 229, 3 }, { 220, 229, 3 }, { 195, 229, 4 }, { 155, 230, 8 }, { 210, 230, 2 }, { 211, 230, 3 }, { 211, 231, 2 }, { 230, 231, 1 }, { 211, 232, 3 }, { 221, 232, 2 }, { 231, 232, 1 }, { 221, 233, 2 }, { 222, 233, 2 }, { 232, 233, 2 }, { 203, 234, 4 }, { 214, 234, 3 }, { 223, 234, 4 }, { 214, 235, 3 }, { 224, 235, 2 }, { 234, 235, 4 }, { 193, 236, 5 }, { 218, 236, 4 }, { 227, 236, 3 }, { 218, 237, 3 }, { 236, 237, 2 }, { 218, 238, 2 }, { 228, 238, 4 }, { 237, 238, 1 }, { 228, 239, 2 }, { 229, 239, 3 }, { 232, 240, 1 }, { 233, 240, 3 }, { 231, 240, 2 }, { 223, 242, 3 }, { 234, 242, 2 }, { 223, 241, 2 }, { 241, 242, 2 }, { 225, 243, 3 }, { 226, 243, 3 }, { 224, 243, 3 }, { 235, 243, 4 }, { 227, 244, 2 }, { 236, 244, 3 }, { 217, 244, 5 }, { 236, 245, 1 }, { 237, 245, 3 }, { 244, 245, 2 }, { 237, 246, 2 }, { 238, 246, 1 }, { 228, 246, 4 }, { 239, 246, 5 }, { 231, 247, 2 }, { 240, 247, 2 }, { 230, 247, 3 }, { 222, 248, 4 }, { 233, 248, 4 }, { 223, 248, 5 }, { 241, 248, 4 }, { 234, 249, 3 }, { 235, 249, 4 }, { 242, 249, 3 }, { 235, 250, 3 }, { 249, 250, 2 }, { 235, 251, 3 }, { 243, 251, 2 }, { 250, 251, 3 }, { 226, 252, 4 }, { 243, 252, 4 }, { 216, 252, 5 }, { 217, 253, 5 }, { 244, 253, 6 }, { 216, 253, 4 }, { 252, 253, 2 }, { 244, 254, 1 }, { 245, 254, 3 }, { 245, 255, 2 }, { 254, 255, 3 }, { 237, 255, 3 }, { 246, 255, 3 }, { 246, 256, 1 }, { 255, 256, 2 }, { 240, 257, 4 }, { 247, 257, 5 }, { 233, 257, 4 }, { 248, 257, 3 }, { 241, 258, 2 }, { 248, 258, 4 }, { 242, 258, 3 }, { 249, 259, 1 }, { 250, 259, 3 }, { 242, 259, 3 }, { 250, 260, 1 }, { 251, 260, 4 }, { 259, 260, 2 }, { 243, 261, 2 }, { 251, 261, 2 }, { 243, 262, 3 }, { 252, 262, 2 }, { 261, 262, 2 }, { 244, 263, 5 }, { 253, 263, 2 }, { 244, 264, 3 }, { 254, 264, 2 }, { 263, 264, 3 }, { 254, 265, 4 }, { 255, 265, 1 }, { 255, 266, 2 }, { 265, 266, 1 }, { 256, 266, 2 }, { 256, 267, 2 }, { 266, 267, 2 }, { 246, 267, 3 }, { 239, 267, 5 }, { 247, 268, 2 }, { 257, 268, 5 }, { 242, 269, 4 }, { 258, 269, 2 }, { 259, 269, 4 }, { 259, 270, 1 }, { 260, 270, 3 }, { 269, 270, 3 }, { 261, 271, 3 }, { 262, 271, 1 }, { 253, 272, 3 }, { 263, 272, 3 }, { 252, 272, 3 }, { 262, 272, 3 }, { 271, 272, 2 }, { 258, 273, 5 }, { 269, 273, 6 }, { 248, 273, 4 }, { 257, 273, 3 }, { 260, 274, 3 }, { 270, 274, 1 }, { 269, 274, 4 }, { 260, 275, 3 }, { 274, 275, 3 }, { 251, 275, 4 }, { 261, 276, 3 }, { 271, 276, 4 }, { 251, 276, 3 }, { 275, 276, 2 }, { 271, 277, 1 }, { 276, 277, 3 }, { 271, 278, 2 }, { 272, 278, 2 }, { 277, 278, 1 }, { 272, 279, 3 }, { 278, 279, 3 }, { 263, 279, 2 }, { 264, 279, 4 }, { 265, 280, 2 }, { 266, 280, 3 }, { 239, 281, 6 }, { 267, 281, 3 }, { 247, 282, 5 }, { 230, 282, 7 }, { 268, 282, 3 }, { 155, 282, 13 }, { 268, 283, 3 }, { 282, 283, 1 }, { 275, 284, 2 }, { 276, 284, 2 }, { 274, 284, 5 }, { 265, 285, 4 }, { 280, 285, 3 }, { 254, 285, 5 }, { 264, 285, 4 }, { 280, 286, 1 }, { 285, 286, 2 }, { 280, 287, 2 }, { 286, 287, 1 }, { 266, 287, 3 }, { 267, 288, 4 }, { 281, 288, 4 }, { 266, 288, 4 }, { 287, 288, 1 }, { 288, 289, 4 }, { 281, 289, 2 }, { 281, 290, 3 }, { 239, 290, 7 }, { 289, 290, 1 }, { 229, 291, 8 }, { 220, 291, 9 }, { 239, 291, 7 }, { 290, 291, 2 }, { 282, 292, 3 }, { 283, 292, 2 }, { 268, 292, 3 }, { 268, 293, 4 }, { 292, 293, 2 }, { 257, 293, 5 }, { 257, 294, 5 }, { 273, 294, 3 }, { 293, 294, 1 }, { 278, 295, 2 }, { 279, 295, 4 }, { 277, 295, 3 }, { 264, 296, 5 }, { 279, 296, 3 }, { 285, 296, 4 }, { 293, 297, 1 }, { 294, 297, 2 }, { 292, 297, 3 }, { 294, 298, 1 }, { 297, 298, 1 }, { 294, 299, 3 }, { 273, 299, 3 }, { 298, 299, 2 }, { 273, 300, 4 }, { 269, 300, 5 }, { 299, 300, 2 }, { 276, 301, 5 }, { 284, 301, 5 }, { 277, 301, 3 }, { 295, 301, 2 }, { 295, 302, 2 }, { 301, 302, 2 }, { 279, 302, 4 }, { 279, 303, 4 }, { 296, 303, 2 }, { 302, 303, 3 }, { 296, 304, 3 }, { 285, 304, 3 }, { 285, 305, 2 }, { 286, 305, 3 }, { 304, 305, 1 }, { 292, 306, 3 }, { 282, 306, 3 }, { 297, 306, 5 }, { 269, 307, 6 }, { 300, 307, 3 }, { 269, 308, 5 }, { 274, 308, 5 }, { 307, 308, 1 }, { 284, 309, 5 }, { 301, 309, 2 }, { 301, 310, 1 }, { 302, 310, 3 }, { 309, 310, 1 }, { 302, 311, 3 }, { 303, 311, 2 }, { 303, 312, 2 }, { 311, 312, 2 }, { 296, 312, 2 }, { 304, 312, 3 }, { 288, 313, 4 }, { 289, 313, 5 }, { 287, 313, 4 }, { 289, 314, 4 }, { 313, 314, 2 }, { 289, 315, 3 }, { 290, 315, 4 }, { 314, 315, 1 }, { 290, 316, 4 }, { 291, 316, 3 }, { 315, 316, 3 }, { 220, 316, 12 }, { 297, 317, 3 }, { 298, 317, 3 }, { 299, 317, 3 }, { 300, 317, 4 }, { 274, 318, 6 }, { 284, 318, 7 }, { 308, 318, 3 }, { 284, 319, 5 }, { 309, 319, 3 }, { 309, 320, 2 }, { 310, 320, 3 }, { 319, 320, 1 }, { 304, 321, 3 }, { 305, 321, 3 }, { 312, 321, 2 }, { 287, 322, 5 }, { 313, 322, 4 }, { 286, 322, 4 }, { 305, 322, 3 }, { 315, 323, 2 }, { 316, 323, 3 }, { 314, 323, 3 }, { 297, 324, 5 }, { 306, 324, 2 }, { 317, 324, 7 }, { 300, 325, 4 }, { 307, 325, 3 }, { 317, 325, 5 }, { 308, 326, 3 }, { 318, 326, 4 }, { 307, 326, 2 }, { 325, 326, 1 }, { 284, 327, 7 }, { 318, 327, 2 }, { 319, 327, 6 }, { 310, 328, 3 }, { 320, 328, 4 }, { 302, 328, 4 }, { 311, 328, 4 }, { 312, 329, 2 }, { 311, 329, 3 }, { 321, 329, 2 }, { 321, 330, 2 }, { 329, 330, 2 }, { 305, 330, 4 }, { 322, 330, 4 }, { 323, 331, 1 }, { 316, 331, 3 }, { 314, 331, 3 }, { 325, 332, 2 }, { 326, 332, 1 }, { 318, 333, 3 }, { 327, 333, 3 }, { 326, 333, 3 }, { 332, 333, 2 }, { 319, 334, 3 }, { 327, 334, 5 }, { 320, 334, 3 }, { 320, 335, 4 }, { 328, 335, 1 }, { 334, 335, 5 }, { 322, 336, 3 }, { 330, 336, 2 }, { 322, 337, 2 }, { 336, 337, 2 }, { 313, 337, 5 }, { 316, 338, 4 }, { 331, 338, 1 }, { 317, 339, 3 }, { 324, 339, 7 }, { 325, 339, 5 }, { 332, 340, 1 }, { 333, 340, 3 }, { 325, 340, 3 }, { 339, 340, 5 }, { 327, 341, 3 }, { 333, 341, 4 }, { 327, 342, 3 }, { 334, 342, 3 }, { 341, 342, 1 }, { 328, 343, 3 }, { 335, 343, 3 }, { 311, 343, 5 }, { 329, 343, 4 }, { 329, 344, 2 }, { 343, 344, 3 }, { 330, 344, 3 }, { 336, 344, 4 }, { 313, 345, 5 }, { 337, 345, 5 }, { 314, 345, 5 }, { 331, 345, 4 }, { 331, 346, 3 }, { 345, 346, 2 }, { 338, 346, 2 }, { 341, 347, 1 }, { 342, 347, 2 }, { 333, 347, 4 }, { 334, 348, 2 }, { 335, 348, 6 }, { 342, 348, 3 }, { 347, 348, 3 }, { 343, 349, 1 }, { 344, 349, 4 }, { 335, 349, 3 }, { 344, 350, 1 }, { 349, 350, 3 }, { 337, 351, 4 }, { 345, 351, 2 }, { 345, 352, 1 }, { 351, 352, 1 }, { 346, 352, 3 }, { 352, 353, 2 }, { 346, 353, 1 }, { 338, 353, 3 }, { 339, 354, 4 }, { 324, 354, 5 }, { 339, 355, 2 }, { 354, 355, 3 }, { 339, 356, 3 }, { 340, 356, 5 }, { 355, 356, 1 }, { 347, 357, 1 }, { 348, 357, 4 }, { 333, 357, 5 }, { 349, 358, 4 }, { 350, 358, 1 }, { 350, 359, 3 }, { 358, 359, 2 }, { 344, 359, 3 }, { 336, 359, 4 }, { 352, 360, 1 }, { 353, 360, 3 }, { 351, 360, 2 }, { 338, 361, 4 }, { 316, 361, 7 }, { 353, 361, 3 }, { 354, 362, 3 }, { 324, 362, 6 }, { 355, 363, 2 }, { 356, 363, 3 }, { 354, 363, 3 }, { 356, 364, 3 }, { 363, 364, 4 }, { 340, 364, 4 }, { 340, 365, 4 }, { 364, 365, 1 }, { 348, 368, 4 }, { 335, 368, 5 }, { 348, 367, 3 }, { 367, 368, 2 }, { 335, 369, 5 }, { 349, 369, 3 }, { 368, 369, 3 }, { 358, 370, 1 }, { 359, 370, 3 }, { 349, 370, 4 }, { 336, 371, 5 }, { 337, 371, 5 }, { 359, 371, 3 }, { 351, 372, 3 }, { 360, 372, 4 }, { 337, 372, 5 }, { 371, 372, 2 }, { 316, 373, 7 }, { 361, 373, 2 }, { 354, 374, 3 }, { 362, 374, 1 }, { 354, 375, 3 }, { 363, 375, 2 }, { 374, 375, 3 }, { 357, 366, 1 }, { 357, 376, 3 }, { 366, 376, 3 }, { 340, 376, 5 }, { 365, 376, 5 }, { 333, 376, 6 }, { 366, 377, 2 }, { 376, 377, 3 }, { 348, 377, 4 }, { 367, 377, 4 }, { 357, 377, 3 }, { 368, 378, 4 }, { 369, 378, 1 }, { 369, 379, 2 }, { 378, 379, 1 }, { 349, 379, 3 }, { 370, 379, 4 }, { 370, 380, 2 }, { 379, 380, 4 }, { 359, 380, 3 }, { 359, 381, 2 }, { 371, 381, 3 }, { 380, 381, 1 }, { 360, 382, 2 }, { 372, 382, 4 }, { 360, 383, 3 }, { 353, 383, 4 }, { 382, 383, 1 }, { 353, 384, 3 }, { 361, 384, 3 }, { 383, 384, 1 }, { 361, 385, 2 }, { 384, 385, 2 }, { 373, 385, 2 }, { 363, 386, 6 }, { 364, 386, 3 }, { 375, 386, 7 }, { 365, 386, 2 }, { 376, 387, 1 }, { 377, 387, 4 }, { 377, 388, 4 }, { 387, 388, 6 }, { 367, 388, 2 }, { 367, 389, 3 }, { 368, 389, 3 }, { 388, 389, 1 }, { 368, 390, 3 }, { 378, 390, 3 }, { 389, 390, 2 }, { 383, 391, 1 }, { 384, 391, 2 }, { 382, 391, 2 }, { 362, 392, 4 }, { 324, 392, 8 }, { 374, 392, 3 }, { 375, 393, 4 }, { 386, 393, 10 }, { 374, 393, 2 }, { 392, 393, 1 }, { 387, 394, 3 }, { 388, 394, 9 }, { 386, 394, 3 }, { 393, 394, 11 }, { 376, 394, 3 }, { 365, 394, 4 }, { 389, 395, 3 }, { 390, 395, 1 }, { 388, 395, 4 }, { 394, 395, 11 }, { 379, 396, 3 }, { 380, 396, 6 }, { 378, 396, 2 }, { 390, 396, 3 }, { 395, 396, 2 }, { 380, 397, 4 }, { 381, 397, 3 }, { 396, 397, 8 }, { 371, 397, 3 }, { 372, 397, 4 }, { 382, 397, 6 }, { 391, 397, 7 }, { 397, 398, 10 }, { 391, 398, 5 }, { 384, 398, 4 }, { 385, 398, 3 }, { 373, 398, 3 }, { 373, 399, 4 }, { 398, 399, 1 }, { 316, 399, 11 }, { 220, 399, 21 } }; double maxWeight = 827; double minWeight = 367; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on triangulation of 500 points */ @Test public void testGetMatching36() { int[][] edges = new int[][] { { 4, 5, 4 }, { 4, 14, 3 }, { 5, 14, 3 }, { 5, 6, 4 }, { 5, 15, 2 }, { 6, 15, 4 }, { 14, 15, 3 }, { 10, 11, 1 }, { 10, 16, 2 }, { 11, 16, 3 }, { 9, 10, 1 }, { 9, 16, 1 }, { 8, 9, 5 }, { 8, 16, 6 }, { 11, 12, 5 }, { 11, 17, 3 }, { 12, 17, 4 }, { 1, 0, 7 }, { 1, 18, 3 }, { 0, 18, 7 }, { 2, 1, 1 }, { 2, 18, 3 }, { 3, 4, 7 }, { 3, 19, 5 }, { 4, 19, 4 }, { 6, 20, 3 }, { 15, 20, 3 }, { 7, 8, 2 }, { 7, 21, 3 }, { 8, 21, 3 }, { 11, 22, 3 }, { 16, 22, 4 }, { 17, 22, 2 }, { 17, 23, 1 }, { 22, 23, 1 }, { 12, 13, 4 }, { 12, 24, 3 }, { 13, 24, 4 }, { 24, 25, 2 }, { 13, 25, 3 }, { 13, 26, 2 }, { 25, 26, 1 }, { 2, 3, 1 }, { 2, 27, 4 }, { 3, 27, 4 }, { 18, 27, 2 }, { 3, 28, 4 }, { 19, 28, 3 }, { 19, 29, 1 }, { 28, 29, 2 }, { 19, 30, 2 }, { 29, 30, 1 }, { 19, 31, 3 }, { 4, 31, 4 }, { 30, 31, 1 }, { 4, 32, 3 }, { 14, 32, 3 }, { 31, 32, 1 }, { 14, 33, 3 }, { 32, 33, 1 }, { 14, 34, 3 }, { 15, 34, 3 }, { 33, 34, 2 }, { 15, 35, 3 }, { 34, 35, 1 }, { 15, 36, 2 }, { 20, 36, 3 }, { 35, 36, 1 }, { 7, 37, 4 }, { 21, 37, 3 }, { 6, 7, 2 }, { 6, 37, 4 }, { 20, 37, 3 }, { 8, 38, 4 }, { 16, 38, 4 }, { 21, 38, 4 }, { 16, 39, 2 }, { 22, 39, 4 }, { 38, 39, 3 }, { 12, 40, 4 }, { 24, 40, 4 }, { 17, 40, 3 }, { 23, 40, 2 }, { 18, 41, 3 }, { 27, 41, 3 }, { 0, 41, 7 }, { 3, 42, 5 }, { 27, 42, 2 }, { 3, 43, 5 }, { 28, 43, 2 }, { 42, 43, 2 }, { 28, 44, 2 }, { 29, 44, 2 }, { 43, 44, 2 }, { 35, 45, 1 }, { 36, 45, 2 }, { 34, 45, 2 }, { 33, 45, 4 }, { 21, 46, 3 }, { 37, 46, 2 }, { 22, 47, 2 }, { 39, 47, 4 }, { 23, 47, 3 }, { 40, 47, 3 }, { 25, 48, 2 }, { 26, 48, 3 }, { 24, 48, 3 }, { 41, 49, 4 }, { 0, 49, 6 }, { 41, 50, 2 }, { 49, 50, 2 }, { 41, 51, 1 }, { 50, 51, 1 }, { 41, 52, 2 }, { 51, 52, 1 }, { 27, 52, 3 }, { 42, 52, 3 }, { 43, 53, 2 }, { 44, 53, 2 }, { 44, 54, 1 }, { 53, 54, 1 }, { 31, 55, 3 }, { 32, 55, 3 }, { 30, 55, 2 }, { 44, 55, 3 }, { 54, 55, 2 }, { 29, 55, 3 }, { 36, 56, 2 }, { 45, 56, 2 }, { 20, 56, 4 }, { 37, 56, 5 }, { 37, 57, 3 }, { 46, 57, 1 }, { 56, 57, 5 }, { 46, 58, 2 }, { 57, 58, 1 }, { 21, 58, 3 }, { 38, 58, 4 }, { 39, 59, 3 }, { 47, 59, 3 }, { 47, 60, 2 }, { 59, 60, 1 }, { 24, 61, 3 }, { 40, 61, 4 }, { 48, 61, 3 }, { 51, 62, 2 }, { 52, 62, 1 }, { 42, 62, 3 }, { 54, 63, 1 }, { 55, 63, 3 }, { 53, 63, 2 }, { 55, 64, 2 }, { 63, 64, 3 }, { 32, 64, 4 }, { 33, 65, 4 }, { 45, 65, 5 }, { 32, 65, 3 }, { 64, 65, 1 }, { 39, 66, 3 }, { 38, 66, 5 }, { 59, 66, 2 }, { 40, 67, 4 }, { 47, 67, 4 }, { 61, 67, 3 }, { 67, 68, 1 }, { 61, 68, 2 }, { 61, 69, 1 }, { 68, 69, 1 }, { 48, 69, 3 }, { 50, 70, 2 }, { 49, 70, 3 }, { 51, 70, 3 }, { 62, 70, 3 }, { 62, 71, 1 }, { 70, 71, 2 }, { 43, 72, 4 }, { 42, 72, 3 }, { 53, 72, 4 }, { 62, 72, 3 }, { 71, 72, 2 }, { 53, 73, 2 }, { 63, 73, 2 }, { 72, 73, 3 }, { 45, 74, 4 }, { 56, 74, 2 }, { 56, 75, 5 }, { 57, 75, 3 }, { 57, 76, 3 }, { 58, 76, 2 }, { 75, 76, 2 }, { 38, 77, 5 }, { 66, 77, 3 }, { 58, 77, 5 }, { 66, 78, 1 }, { 77, 78, 2 }, { 66, 79, 2 }, { 78, 79, 1 }, { 59, 79, 2 }, { 60, 79, 3 }, { 60, 80, 3 }, { 79, 80, 2 }, { 47, 80, 3 }, { 67, 80, 4 }, { 70, 81, 2 }, { 49, 81, 4 }, { 70, 82, 2 }, { 71, 82, 2 }, { 81, 82, 2 }, { 71, 83, 2 }, { 72, 83, 2 }, { 82, 83, 2 }, { 72, 84, 1 }, { 73, 84, 4 }, { 83, 84, 1 }, { 63, 85, 2 }, { 64, 85, 4 }, { 73, 85, 2 }, { 64, 86, 2 }, { 65, 86, 3 }, { 85, 86, 3 }, { 65, 87, 2 }, { 86, 87, 1 }, { 45, 88, 4 }, { 65, 88, 5 }, { 74, 88, 2 }, { 74, 89, 1 }, { 88, 89, 1 }, { 74, 90, 3 }, { 89, 90, 2 }, { 56, 90, 4 }, { 75, 90, 3 }, { 75, 91, 3 }, { 76, 91, 1 }, { 76, 92, 3 }, { 91, 92, 2 }, { 58, 92, 4 }, { 77, 92, 3 }, { 77, 93, 2 }, { 92, 93, 1 }, { 77, 94, 2 }, { 78, 94, 2 }, { 93, 94, 2 }, { 78, 95, 1 }, { 79, 95, 2 }, { 94, 95, 1 }, { 67, 96, 2 }, { 68, 96, 3 }, { 80, 96, 4 }, { 68, 97, 2 }, { 69, 97, 3 }, { 96, 97, 1 }, { 69, 98, 3 }, { 48, 98, 4 }, { 97, 98, 3 }, { 26, 98, 7 }, { 83, 99, 2 }, { 84, 99, 1 }, { 73, 100, 3 }, { 85, 100, 1 }, { 85, 101, 2 }, { 86, 101, 3 }, { 100, 101, 1 }, { 86, 102, 2 }, { 101, 102, 1 }, { 86, 103, 2 }, { 87, 103, 1 }, { 102, 103, 2 }, { 87, 104, 3 }, { 103, 104, 2 }, { 65, 104, 4 }, { 88, 104, 3 }, { 88, 105, 2 }, { 104, 105, 1 }, { 89, 106, 1 }, { 90, 106, 3 }, { 88, 106, 2 }, { 105, 106, 2 }, { 75, 107, 2 }, { 90, 107, 3 }, { 75, 108, 3 }, { 91, 108, 2 }, { 107, 108, 1 }, { 91, 109, 2 }, { 92, 109, 2 }, { 108, 109, 2 }, { 79, 110, 2 }, { 80, 110, 3 }, { 95, 110, 2 }, { 97, 111, 2 }, { 98, 111, 3 }, { 111, 112, 2 }, { 98, 112, 1 }, { 49, 113, 6 }, { 81, 113, 3 }, { 0, 113, 11 }, { 81, 114, 3 }, { 82, 114, 3 }, { 113, 114, 3 }, { 73, 115, 4 }, { 100, 115, 4 }, { 84, 115, 3 }, { 99, 115, 2 }, { 102, 116, 2 }, { 103, 116, 2 }, { 103, 117, 2 }, { 104, 117, 2 }, { 116, 117, 2 }, { 104, 118, 1 }, { 105, 118, 2 }, { 117, 118, 1 }, { 105, 119, 1 }, { 106, 119, 3 }, { 118, 119, 1 }, { 93, 120, 2 }, { 94, 120, 3 }, { 92, 120, 3 }, { 109, 120, 3 }, { 94, 121, 2 }, { 120, 121, 2 }, { 95, 121, 3 }, { 95, 122, 2 }, { 110, 122, 2 }, { 121, 122, 1 }, { 110, 123, 1 }, { 122, 123, 1 }, { 97, 124, 3 }, { 96, 124, 2 }, { 111, 124, 3 }, { 80, 124, 5 }, { 113, 126, 3 }, { 114, 126, 2 }, { 113, 125, 1 }, { 125, 126, 2 }, { 99, 127, 3 }, { 115, 127, 3 }, { 83, 127, 3 }, { 82, 127, 4 }, { 114, 127, 4 }, { 101, 128, 2 }, { 100, 128, 3 }, { 102, 128, 3 }, { 102, 129, 2 }, { 116, 129, 2 }, { 128, 129, 1 }, { 116, 130, 1 }, { 117, 130, 3 }, { 129, 130, 1 }, { 106, 131, 3 }, { 119, 131, 4 }, { 90, 131, 4 }, { 107, 131, 4 }, { 122, 132, 3 }, { 123, 132, 2 }, { 110, 132, 3 }, { 80, 132, 5 }, { 80, 133, 5 }, { 132, 133, 3 }, { 124, 133, 2 }, { 111, 134, 2 }, { 124, 134, 3 }, { 111, 135, 3 }, { 112, 135, 3 }, { 134, 135, 1 }, { 113, 136, 3 }, { 0, 136, 12 }, { 125, 136, 2 }, { 125, 137, 3 }, { 126, 137, 1 }, { 136, 137, 3 }, { 114, 138, 2 }, { 127, 138, 4 }, { 126, 138, 2 }, { 137, 138, 1 }, { 127, 139, 1 }, { 138, 139, 3 }, { 115, 139, 3 }, { 115, 140, 3 }, { 139, 140, 3 }, { 100, 140, 4 }, { 128, 140, 4 }, { 117, 141, 3 }, { 130, 141, 1 }, { 129, 141, 2 }, { 128, 141, 3 }, { 109, 142, 4 }, { 108, 142, 3 }, { 120, 142, 5 }, { 107, 142, 4 }, { 131, 142, 5 }, { 132, 143, 2 }, { 133, 143, 3 }, { 124, 144, 2 }, { 133, 144, 2 }, { 134, 144, 3 }, { 134, 145, 2 }, { 135, 145, 1 }, { 135, 146, 2 }, { 112, 146, 3 }, { 145, 146, 1 }, { 112, 147, 4 }, { 146, 147, 1 }, { 98, 147, 5 }, { 26, 147, 10 }, { 136, 148, 1 }, { 137, 148, 4 }, { 138, 149, 1 }, { 139, 149, 4 }, { 137, 149, 2 }, { 139, 150, 3 }, { 140, 150, 2 }, { 117, 151, 4 }, { 141, 151, 2 }, { 118, 152, 4 }, { 119, 152, 4 }, { 117, 152, 3 }, { 151, 152, 1 }, { 133, 154, 2 }, { 143, 154, 3 }, { 144, 154, 2 }, { 144, 155, 1 }, { 154, 155, 1 }, { 144, 156, 2 }, { 155, 156, 1 }, { 134, 156, 3 }, { 145, 156, 3 }, { 146, 157, 1 }, { 147, 157, 2 }, { 145, 157, 2 }, { 147, 158, 1 }, { 157, 158, 1 }, { 139, 159, 3 }, { 149, 159, 2 }, { 139, 160, 3 }, { 150, 160, 2 }, { 159, 160, 3 }, { 150, 161, 2 }, { 160, 161, 2 }, { 140, 161, 2 }, { 141, 162, 4 }, { 151, 162, 5 }, { 128, 162, 4 }, { 140, 162, 3 }, { 161, 162, 2 }, { 119, 163, 4 }, { 131, 163, 5 }, { 152, 163, 3 }, { 131, 164, 4 }, { 163, 164, 4 }, { 142, 164, 4 }, { 142, 153, 1 }, { 153, 164, 4 }, { 153, 165, 1 }, { 164, 165, 3 }, { 153, 166, 2 }, { 165, 166, 1 }, { 142, 166, 3 }, { 120, 166, 5 }, { 120, 167, 5 }, { 121, 167, 5 }, { 166, 167, 4 }, { 122, 168, 4 }, { 132, 168, 4 }, { 121, 168, 5 }, { 167, 168, 2 }, { 143, 169, 3 }, { 154, 169, 4 }, { 132, 169, 3 }, { 168, 169, 2 }, { 155, 170, 1 }, { 156, 170, 2 }, { 154, 170, 2 }, { 156, 171, 2 }, { 170, 171, 2 }, { 145, 171, 3 }, { 157, 171, 3 }, { 149, 173, 2 }, { 159, 173, 2 }, { 159, 174, 3 }, { 160, 174, 2 }, { 173, 174, 3 }, { 161, 175, 1 }, { 162, 175, 3 }, { 160, 175, 3 }, { 162, 176, 1 }, { 175, 176, 2 }, { 152, 177, 3 }, { 151, 177, 2 }, { 163, 177, 4 }, { 162, 177, 5 }, { 176, 177, 4 }, { 163, 178, 1 }, { 177, 178, 3 }, { 163, 179, 2 }, { 164, 179, 4 }, { 178, 179, 1 }, { 164, 180, 2 }, { 165, 180, 3 }, { 167, 181, 1 }, { 168, 181, 3 }, { 166, 181, 5 }, { 154, 182, 3 }, { 169, 182, 3 }, { 170, 182, 3 }, { 170, 183, 1 }, { 182, 183, 2 }, { 171, 183, 3 }, { 171, 184, 1 }, { 183, 184, 2 }, { 157, 185, 2 }, { 158, 185, 3 }, { 171, 185, 3 }, { 184, 185, 2 }, { 172, 186, 2 }, { 172, 187, 3 }, { 186, 187, 1 }, { 149, 187, 4 }, { 173, 187, 3 }, { 148, 172, 2 }, { 148, 187, 4 }, { 137, 187, 5 }, { 160, 188, 2 }, { 174, 188, 2 }, { 175, 188, 3 }, { 177, 189, 2 }, { 178, 189, 3 }, { 178, 190, 2 }, { 179, 190, 1 }, { 189, 190, 3 }, { 165, 191, 3 }, { 180, 191, 1 }, { 164, 191, 3 }, { 166, 192, 2 }, { 181, 192, 5 }, { 165, 192, 3 }, { 191, 192, 3 }, { 182, 193, 2 }, { 183, 193, 2 }, { 186, 194, 2 }, { 187, 194, 1 }, { 187, 195, 2 }, { 173, 195, 3 }, { 194, 195, 1 }, { 174, 196, 3 }, { 188, 196, 3 }, { 173, 196, 3 }, { 195, 196, 3 }, { 177, 197, 3 }, { 176, 197, 3 }, { 177, 198, 3 }, { 197, 198, 1 }, { 177, 199, 2 }, { 189, 199, 2 }, { 198, 199, 1 }, { 164, 200, 4 }, { 191, 200, 4 }, { 179, 200, 3 }, { 190, 200, 2 }, { 181, 201, 3 }, { 192, 201, 4 }, { 169, 202, 4 }, { 182, 202, 4 }, { 168, 202, 4 }, { 181, 202, 4 }, { 201, 202, 4 }, { 182, 203, 3 }, { 193, 203, 1 }, { 183, 203, 3 }, { 184, 203, 4 }, { 158, 204, 4 }, { 185, 204, 3 }, { 194, 205, 1 }, { 195, 205, 2 }, { 186, 205, 3 }, { 175, 206, 3 }, { 188, 206, 3 }, { 176, 206, 4 }, { 198, 207, 1 }, { 199, 207, 2 }, { 197, 207, 2 }, { 191, 208, 3 }, { 200, 208, 2 }, { 191, 209, 3 }, { 192, 209, 3 }, { 192, 210, 2 }, { 201, 210, 4 }, { 209, 210, 1 }, { 201, 211, 5 }, { 202, 211, 1 }, { 202, 212, 4 }, { 211, 212, 3 }, { 182, 212, 3 }, { 203, 212, 2 }, { 184, 213, 4 }, { 203, 213, 5 }, { 185, 213, 4 }, { 204, 213, 3 }, { 213, 214, 2 }, { 204, 214, 1 }, { 186, 215, 3 }, { 205, 215, 2 }, { 172, 215, 5 }, { 195, 216, 3 }, { 205, 216, 3 }, { 196, 216, 3 }, { 197, 217, 3 }, { 207, 217, 4 }, { 176, 217, 4 }, { 206, 217, 3 }, { 199, 218, 2 }, { 207, 218, 2 }, { 200, 219, 3 }, { 208, 219, 4 }, { 190, 219, 4 }, { 189, 219, 4 }, { 199, 219, 4 }, { 218, 219, 3 }, { 191, 220, 4 }, { 208, 220, 2 }, { 209, 221, 2 }, { 210, 221, 3 }, { 191, 221, 4 }, { 220, 221, 2 }, { 201, 222, 3 }, { 210, 222, 3 }, { 211, 223, 3 }, { 212, 223, 2 }, { 212, 224, 1 }, { 223, 224, 1 }, { 212, 225, 2 }, { 203, 225, 2 }, { 224, 225, 1 }, { 213, 226, 2 }, { 214, 226, 2 }, { 205, 227, 3 }, { 215, 227, 3 }, { 216, 227, 2 }, { 216, 228, 1 }, { 227, 228, 1 }, { 216, 229, 2 }, { 196, 229, 4 }, { 228, 229, 1 }, { 196, 230, 4 }, { 229, 230, 3 }, { 188, 230, 4 }, { 206, 230, 3 }, { 206, 231, 3 }, { 217, 231, 2 }, { 230, 231, 3 }, { 207, 232, 3 }, { 217, 232, 3 }, { 207, 233, 2 }, { 218, 233, 2 }, { 232, 233, 1 }, { 208, 234, 3 }, { 219, 234, 2 }, { 208, 235, 3 }, { 220, 235, 3 }, { 234, 235, 1 }, { 220, 236, 1 }, { 221, 236, 3 }, { 235, 236, 2 }, { 221, 237, 1 }, { 236, 237, 2 }, { 210, 237, 3 }, { 211, 238, 3 }, { 223, 238, 2 }, { 225, 239, 2 }, { 225, 240, 3 }, { 239, 240, 1 }, { 203, 240, 4 }, { 213, 240, 3 }, { 213, 241, 3 }, { 240, 241, 1 }, { 226, 241, 3 }, { 214, 242, 2 }, { 226, 242, 2 }, { 172, 243, 6 }, { 215, 243, 3 }, { 215, 244, 3 }, { 227, 244, 2 }, { 243, 244, 2 }, { 230, 245, 2 }, { 231, 245, 3 }, { 231, 246, 1 }, { 245, 246, 2 }, { 231, 247, 2 }, { 246, 247, 1 }, { 217, 247, 2 }, { 232, 247, 3 }, { 218, 248, 3 }, { 219, 248, 3 }, { 233, 248, 4 }, { 234, 249, 2 }, { 235, 249, 3 }, { 219, 249, 2 }, { 248, 249, 1 }, { 236, 250, 2 }, { 237, 250, 2 }, { 235, 250, 4 }, { 237, 251, 4 }, { 250, 251, 4 }, { 210, 251, 4 }, { 222, 251, 3 }, { 211, 252, 4 }, { 238, 252, 3 }, { 201, 252, 5 }, { 222, 252, 5 }, { 223, 253, 3 }, { 224, 253, 2 }, { 238, 253, 3 }, { 225, 253, 3 }, { 239, 254, 2 }, { 240, 254, 3 }, { 225, 254, 2 }, { 253, 254, 1 }, { 240, 255, 2 }, { 241, 255, 1 }, { 254, 255, 3 }, { 241, 256, 3 }, { 255, 256, 2 }, { 226, 256, 2 }, { 242, 256, 2 }, { 243, 257, 3 }, { 244, 257, 1 }, { 228, 258, 2 }, { 229, 258, 3 }, { 244, 258, 3 }, { 257, 258, 2 }, { 227, 258, 3 }, { 232, 259, 3 }, { 233, 259, 3 }, { 247, 259, 2 }, { 246, 259, 3 }, { 222, 260, 3 }, { 251, 260, 2 }, { 222, 261, 4 }, { 252, 261, 3 }, { 260, 261, 2 }, { 255, 262, 1 }, { 256, 262, 3 }, { 254, 262, 4 }, { 230, 263, 4 }, { 245, 263, 3 }, { 229, 263, 4 }, { 258, 263, 4 }, { 245, 264, 3 }, { 263, 264, 1 }, { 245, 265, 2 }, { 246, 265, 3 }, { 264, 265, 1 }, { 248, 266, 2 }, { 249, 266, 3 }, { 233, 266, 5 }, { 259, 266, 6 }, { 249, 267, 2 }, { 266, 267, 1 }, { 235, 268, 4 }, { 250, 268, 5 }, { 249, 268, 3 }, { 267, 268, 1 }, { 251, 269, 2 }, { 250, 269, 5 }, { 260, 269, 2 }, { 252, 270, 3 }, { 261, 270, 4 }, { 238, 270, 4 }, { 238, 271, 3 }, { 253, 271, 3 }, { 270, 271, 1 }, { 253, 272, 3 }, { 254, 272, 2 }, { 271, 272, 3 }, { 254, 273, 3 }, { 272, 273, 2 }, { 262, 273, 2 }, { 262, 274, 3 }, { 256, 274, 2 }, { 242, 274, 4 }, { 264, 275, 3 }, { 265, 275, 2 }, { 246, 275, 4 }, { 259, 275, 4 }, { 267, 276, 2 }, { 268, 276, 1 }, { 266, 276, 3 }, { 268, 277, 2 }, { 250, 277, 5 }, { 276, 277, 1 }, { 270, 278, 1 }, { 271, 278, 2 }, { 272, 279, 1 }, { 273, 279, 3 }, { 271, 279, 4 }, { 262, 280, 2 }, { 273, 280, 2 }, { 274, 280, 3 }, { 266, 281, 3 }, { 276, 281, 4 }, { 259, 281, 5 }, { 250, 282, 5 }, { 277, 282, 2 }, { 250, 283, 4 }, { 282, 283, 2 }, { 250, 284, 5 }, { 269, 284, 4 }, { 283, 284, 1 }, { 270, 285, 3 }, { 261, 285, 4 }, { 278, 285, 3 }, { 278, 286, 3 }, { 285, 286, 4 }, { 271, 286, 3 }, { 279, 286, 3 }, { 279, 287, 1 }, { 286, 287, 2 }, { 279, 288, 3 }, { 287, 288, 2 }, { 273, 288, 2 }, { 280, 288, 2 }, { 280, 289, 2 }, { 288, 289, 2 }, { 274, 289, 3 }, { 274, 290, 2 }, { 289, 290, 1 }, { 257, 291, 5 }, { 243, 291, 5 }, { 257, 292, 5 }, { 258, 292, 5 }, { 291, 292, 1 }, { 264, 293, 4 }, { 263, 293, 4 }, { 275, 293, 3 }, { 275, 294, 2 }, { 293, 294, 1 }, { 259, 295, 5 }, { 281, 295, 6 }, { 275, 295, 3 }, { 294, 295, 2 }, { 281, 296, 1 }, { 295, 296, 5 }, { 281, 297, 2 }, { 276, 297, 3 }, { 296, 297, 1 }, { 276, 298, 2 }, { 297, 298, 2 }, { 277, 298, 3 }, { 282, 298, 3 }, { 261, 299, 5 }, { 285, 299, 3 }, { 260, 299, 5 }, { 269, 299, 4 }, { 285, 300, 2 }, { 299, 300, 1 }, { 287, 301, 2 }, { 288, 301, 2 }, { 291, 302, 1 }, { 292, 302, 2 }, { 292, 303, 1 }, { 302, 303, 1 }, { 292, 304, 2 }, { 303, 304, 1 }, { 258, 304, 6 }, { 263, 304, 7 }, { 293, 305, 1 }, { 294, 305, 2 }, { 294, 306, 2 }, { 295, 306, 2 }, { 305, 306, 2 }, { 282, 307, 3 }, { 283, 307, 3 }, { 298, 307, 4 }, { 283, 308, 2 }, { 284, 308, 3 }, { 307, 308, 1 }, { 269, 309, 5 }, { 284, 309, 5 }, { 299, 309, 2 }, { 299, 310, 1 }, { 300, 310, 2 }, { 309, 310, 1 }, { 288, 311, 2 }, { 289, 311, 3 }, { 301, 311, 2 }, { 289, 312, 3 }, { 311, 312, 4 }, { 290, 312, 3 }, { 274, 312, 5 }, { 242, 312, 7 }, { 293, 313, 3 }, { 305, 313, 3 }, { 263, 313, 5 }, { 304, 313, 6 }, { 305, 314, 2 }, { 306, 314, 2 }, { 313, 314, 3 }, { 296, 315, 2 }, { 297, 315, 3 }, { 284, 316, 4 }, { 308, 316, 3 }, { 309, 316, 4 }, { 309, 317, 1 }, { 310, 317, 2 }, { 316, 317, 3 }, { 313, 318, 4 }, { 314, 318, 1 }, { 295, 319, 4 }, { 306, 319, 2 }, { 314, 319, 2 }, { 318, 319, 1 }, { 295, 320, 5 }, { 319, 320, 4 }, { 296, 320, 4 }, { 315, 320, 3 }, { 297, 321, 4 }, { 298, 321, 4 }, { 315, 321, 3 }, { 298, 322, 4 }, { 307, 322, 3 }, { 321, 322, 2 }, { 316, 323, 4 }, { 317, 323, 1 }, { 317, 324, 2 }, { 310, 324, 2 }, { 323, 324, 1 }, { 310, 325, 3 }, { 300, 325, 3 }, { 324, 325, 1 }, { 300, 326, 4 }, { 285, 326, 4 }, { 325, 326, 1 }, { 285, 327, 5 }, { 286, 327, 5 }, { 326, 327, 3 }, { 286, 328, 5 }, { 327, 328, 2 }, { 287, 328, 5 }, { 301, 328, 4 }, { 301, 329, 3 }, { 311, 329, 3 }, { 328, 329, 2 }, { 311, 330, 2 }, { 329, 330, 1 }, { 312, 330, 5 }, { 303, 331, 4 }, { 302, 331, 3 }, { 303, 332, 3 }, { 304, 332, 4 }, { 331, 332, 1 }, { 304, 333, 3 }, { 332, 333, 1 }, { 313, 334, 3 }, { 318, 334, 3 }, { 315, 335, 3 }, { 320, 335, 2 }, { 315, 336, 3 }, { 321, 336, 2 }, { 335, 336, 2 }, { 329, 337, 1 }, { 330, 337, 2 }, { 328, 337, 3 }, { 330, 338, 5 }, { 312, 338, 3 }, { 331, 339, 1 }, { 332, 339, 2 }, { 313, 341, 5 }, { 334, 341, 5 }, { 333, 340, 2 }, { 333, 341, 3 }, { 340, 341, 1 }, { 304, 341, 5 }, { 318, 342, 3 }, { 334, 342, 1 }, { 318, 343, 3 }, { 319, 343, 2 }, { 342, 343, 3 }, { 319, 344, 3 }, { 343, 344, 1 }, { 319, 345, 3 }, { 320, 345, 3 }, { 344, 345, 1 }, { 320, 346, 2 }, { 335, 346, 2 }, { 345, 346, 2 }, { 308, 347, 4 }, { 316, 347, 4 }, { 307, 347, 5 }, { 322, 347, 4 }, { 316, 348, 4 }, { 323, 348, 5 }, { 347, 348, 1 }, { 327, 349, 2 }, { 326, 349, 4 }, { 328, 349, 3 }, { 330, 350, 3 }, { 337, 350, 3 }, { 330, 351, 3 }, { 350, 351, 1 }, { 338, 351, 3 }, { 333, 352, 2 }, { 340, 352, 2 }, { 332, 352, 3 }, { 339, 352, 3 }, { 340, 353, 1 }, { 341, 353, 2 }, { 352, 353, 1 }, { 341, 354, 2 }, { 353, 354, 2 }, { 334, 354, 4 }, { 342, 354, 4 }, { 345, 355, 1 }, { 346, 355, 3 }, { 344, 355, 2 }, { 343, 355, 3 }, { 346, 356, 1 }, { 355, 356, 2 }, { 335, 356, 3 }, { 321, 357, 4 }, { 322, 357, 4 }, { 336, 357, 3 }, { 322, 358, 4 }, { 347, 358, 3 }, { 357, 358, 2 }, { 347, 359, 2 }, { 348, 359, 1 }, { 358, 359, 3 }, { 323, 360, 4 }, { 324, 360, 4 }, { 348, 360, 4 }, { 359, 360, 3 }, { 326, 361, 4 }, { 349, 361, 5 }, { 325, 361, 3 }, { 324, 361, 4 }, { 360, 361, 3 }, { 349, 362, 1 }, { 361, 362, 4 }, { 349, 363, 3 }, { 362, 363, 2 }, { 328, 363, 3 }, { 337, 363, 3 }, { 337, 364, 3 }, { 350, 364, 2 }, { 363, 364, 3 }, { 350, 365, 1 }, { 364, 365, 1 }, { 351, 365, 2 }, { 351, 366, 2 }, { 365, 366, 2 }, { 338, 366, 3 }, { 339, 367, 3 }, { 352, 367, 2 }, { 352, 368, 2 }, { 353, 368, 1 }, { 367, 368, 2 }, { 353, 369, 2 }, { 354, 369, 2 }, { 368, 369, 1 }, { 354, 370, 1 }, { 369, 370, 1 }, { 354, 371, 2 }, { 342, 371, 3 }, { 370, 371, 1 }, { 342, 372, 3 }, { 343, 372, 3 }, { 371, 372, 3 }, { 355, 373, 1 }, { 356, 373, 3 }, { 343, 373, 3 }, { 356, 374, 1 }, { 373, 374, 2 }, { 356, 375, 3 }, { 374, 375, 2 }, { 336, 375, 4 }, { 357, 375, 4 }, { 335, 375, 4 }, { 365, 376, 2 }, { 366, 376, 2 }, { 370, 377, 1 }, { 371, 377, 2 }, { 369, 377, 2 }, { 368, 377, 3 }, { 343, 378, 4 }, { 372, 378, 2 }, { 343, 379, 3 }, { 373, 379, 3 }, { 378, 379, 1 }, { 373, 380, 1 }, { 374, 380, 3 }, { 379, 380, 2 }, { 374, 381, 3 }, { 375, 381, 1 }, { 375, 382, 3 }, { 357, 382, 3 }, { 381, 382, 2 }, { 360, 383, 3 }, { 361, 383, 3 }, { 361, 384, 3 }, { 362, 384, 3 }, { 362, 385, 3 }, { 363, 385, 2 }, { 364, 385, 4 }, { 364, 386, 2 }, { 385, 386, 3 }, { 365, 386, 3 }, { 376, 386, 3 }, { 368, 387, 3 }, { 377, 387, 4 }, { 367, 387, 3 }, { 371, 388, 2 }, { 372, 388, 4 }, { 377, 388, 2 }, { 378, 389, 2 }, { 379, 389, 3 }, { 372, 389, 2 }, { 388, 389, 3 }, { 379, 390, 2 }, { 380, 390, 2 }, { 389, 390, 3 }, { 374, 391, 2 }, { 380, 391, 3 }, { 381, 391, 3 }, { 381, 392, 2 }, { 382, 392, 2 }, { 391, 392, 3 }, { 357, 393, 4 }, { 358, 393, 4 }, { 382, 393, 3 }, { 360, 394, 4 }, { 359, 394, 4 }, { 383, 394, 3 }, { 383, 395, 1 }, { 394, 395, 2 }, { 383, 396, 3 }, { 395, 396, 2 }, { 361, 396, 3 }, { 384, 396, 3 }, { 384, 397, 2 }, { 396, 397, 1 }, { 362, 398, 3 }, { 384, 398, 3 }, { 385, 398, 3 }, { 386, 399, 4 }, { 376, 399, 3 }, { 366, 399, 3 }, { 339, 400, 5 }, { 367, 400, 4 }, { 387, 400, 3 }, { 388, 401, 2 }, { 389, 401, 3 }, { 389, 402, 2 }, { 401, 402, 1 }, { 389, 403, 4 }, { 390, 403, 1 }, { 390, 404, 3 }, { 403, 404, 2 }, { 380, 404, 3 }, { 391, 404, 2 }, { 391, 405, 4 }, { 392, 405, 1 }, { 392, 406, 3 }, { 405, 406, 2 }, { 382, 406, 3 }, { 393, 406, 2 }, { 358, 407, 5 }, { 359, 407, 5 }, { 393, 407, 3 }, { 359, 408, 5 }, { 394, 408, 4 }, { 407, 408, 1 }, { 394, 409, 2 }, { 395, 409, 2 }, { 395, 410, 1 }, { 396, 410, 3 }, { 409, 410, 1 }, { 396, 411, 2 }, { 397, 411, 1 }, { 410, 411, 3 }, { 397, 412, 2 }, { 411, 412, 1 }, { 384, 412, 2 }, { 398, 412, 3 }, { 398, 413, 1 }, { 412, 413, 2 }, { 385, 413, 3 }, { 366, 414, 5 }, { 338, 414, 6 }, { 399, 414, 2 }, { 388, 415, 3 }, { 401, 415, 4 }, { 377, 415, 4 }, { 387, 415, 3 }, { 389, 416, 2 }, { 402, 416, 2 }, { 403, 416, 4 }, { 401, 416, 3 }, { 403, 417, 1 }, { 404, 417, 3 }, { 416, 417, 3 }, { 404, 418, 2 }, { 417, 418, 3 }, { 391, 418, 2 }, { 405, 418, 4 }, { 393, 419, 2 }, { 406, 419, 2 }, { 407, 419, 3 }, { 394, 420, 3 }, { 408, 420, 2 }, { 407, 420, 3 }, { 394, 421, 2 }, { 409, 421, 2 }, { 420, 421, 2 }, { 411, 422, 2 }, { 412, 422, 1 }, { 412, 423, 2 }, { 413, 423, 2 }, { 422, 423, 1 }, { 387, 425, 3 }, { 415, 425, 3 }, { 400, 425, 3 }, { 400, 424, 2 }, { 424, 425, 2 }, { 401, 426, 3 }, { 415, 426, 3 }, { 416, 426, 4 }, { 417, 427, 1 }, { 418, 427, 4 }, { 416, 427, 4 }, { 409, 428, 2 }, { 410, 428, 3 }, { 421, 428, 2 }, { 411, 429, 2 }, { 422, 429, 2 }, { 410, 429, 4 }, { 428, 429, 4 }, { 422, 430, 2 }, { 423, 430, 1 }, { 429, 430, 2 }, { 413, 430, 3 }, { 385, 431, 5 }, { 413, 431, 5 }, { 386, 431, 5 }, { 386, 432, 4 }, { 431, 432, 1 }, { 399, 433, 4 }, { 414, 433, 4 }, { 386, 433, 5 }, { 432, 433, 1 }, { 433, 434, 3 }, { 414, 434, 2 }, { 424, 435, 2 }, { 425, 435, 2 }, { 425, 436, 1 }, { 435, 436, 1 }, { 415, 437, 3 }, { 426, 437, 4 }, { 425, 437, 2 }, { 436, 437, 1 }, { 418, 438, 3 }, { 427, 438, 3 }, { 418, 439, 4 }, { 405, 439, 3 }, { 438, 439, 4 }, { 406, 440, 4 }, { 419, 440, 3 }, { 405, 440, 4 }, { 439, 440, 1 }, { 419, 441, 3 }, { 440, 441, 3 }, { 407, 441, 4 }, { 407, 442, 3 }, { 420, 442, 3 }, { 441, 442, 1 }, { 429, 443, 2 }, { 430, 443, 2 }, { 430, 444, 2 }, { 443, 444, 2 }, { 413, 444, 3 }, { 431, 445, 2 }, { 432, 445, 3 }, { 413, 445, 5 }, { 444, 445, 3 }, { 433, 446, 2 }, { 434, 446, 3 }, { 436, 447, 1 }, { 437, 447, 2 }, { 435, 447, 2 }, { 439, 448, 1 }, { 440, 448, 2 }, { 440, 449, 1 }, { 448, 449, 1 }, { 440, 450, 3 }, { 441, 450, 2 }, { 449, 450, 2 }, { 441, 451, 2 }, { 442, 451, 1 }, { 450, 451, 2 }, { 421, 452, 4 }, { 420, 452, 3 }, { 442, 452, 3 }, { 451, 452, 2 }, { 421, 453, 3 }, { 428, 453, 3 }, { 452, 453, 2 }, { 428, 454, 3 }, { 429, 454, 4 }, { 453, 454, 2 }, { 429, 455, 3 }, { 443, 455, 3 }, { 454, 455, 2 }, { 444, 456, 2 }, { 445, 456, 3 }, { 445, 457, 1 }, { 456, 457, 2 }, { 432, 457, 3 }, { 432, 458, 3 }, { 457, 458, 3 }, { 433, 458, 2 }, { 446, 458, 2 }, { 424, 459, 3 }, { 435, 459, 3 }, { 435, 460, 2 }, { 447, 460, 2 }, { 459, 460, 1 }, { 427, 461, 3 }, { 438, 461, 3 }, { 416, 461, 5 }, { 438, 462, 3 }, { 461, 462, 1 }, { 439, 463, 3 }, { 438, 463, 4 }, { 448, 463, 2 }, { 452, 464, 1 }, { 453, 464, 3 }, { 451, 464, 3 }, { 454, 465, 3 }, { 455, 465, 1 }, { 455, 466, 2 }, { 443, 466, 3 }, { 465, 466, 1 }, { 444, 467, 3 }, { 456, 467, 4 }, { 443, 467, 2 }, { 466, 467, 1 }, { 456, 468, 3 }, { 457, 468, 1 }, { 457, 469, 2 }, { 468, 469, 1 }, { 458, 469, 3 }, { 459, 470, 3 }, { 460, 470, 2 }, { 447, 470, 2 }, { 437, 470, 4 }, { 461, 471, 2 }, { 462, 471, 3 }, { 416, 471, 6 }, { 426, 471, 7 }, { 438, 472, 4 }, { 462, 472, 4 }, { 463, 472, 2 }, { 463, 473, 3 }, { 472, 473, 3 }, { 449, 473, 2 }, { 450, 473, 3 }, { 448, 473, 3 }, { 453, 474, 2 }, { 454, 474, 3 }, { 464, 474, 3 }, { 466, 475, 2 }, { 467, 475, 1 }, { 465, 475, 3 }, { 456, 476, 2 }, { 468, 476, 3 }, { 467, 476, 4 }, { 475, 476, 3 }, { 468, 477, 1 }, { 476, 477, 2 }, { 469, 477, 2 }, { 469, 478, 1 }, { 477, 478, 1 }, { 458, 478, 3 }, { 446, 479, 4 }, { 458, 479, 3 }, { 446, 480, 4 }, { 434, 480, 4 }, { 479, 480, 1 }, { 459, 481, 3 }, { 470, 481, 1 }, { 462, 482, 3 }, { 471, 482, 4 }, { 472, 482, 3 }, { 450, 483, 4 }, { 451, 483, 5 }, { 473, 483, 2 }, { 464, 484, 3 }, { 474, 484, 1 }, { 474, 485, 3 }, { 484, 485, 2 }, { 454, 485, 3 }, { 465, 485, 3 }, { 475, 486, 2 }, { 476, 486, 3 }, { 476, 487, 2 }, { 486, 487, 1 }, { 477, 488, 1 }, { 478, 488, 2 }, { 476, 488, 3 }, { 487, 488, 3 }, { 458, 489, 3 }, { 478, 489, 3 }, { 479, 489, 3 }, { 479, 490, 2 }, { 489, 490, 3 }, { 480, 490, 1 }, { 481, 491, 2 }, { 459, 491, 4 }, { 471, 492, 6 }, { 482, 492, 9 }, { 481, 492, 5 }, { 491, 492, 5 }, { 470, 492, 5 }, { 426, 492, 6 }, { 437, 492, 6 }, { 482, 493, 3 }, { 492, 493, 10 }, { 472, 493, 2 }, { 473, 493, 4 }, { 473, 494, 3 }, { 483, 494, 1 }, { 493, 494, 4 }, { 464, 495, 4 }, { 484, 495, 5 }, { 483, 495, 4 }, { 494, 495, 3 }, { 451, 495, 4 }, { 484, 496, 1 }, { 485, 496, 3 }, { 495, 496, 4 }, { 485, 497, 4 }, { 496, 497, 5 }, { 465, 497, 4 }, { 475, 497, 3 }, { 487, 498, 3 }, { 488, 498, 6 }, { 486, 498, 2 }, { 475, 498, 2 }, { 497, 498, 1 }, { 489, 499, 2 }, { 490, 499, 5 }, { 488, 499, 3 }, { 498, 499, 7 }, { 478, 499, 3 } }; double maxWeight = 866; double minWeight = 425; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on an unweighted $K_{50}$ */ @Test public void testGetMatching37() { DefaultUndirectedWeightedGraph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); CompleteGraphGenerator generator = new CompleteGraphGenerator<>(20); generator.generateGraph(graph); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options, objectiveSense); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); assertEquals(10, matching.getWeight(), EPS); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), objectiveSense); } /** * Test on a weighted $K_{50}$ */ @Test public void testGetMatching38() { int[][] edges = new int[][] { { 1, 0, 597 }, { 2, 0, 614 }, { 2, 1, 57 }, { 3, 0, 554 }, { 3, 1, 883 }, { 3, 2, 883 }, { 4, 0, 752 }, { 4, 1, 191 }, { 4, 2, 972 }, { 4, 3, 392 }, { 5, 0, 542 }, { 5, 1, 507 }, { 5, 2, 931 }, { 5, 3, 223 }, { 5, 4, 38 }, { 6, 0, 125 }, { 6, 1, 261 }, { 6, 2, 511 }, { 6, 3, 892 }, { 6, 4, 250 }, { 6, 5, 791 }, { 7, 0, 118 }, { 7, 1, 184 }, { 7, 2, 33 }, { 7, 3, 665 }, { 7, 4, 446 }, { 7, 5, 908 }, { 7, 6, 355 }, { 8, 0, 812 }, { 8, 1, 794 }, { 8, 2, 239 }, { 8, 3, 222 }, { 8, 4, 447 }, { 8, 5, 764 }, { 8, 6, 779 }, { 8, 7, 693 }, { 9, 0, 653 }, { 9, 1, 444 }, { 9, 2, 344 }, { 9, 3, 565 }, { 9, 4, 995 }, { 9, 5, 999 }, { 9, 6, 251 }, { 9, 7, 173 }, { 9, 8, 501 }, { 10, 0, 945 }, { 10, 1, 754 }, { 10, 2, 147 }, { 10, 3, 912 }, { 10, 4, 464 }, { 10, 5, 766 }, { 10, 6, 311 }, { 10, 7, 242 }, { 10, 8, 887 }, { 10, 9, 771 }, { 11, 0, 63 }, { 11, 1, 566 }, { 11, 2, 219 }, { 11, 3, 931 }, { 11, 4, 519 }, { 11, 5, 707 }, { 11, 6, 630 }, { 11, 7, 702 }, { 11, 8, 258 }, { 11, 9, 164 }, { 11, 10, 13 }, { 12, 0, 238 }, { 12, 1, 769 }, { 12, 2, 498 }, { 12, 3, 928 }, { 12, 4, 335 }, { 12, 5, 255 }, { 12, 6, 254 }, { 12, 7, 742 }, { 12, 8, 796 }, { 12, 9, 191 }, { 12, 10, 915 }, { 12, 11, 179 }, { 13, 0, 292 }, { 13, 1, 586 }, { 13, 2, 743 }, { 13, 3, 543 }, { 13, 4, 180 }, { 13, 5, 9 }, { 13, 6, 394 }, { 13, 7, 843 }, { 13, 8, 198 }, { 13, 9, 843 }, { 13, 10, 768 }, { 13, 11, 818 }, { 13, 12, 734 }, { 14, 0, 115 }, { 14, 1, 281 }, { 14, 2, 570 }, { 14, 3, 517 }, { 14, 4, 346 }, { 14, 5, 855 }, { 14, 6, 615 }, { 14, 7, 943 }, { 14, 8, 684 }, { 14, 9, 399 }, { 14, 10, 906 }, { 14, 11, 87 }, { 14, 12, 753 }, { 14, 13, 392 }, { 15, 0, 851 }, { 15, 1, 171 }, { 15, 2, 547 }, { 15, 3, 321 }, { 15, 4, 400 }, { 15, 5, 98 }, { 15, 6, 423 }, { 15, 7, 879 }, { 15, 8, 872 }, { 15, 9, 33 }, { 15, 10, 714 }, { 15, 11, 540 }, { 15, 12, 360 }, { 15, 13, 245 }, { 15, 14, 161 }, { 16, 0, 163 }, { 16, 1, 729 }, { 16, 2, 442 }, { 16, 3, 509 }, { 16, 4, 352 }, { 16, 5, 301 }, { 16, 6, 130 }, { 16, 7, 177 }, { 16, 8, 573 }, { 16, 9, 916 }, { 16, 10, 922 }, { 16, 11, 889 }, { 16, 12, 102 }, { 16, 13, 391 }, { 16, 14, 718 }, { 16, 15, 577 }, { 17, 0, 70 }, { 17, 1, 336 }, { 17, 2, 765 }, { 17, 3, 407 }, { 17, 4, 19 }, { 17, 5, 892 }, { 17, 6, 807 }, { 17, 7, 246 }, { 17, 8, 28 }, { 17, 9, 294 }, { 17, 10, 499 }, { 17, 11, 952 }, { 17, 12, 946 }, { 17, 13, 562 }, { 17, 14, 60 }, { 17, 15, 231 }, { 17, 16, 996 }, { 18, 0, 106 }, { 18, 1, 107 }, { 18, 2, 408 }, { 18, 3, 341 }, { 18, 4, 64 }, { 18, 5, 657 }, { 18, 6, 748 }, { 18, 7, 657 }, { 18, 8, 177 }, { 18, 9, 59 }, { 18, 10, 700 }, { 18, 11, 861 }, { 18, 12, 106 }, { 18, 13, 752 }, { 18, 14, 417 }, { 18, 15, 531 }, { 18, 16, 890 }, { 18, 17, 302 }, { 19, 0, 262 }, { 19, 1, 277 }, { 19, 2, 684 }, { 19, 3, 440 }, { 19, 4, 170 }, { 19, 5, 41 }, { 19, 6, 135 }, { 19, 7, 508 }, { 19, 8, 805 }, { 19, 9, 378 }, { 19, 10, 419 }, { 19, 11, 280 }, { 19, 12, 655 }, { 19, 13, 367 }, { 19, 14, 723 }, { 19, 15, 661 }, { 19, 16, 871 }, { 19, 17, 549 }, { 19, 18, 289 }, { 20, 0, 324 }, { 20, 1, 559 }, { 20, 2, 264 }, { 20, 3, 426 }, { 20, 4, 837 }, { 20, 5, 138 }, { 20, 6, 838 }, { 20, 7, 744 }, { 20, 8, 215 }, { 20, 9, 982 }, { 20, 10, 332 }, { 20, 11, 270 }, { 20, 12, 313 }, { 20, 13, 596 }, { 20, 14, 883 }, { 20, 15, 859 }, { 20, 16, 303 }, { 20, 17, 192 }, { 20, 18, 330 }, { 20, 19, 670 }, { 21, 0, 801 }, { 21, 1, 527 }, { 21, 2, 367 }, { 21, 3, 322 }, { 21, 4, 429 }, { 21, 5, 717 }, { 21, 6, 343 }, { 21, 7, 550 }, { 21, 8, 52 }, { 21, 9, 505 }, { 21, 10, 642 }, { 21, 11, 220 }, { 21, 12, 1 }, { 21, 13, 877 }, { 21, 14, 533 }, { 21, 15, 363 }, { 21, 16, 374 }, { 21, 17, 285 }, { 21, 18, 295 }, { 21, 19, 956 }, { 21, 20, 393 }, { 22, 0, 235 }, { 22, 1, 405 }, { 22, 2, 302 }, { 22, 3, 804 }, { 22, 4, 421 }, { 22, 5, 857 }, { 22, 6, 774 }, { 22, 7, 306 }, { 22, 8, 52 }, { 22, 9, 596 }, { 22, 10, 558 }, { 22, 11, 749 }, { 22, 12, 817 }, { 22, 13, 244 }, { 22, 14, 656 }, { 22, 15, 773 }, { 22, 16, 150 }, { 22, 17, 729 }, { 22, 18, 584 }, { 22, 19, 499 }, { 22, 20, 839 }, { 22, 21, 658 }, { 23, 0, 700 }, { 23, 1, 395 }, { 23, 2, 891 }, { 23, 3, 813 }, { 23, 4, 360 }, { 23, 5, 208 }, { 23, 6, 67 }, { 23, 7, 739 }, { 23, 8, 386 }, { 23, 9, 1 }, { 23, 10, 157 }, { 23, 11, 689 }, { 23, 12, 159 }, { 23, 13, 385 }, { 23, 14, 901 }, { 23, 15, 531 }, { 23, 16, 56 }, { 23, 17, 684 }, { 23, 18, 305 }, { 23, 19, 947 }, { 23, 20, 876 }, { 23, 21, 686 }, { 23, 22, 544 }, { 24, 0, 631 }, { 24, 1, 923 }, { 24, 2, 110 }, { 24, 3, 222 }, { 24, 4, 313 }, { 24, 5, 253 }, { 24, 6, 415 }, { 24, 7, 14 }, { 24, 8, 671 }, { 24, 9, 21 }, { 24, 10, 471 }, { 24, 11, 741 }, { 24, 12, 707 }, { 24, 13, 805 }, { 24, 14, 496 }, { 24, 15, 598 }, { 24, 16, 445 }, { 24, 17, 623 }, { 24, 18, 275 }, { 24, 19, 256 }, { 24, 20, 340 }, { 24, 21, 829 }, { 24, 22, 493 }, { 24, 23, 904 }, { 25, 0, 637 }, { 25, 1, 157 }, { 25, 2, 730 }, { 25, 3, 484 }, { 25, 4, 34 }, { 25, 5, 813 }, { 25, 6, 190 }, { 25, 7, 246 }, { 25, 8, 91 }, { 25, 9, 271 }, { 25, 10, 595 }, { 25, 11, 897 }, { 25, 12, 897 }, { 25, 13, 713 }, { 25, 14, 684 }, { 25, 15, 187 }, { 25, 16, 279 }, { 25, 17, 699 }, { 25, 18, 183 }, { 25, 19, 974 }, { 25, 20, 236 }, { 25, 21, 430 }, { 25, 22, 89 }, { 25, 23, 68 }, { 25, 24, 59 }, { 26, 0, 766 }, { 26, 1, 60 }, { 26, 2, 247 }, { 26, 3, 804 }, { 26, 4, 387 }, { 26, 5, 356 }, { 26, 6, 771 }, { 26, 7, 507 }, { 26, 8, 578 }, { 26, 9, 287 }, { 26, 10, 353 }, { 26, 11, 989 }, { 26, 12, 963 }, { 26, 13, 698 }, { 26, 14, 968 }, { 26, 15, 75 }, { 26, 16, 799 }, { 26, 17, 560 }, { 26, 18, 778 }, { 26, 19, 872 }, { 26, 20, 543 }, { 26, 21, 237 }, { 26, 22, 584 }, { 26, 23, 748 }, { 26, 24, 596 }, { 26, 25, 305 }, { 27, 0, 995 }, { 27, 1, 115 }, { 27, 2, 854 }, { 27, 3, 185 }, { 27, 4, 617 }, { 27, 5, 96 }, { 27, 6, 987 }, { 27, 7, 511 }, { 27, 8, 951 }, { 27, 9, 626 }, { 27, 10, 711 }, { 27, 11, 705 }, { 27, 12, 473 }, { 27, 13, 409 }, { 27, 14, 106 }, { 27, 15, 247 }, { 27, 16, 659 }, { 27, 17, 914 }, { 27, 18, 995 }, { 27, 19, 718 }, { 27, 20, 931 }, { 27, 21, 557 }, { 27, 22, 343 }, { 27, 23, 137 }, { 27, 24, 422 }, { 27, 25, 24 }, { 27, 26, 517 }, { 28, 0, 768 }, { 28, 1, 459 }, { 28, 2, 924 }, { 28, 3, 793 }, { 28, 4, 697 }, { 28, 5, 288 }, { 28, 6, 557 }, { 28, 7, 355 }, { 28, 8, 210 }, { 28, 9, 911 }, { 28, 10, 583 }, { 28, 11, 875 }, { 28, 12, 280 }, { 28, 13, 540 }, { 28, 14, 380 }, { 28, 15, 916 }, { 28, 16, 113 }, { 28, 17, 286 }, { 28, 18, 51 }, { 28, 19, 82 }, { 28, 20, 574 }, { 28, 21, 519 }, { 28, 22, 682 }, { 28, 23, 209 }, { 28, 24, 13 }, { 28, 25, 642 }, { 28, 26, 780 }, { 28, 27, 971 }, { 29, 0, 291 }, { 29, 1, 672 }, { 29, 2, 669 }, { 29, 3, 414 }, { 29, 4, 679 }, { 29, 5, 407 }, { 29, 6, 450 }, { 29, 7, 824 }, { 29, 8, 721 }, { 29, 9, 408 }, { 29, 10, 781 }, { 29, 11, 791 }, { 29, 12, 705 }, { 29, 13, 779 }, { 29, 14, 14 }, { 29, 15, 66 }, { 29, 16, 780 }, { 29, 17, 353 }, { 29, 18, 862 }, { 29, 19, 69 }, { 29, 20, 424 }, { 29, 21, 558 }, { 29, 22, 925 }, { 29, 23, 157 }, { 29, 24, 105 }, { 29, 25, 1 }, { 29, 26, 731 }, { 29, 27, 409 }, { 29, 28, 341 }, { 30, 0, 217 }, { 30, 1, 756 }, { 30, 2, 72 }, { 30, 3, 233 }, { 30, 4, 718 }, { 30, 5, 492 }, { 30, 6, 373 }, { 30, 7, 159 }, { 30, 8, 512 }, { 30, 9, 321 }, { 30, 10, 116 }, { 30, 11, 927 }, { 30, 12, 32 }, { 30, 13, 48 }, { 30, 14, 520 }, { 30, 15, 130 }, { 30, 16, 370 }, { 30, 17, 672 }, { 30, 18, 898 }, { 30, 19, 221 }, { 30, 20, 475 }, { 30, 21, 619 }, { 30, 22, 168 }, { 30, 23, 642 }, { 30, 24, 637 }, { 30, 25, 651 }, { 30, 26, 952 }, { 30, 27, 155 }, { 30, 28, 212 }, { 30, 29, 681 }, { 31, 0, 135 }, { 31, 1, 912 }, { 31, 2, 420 }, { 31, 3, 696 }, { 31, 4, 930 }, { 31, 5, 784 }, { 31, 6, 633 }, { 31, 7, 104 }, { 31, 8, 592 }, { 31, 9, 451 }, { 31, 10, 747 }, { 31, 11, 423 }, { 31, 12, 618 }, { 31, 13, 886 }, { 31, 14, 660 }, { 31, 15, 810 }, { 31, 16, 704 }, { 31, 17, 32 }, { 31, 18, 423 }, { 31, 19, 977 }, { 31, 20, 586 }, { 31, 21, 441 }, { 31, 22, 878 }, { 31, 23, 1000 }, { 31, 24, 15 }, { 31, 25, 379 }, { 31, 26, 136 }, { 31, 27, 145 }, { 31, 28, 445 }, { 31, 29, 736 }, { 31, 30, 309 }, { 32, 0, 583 }, { 32, 1, 798 }, { 32, 2, 651 }, { 32, 3, 799 }, { 32, 4, 910 }, { 32, 5, 524 }, { 32, 6, 819 }, { 32, 7, 944 }, { 32, 8, 446 }, { 32, 9, 497 }, { 32, 10, 769 }, { 32, 11, 82 }, { 32, 12, 263 }, { 32, 13, 53 }, { 32, 14, 930 }, { 32, 15, 671 }, { 32, 16, 622 }, { 32, 17, 20 }, { 32, 18, 960 }, { 32, 19, 829 }, { 32, 20, 445 }, { 32, 21, 827 }, { 32, 22, 444 }, { 32, 23, 861 }, { 32, 24, 120 }, { 32, 25, 903 }, { 32, 26, 462 }, { 32, 27, 225 }, { 32, 28, 804 }, { 32, 29, 267 }, { 32, 30, 821 }, { 32, 31, 120 }, { 33, 0, 194 }, { 33, 1, 476 }, { 33, 2, 289 }, { 33, 3, 951 }, { 33, 4, 857 }, { 33, 5, 298 }, { 33, 6, 365 }, { 33, 7, 501 }, { 33, 8, 722 }, { 33, 9, 213 }, { 33, 10, 515 }, { 33, 11, 379 }, { 33, 12, 637 }, { 33, 13, 409 }, { 33, 14, 992 }, { 33, 15, 390 }, { 33, 16, 936 }, { 33, 17, 112 }, { 33, 18, 382 }, { 33, 19, 602 }, { 33, 20, 888 }, { 33, 21, 995 }, { 33, 22, 376 }, { 33, 23, 581 }, { 33, 24, 520 }, { 33, 25, 677 }, { 33, 26, 936 }, { 33, 27, 750 }, { 33, 28, 270 }, { 33, 29, 715 }, { 33, 30, 845 }, { 33, 31, 40 }, { 33, 32, 741 }, { 34, 0, 308 }, { 34, 1, 848 }, { 34, 2, 63 }, { 34, 3, 613 }, { 34, 4, 745 }, { 34, 5, 29 }, { 34, 6, 845 }, { 34, 7, 879 }, { 34, 8, 80 }, { 34, 9, 518 }, { 34, 10, 985 }, { 34, 11, 140 }, { 34, 12, 947 }, { 34, 13, 467 }, { 34, 14, 149 }, { 34, 15, 894 }, { 34, 16, 680 }, { 34, 17, 998 }, { 34, 18, 258 }, { 34, 19, 6 }, { 34, 20, 285 }, { 34, 21, 691 }, { 34, 22, 58 }, { 34, 23, 515 }, { 34, 24, 227 }, { 34, 25, 389 }, { 34, 26, 426 }, { 34, 27, 36 }, { 34, 28, 387 }, { 34, 29, 276 }, { 34, 30, 250 }, { 34, 31, 661 }, { 34, 32, 257 }, { 34, 33, 602 }, { 35, 0, 642 }, { 35, 1, 734 }, { 35, 2, 884 }, { 35, 3, 764 }, { 35, 4, 587 }, { 35, 5, 458 }, { 35, 6, 478 }, { 35, 7, 502 }, { 35, 8, 240 }, { 35, 9, 722 }, { 35, 10, 847 }, { 35, 11, 407 }, { 35, 12, 175 }, { 35, 13, 345 }, { 35, 14, 376 }, { 35, 15, 356 }, { 35, 16, 295 }, { 35, 17, 627 }, { 35, 18, 214 }, { 35, 19, 272 }, { 35, 20, 623 }, { 35, 21, 110 }, { 35, 22, 614 }, { 35, 23, 969 }, { 35, 24, 209 }, { 35, 25, 107 }, { 35, 26, 183 }, { 35, 27, 998 }, { 35, 28, 385 }, { 35, 29, 310 }, { 35, 30, 832 }, { 35, 31, 492 }, { 35, 32, 102 }, { 35, 33, 344 }, { 35, 34, 983 }, { 36, 0, 40 }, { 36, 1, 276 }, { 36, 2, 446 }, { 36, 3, 283 }, { 36, 4, 103 }, { 36, 5, 564 }, { 36, 6, 210 }, { 36, 7, 431 }, { 36, 8, 737 }, { 36, 9, 905 }, { 36, 10, 343 }, { 36, 11, 684 }, { 36, 12, 267 }, { 36, 13, 519 }, { 36, 14, 747 }, { 36, 15, 729 }, { 36, 16, 452 }, { 36, 17, 665 }, { 36, 18, 845 }, { 36, 19, 450 }, { 36, 20, 264 }, { 36, 21, 455 }, { 36, 22, 845 }, { 36, 23, 858 }, { 36, 24, 944 }, { 36, 25, 995 }, { 36, 26, 867 }, { 36, 27, 998 }, { 36, 28, 610 }, { 36, 29, 726 }, { 36, 30, 863 }, { 36, 31, 583 }, { 36, 32, 348 }, { 36, 33, 468 }, { 36, 34, 152 }, { 36, 35, 923 }, { 37, 0, 828 }, { 37, 1, 41 }, { 37, 2, 48 }, { 37, 3, 513 }, { 37, 4, 627 }, { 37, 5, 638 }, { 37, 6, 340 }, { 37, 7, 904 }, { 37, 8, 881 }, { 37, 9, 804 }, { 37, 10, 50 }, { 37, 11, 299 }, { 37, 12, 642 }, { 37, 13, 588 }, { 37, 14, 499 }, { 37, 15, 778 }, { 37, 16, 389 }, { 37, 17, 784 }, { 37, 18, 759 }, { 37, 19, 368 }, { 37, 20, 245 }, { 37, 21, 210 }, { 37, 22, 864 }, { 37, 23, 861 }, { 37, 24, 944 }, { 37, 25, 887 }, { 37, 26, 255 }, { 37, 27, 871 }, { 37, 28, 636 }, { 37, 29, 391 }, { 37, 30, 83 }, { 37, 31, 250 }, { 37, 32, 970 }, { 37, 33, 179 }, { 37, 34, 656 }, { 37, 35, 777 }, { 37, 36, 307 }, { 38, 0, 915 }, { 38, 1, 259 }, { 38, 2, 119 }, { 38, 3, 680 }, { 38, 4, 172 }, { 38, 5, 452 }, { 38, 6, 983 }, { 38, 7, 614 }, { 38, 8, 883 }, { 38, 9, 252 }, { 38, 10, 460 }, { 38, 11, 477 }, { 38, 12, 905 }, { 38, 13, 894 }, { 38, 14, 406 }, { 38, 15, 337 }, { 38, 16, 514 }, { 38, 17, 90 }, { 38, 18, 258 }, { 38, 19, 385 }, { 38, 20, 883 }, { 38, 21, 380 }, { 38, 22, 548 }, { 38, 23, 557 }, { 38, 24, 872 }, { 38, 25, 731 }, { 38, 26, 476 }, { 38, 27, 941 }, { 38, 28, 391 }, { 38, 29, 238 }, { 38, 30, 746 }, { 38, 31, 845 }, { 38, 32, 772 }, { 38, 33, 689 }, { 38, 34, 975 }, { 38, 35, 828 }, { 38, 36, 82 }, { 38, 37, 253 }, { 39, 0, 889 }, { 39, 1, 284 }, { 39, 2, 419 }, { 39, 3, 69 }, { 39, 4, 459 }, { 39, 5, 132 }, { 39, 6, 28 }, { 39, 7, 452 }, { 39, 8, 79 }, { 39, 9, 528 }, { 39, 10, 120 }, { 39, 11, 581 }, { 39, 12, 649 }, { 39, 13, 346 }, { 39, 14, 512 }, { 39, 15, 458 }, { 39, 16, 689 }, { 39, 17, 282 }, { 39, 18, 99 }, { 39, 19, 238 }, { 39, 20, 924 }, { 39, 21, 377 }, { 39, 22, 664 }, { 39, 23, 326 }, { 39, 24, 104 }, { 39, 25, 787 }, { 39, 26, 589 }, { 39, 27, 481 }, { 39, 28, 378 }, { 39, 29, 755 }, { 39, 30, 18 }, { 39, 31, 425 }, { 39, 32, 318 }, { 39, 33, 496 }, { 39, 34, 431 }, { 39, 35, 410 }, { 39, 36, 818 }, { 39, 37, 173 }, { 39, 38, 543 }, { 40, 0, 719 }, { 40, 1, 24 }, { 40, 2, 631 }, { 40, 3, 216 }, { 40, 4, 636 }, { 40, 5, 318 }, { 40, 6, 343 }, { 40, 7, 888 }, { 40, 8, 229 }, { 40, 9, 619 }, { 40, 10, 79 }, { 40, 11, 998 }, { 40, 12, 142 }, { 40, 13, 496 }, { 40, 14, 314 }, { 40, 15, 233 }, { 40, 16, 364 }, { 40, 17, 584 }, { 40, 18, 891 }, { 40, 19, 353 }, { 40, 20, 528 }, { 40, 21, 773 }, { 40, 22, 247 }, { 40, 23, 312 }, { 40, 24, 804 }, { 40, 25, 345 }, { 40, 26, 775 }, { 40, 27, 381 }, { 40, 28, 518 }, { 40, 29, 7 }, { 40, 30, 462 }, { 40, 31, 149 }, { 40, 32, 873 }, { 40, 33, 124 }, { 40, 34, 833 }, { 40, 35, 878 }, { 40, 36, 515 }, { 40, 37, 532 }, { 40, 38, 244 }, { 40, 39, 73 }, { 41, 0, 690 }, { 41, 1, 261 }, { 41, 2, 457 }, { 41, 3, 379 }, { 41, 4, 15 }, { 41, 5, 538 }, { 41, 6, 427 }, { 41, 7, 689 }, { 41, 8, 380 }, { 41, 9, 93 }, { 41, 10, 57 }, { 41, 11, 573 }, { 41, 12, 705 }, { 41, 13, 877 }, { 41, 14, 549 }, { 41, 15, 721 }, { 41, 16, 418 }, { 41, 17, 575 }, { 41, 18, 796 }, { 41, 19, 975 }, { 41, 20, 573 }, { 41, 21, 948 }, { 41, 22, 305 }, { 41, 23, 322 }, { 41, 24, 864 }, { 41, 25, 903 }, { 41, 26, 210 }, { 41, 27, 261 }, { 41, 28, 919 }, { 41, 29, 9 }, { 41, 30, 372 }, { 41, 31, 118 }, { 41, 32, 115 }, { 41, 33, 611 }, { 41, 34, 883 }, { 41, 35, 231 }, { 41, 36, 646 }, { 41, 37, 247 }, { 41, 38, 753 }, { 41, 39, 905 }, { 41, 40, 698 }, { 42, 0, 708 }, { 42, 1, 111 }, { 42, 2, 548 }, { 42, 3, 825 }, { 42, 4, 740 }, { 42, 5, 41 }, { 42, 6, 352 }, { 42, 7, 479 }, { 42, 8, 555 }, { 42, 9, 460 }, { 42, 10, 1 }, { 42, 11, 884 }, { 42, 12, 310 }, { 42, 13, 539 }, { 42, 14, 411 }, { 42, 15, 175 }, { 42, 16, 526 }, { 42, 17, 719 }, { 42, 18, 234 }, { 42, 19, 444 }, { 42, 20, 336 }, { 42, 21, 629 }, { 42, 22, 859 }, { 42, 23, 717 }, { 42, 24, 634 }, { 42, 25, 553 }, { 42, 26, 557 }, { 42, 27, 934 }, { 42, 28, 955 }, { 42, 29, 656 }, { 42, 30, 398 }, { 42, 31, 442 }, { 42, 32, 16 }, { 42, 33, 875 }, { 42, 34, 939 }, { 42, 35, 812 }, { 42, 36, 631 }, { 42, 37, 551 }, { 42, 38, 824 }, { 42, 39, 936 }, { 42, 40, 602 }, { 42, 41, 273 }, { 43, 0, 920 }, { 43, 1, 183 }, { 43, 2, 1 }, { 43, 3, 656 }, { 43, 4, 2 }, { 43, 5, 353 }, { 43, 6, 876 }, { 43, 7, 419 }, { 43, 8, 272 }, { 43, 9, 117 }, { 43, 10, 404 }, { 43, 11, 929 }, { 43, 12, 669 }, { 43, 13, 180 }, { 43, 14, 277 }, { 43, 15, 87 }, { 43, 16, 522 }, { 43, 17, 748 }, { 43, 18, 819 }, { 43, 19, 387 }, { 43, 20, 285 }, { 43, 21, 376 }, { 43, 22, 861 }, { 43, 23, 888 }, { 43, 24, 158 }, { 43, 25, 53 }, { 43, 26, 482 }, { 43, 27, 27 }, { 43, 28, 543 }, { 43, 29, 319 }, { 43, 30, 414 }, { 43, 31, 465 }, { 43, 32, 616 }, { 43, 33, 553 }, { 43, 34, 345 }, { 43, 35, 491 }, { 43, 36, 277 }, { 43, 37, 196 }, { 43, 38, 893 }, { 43, 39, 290 }, { 43, 40, 572 }, { 43, 41, 967 }, { 43, 42, 747 }, { 44, 0, 141 }, { 44, 1, 638 }, { 44, 2, 532 }, { 44, 3, 968 }, { 44, 4, 90 }, { 44, 5, 738 }, { 44, 6, 196 }, { 44, 7, 633 }, { 44, 8, 600 }, { 44, 9, 304 }, { 44, 10, 128 }, { 44, 11, 792 }, { 44, 12, 586 }, { 44, 13, 1 }, { 44, 14, 344 }, { 44, 15, 857 }, { 44, 16, 896 }, { 44, 17, 848 }, { 44, 18, 985 }, { 44, 19, 902 }, { 44, 20, 632 }, { 44, 21, 21 }, { 44, 22, 447 }, { 44, 23, 338 }, { 44, 24, 722 }, { 44, 25, 278 }, { 44, 26, 355 }, { 44, 27, 582 }, { 44, 28, 872 }, { 44, 29, 722 }, { 44, 30, 21 }, { 44, 31, 231 }, { 44, 32, 156 }, { 44, 33, 980 }, { 44, 34, 250 }, { 44, 35, 472 }, { 44, 36, 172 }, { 44, 37, 448 }, { 44, 38, 856 }, { 44, 39, 177 }, { 44, 40, 41 }, { 44, 41, 29 }, { 44, 42, 623 }, { 44, 43, 20 }, { 45, 0, 806 }, { 45, 1, 991 }, { 45, 2, 234 }, { 45, 3, 191 }, { 45, 4, 195 }, { 45, 5, 336 }, { 45, 6, 595 }, { 45, 7, 699 }, { 45, 8, 464 }, { 45, 9, 490 }, { 45, 10, 292 }, { 45, 11, 176 }, { 45, 12, 511 }, { 45, 13, 227 }, { 45, 14, 585 }, { 45, 15, 275 }, { 45, 16, 610 }, { 45, 17, 814 }, { 45, 18, 932 }, { 45, 19, 661 }, { 45, 20, 644 }, { 45, 21, 82 }, { 45, 22, 992 }, { 45, 23, 985 }, { 45, 24, 140 }, { 45, 25, 377 }, { 45, 26, 319 }, { 45, 27, 137 }, { 45, 28, 147 }, { 45, 29, 447 }, { 45, 30, 23 }, { 45, 31, 382 }, { 45, 32, 607 }, { 45, 33, 429 }, { 45, 34, 903 }, { 45, 35, 962 }, { 45, 36, 624 }, { 45, 37, 593 }, { 45, 38, 207 }, { 45, 39, 363 }, { 45, 40, 87 }, { 45, 41, 644 }, { 45, 42, 61 }, { 45, 43, 159 }, { 45, 44, 967 }, { 46, 0, 991 }, { 46, 1, 772 }, { 46, 2, 735 }, { 46, 3, 147 }, { 46, 4, 355 }, { 46, 5, 327 }, { 46, 6, 89 }, { 46, 7, 103 }, { 46, 8, 484 }, { 46, 9, 924 }, { 46, 10, 886 }, { 46, 11, 155 }, { 46, 12, 657 }, { 46, 13, 435 }, { 46, 14, 508 }, { 46, 15, 986 }, { 46, 16, 658 }, { 46, 17, 941 }, { 46, 18, 641 }, { 46, 19, 777 }, { 46, 20, 390 }, { 46, 21, 442 }, { 46, 22, 845 }, { 46, 23, 834 }, { 46, 24, 216 }, { 46, 25, 986 }, { 46, 26, 370 }, { 46, 27, 160 }, { 46, 28, 774 }, { 46, 29, 429 }, { 46, 30, 275 }, { 46, 31, 453 }, { 46, 32, 246 }, { 46, 33, 311 }, { 46, 34, 702 }, { 46, 35, 496 }, { 46, 36, 397 }, { 46, 37, 307 }, { 46, 38, 679 }, { 46, 39, 842 }, { 46, 40, 934 }, { 46, 41, 368 }, { 46, 42, 323 }, { 46, 43, 607 }, { 46, 44, 123 }, { 46, 45, 881 }, { 47, 0, 990 }, { 47, 1, 337 }, { 47, 2, 650 }, { 47, 3, 973 }, { 47, 4, 665 }, { 47, 5, 248 }, { 47, 6, 895 }, { 47, 7, 911 }, { 47, 8, 379 }, { 47, 9, 775 }, { 47, 10, 242 }, { 47, 11, 801 }, { 47, 12, 323 }, { 47, 13, 960 }, { 47, 14, 605 }, { 47, 15, 81 }, { 47, 16, 455 }, { 47, 17, 119 }, { 47, 18, 21 }, { 47, 19, 438 }, { 47, 20, 164 }, { 47, 21, 507 }, { 47, 22, 450 }, { 47, 23, 555 }, { 47, 24, 635 }, { 47, 25, 257 }, { 47, 26, 700 }, { 47, 27, 641 }, { 47, 28, 676 }, { 47, 29, 195 }, { 47, 30, 692 }, { 47, 31, 140 }, { 47, 32, 386 }, { 47, 33, 662 }, { 47, 34, 556 }, { 47, 35, 220 }, { 47, 36, 595 }, { 47, 37, 193 }, { 47, 38, 620 }, { 47, 39, 963 }, { 47, 40, 330 }, { 47, 41, 81 }, { 47, 42, 154 }, { 47, 43, 966 }, { 47, 44, 674 }, { 47, 45, 337 }, { 47, 46, 621 }, { 48, 0, 829 }, { 48, 1, 457 }, { 48, 2, 407 }, { 48, 3, 350 }, { 48, 4, 732 }, { 48, 5, 406 }, { 48, 6, 106 }, { 48, 7, 548 }, { 48, 8, 484 }, { 48, 9, 273 }, { 48, 10, 383 }, { 48, 11, 91 }, { 48, 12, 572 }, { 48, 13, 870 }, { 48, 14, 235 }, { 48, 15, 863 }, { 48, 16, 328 }, { 48, 17, 6 }, { 48, 18, 835 }, { 48, 19, 267 }, { 48, 20, 4 }, { 48, 21, 418 }, { 48, 22, 31 }, { 48, 23, 664 }, { 48, 24, 482 }, { 48, 25, 380 }, { 48, 26, 525 }, { 48, 27, 252 }, { 48, 28, 831 }, { 48, 29, 356 }, { 48, 30, 121 }, { 48, 31, 505 }, { 48, 32, 4 }, { 48, 33, 171 }, { 48, 34, 602 }, { 48, 35, 23 }, { 48, 36, 935 }, { 48, 37, 856 }, { 48, 38, 824 }, { 48, 39, 184 }, { 48, 40, 514 }, { 48, 41, 621 }, { 48, 42, 698 }, { 48, 43, 821 }, { 48, 44, 307 }, { 48, 45, 789 }, { 48, 46, 132 }, { 48, 47, 434 }, { 49, 0, 147 }, { 49, 1, 52 }, { 49, 2, 493 }, { 49, 3, 178 }, { 49, 4, 635 }, { 49, 5, 299 }, { 49, 6, 526 }, { 49, 7, 680 }, { 49, 8, 383 }, { 49, 9, 878 }, { 49, 10, 806 }, { 49, 11, 60 }, { 49, 12, 614 }, { 49, 13, 220 }, { 49, 14, 467 }, { 49, 15, 447 }, { 49, 16, 130 }, { 49, 17, 266 }, { 49, 18, 412 }, { 49, 19, 251 }, { 49, 20, 106 }, { 49, 21, 357 }, { 49, 22, 858 }, { 49, 23, 729 }, { 49, 24, 975 }, { 49, 25, 106 }, { 49, 26, 356 }, { 49, 27, 112 }, { 49, 28, 588 }, { 49, 29, 243 }, { 49, 30, 361 }, { 49, 31, 151 }, { 49, 32, 190 }, { 49, 33, 849 }, { 49, 34, 607 }, { 49, 35, 719 }, { 49, 36, 435 }, { 49, 37, 961 }, { 49, 38, 739 }, { 49, 39, 408 }, { 49, 40, 951 }, { 49, 41, 28 }, { 49, 42, 346 }, { 49, 43, 335 }, { 49, 44, 681 }, { 49, 45, 38 }, { 49, 46, 172 }, { 49, 47, 144 }, { 49, 48, 164 } }; double maxWeight = 24192; double minWeight = 933; test(edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a small pseudograph */ @Test public void testGetMatching39() { int[][] edges = new int[][] { { 1, 1, 1 }, { 1, 2, 5 }, { 1, 2, 10 }, }; double maxWeight = 10; double minWeight = 5; test( new WeightedPseudograph<>(DefaultEdge.class), edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a pseudograph */ @Test public void testGetMatching40() { int[][] edges = new int[][] { { 1, 1, 1 }, { 2, 2, 1 }, { 3, 3, 1 }, { 4, 4, 1 }, { 1, 2, 5 }, { 1, 2, 10 }, { 1, 3, 2 }, { 1, 3, 5 }, { 1, 4, 4 }, { 1, 4, 6 }, { 2, 3, 3 }, { 2, 3, 4 }, { 2, 4, 6 }, { 2, 4, 8 }, { 3, 4, 1 }, { 3, 4, 3 } }; double maxWeight = 13; double minWeight = 6; test( new WeightedPseudograph<>(DefaultEdge.class), edges, objectiveSense == MAXIMIZE ? maxWeight : minWeight, objectiveSense); } /** * Test on a graph with odd number of vertices */ @Test(expected = IllegalArgumentException.class) public void testGetMatching41() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); KolmogorovWeightedPerfectMatching matching = new KolmogorovWeightedPerfectMatching<>(graph, options); matching.getMatching(); } @Test(expected = IllegalArgumentException.class) public void testGetMatching42() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); perfectMatching.getMatching(); } /** * Test on a $K_{3}$ with a zero-degree vertex */ @Test(expected = IllegalArgumentException.class) public void testGetMatching43() { int[][] edges = { { 1, 2, 1 }, { 1, 3, 2 }, { 2, 3, 3 }, }; Graph graph = TestUtil.createUndirected(edges); graph.addVertex(4); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); perfectMatching.getMatching(); } /** * Test on triangulation of 9 points with a zero-degree vertex */ @Test(expected = IllegalArgumentException.class) public void testGetMatching44() { int[][] edges = new int[][] { { 1, 2, 3 }, { 1, 3, 5 }, { 2, 3, 7 }, { 1, 4, 6 }, { 3, 4, 1 }, { 1, 0, 11 }, { 0, 4, 7 }, { 3, 5, 3 }, { 4, 5, 2 }, { 4, 6, 3 }, { 5, 6, 1 }, { 4, 7, 3 }, { 0, 7, 7 }, { 6, 7, 1 }, { 6, 8, 9 }, { 5, 8, 8 }, { 7, 8, 10 }, { 3, 8, 7 }, { 2, 8, 4 } }; Graph graph = TestUtil.createUndirected(edges); graph.addVertex(9); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); perfectMatching.getMatching(); } /** * Test on triangulation of 9 points with a zero-degree vertex */ @Test(expected = IllegalArgumentException.class) public void testGetMatching45() { int[][] edges = new int[][] { { 0, 1, 8 }, { 0, 2, 9 }, { 1, 2, 1 }, { 0, 3, 4 }, { 1, 3, 5 }, { 0, 4, 4 }, { 3, 4, 2 }, { 0, 5, 5 }, { 4, 5, 2 }, { 4, 6, 7 }, { 5, 6, 6 }, { 1, 7, 9 }, { 2, 7, 9 }, { 3, 7, 8 }, { 4, 7, 7 }, { 6, 7, 2 }, { 6, 8, 4 }, { 7, 8, 2 }, { 2, 8, 10 } }; Graph graph = TestUtil.createUndirected(edges); graph.addVertex(9); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); perfectMatching.getMatching(); } /** * Test on triangulation of 99 points with a zero-degree vertex */ @Test(expected = IllegalArgumentException.class) public void testGetMatching46() { int[][] edges = new int[][] { { 1, 2, 22 }, { 1, 3, 8 }, { 2, 3, 16 }, { 0, 1, 46 }, { 0, 5, 24 }, { 1, 5, 24 }, { 0, 4, 20 }, { 4, 5, 4 }, { 0, 6, 5 }, { 0, 7, 7 }, { 6, 7, 3 }, { 0, 8, 19 }, { 6, 8, 16 }, { 0, 9, 17 }, { 4, 9, 8 }, { 0, 10, 10 }, { 7, 10, 6 }, { 9, 10, 11 }, { 4, 11, 10 }, { 5, 11, 7 }, { 9, 11, 13 }, { 5, 12, 16 }, { 1, 12, 13 }, { 11, 12, 11 }, { 1, 13, 10 }, { 3, 13, 9 }, { 12, 13, 12 }, { 11, 14, 8 }, { 12, 14, 5 }, { 9, 15, 6 }, { 10, 15, 7 }, { 7, 16, 14 }, { 10, 16, 17 }, { 6, 16, 14 }, { 8, 16, 8 }, { 11, 17, 5 }, { 14, 17, 6 }, { 10, 18, 6 }, { 15, 18, 4 }, { 16, 18, 20 }, { 11, 19, 5 }, { 9, 19, 14 }, { 17, 19, 3 }, { 15, 20, 4 }, { 18, 20, 3 }, { 3, 21, 16 }, { 13, 21, 12 }, { 2, 21, 19 }, { 16, 22, 5 }, { 8, 22, 11 }, { 15, 23, 6 }, { 20, 23, 4 }, { 13, 24, 9 }, { 21, 24, 10 }, { 12, 24, 15 }, { 16, 25, 20 }, { 18, 25, 5 }, { 22, 25, 22 }, { 20, 25, 5 }, { 23, 25, 4 }, { 9, 26, 13 }, { 19, 26, 11 }, { 15, 26, 11 }, { 23, 26, 8 }, { 23, 27, 4 }, { 25, 27, 5 }, { 26, 27, 7 }, { 17, 28, 17 }, { 19, 28, 19 }, { 14, 28, 15 }, { 12, 28, 14 }, { 24, 28, 7 }, { 8, 29, 21 }, { 22, 29, 15 }, { 25, 30, 7 }, { 27, 30, 5 }, { 26, 30, 9 }, { 24, 31, 10 }, { 28, 31, 6 }, { 25, 33, 28 }, { 22, 33, 12 }, { 30, 33, 30 }, { 29, 33, 8 }, { 29, 32, 4 }, { 32, 33, 4 }, { 33, 34, 4 }, { 32, 34, 3 }, { 29, 34, 6 }, { 33, 35, 4 }, { 34, 35, 2 }, { 26, 36, 10 }, { 30, 36, 9 }, { 19, 36, 19 }, { 24, 37, 13 }, { 31, 37, 10 }, { 21, 37, 15 }, { 19, 38, 23 }, { 36, 38, 24 }, { 28, 38, 12 }, { 31, 38, 8 }, { 31, 39, 9 }, { 37, 39, 11 }, { 38, 39, 4 }, { 37, 40, 5 }, { 39, 40, 9 }, { 33, 41, 11 }, { 35, 41, 9 }, { 38, 42, 20 }, { 36, 42, 11 }, { 39, 43, 9 }, { 40, 43, 9 }, { 40, 45, 9 }, { 43, 45, 7 }, { 37, 45, 13 }, { 34, 46, 15 }, { 29, 46, 18 }, { 35, 46, 14 }, { 41, 46, 11 }, { 41, 44, 8 }, { 41, 47, 12 }, { 44, 47, 6 }, { 33, 47, 20 }, { 30, 47, 25 }, { 42, 48, 6 }, { 42, 49, 6 }, { 48, 49, 3 }, { 38, 49, 20 }, { 41, 50, 11 }, { 44, 50, 13 }, { 46, 50, 3 }, { 48, 51, 4 }, { 49, 51, 1 }, { 44, 53, 4 }, { 47, 53, 6 }, { 50, 53, 13 }, { 47, 54, 8 }, { 53, 54, 12 }, { 30, 54, 24 }, { 42, 55, 21 }, { 36, 55, 23 }, { 48, 55, 19 }, { 30, 55, 24 }, { 54, 55, 4 }, { 45, 56, 9 }, { 43, 56, 7 }, { 45, 52, 4 }, { 52, 56, 9 }, { 21, 57, 35 }, { 2, 57, 48 }, { 45, 57, 19 }, { 52, 57, 18 }, { 37, 57, 26 }, { 49, 58, 11 }, { 51, 58, 11 }, { 38, 58, 20 }, { 39, 58, 19 }, { 43, 58, 18 }, { 56, 58, 15 }, { 52, 59, 8 }, { 56, 59, 4 }, { 53, 60, 14 }, { 50, 60, 8 }, { 46, 60, 11 }, { 51, 61, 11 }, { 58, 61, 6 }, { 52, 62, 10 }, { 59, 62, 9 }, { 59, 63, 6 }, { 62, 63, 7 }, { 52, 64, 14 }, { 62, 64, 7 }, { 52, 65, 14 }, { 64, 65, 1 }, { 57, 65, 13 }, { 59, 66, 9 }, { 63, 66, 6 }, { 56, 66, 11 }, { 58, 66, 14 }, { 48, 67, 19 }, { 55, 67, 21 }, { 51, 67, 19 }, { 61, 67, 15 }, { 58, 68, 14 }, { 61, 68, 12 }, { 66, 68, 11 }, { 64, 69, 9 }, { 65, 69, 9 }, { 62, 69, 11 }, { 63, 69, 14 }, { 53, 70, 22 }, { 60, 70, 24 }, { 55, 70, 21 }, { 67, 70, 23 }, { 54, 70, 21 }, { 67, 71, 4 }, { 70, 71, 22 }, { 67, 72, 10 }, { 71, 72, 9 }, { 61, 72, 14 }, { 68, 72, 12 }, { 63, 73, 14 }, { 69, 73, 5 }, { 71, 74, 5 }, { 72, 74, 5 }, { 66, 75, 16 }, { 68, 75, 20 }, { 63, 75, 15 }, { 73, 75, 3 }, { 69, 76, 6 }, { 73, 76, 7 }, { 65, 76, 13 }, { 57, 76, 24 }, { 71, 77, 6 }, { 74, 77, 6 }, { 70, 78, 13 }, { 60, 78, 24 }, { 73, 79, 8 }, { 75, 79, 6 }, { 76, 79, 9 }, { 70, 80, 10 }, { 78, 80, 11 }, { 70, 81, 19 }, { 80, 81, 16 }, { 71, 81, 12 }, { 77, 81, 8 }, { 79, 82, 7 }, { 76, 82, 9 }, { 80, 83, 5 }, { 81, 83, 13 }, { 80, 84, 8 }, { 83, 84, 4 }, { 81, 84, 14 }, { 76, 85, 14 }, { 82, 85, 8 }, { 72, 86, 19 }, { 74, 86, 20 }, { 68, 86, 21 }, { 77, 86, 23 }, { 81, 86, 25 }, { 82, 87, 7 }, { 85, 87, 4 }, { 80, 88, 12 }, { 78, 88, 11 }, { 75, 89, 18 }, { 79, 89, 15 }, { 68, 89, 24 }, { 86, 89, 12 }, { 80, 90, 11 }, { 84, 90, 8 }, { 88, 90, 4 }, { 76, 91, 19 }, { 57, 91, 38 }, { 85, 91, 6 }, { 88, 92, 2 }, { 90, 92, 4 }, { 78, 92, 13 }, { 60, 92, 36 }, { 79, 93, 14 }, { 89, 93, 15 }, { 82, 93, 11 }, { 87, 93, 6 }, { 87, 94, 8 }, { 93, 94, 10 }, { 85, 94, 6 }, { 91, 94, 3 }, { 89, 95, 13 }, { 93, 95, 10 }, { 94, 95, 18 }, { 90, 96, 12 }, { 92, 96, 13 }, { 84, 96, 14 }, { 84, 97, 23 }, { 81, 97, 23 }, { 96, 97, 18 }, { 81, 98, 26 }, { 86, 98, 19 }, { 97, 98, 9 }, { 89, 98, 27 }, { 95, 98, 31 } }; Graph graph = TestUtil.createUndirected(edges); graph.addVertex(99); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); perfectMatching.getMatching(); } /** * Test on a 3-regular graph with no perfect matching */ @Test(expected = IllegalArgumentException.class) public void testGetMatching47() { int[][] edges = new int[][] { { 0, 2, 18 }, { 1, 2, 27 }, { 0, 3, 68 }, { 1, 3, 15 }, { 2, 3, 19 }, { 0, 12, 93 }, { 1, 12, 13 }, { 12, 15, 85 }, { 4, 6, 50 }, { 5, 6, 6 }, { 4, 7, 79 }, { 5, 7, 95 }, { 6, 7, 95 }, { 4, 13, 40 }, { 5, 13, 6 }, { 13, 15, 87 }, { 8, 10, 51 }, { 9, 10, 44 }, { 8, 11, 96 }, { 9, 11, 95 }, { 10, 11, 9 }, { 8, 14, 86 }, { 9, 14, 56 }, { 14, 15, 36 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); perfectMatching.getMatching(); } /** * Test on a 7-regular graph without perfect matching: |V| = 64, |max. cardinality matching| = * 29 */ @Test(expected = IllegalArgumentException.class) public void testGetMatching48() { int[][] edges = new int[][] { { 0, 2, 72 }, { 1, 2, 85 }, { 0, 3, 5 }, { 1, 3, 59 }, { 0, 4, 96 }, { 1, 4, 38 }, { 2, 4, 94 }, { 3, 4, 17 }, { 0, 5, 54 }, { 1, 5, 49 }, { 2, 5, 81 }, { 3, 5, 56 }, { 0, 6, 69 }, { 1, 6, 82 }, { 2, 6, 18 }, { 3, 6, 53 }, { 4, 6, 25 }, { 5, 6, 9 }, { 0, 7, 24 }, { 1, 7, 70 }, { 2, 7, 72 }, { 3, 7, 19 }, { 4, 7, 16 }, { 5, 7, 61 }, { 6, 7, 92 }, { 0, 56, 0 }, { 1, 56, 77 }, { 2, 56, 11 }, { 3, 56, 20 }, { 4, 56, 23 }, { 5, 56, 95 }, { 56, 63, 56 }, { 8, 10, 24 }, { 9, 10, 21 }, { 8, 11, 87 }, { 9, 11, 76 }, { 8, 12, 24 }, { 9, 12, 43 }, { 10, 12, 81 }, { 11, 12, 79 }, { 8, 13, 5 }, { 9, 13, 15 }, { 10, 13, 29 }, { 11, 13, 83 }, { 8, 14, 53 }, { 9, 14, 66 }, { 10, 14, 8 }, { 11, 14, 52 }, { 12, 14, 73 }, { 13, 14, 61 }, { 8, 15, 83 }, { 9, 15, 78 }, { 10, 15, 9 }, { 11, 15, 73 }, { 12, 15, 27 }, { 13, 15, 69 }, { 14, 15, 24 }, { 8, 57, 11 }, { 9, 57, 42 }, { 10, 57, 57 }, { 11, 57, 62 }, { 12, 57, 47 }, { 13, 57, 83 }, { 57, 63, 93 }, { 16, 18, 36 }, { 17, 18, 59 }, { 16, 19, 71 }, { 17, 19, 48 }, { 16, 20, 88 }, { 17, 20, 66 }, { 18, 20, 43 }, { 19, 20, 84 }, { 16, 21, 63 }, { 17, 21, 87 }, { 18, 21, 53 }, { 19, 21, 79 }, { 16, 22, 65 }, { 17, 22, 39 }, { 18, 22, 23 }, { 19, 22, 12 }, { 20, 22, 11 }, { 21, 22, 43 }, { 16, 23, 64 }, { 17, 23, 94 }, { 18, 23, 25 }, { 19, 23, 89 }, { 20, 23, 24 }, { 21, 23, 50 }, { 22, 23, 54 }, { 16, 58, 66 }, { 17, 58, 11 }, { 18, 58, 81 }, { 19, 58, 0 }, { 20, 58, 8 }, { 21, 58, 12 }, { 58, 63, 74 }, { 24, 26, 32 }, { 25, 26, 47 }, { 24, 27, 86 }, { 25, 27, 64 }, { 24, 28, 71 }, { 25, 28, 49 }, { 26, 28, 87 }, { 27, 28, 94 }, { 24, 29, 84 }, { 25, 29, 71 }, { 26, 29, 52 }, { 27, 29, 92 }, { 24, 30, 98 }, { 25, 30, 86 }, { 26, 30, 70 }, { 27, 30, 47 }, { 28, 30, 56 }, { 29, 30, 30 }, { 24, 31, 2 }, { 25, 31, 18 }, { 26, 31, 9 }, { 27, 31, 26 }, { 28, 31, 81 }, { 29, 31, 98 }, { 30, 31, 9 }, { 24, 59, 95 }, { 25, 59, 82 }, { 26, 59, 94 }, { 27, 59, 88 }, { 28, 59, 74 }, { 29, 59, 91 }, { 59, 63, 77 }, { 32, 34, 27 }, { 33, 34, 21 }, { 32, 35, 49 }, { 33, 35, 6 }, { 32, 36, 80 }, { 33, 36, 89 }, { 34, 36, 78 }, { 35, 36, 0 }, { 32, 37, 92 }, { 33, 37, 60 }, { 34, 37, 2 }, { 35, 37, 51 }, { 32, 38, 36 }, { 33, 38, 74 }, { 34, 38, 47 }, { 35, 38, 14 }, { 36, 38, 54 }, { 37, 38, 6 }, { 32, 39, 29 }, { 33, 39, 26 }, { 34, 39, 95 }, { 35, 39, 17 }, { 36, 39, 26 }, { 37, 39, 20 }, { 38, 39, 67 }, { 32, 60, 66 }, { 33, 60, 38 }, { 34, 60, 10 }, { 35, 60, 82 }, { 36, 60, 92 }, { 37, 60, 98 }, { 60, 63, 7 }, { 40, 42, 75 }, { 41, 42, 90 }, { 40, 43, 77 }, { 41, 43, 3 }, { 40, 44, 97 }, { 41, 44, 6 }, { 42, 44, 32 }, { 43, 44, 6 }, { 40, 45, 46 }, { 41, 45, 36 }, { 42, 45, 67 }, { 43, 45, 88 }, { 40, 46, 59 }, { 41, 46, 88 }, { 42, 46, 29 }, { 43, 46, 79 }, { 44, 46, 65 }, { 45, 46, 22 }, { 40, 47, 62 }, { 41, 47, 96 }, { 42, 47, 4 }, { 43, 47, 71 }, { 44, 47, 22 }, { 45, 47, 97 }, { 46, 47, 28 }, { 40, 61, 71 }, { 41, 61, 35 }, { 42, 61, 33 }, { 43, 61, 63 }, { 44, 61, 22 }, { 45, 61, 25 }, { 61, 63, 5 }, { 48, 50, 97 }, { 49, 50, 73 }, { 48, 51, 41 }, { 49, 51, 92 }, { 48, 52, 36 }, { 49, 52, 47 }, { 50, 52, 72 }, { 51, 52, 38 }, { 48, 53, 2 }, { 49, 53, 91 }, { 50, 53, 83 }, { 51, 53, 19 }, { 48, 54, 0 }, { 49, 54, 75 }, { 50, 54, 44 }, { 51, 54, 4 }, { 52, 54, 73 }, { 53, 54, 17 }, { 48, 55, 54 }, { 49, 55, 70 }, { 50, 55, 84 }, { 51, 55, 38 }, { 52, 55, 70 }, { 53, 55, 80 }, { 54, 55, 73 }, { 48, 62, 73 }, { 49, 62, 24 }, { 50, 62, 86 }, { 51, 62, 65 }, { 52, 62, 10 }, { 53, 62, 95 }, { 62, 63, 45 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); perfectMatching.getMatching(); } /** * Test on a 9-regular graph without perfect matching: |V| = 100, |max. cardinality matching| = * 46 */ @Test(expected = IllegalArgumentException.class) public void testGetMatching49() { int[][] edges = new int[][] { { 0, 2, 87 }, { 1, 2, 56 }, { 0, 3, 21 }, { 1, 3, 81 }, { 0, 4, 26 }, { 1, 4, 94 }, { 2, 4, 36 }, { 3, 4, 28 }, { 0, 5, 32 }, { 1, 5, 94 }, { 2, 5, 12 }, { 3, 5, 86 }, { 0, 6, 52 }, { 1, 6, 78 }, { 2, 6, 93 }, { 3, 6, 89 }, { 4, 6, 11 }, { 5, 6, 4 }, { 0, 7, 64 }, { 1, 7, 93 }, { 2, 7, 16 }, { 3, 7, 70 }, { 4, 7, 14 }, { 5, 7, 21 }, { 0, 8, 64 }, { 1, 8, 65 }, { 2, 8, 3 }, { 3, 8, 52 }, { 4, 8, 65 }, { 5, 8, 22 }, { 6, 8, 20 }, { 7, 8, 50 }, { 0, 9, 93 }, { 1, 9, 89 }, { 2, 9, 58 }, { 3, 9, 12 }, { 4, 9, 96 }, { 5, 9, 87 }, { 6, 9, 57 }, { 7, 9, 49 }, { 8, 9, 78 }, { 0, 90, 0 }, { 1, 90, 95 }, { 2, 90, 79 }, { 3, 90, 15 }, { 4, 90, 15 }, { 5, 90, 78 }, { 6, 90, 81 }, { 7, 90, 64 }, { 90, 99, 72 }, { 10, 12, 50 }, { 11, 12, 8 }, { 10, 13, 30 }, { 11, 13, 7 }, { 10, 14, 22 }, { 11, 14, 30 }, { 12, 14, 3 }, { 13, 14, 2 }, { 10, 15, 56 }, { 11, 15, 52 }, { 12, 15, 6 }, { 13, 15, 66 }, { 10, 16, 53 }, { 11, 16, 64 }, { 12, 16, 72 }, { 13, 16, 61 }, { 14, 16, 90 }, { 15, 16, 57 }, { 10, 17, 79 }, { 11, 17, 41 }, { 12, 17, 33 }, { 13, 17, 53 }, { 14, 17, 13 }, { 15, 17, 10 }, { 10, 18, 70 }, { 11, 18, 0 }, { 12, 18, 30 }, { 13, 18, 67 }, { 14, 18, 13 }, { 15, 18, 16 }, { 16, 18, 10 }, { 17, 18, 92 }, { 10, 19, 97 }, { 11, 19, 52 }, { 12, 19, 71 }, { 13, 19, 51 }, { 14, 19, 92 }, { 15, 19, 28 }, { 16, 19, 96 }, { 17, 19, 21 }, { 18, 19, 82 }, { 10, 91, 45 }, { 11, 91, 46 }, { 12, 91, 19 }, { 13, 91, 35 }, { 14, 91, 28 }, { 15, 91, 95 }, { 16, 91, 20 }, { 17, 91, 92 }, { 91, 99, 10 }, { 20, 22, 55 }, { 21, 22, 25 }, { 20, 23, 46 }, { 21, 23, 76 }, { 20, 24, 14 }, { 21, 24, 91 }, { 22, 24, 31 }, { 23, 24, 49 }, { 20, 25, 30 }, { 21, 25, 77 }, { 22, 25, 22 }, { 23, 25, 0 }, { 20, 26, 46 }, { 21, 26, 21 }, { 22, 26, 80 }, { 23, 26, 18 }, { 24, 26, 68 }, { 25, 26, 40 }, { 20, 27, 32 }, { 21, 27, 43 }, { 22, 27, 74 }, { 23, 27, 32 }, { 24, 27, 31 }, { 25, 27, 65 }, { 20, 28, 91 }, { 21, 28, 38 }, { 22, 28, 77 }, { 23, 28, 80 }, { 24, 28, 69 }, { 25, 28, 88 }, { 26, 28, 41 }, { 27, 28, 40 }, { 20, 29, 7 }, { 21, 29, 85 }, { 22, 29, 33 }, { 23, 29, 8 }, { 24, 29, 47 }, { 25, 29, 90 }, { 26, 29, 78 }, { 27, 29, 49 }, { 28, 29, 34 }, { 20, 92, 93 }, { 21, 92, 88 }, { 22, 92, 90 }, { 23, 92, 54 }, { 24, 92, 33 }, { 25, 92, 4 }, { 26, 92, 75 }, { 27, 92, 13 }, { 92, 99, 30 }, { 30, 32, 30 }, { 31, 32, 87 }, { 30, 33, 87 }, { 31, 33, 21 }, { 30, 34, 8 }, { 31, 34, 80 }, { 32, 34, 72 }, { 33, 34, 94 }, { 30, 35, 17 }, { 31, 35, 50 }, { 32, 35, 12 }, { 33, 35, 86 }, { 30, 36, 26 }, { 31, 36, 72 }, { 32, 36, 37 }, { 33, 36, 81 }, { 34, 36, 39 }, { 35, 36, 38 }, { 30, 37, 85 }, { 31, 37, 38 }, { 32, 37, 60 }, { 33, 37, 37 }, { 34, 37, 24 }, { 35, 37, 79 }, { 30, 38, 96 }, { 31, 38, 87 }, { 32, 38, 29 }, { 33, 38, 90 }, { 34, 38, 97 }, { 35, 38, 46 }, { 36, 38, 59 }, { 37, 38, 44 }, { 30, 39, 18 }, { 31, 39, 55 }, { 32, 39, 87 }, { 33, 39, 93 }, { 34, 39, 86 }, { 35, 39, 69 }, { 36, 39, 96 }, { 37, 39, 15 }, { 38, 39, 34 }, { 30, 93, 53 }, { 31, 93, 42 }, { 32, 93, 59 }, { 33, 93, 90 }, { 34, 93, 15 }, { 35, 93, 79 }, { 36, 93, 86 }, { 37, 93, 18 }, { 93, 99, 56 }, { 40, 42, 37 }, { 41, 42, 41 }, { 40, 43, 91 }, { 41, 43, 4 }, { 40, 44, 81 }, { 41, 44, 55 }, { 42, 44, 82 }, { 43, 44, 53 }, { 40, 45, 83 }, { 41, 45, 12 }, { 42, 45, 19 }, { 43, 45, 79 }, { 40, 46, 62 }, { 41, 46, 26 }, { 42, 46, 46 }, { 43, 46, 3 }, { 44, 46, 63 }, { 45, 46, 28 }, { 40, 47, 50 }, { 41, 47, 63 }, { 42, 47, 23 }, { 43, 47, 16 }, { 44, 47, 5 }, { 45, 47, 52 }, { 40, 48, 91 }, { 41, 48, 33 }, { 42, 48, 3 }, { 43, 48, 55 }, { 44, 48, 86 }, { 45, 48, 99 }, { 46, 48, 67 }, { 47, 48, 77 }, { 40, 49, 64 }, { 41, 49, 1 }, { 42, 49, 59 }, { 43, 49, 96 }, { 44, 49, 4 }, { 45, 49, 3 }, { 46, 49, 22 }, { 47, 49, 77 }, { 48, 49, 36 }, { 40, 94, 31 }, { 41, 94, 12 }, { 42, 94, 6 }, { 43, 94, 91 }, { 44, 94, 30 }, { 45, 94, 58 }, { 46, 94, 69 }, { 47, 94, 66 }, { 94, 99, 63 }, { 50, 52, 8 }, { 51, 52, 5 }, { 50, 53, 63 }, { 51, 53, 89 }, { 50, 54, 58 }, { 51, 54, 75 }, { 52, 54, 91 }, { 53, 54, 9 }, { 50, 55, 7 }, { 51, 55, 3 }, { 52, 55, 65 }, { 53, 55, 4 }, { 50, 56, 71 }, { 51, 56, 90 }, { 52, 56, 69 }, { 53, 56, 89 }, { 54, 56, 60 }, { 55, 56, 15 }, { 50, 57, 29 }, { 51, 57, 26 }, { 52, 57, 0 }, { 53, 57, 76 }, { 54, 57, 83 }, { 55, 57, 94 }, { 50, 58, 59 }, { 51, 58, 86 }, { 52, 58, 61 }, { 53, 58, 95 }, { 54, 58, 58 }, { 55, 58, 50 }, { 56, 58, 52 }, { 57, 58, 35 }, { 50, 59, 70 }, { 51, 59, 56 }, { 52, 59, 48 }, { 53, 59, 0 }, { 54, 59, 51 }, { 55, 59, 35 }, { 56, 59, 95 }, { 57, 59, 16 }, { 58, 59, 35 }, { 50, 95, 86 }, { 51, 95, 56 }, { 52, 95, 29 }, { 53, 95, 10 }, { 54, 95, 78 }, { 55, 95, 23 }, { 56, 95, 3 }, { 57, 95, 45 }, { 95, 99, 12 }, { 60, 62, 6 }, { 61, 62, 82 }, { 60, 63, 94 }, { 61, 63, 29 }, { 60, 64, 0 }, { 61, 64, 40 }, { 62, 64, 99 }, { 63, 64, 44 }, { 60, 65, 84 }, { 61, 65, 76 }, { 62, 65, 6 }, { 63, 65, 15 }, { 60, 66, 25 }, { 61, 66, 36 }, { 62, 66, 88 }, { 63, 66, 60 }, { 64, 66, 60 }, { 65, 66, 3 }, { 60, 67, 44 }, { 61, 67, 14 }, { 62, 67, 37 }, { 63, 67, 12 }, { 64, 67, 51 }, { 65, 67, 7 }, { 60, 68, 1 }, { 61, 68, 13 }, { 62, 68, 80 }, { 63, 68, 42 }, { 64, 68, 28 }, { 65, 68, 85 }, { 66, 68, 14 }, { 67, 68, 50 }, { 60, 69, 62 }, { 61, 69, 14 }, { 62, 69, 2 }, { 63, 69, 10 }, { 64, 69, 74 }, { 65, 69, 16 }, { 66, 69, 37 }, { 67, 69, 51 }, { 68, 69, 45 }, { 60, 96, 83 }, { 61, 96, 58 }, { 62, 96, 16 }, { 63, 96, 28 }, { 64, 96, 75 }, { 65, 96, 60 }, { 66, 96, 76 }, { 67, 96, 54 }, { 96, 99, 85 }, { 70, 72, 38 }, { 71, 72, 52 }, { 70, 73, 73 }, { 71, 73, 5 }, { 70, 74, 79 }, { 71, 74, 97 }, { 72, 74, 94 }, { 73, 74, 47 }, { 70, 75, 96 }, { 71, 75, 14 }, { 72, 75, 87 }, { 73, 75, 24 }, { 70, 76, 85 }, { 71, 76, 36 }, { 72, 76, 20 }, { 73, 76, 15 }, { 74, 76, 78 }, { 75, 76, 97 }, { 70, 77, 9 }, { 71, 77, 87 }, { 72, 77, 21 }, { 73, 77, 18 }, { 74, 77, 76 }, { 75, 77, 30 }, { 70, 78, 0 }, { 71, 78, 96 }, { 72, 78, 4 }, { 73, 78, 7 }, { 74, 78, 17 }, { 75, 78, 65 }, { 76, 78, 63 }, { 77, 78, 24 }, { 70, 79, 52 }, { 71, 79, 25 }, { 72, 79, 30 }, { 73, 79, 20 }, { 74, 79, 48 }, { 75, 79, 14 }, { 76, 79, 29 }, { 77, 79, 35 }, { 78, 79, 87 }, { 70, 97, 10 }, { 71, 97, 15 }, { 72, 97, 96 }, { 73, 97, 27 }, { 74, 97, 69 }, { 75, 97, 22 }, { 76, 97, 54 }, { 77, 97, 28 }, { 97, 99, 38 }, { 80, 82, 70 }, { 81, 82, 61 }, { 80, 83, 37 }, { 81, 83, 42 }, { 80, 84, 53 }, { 81, 84, 75 }, { 82, 84, 78 }, { 83, 84, 91 }, { 80, 85, 14 }, { 81, 85, 70 }, { 82, 85, 70 }, { 83, 85, 42 }, { 80, 86, 40 }, { 81, 86, 25 }, { 82, 86, 94 }, { 83, 86, 77 }, { 84, 86, 5 }, { 85, 86, 51 }, { 80, 87, 78 }, { 81, 87, 49 }, { 82, 87, 43 }, { 83, 87, 72 }, { 84, 87, 91 }, { 85, 87, 14 }, { 80, 88, 90 }, { 81, 88, 80 }, { 82, 88, 2 }, { 83, 88, 4 }, { 84, 88, 6 }, { 85, 88, 37 }, { 86, 88, 99 }, { 87, 88, 30 }, { 80, 89, 54 }, { 81, 89, 44 }, { 82, 89, 5 }, { 83, 89, 65 }, { 84, 89, 46 }, { 85, 89, 33 }, { 86, 89, 39 }, { 87, 89, 13 }, { 88, 89, 93 }, { 80, 98, 13 }, { 81, 98, 93 }, { 82, 98, 28 }, { 83, 98, 64 }, { 84, 98, 42 }, { 85, 98, 86 }, { 86, 98, 22 }, { 87, 98, 17 }, { 98, 99, 46 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); perfectMatching.getMatching(); } /** * Test on a 11-regular graph without perfect matching: |V| = 144, |max. cardinality matching| = * 67 */ @Test(expected = IllegalArgumentException.class) public void testGetMatching50() { int[][] edges = new int[][] { { 0, 2, 44 }, { 1, 2, 27 }, { 0, 3, 2 }, { 1, 3, 73 }, { 0, 4, 11 }, { 1, 4, 4 }, { 2, 4, 98 }, { 3, 4, 28 }, { 0, 5, 0 }, { 1, 5, 53 }, { 2, 5, 19 }, { 3, 5, 41 }, { 0, 6, 27 }, { 1, 6, 8 }, { 2, 6, 21 }, { 3, 6, 91 }, { 4, 6, 88 }, { 5, 6, 37 }, { 0, 7, 6 }, { 1, 7, 84 }, { 2, 7, 92 }, { 3, 7, 71 }, { 4, 7, 90 }, { 5, 7, 13 }, { 0, 8, 79 }, { 1, 8, 96 }, { 2, 8, 53 }, { 3, 8, 49 }, { 4, 8, 48 }, { 5, 8, 32 }, { 6, 8, 46 }, { 7, 8, 63 }, { 0, 9, 97 }, { 1, 9, 43 }, { 2, 9, 55 }, { 3, 9, 99 }, { 4, 9, 21 }, { 5, 9, 20 }, { 6, 9, 24 }, { 7, 9, 4 }, { 0, 10, 1 }, { 1, 10, 61 }, { 2, 10, 20 }, { 3, 10, 20 }, { 4, 10, 90 }, { 5, 10, 0 }, { 6, 10, 27 }, { 7, 10, 95 }, { 8, 10, 85 }, { 9, 10, 26 }, { 0, 11, 12 }, { 1, 11, 18 }, { 2, 11, 85 }, { 3, 11, 0 }, { 4, 11, 50 }, { 5, 11, 72 }, { 6, 11, 37 }, { 7, 11, 48 }, { 8, 11, 94 }, { 9, 11, 33 }, { 10, 11, 86 }, { 0, 132, 41 }, { 1, 132, 55 }, { 2, 132, 9 }, { 3, 132, 45 }, { 4, 132, 25 }, { 5, 132, 98 }, { 6, 132, 52 }, { 7, 132, 24 }, { 8, 132, 63 }, { 9, 132, 47 }, { 132, 143, 14 }, { 12, 14, 12 }, { 13, 14, 71 }, { 12, 15, 19 }, { 13, 15, 31 }, { 12, 16, 16 }, { 13, 16, 35 }, { 14, 16, 51 }, { 15, 16, 61 }, { 12, 17, 68 }, { 13, 17, 88 }, { 14, 17, 27 }, { 15, 17, 52 }, { 12, 18, 21 }, { 13, 18, 42 }, { 14, 18, 80 }, { 15, 18, 20 }, { 16, 18, 67 }, { 17, 18, 99 }, { 12, 19, 87 }, { 13, 19, 9 }, { 14, 19, 46 }, { 15, 19, 44 }, { 16, 19, 53 }, { 17, 19, 16 }, { 12, 20, 40 }, { 13, 20, 96 }, { 14, 20, 27 }, { 15, 20, 16 }, { 16, 20, 80 }, { 17, 20, 83 }, { 18, 20, 81 }, { 19, 20, 80 }, { 12, 21, 53 }, { 13, 21, 23 }, { 14, 21, 73 }, { 15, 21, 51 }, { 16, 21, 24 }, { 17, 21, 71 }, { 18, 21, 55 }, { 19, 21, 81 }, { 12, 22, 48 }, { 13, 22, 45 }, { 14, 22, 1 }, { 15, 22, 71 }, { 16, 22, 97 }, { 17, 22, 74 }, { 18, 22, 45 }, { 19, 22, 67 }, { 20, 22, 6 }, { 21, 22, 18 }, { 12, 23, 65 }, { 13, 23, 4 }, { 14, 23, 7 }, { 15, 23, 66 }, { 16, 23, 0 }, { 17, 23, 88 }, { 18, 23, 56 }, { 19, 23, 74 }, { 20, 23, 48 }, { 21, 23, 74 }, { 22, 23, 44 }, { 12, 133, 61 }, { 13, 133, 84 }, { 14, 133, 7 }, { 15, 133, 70 }, { 16, 133, 11 }, { 17, 133, 13 }, { 18, 133, 16 }, { 19, 133, 14 }, { 20, 133, 38 }, { 21, 133, 22 }, { 133, 143, 35 }, { 24, 26, 40 }, { 25, 26, 45 }, { 24, 27, 80 }, { 25, 27, 83 }, { 24, 28, 51 }, { 25, 28, 65 }, { 26, 28, 46 }, { 27, 28, 46 }, { 24, 29, 3 }, { 25, 29, 70 }, { 26, 29, 75 }, { 27, 29, 82 }, { 24, 30, 47 }, { 25, 30, 55 }, { 26, 30, 21 }, { 27, 30, 50 }, { 28, 30, 31 }, { 29, 30, 96 }, { 24, 31, 47 }, { 25, 31, 58 }, { 26, 31, 67 }, { 27, 31, 29 }, { 28, 31, 46 }, { 29, 31, 93 }, { 24, 32, 65 }, { 25, 32, 88 }, { 26, 32, 54 }, { 27, 32, 81 }, { 28, 32, 78 }, { 29, 32, 84 }, { 30, 32, 97 }, { 31, 32, 45 }, { 24, 33, 71 }, { 25, 33, 96 }, { 26, 33, 37 }, { 27, 33, 50 }, { 28, 33, 60 }, { 29, 33, 89 }, { 30, 33, 55 }, { 31, 33, 76 }, { 24, 34, 35 }, { 25, 34, 45 }, { 26, 34, 26 }, { 27, 34, 27 }, { 28, 34, 60 }, { 29, 34, 37 }, { 30, 34, 38 }, { 31, 34, 79 }, { 32, 34, 88 }, { 33, 34, 30 }, { 24, 35, 90 }, { 25, 35, 67 }, { 26, 35, 18 }, { 27, 35, 5 }, { 28, 35, 22 }, { 29, 35, 75 }, { 30, 35, 58 }, { 31, 35, 37 }, { 32, 35, 82 }, { 33, 35, 32 }, { 34, 35, 47 }, { 24, 134, 85 }, { 25, 134, 67 }, { 26, 134, 31 }, { 27, 134, 72 }, { 28, 134, 40 }, { 29, 134, 38 }, { 30, 134, 43 }, { 31, 134, 68 }, { 32, 134, 63 }, { 33, 134, 80 }, { 134, 143, 82 }, { 36, 38, 87 }, { 37, 38, 64 }, { 36, 39, 47 }, { 37, 39, 3 }, { 36, 40, 19 }, { 37, 40, 58 }, { 38, 40, 31 }, { 39, 40, 92 }, { 36, 41, 87 }, { 37, 41, 57 }, { 38, 41, 7 }, { 39, 41, 39 }, { 36, 42, 66 }, { 37, 42, 55 }, { 38, 42, 60 }, { 39, 42, 67 }, { 40, 42, 78 }, { 41, 42, 43 }, { 36, 43, 5 }, { 37, 43, 5 }, { 38, 43, 16 }, { 39, 43, 52 }, { 40, 43, 99 }, { 41, 43, 8 }, { 36, 44, 4 }, { 37, 44, 33 }, { 38, 44, 41 }, { 39, 44, 20 }, { 40, 44, 42 }, { 41, 44, 67 }, { 42, 44, 6 }, { 43, 44, 89 }, { 36, 45, 85 }, { 37, 45, 61 }, { 38, 45, 22 }, { 39, 45, 99 }, { 40, 45, 93 }, { 41, 45, 56 }, { 42, 45, 48 }, { 43, 45, 78 }, { 36, 46, 84 }, { 37, 46, 57 }, { 38, 46, 93 }, { 39, 46, 87 }, { 40, 46, 1 }, { 41, 46, 75 }, { 42, 46, 57 }, { 43, 46, 69 }, { 44, 46, 68 }, { 45, 46, 2 }, { 36, 47, 7 }, { 37, 47, 56 }, { 38, 47, 6 }, { 39, 47, 25 }, { 40, 47, 23 }, { 41, 47, 4 }, { 42, 47, 59 }, { 43, 47, 99 }, { 44, 47, 4 }, { 45, 47, 36 }, { 46, 47, 60 }, { 36, 135, 20 }, { 37, 135, 89 }, { 38, 135, 60 }, { 39, 135, 30 }, { 40, 135, 36 }, { 41, 135, 67 }, { 42, 135, 97 }, { 43, 135, 23 }, { 44, 135, 34 }, { 45, 135, 43 }, { 135, 143, 84 }, { 48, 50, 9 }, { 49, 50, 39 }, { 48, 51, 39 }, { 49, 51, 66 }, { 48, 52, 96 }, { 49, 52, 85 }, { 50, 52, 60 }, { 51, 52, 36 }, { 48, 53, 22 }, { 49, 53, 33 }, { 50, 53, 97 }, { 51, 53, 93 }, { 48, 54, 47 }, { 49, 54, 85 }, { 50, 54, 30 }, { 51, 54, 35 }, { 52, 54, 19 }, { 53, 54, 22 }, { 48, 55, 77 }, { 49, 55, 52 }, { 50, 55, 35 }, { 51, 55, 85 }, { 52, 55, 27 }, { 53, 55, 43 }, { 48, 56, 40 }, { 49, 56, 32 }, { 50, 56, 99 }, { 51, 56, 24 }, { 52, 56, 79 }, { 53, 56, 56 }, { 54, 56, 90 }, { 55, 56, 90 }, { 48, 57, 63 }, { 49, 57, 75 }, { 50, 57, 88 }, { 51, 57, 59 }, { 52, 57, 59 }, { 53, 57, 7 }, { 54, 57, 30 }, { 55, 57, 14 }, { 48, 58, 71 }, { 49, 58, 96 }, { 50, 58, 5 }, { 51, 58, 61 }, { 52, 58, 98 }, { 53, 58, 59 }, { 54, 58, 27 }, { 55, 58, 33 }, { 56, 58, 42 }, { 57, 58, 78 }, { 48, 59, 17 }, { 49, 59, 53 }, { 50, 59, 5 }, { 51, 59, 49 }, { 52, 59, 28 }, { 53, 59, 32 }, { 54, 59, 15 }, { 55, 59, 43 }, { 56, 59, 68 }, { 57, 59, 4 }, { 58, 59, 91 }, { 48, 136, 29 }, { 49, 136, 21 }, { 50, 136, 14 }, { 51, 136, 63 }, { 52, 136, 68 }, { 53, 136, 59 }, { 54, 136, 25 }, { 55, 136, 13 }, { 56, 136, 76 }, { 57, 136, 88 }, { 136, 143, 65 }, { 60, 62, 12 }, { 61, 62, 57 }, { 60, 63, 93 }, { 61, 63, 92 }, { 60, 64, 45 }, { 61, 64, 22 }, { 62, 64, 7 }, { 63, 64, 62 }, { 60, 65, 84 }, { 61, 65, 95 }, { 62, 65, 89 }, { 63, 65, 15 }, { 60, 66, 65 }, { 61, 66, 83 }, { 62, 66, 74 }, { 63, 66, 6 }, { 64, 66, 81 }, { 65, 66, 88 }, { 60, 67, 4 }, { 61, 67, 63 }, { 62, 67, 97 }, { 63, 67, 89 }, { 64, 67, 53 }, { 65, 67, 65 }, { 60, 68, 55 }, { 61, 68, 62 }, { 62, 68, 70 }, { 63, 68, 13 }, { 64, 68, 12 }, { 65, 68, 4 }, { 66, 68, 37 }, { 67, 68, 46 }, { 60, 69, 14 }, { 61, 69, 38 }, { 62, 69, 20 }, { 63, 69, 40 }, { 64, 69, 40 }, { 65, 69, 9 }, { 66, 69, 66 }, { 67, 69, 71 }, { 60, 70, 43 }, { 61, 70, 29 }, { 62, 70, 33 }, { 63, 70, 80 }, { 64, 70, 61 }, { 65, 70, 28 }, { 66, 70, 36 }, { 67, 70, 9 }, { 68, 70, 43 }, { 69, 70, 0 }, { 60, 71, 31 }, { 61, 71, 81 }, { 62, 71, 74 }, { 63, 71, 81 }, { 64, 71, 86 }, { 65, 71, 22 }, { 66, 71, 38 }, { 67, 71, 8 }, { 68, 71, 62 }, { 69, 71, 78 }, { 70, 71, 90 }, { 60, 137, 19 }, { 61, 137, 64 }, { 62, 137, 94 }, { 63, 137, 8 }, { 64, 137, 53 }, { 65, 137, 43 }, { 66, 137, 92 }, { 67, 137, 62 }, { 68, 137, 64 }, { 69, 137, 57 }, { 137, 143, 79 }, { 72, 74, 41 }, { 73, 74, 35 }, { 72, 75, 17 }, { 73, 75, 36 }, { 72, 76, 21 }, { 73, 76, 57 }, { 74, 76, 18 }, { 75, 76, 97 }, { 72, 77, 25 }, { 73, 77, 2 }, { 74, 77, 20 }, { 75, 77, 2 }, { 72, 78, 73 }, { 73, 78, 98 }, { 74, 78, 55 }, { 75, 78, 15 }, { 76, 78, 39 }, { 77, 78, 82 }, { 72, 79, 44 }, { 73, 79, 79 }, { 74, 79, 3 }, { 75, 79, 44 }, { 76, 79, 19 }, { 77, 79, 60 }, { 72, 80, 10 }, { 73, 80, 62 }, { 74, 80, 17 }, { 75, 80, 25 }, { 76, 80, 73 }, { 77, 80, 12 }, { 78, 80, 4 }, { 79, 80, 67 }, { 72, 81, 54 }, { 73, 81, 32 }, { 74, 81, 2 }, { 75, 81, 92 }, { 76, 81, 5 }, { 77, 81, 25 }, { 78, 81, 93 }, { 79, 81, 57 }, { 72, 82, 46 }, { 73, 82, 14 }, { 74, 82, 87 }, { 75, 82, 36 }, { 76, 82, 62 }, { 77, 82, 88 }, { 78, 82, 46 }, { 79, 82, 95 }, { 80, 82, 40 }, { 81, 82, 11 }, { 72, 83, 1 }, { 73, 83, 59 }, { 74, 83, 18 }, { 75, 83, 6 }, { 76, 83, 19 }, { 77, 83, 88 }, { 78, 83, 88 }, { 79, 83, 22 }, { 80, 83, 74 }, { 81, 83, 7 }, { 82, 83, 77 }, { 72, 138, 99 }, { 73, 138, 53 }, { 74, 138, 51 }, { 75, 138, 13 }, { 76, 138, 65 }, { 77, 138, 78 }, { 78, 138, 68 }, { 79, 138, 85 }, { 80, 138, 25 }, { 81, 138, 98 }, { 138, 143, 58 }, { 84, 86, 92 }, { 85, 86, 55 }, { 84, 87, 55 }, { 85, 87, 54 }, { 84, 88, 25 }, { 85, 88, 94 }, { 86, 88, 15 }, { 87, 88, 59 }, { 84, 89, 36 }, { 85, 89, 59 }, { 86, 89, 51 }, { 87, 89, 53 }, { 84, 90, 37 }, { 85, 90, 70 }, { 86, 90, 21 }, { 87, 90, 68 }, { 88, 90, 11 }, { 89, 90, 76 }, { 84, 91, 44 }, { 85, 91, 72 }, { 86, 91, 88 }, { 87, 91, 83 }, { 88, 91, 15 }, { 89, 91, 15 }, { 84, 92, 8 }, { 85, 92, 9 }, { 86, 92, 68 }, { 87, 92, 89 }, { 88, 92, 55 }, { 89, 92, 37 }, { 90, 92, 62 }, { 91, 92, 50 }, { 84, 93, 66 }, { 85, 93, 63 }, { 86, 93, 74 }, { 87, 93, 10 }, { 88, 93, 13 }, { 89, 93, 4 }, { 90, 93, 65 }, { 91, 93, 90 }, { 84, 94, 93 }, { 85, 94, 52 }, { 86, 94, 24 }, { 87, 94, 84 }, { 88, 94, 58 }, { 89, 94, 49 }, { 90, 94, 7 }, { 91, 94, 18 }, { 92, 94, 75 }, { 93, 94, 60 }, { 84, 95, 1 }, { 85, 95, 98 }, { 86, 95, 12 }, { 87, 95, 91 }, { 88, 95, 66 }, { 89, 95, 66 }, { 90, 95, 75 }, { 91, 95, 12 }, { 92, 95, 57 }, { 93, 95, 60 }, { 94, 95, 95 }, { 84, 139, 81 }, { 85, 139, 27 }, { 86, 139, 62 }, { 87, 139, 97 }, { 88, 139, 73 }, { 89, 139, 76 }, { 90, 139, 26 }, { 91, 139, 22 }, { 92, 139, 30 }, { 93, 139, 50 }, { 139, 143, 81 }, { 96, 98, 59 }, { 97, 98, 38 }, { 96, 99, 42 }, { 97, 99, 48 }, { 96, 100, 18 }, { 97, 100, 30 }, { 98, 100, 33 }, { 99, 100, 32 }, { 96, 101, 9 }, { 97, 101, 26 }, { 98, 101, 8 }, { 99, 101, 15 }, { 96, 102, 56 }, { 97, 102, 97 }, { 98, 102, 42 }, { 99, 102, 30 }, { 100, 102, 83 }, { 101, 102, 76 }, { 96, 103, 37 }, { 97, 103, 45 }, { 98, 103, 79 }, { 99, 103, 23 }, { 100, 103, 95 }, { 101, 103, 4 }, { 96, 104, 84 }, { 97, 104, 69 }, { 98, 104, 77 }, { 99, 104, 22 }, { 100, 104, 2 }, { 101, 104, 17 }, { 102, 104, 6 }, { 103, 104, 39 }, { 96, 105, 37 }, { 97, 105, 13 }, { 98, 105, 64 }, { 99, 105, 4 }, { 100, 105, 61 }, { 101, 105, 29 }, { 102, 105, 1 }, { 103, 105, 58 }, { 96, 106, 18 }, { 97, 106, 76 }, { 98, 106, 94 }, { 99, 106, 46 }, { 100, 106, 33 }, { 101, 106, 47 }, { 102, 106, 68 }, { 103, 106, 44 }, { 104, 106, 56 }, { 105, 106, 59 }, { 96, 107, 72 }, { 97, 107, 98 }, { 98, 107, 8 }, { 99, 107, 45 }, { 100, 107, 29 }, { 101, 107, 8 }, { 102, 107, 21 }, { 103, 107, 78 }, { 104, 107, 97 }, { 105, 107, 62 }, { 106, 107, 42 }, { 96, 140, 65 }, { 97, 140, 81 }, { 98, 140, 39 }, { 99, 140, 97 }, { 100, 140, 84 }, { 101, 140, 73 }, { 102, 140, 67 }, { 103, 140, 23 }, { 104, 140, 36 }, { 105, 140, 56 }, { 140, 143, 44 }, { 108, 110, 67 }, { 109, 110, 26 }, { 108, 111, 6 }, { 109, 111, 30 }, { 108, 112, 95 }, { 109, 112, 41 }, { 110, 112, 56 }, { 111, 112, 27 }, { 108, 113, 14 }, { 109, 113, 73 }, { 110, 113, 32 }, { 111, 113, 95 }, { 108, 114, 40 }, { 109, 114, 54 }, { 110, 114, 88 }, { 111, 114, 85 }, { 112, 114, 0 }, { 113, 114, 56 }, { 108, 115, 63 }, { 109, 115, 18 }, { 110, 115, 87 }, { 111, 115, 74 }, { 112, 115, 12 }, { 113, 115, 69 }, { 108, 116, 41 }, { 109, 116, 42 }, { 110, 116, 58 }, { 111, 116, 44 }, { 112, 116, 1 }, { 113, 116, 54 }, { 114, 116, 14 }, { 115, 116, 14 }, { 108, 117, 81 }, { 109, 117, 75 }, { 110, 117, 64 }, { 111, 117, 5 }, { 112, 117, 60 }, { 113, 117, 72 }, { 114, 117, 40 }, { 115, 117, 84 }, { 108, 118, 67 }, { 109, 118, 11 }, { 110, 118, 49 }, { 111, 118, 12 }, { 112, 118, 5 }, { 113, 118, 2 }, { 114, 118, 78 }, { 115, 118, 17 }, { 116, 118, 67 }, { 117, 118, 56 }, { 108, 119, 73 }, { 109, 119, 50 }, { 110, 119, 95 }, { 111, 119, 66 }, { 112, 119, 82 }, { 113, 119, 52 }, { 114, 119, 53 }, { 115, 119, 90 }, { 116, 119, 11 }, { 117, 119, 13 }, { 118, 119, 61 }, { 108, 141, 78 }, { 109, 141, 73 }, { 110, 141, 29 }, { 111, 141, 18 }, { 112, 141, 31 }, { 113, 141, 2 }, { 114, 141, 0 }, { 115, 141, 46 }, { 116, 141, 42 }, { 117, 141, 3 }, { 141, 143, 87 }, { 120, 122, 74 }, { 121, 122, 90 }, { 120, 123, 23 }, { 121, 123, 0 }, { 120, 124, 43 }, { 121, 124, 49 }, { 122, 124, 49 }, { 123, 124, 33 }, { 120, 125, 52 }, { 121, 125, 55 }, { 122, 125, 53 }, { 123, 125, 19 }, { 120, 126, 56 }, { 121, 126, 50 }, { 122, 126, 18 }, { 123, 126, 56 }, { 124, 126, 28 }, { 125, 126, 8 }, { 120, 127, 61 }, { 121, 127, 79 }, { 122, 127, 27 }, { 123, 127, 45 }, { 124, 127, 92 }, { 125, 127, 81 }, { 120, 128, 64 }, { 121, 128, 53 }, { 122, 128, 59 }, { 123, 128, 70 }, { 124, 128, 91 }, { 125, 128, 21 }, { 126, 128, 49 }, { 127, 128, 76 }, { 120, 129, 40 }, { 121, 129, 25 }, { 122, 129, 8 }, { 123, 129, 46 }, { 124, 129, 30 }, { 125, 129, 30 }, { 126, 129, 82 }, { 127, 129, 67 }, { 120, 130, 73 }, { 121, 130, 31 }, { 122, 130, 92 }, { 123, 130, 64 }, { 124, 130, 60 }, { 125, 130, 65 }, { 126, 130, 31 }, { 127, 130, 40 }, { 128, 130, 55 }, { 129, 130, 1 }, { 120, 131, 71 }, { 121, 131, 85 }, { 122, 131, 90 }, { 123, 131, 93 }, { 124, 131, 21 }, { 125, 131, 84 }, { 126, 131, 41 }, { 127, 131, 23 }, { 128, 131, 16 }, { 129, 131, 20 }, { 130, 131, 82 }, { 120, 142, 36 }, { 121, 142, 49 }, { 122, 142, 87 }, { 123, 142, 6 }, { 124, 142, 55 }, { 125, 142, 89 }, { 126, 142, 98 }, { 127, 142, 79 }, { 128, 142, 77 }, { 129, 142, 25 }, { 142, 143, 29 } }; Graph graph = TestUtil.createUndirected(edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options); perfectMatching.getMatching(); } /** * A method to run a test case. * * @param edges array of edges with their weights * @param result the expected weight of a resulting matching * @param objectiveSense objective sense of the algorithm */ private void test(int[][] edges, double result, ObjectiveSense objectiveSense) { test( new DefaultUndirectedWeightedGraph<>(DefaultEdge.class), edges, result, objectiveSense); } /** * A method to run a test case. * * @param graph the graph to add edges to * @param edges array of edges with their weights * @param result the expected weight of a resulting matching * @param objectiveSense objective sense of the algorithm */ private void test( Graph graph, int[][] edges, double result, ObjectiveSense objectiveSense) { TestUtil.constructGraph(graph, edges); KolmogorovWeightedPerfectMatching perfectMatching = new KolmogorovWeightedPerfectMatching<>(graph, options, objectiveSense); MatchingAlgorithm.Matching matching = perfectMatching.getMatching(); assertEquals(result, matching.getWeight(), EPS); assertTrue(perfectMatching.testOptimality()); checkMatchingAndDualSolution(matching, perfectMatching.getDualSolution(), objectiveSense); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/partition/000077500000000000000000000000001402514743400266745ustar00rootroot00000000000000BipartitePartitioningTest.java000066400000000000000000000233521402514743400346400ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/partition/* * (C) Copyright 2016-2021, by Dimitrios Michail, Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.partition; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * Tests for {@link BipartitePartitioning} * * @author Alexandru Valeanu * @author Dimitrios Michail */ public class BipartitePartitioningTest { @Test public void testBipartite10() { Graph g = new Pseudograph<>(DefaultEdge.class); assertTrue(GraphTests.isBipartite(g)); g.addVertex(1); assertTrue(GraphTests.isBipartite(g)); g.addVertex(2); assertTrue(GraphTests.isBipartite(g)); g.addEdge(1, 2); assertTrue(GraphTests.isBipartite(g)); g.addVertex(3); assertTrue(GraphTests.isBipartite(g)); g.addEdge(2, 3); assertTrue(GraphTests.isBipartite(g)); g.addEdge(3, 1); assertFalse(GraphTests.isBipartite(g)); } @Test public void testBipartite20() { Graph g = new Pseudograph<>(DefaultEdge.class); for (int i = 0; i < 100; i++) { g.addVertex(i); if (i > 0) { g.addEdge(i, i - 1); } } g.addEdge(99, 0); assertTrue(GraphTests.isBipartite(g)); } @Test public void testBipartite30() { Graph g = new Pseudograph<>(DefaultEdge.class); for (int i = 0; i < 101; i++) { g.addVertex(i); if (i > 0) { g.addEdge(i, i - 1); } } g.addEdge(100, 0); assertFalse(GraphTests.isBipartite(g)); } @Test public void testBipartite40() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); for (int i = 0; i < 101; i++) { g.addVertex(i); if (i > 0) { g.addEdge(i, i - 1); } } g.addEdge(100, 0); assertFalse(GraphTests.isBipartite(g)); } @Test public void testRandomBipartite() { GnpRandomBipartiteGraphGenerator generator = new GnpRandomBipartiteGraphGenerator<>(10, 10, 0.8); for (int i = 0; i < 100; i++) { Graph g = GraphTestsUtils.createPseudograph(); generator.generateGraph(g); assertTrue(GraphTests.isBipartite(g)); } } @Test public void testIsBipartitePartition() { List> gList = new ArrayList<>(); gList.add(new Pseudograph<>(DefaultEdge.class)); gList.add(new DirectedPseudograph<>(DefaultEdge.class)); for (Graph g : gList) { Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); Set a = new HashSet<>(Arrays.asList(1, 2)); Set b = Set.of(3, 4); assertTrue(GraphTests.isBipartitePartition(g, a, b)); g.addEdge(1, 3); g.addEdge(1, 4); g.addEdge(1, 3); g.addEdge(2, 3); g.addEdge(2, 4); g.addEdge(4, 1); g.addEdge(3, 1); assertTrue(GraphTests.isBipartitePartition(g, a, b)); a.remove(1); assertFalse(GraphTests.isBipartitePartition(g, a, b)); a.add(1); assertTrue(GraphTests.isBipartitePartition(g, a, b)); DefaultEdge e11 = g.addEdge(1, 1); assertFalse(GraphTests.isBipartitePartition(g, a, b)); g.removeEdge(e11); assertTrue(GraphTests.isBipartitePartition(g, a, b)); DefaultEdge e44 = g.addEdge(4, 4); assertFalse(GraphTests.isBipartitePartition(g, a, b)); g.removeEdge(e44); assertTrue(GraphTests.isBipartitePartition(g, a, b)); g.addEdge(4, 3); assertFalse(GraphTests.isBipartitePartition(g, a, b)); } } @Test public void testEmptyGraph() { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BipartitePartitioning finder = new BipartitePartitioning<>(graph); Assert.assertTrue(finder.isBipartite()); Assert.assertTrue(finder.isValidPartitioning(finder.getPartitioning())); } @Test public void testBipartite() { Random random = new Random(0x88); for (int i = 0; i < 100; i++) { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteBipartiteGraphGenerator generator = new CompleteBipartiteGraphGenerator<>( 1 + random.nextInt(100), 1 + random.nextInt(200)); generator.generateGraph(graph); BipartitePartitioning finder = new BipartitePartitioning<>(graph); Assert.assertTrue(finder.isBipartite()); Assert.assertTrue(finder.isValidPartitioning(finder.getPartitioning())); } } @Test public void testBipartite2() { Random random = new Random(0x88); for (int i = 0; i < 100; i++) { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); int n1 = 1 + random.nextInt(100); int n2 = 1 + random.nextInt(200); int m = 4 * n1 * n2 / 10; GnmRandomBipartiteGraphGenerator generator = new GnmRandomBipartiteGraphGenerator<>(n1, n2, m); generator.generateGraph(graph); BipartitePartitioning finder = new BipartitePartitioning<>(graph); Assert.assertTrue(finder.isBipartite()); Assert.assertTrue(finder.isValidPartitioning(finder.getPartitioning())); } } @Test public void testStarGraph() { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); StarGraphGenerator generator = new StarGraphGenerator<>(100); generator.generateGraph(graph); BipartitePartitioning finder = new BipartitePartitioning<>(graph); Assert.assertTrue(finder.isBipartite()); Assert.assertTrue(finder.isValidPartitioning(finder.getPartitioning())); } @Test public void testForest() { Random random = new Random(0x88); for (int i = 0; i < 100; i++) { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); final int T = 10 + random.nextInt(50); final int N = 100 + random.nextInt(200); BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>(T, N); generator.generateGraph(graph); BipartitePartitioning finder = new BipartitePartitioning<>(graph); Assert.assertTrue(finder.isBipartite()); Assert.assertTrue(finder.isValidPartitioning(finder.getPartitioning())); } } @Test public void testComplete() { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator generator = new CompleteGraphGenerator<>(100); generator.generateGraph(graph); BipartitePartitioning finder = new BipartitePartitioning<>(graph); Assert.assertFalse(finder.isBipartite()); Assert.assertNull(finder.getPartitioning()); } @Test public void testEvenCycle() { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); RingGraphGenerator generator = new RingGraphGenerator<>(100); generator.generateGraph(graph); BipartitePartitioning finder = new BipartitePartitioning<>(graph); Assert.assertTrue(finder.isBipartite()); Assert.assertTrue(finder.isValidPartitioning(finder.getPartitioning())); } @Test public void testOddCycle() { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); RingGraphGenerator generator = new RingGraphGenerator<>(101); generator.generateGraph(graph); BipartitePartitioning finder = new BipartitePartitioning<>(graph); Assert.assertFalse(finder.isBipartite()); Assert.assertNull(finder.getPartitioning()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/planar/000077500000000000000000000000001402514743400261405ustar00rootroot00000000000000BoyerMyrvoldPlanarityInspectorTest.java000066400000000000000000001655661402514743400360170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/planar/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.planar; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for the {@link BoyerMyrvoldPlanarityInspector} * * @author Timofey Chudakov */ public class BoyerMyrvoldPlanarityInspectorTest { /** * Does a generic verification of the algorithm on the graph defined by the {@code edges} * * @param edges an array of the edge of the graph */ private void testOnGraph(int[][] edges) { Graph graph = TestUtil.createUndirected(edges); PlanarityTestingAlgorithm inspector = new BoyerMyrvoldPlanarityInspector<>(graph); boolean planar = inspector.isPlanar(); if (planar) { PlanarityTestingAlgorithm.Embedding embedding = inspector.getEmbedding(); testEmbedding(embedding); } else { Graph subdivision = inspector.getKuratowskiSubdivision(); boolean isSubdivision = GraphTests.isKuratowskiSubdivision(subdivision); assertTrue(isSubdivision); } } /** * Performs a basic verification of the embedding * * @param embedding a graph embedding */ private void testEmbedding(PlanarityTestingAlgorithm.Embedding embedding) { Graph graph = embedding.getGraph(); Map cnt = new HashMap<>(); int degreeSum = 0; for (int vertex : graph.vertexSet()) { List edges = embedding.getEdgesAround(vertex); Set set = new HashSet<>(edges); if (set.size() != edges.size()) { System.out.println(edges); } for (DefaultEdge edge : embedding.getEdgesAround(vertex)) { assertTrue(graph.containsEdge(edge)); if (cnt.containsKey(edge)) { cnt.put(edge, cnt.get(edge) + 1); } else { cnt.put(edge, 1); } } degreeSum += embedding.getEdgesAround(vertex).size(); } for (DefaultEdge edge : graph.edgeSet()) { assertTrue(cnt.containsKey(edge)); if (cnt.get(edge) != 2) { System.out.println(graph.getEdgeSource(edge) + " " + graph.getEdgeTarget(edge)); } assertEquals(2, (int) cnt.get(edge)); } assertEquals(2 * graph.edgeSet().size(), degreeSum); } @Test(expected = IllegalArgumentException.class) public void testNonPlanarGraphNoEmbedding() { int[][] k_5 = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 0, 4 }, { 1, 2 }, { 1, 3 }, { 1, 4 }, { 2, 3 }, { 2, 4 }, { 3, 4 }, }; Graph graph = TestUtil.createUndirected(k_5); PlanarityTestingAlgorithm algorithm = new BoyerMyrvoldPlanarityInspector<>(graph); algorithm.getEmbedding(); } @Test(expected = IllegalArgumentException.class) public void testPlanarGraphNoKuratowskiSubdivision() { int[][] k_4 = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 2 }, { 1, 3 }, { 2, 3 }, }; Graph graph = TestUtil.createUndirected(k_4); PlanarityTestingAlgorithm algorithm = new BoyerMyrvoldPlanarityInspector<>(graph); algorithm.getKuratowskiSubdivision(); } @Test public void testPlanarity0() { int[][] edges = {}; testOnGraph(edges); } @Test public void testPlanarity1() { int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 0 } }; testOnGraph(edges); } @Test public void testPlanarity2() { int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 1, 4 }, { 4, 5 } }; testOnGraph(edges); } @Test public void testPlanarity3() { int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 0 }, { 1, 4 }, { 4, 5 } }; testOnGraph(edges); } @Test public void testPlanarity4() { int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 }, { 0, 2 }, { 0, 3 }, { 3, 1 }, { 1, 4 } }; testOnGraph(edges); } @Test public void testPlanarity5() { int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 2 }, { 1, 5 }, { 5, 6 }, { 6, 7 }, { 7, 5 }, }; testOnGraph(edges); } @Test public void testPlanarity6() { int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 0 }, { 5, 2 }, { 4, 1 }, { 3, 0 }, }; testOnGraph(edges); } @Test public void testPlanarity7() { int[][] edges = { { 0, 1 }, { 1, 2 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 4, 2 }, { 2, 0 }, { 0, 5 }, { 4, 1 }, { 5, 1 }, { 1, 3 } }; testOnGraph(edges); } @Test public void testPlanarity8() { int[][] k_33 = { { 0, 3 }, { 0, 4 }, { 0, 5 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, { 2, 3 }, { 2, 4 }, { 2, 5 }, }; testOnGraph(k_33); } @Test public void testPlanarity9() { int[][] edges = new int[][] { { 0, 2 }, { 1, 3 }, { 2, 6 }, { 3, 6 }, { 4, 0 }, { 5, 4 }, { 6, 4 }, { 0, 5 }, { 1, 6 }, { 2, 3 }, { 3, 4 }, { 4, 1 }, { 5, 3 }, { 6, 5 } }; testOnGraph(edges); } @Test public void testPlanarity10() { int[][] edges = new int[][] { { 0, 5 }, { 1, 6 }, { 2, 5 }, { 3, 2 }, { 4, 1 }, { 5, 4 }, { 6, 0 }, { 0, 1 }, { 1, 2 }, { 2, 6 }, { 3, 1 }, { 4, 6 }, { 5, 6 }, { 6, 3 } }; testOnGraph(edges); } @Test public void testPlanarity11() { int[][] edges = new int[][] { { 0, 5 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, { 4, 6 }, { 5, 6 }, { 6, 2 }, { 1, 4 }, { 2, 3 }, { 3, 4 }, { 4, 5 }, { 5, 3 }, { 6, 0 } }; testOnGraph(edges); } @Test public void testPlanarity12() { int[][] edges = new int[][] { { 0, 6 }, { 1, 6 }, { 2, 6 }, { 3, 4 }, { 4, 1 }, { 5, 0 }, { 6, 5 }, { 0, 4 }, { 1, 0 }, { 2, 0 }, { 3, 0 }, { 4, 2 }, { 5, 1 }, { 6, 3 } }; testOnGraph(edges); } @Test public void testPlanarity13() { int[][] edges = new int[][] { { 0, 6 }, { 1, 0 }, { 2, 6 }, { 3, 6 }, { 4, 1 }, { 5, 2 }, { 6, 1 }, { 0, 4 }, { 1, 5 }, { 2, 0 }, { 3, 5 }, { 4, 2 }, { 5, 6 }, { 6, 4 } }; testOnGraph(edges); } @Test public void testPlanarity14() { int[][] edges = new int[][] { { 0, 4 }, { 1, 6 }, { 2, 0 }, { 3, 2 }, { 4, 2 }, { 5, 2 }, { 6, 4 }, { 0, 3 }, { 1, 2 }, { 2, 6 }, { 3, 4 }, { 4, 5 }, { 5, 0 }, { 6, 0 } }; testOnGraph(edges); } @Test public void testPlanarity15() { int[][] edges = new int[][] { { 0, 4 }, { 1, 3 }, { 2, 4 }, { 3, 6 }, { 4, 1 }, { 5, 1 }, { 6, 5 }, { 0, 1 }, { 2, 3 }, { 3, 5 }, { 4, 6 }, { 5, 4 }, { 6, 0 } }; testOnGraph(edges); } @Test public void testPlanarity16() { int[][] edges = new int[][] { { 0, 1 }, { 1, 5 }, { 2, 4 }, { 3, 9 }, { 4, 1 }, { 5, 9 }, { 6, 2 }, { 7, 8 }, { 8, 6 }, { 9, 0 }, { 0, 6 }, { 1, 6 }, { 2, 8 }, { 3, 1 }, { 4, 3 }, { 5, 7 }, { 6, 4 }, { 7, 3 }, { 8, 3 }, { 9, 7 } }; testOnGraph(edges); } @Test public void testPlanarity17() { int[][] edges = new int[][] { { 0, 5 }, { 1, 0 }, { 2, 3 }, { 3, 5 }, { 4, 5 }, { 5, 8 }, { 6, 5 }, { 7, 1 }, { 8, 7 }, { 9, 2 }, { 0, 2 }, { 1, 5 }, { 2, 6 }, { 3, 1 }, { 4, 6 }, { 5, 2 }, { 6, 0 }, { 7, 4 }, { 8, 1 }, { 9, 7 } }; testOnGraph(edges); } @Test public void testPlanarity18() { int[][] edges = new int[][] { { 0, 9 }, { 1, 9 }, { 2, 3 }, { 3, 1 }, { 4, 2 }, { 5, 7 }, { 6, 8 }, { 7, 9 }, { 8, 9 }, { 9, 2 }, { 0, 7 }, { 1, 4 }, { 2, 0 }, { 3, 9 }, { 4, 8 }, { 5, 4 }, { 6, 7 }, { 7, 8 }, { 8, 0 }, { 9, 4 } }; testOnGraph(edges); } @Test public void testPlanarity19() { int[][] edges = new int[][] { { 0, 4 }, { 1, 3 }, { 2, 6 }, { 3, 2 }, { 4, 2 }, { 5, 2 }, { 6, 4 }, { 7, 5 }, { 0, 2 }, { 1, 7 }, { 2, 7 }, { 3, 6 }, { 4, 7 }, { 5, 4 }, { 6, 1 }, { 7, 0 } }; testOnGraph(edges); } @Test public void testPlanarity20() { int[][] edges = new int[][] { { 0, 6 }, { 1, 6 }, { 2, 8 }, { 3, 6 }, { 4, 9 }, { 5, 7 }, { 6, 7 }, { 7, 3 }, { 8, 6 }, { 9, 0 }, { 0, 7 }, { 1, 3 }, { 2, 6 }, { 3, 4 }, { 4, 2 }, { 5, 3 }, { 6, 5 }, { 7, 8 }, { 8, 5 }, { 9, 8 } }; testOnGraph(edges); } @Test public void testPlanarity21() { int[][] edges = new int[][] { { 0, 6 }, { 1, 7 }, { 2, 0 }, { 3, 6 }, { 4, 7 }, { 5, 0 }, { 6, 5 }, { 7, 5 }, { 0, 7 }, { 1, 4 }, { 2, 1 }, { 3, 0 }, { 4, 6 }, { 5, 2 }, { 6, 2 } }; testOnGraph(edges); } @Test public void testPlanarity22() { int[][] edges = new int[][] { { 0, 5 }, { 1, 7 }, { 2, 5 }, { 3, 5 }, { 4, 7 }, { 5, 6 }, { 6, 1 }, { 7, 6 }, { 0, 2 }, { 1, 2 }, { 2, 6 }, { 3, 2 }, { 4, 6 }, { 5, 1 }, { 6, 3 }, { 7, 3 } }; testOnGraph(edges); } @Test public void testPlanarity23() { int[][] edges = new int[][] { { 0, 4 }, { 1, 0 }, { 2, 5 }, { 3, 7 }, { 4, 5 }, { 5, 7 }, { 6, 2 }, { 7, 1 }, { 0, 2 }, { 1, 5 }, { 2, 3 }, { 3, 6 }, { 4, 6 }, { 5, 0 }, { 6, 5 }, { 7, 2 } }; testOnGraph(edges); } @Test public void testPlanarity24() { int[][] edges = new int[][] { { 0, 1 }, { 1, 2 }, { 2, 4 }, { 3, 6 }, { 4, 6 }, { 5, 3 }, { 6, 2 }, { 0, 5 }, { 1, 6 }, { 2, 5 }, { 3, 4 }, { 4, 1 }, { 5, 1 }, { 6, 0 } }; testOnGraph(edges); } @Test public void testPlanarity25() { int[][] edges = new int[][] { { 0, 4 }, { 1, 2 }, { 2, 3 }, { 3, 1 }, { 4, 3 }, { 5, 3 }, { 6, 1 }, { 0, 3 }, { 1, 5 }, { 2, 5 }, { 3, 6 }, { 4, 2 }, { 5, 4 }, { 6, 0 } }; testOnGraph(edges); } @Test public void testPlanarity26() { int[][] edges = new int[][] { { 0, 6 }, { 1, 4 }, { 2, 3 }, { 3, 4 }, { 4, 2 }, { 5, 0 }, { 6, 4 }, { 0, 2 }, { 1, 5 }, { 2, 1 }, { 3, 0 }, { 4, 5 }, { 5, 2 }, { 6, 2 } }; testOnGraph(edges); } @Test public void testPlanarity27() { int[][] edges = new int[][] { { 0, 5 }, { 1, 4 }, { 2, 7 }, { 3, 5 }, { 4, 2 }, { 5, 4 }, { 6, 5 }, { 7, 1 }, { 0, 1 }, { 1, 2 }, { 2, 0 }, { 3, 6 }, { 4, 0 }, { 5, 7 }, { 6, 7 }, { 7, 3 } }; testOnGraph(edges); } /** * Triangulation of 50 points */ @Test public void testPlanarity28() { int[][] edges = new int[][] { { 8, 1 }, { 1, 2 }, { 2, 4 }, { 4, 1 }, { 0, 1 }, { 4, 0 }, { 2, 3 }, { 3, 4 }, { 8, 2 }, { 13, 3 }, { 3, 11 }, { 11, 13 }, { 4, 5 }, { 5, 0 }, { 4, 6 }, { 6, 5 }, { 5, 7 }, { 7, 0 }, { 8, 3 }, { 14, 17 }, { 17, 4 }, { 4, 14 }, { 6, 7 }, { 8, 11 }, { 6, 9 }, { 9, 7 }, { 6, 10 }, { 10, 9 }, { 9, 16 }, { 16, 7 }, { 13, 14 }, { 14, 3 }, { 16, 10 }, { 10, 12 }, { 12, 16 }, { 11, 15 }, { 15, 13 }, { 6, 12 }, { 8, 15 }, { 17, 6 }, { 20, 13 }, { 15, 20 }, { 17, 12 }, { 22, 8 }, { 18, 12 }, { 17, 18 }, { 19, 14 }, { 13, 19 }, { 24, 14 }, { 19, 24 }, { 18, 16 }, { 20, 21 }, { 21, 24 }, { 24, 20 }, { 18, 25 }, { 25, 16 }, { 20, 19 }, { 15, 21 }, { 24, 26 }, { 26, 14 }, { 15, 22 }, { 22, 21 }, { 26, 17 }, { 22, 23 }, { 23, 21 }, { 23, 29 }, { 29, 21 }, { 25, 17 }, { 26, 25 }, { 29, 32 }, { 32, 37 }, { 37, 29 }, { 25, 31 }, { 31, 16 }, { 24, 27 }, { 27, 26 }, { 28, 26 }, { 27, 28 }, { 45, 23 }, { 28, 25 }, { 33, 34 }, { 34, 24 }, { 24, 33 }, { 28, 31 }, { 37, 21 }, { 27, 30 }, { 30, 28 }, { 21, 33 }, { 30, 35 }, { 35, 28 }, { 40, 34 }, { 34, 39 }, { 39, 40 }, { 31, 36 }, { 36, 16 }, { 45, 29 }, { 40, 24 }, { 38, 34 }, { 33, 38 }, { 38, 39 }, { 37, 33 }, { 35, 31 }, { 40, 30 }, { 27, 40 }, { 35, 36 }, { 37, 38 }, { 45, 32 }, { 46, 38 }, { 37, 46 }, { 40, 47 }, { 47, 30 }, { 38, 44 }, { 44, 39 }, { 41, 35 }, { 30, 41 }, { 42, 30 }, { 30, 43 }, { 43, 42 }, { 41, 36 }, { 42, 41 }, { 41, 48 }, { 48, 36 }, { 44, 40 }, { 49, 42 }, { 43, 49 }, { 44, 47 }, { 43, 47 }, { 47, 49 }, { 45, 46 }, { 37, 45 }, { 44, 46 }, { 46, 47 }, { 48, 42 }, { 49, 48 }, { 46, 49 }, { 45, 49 } }; testOnGraph(edges); } /** * Random not planar graph on 50 vertices */ @Test public void testPlanarity29() { int[][] edges = new int[][] { { 0, 39 }, { 1, 31 }, { 2, 48 }, { 3, 11 }, { 4, 39 }, { 5, 26 }, { 6, 18 }, { 7, 38 }, { 8, 0 }, { 9, 23 }, { 10, 48 }, { 11, 6 }, { 12, 4 }, { 13, 46 }, { 14, 35 }, { 15, 25 }, { 16, 40 }, { 17, 7 }, { 18, 29 }, { 19, 10 }, { 20, 19 }, { 21, 35 }, { 22, 8 }, { 23, 13 }, { 24, 8 }, { 25, 17 }, { 26, 47 }, { 27, 37 }, { 28, 20 }, { 29, 4 }, { 30, 37 }, { 31, 21 }, { 32, 42 }, { 33, 32 }, { 34, 44 }, { 35, 20 }, { 36, 47 }, { 37, 28 }, { 38, 8 }, { 39, 26 }, { 40, 22 }, { 41, 28 }, { 42, 21 }, { 43, 7 }, { 44, 35 }, { 45, 17 }, { 46, 33 }, { 47, 18 }, { 48, 1 }, { 49, 13 }, { 0, 34 }, { 1, 35 }, { 2, 5 }, { 3, 45 }, { 4, 24 }, { 5, 15 }, { 6, 5 }, { 7, 8 }, { 8, 26 }, { 9, 42 } }; testOnGraph(edges); } /** * Triangulation of 100 points */ @Test public void testPlanarity30() { int[][] edges = new int[][] { { 1, 2 }, { 2, 3 }, { 3, 1 }, { 0, 2 }, { 1, 0 }, { 3, 5 }, { 5, 1 }, { 1, 4 }, { 4, 0 }, { 7, 13 }, { 13, 2 }, { 2, 7 }, { 10, 11 }, { 11, 5 }, { 5, 10 }, { 4, 12 }, { 12, 0 }, { 6, 7 }, { 2, 6 }, { 13, 18 }, { 18, 19 }, { 19, 13 }, { 13, 3 }, { 32, 6 }, { 1, 8 }, { 8, 4 }, { 1, 9 }, { 9, 8 }, { 8, 12 }, { 11, 1 }, { 14, 16 }, { 16, 11 }, { 11, 14 }, { 13, 20 }, { 20, 18 }, { 11, 9 }, { 19, 3 }, { 10, 14 }, { 15, 8 }, { 9, 15 }, { 12, 26 }, { 26, 0 }, { 5, 21 }, { 21, 10 }, { 16, 9 }, { 23, 24 }, { 24, 21 }, { 21, 23 }, { 15, 12 }, { 16, 15 }, { 34, 15 }, { 15, 17 }, { 17, 34 }, { 19, 22 }, { 22, 3 }, { 16, 17 }, { 3, 21 }, { 34, 12 }, { 7, 20 }, { 21, 14 }, { 32, 18 }, { 20, 32 }, { 30, 19 }, { 18, 30 }, { 32, 7 }, { 24, 25 }, { 25, 21 }, { 22, 21 }, { 22, 23 }, { 19, 27 }, { 27, 22 }, { 25, 14 }, { 28, 22 }, { 27, 28 }, { 25, 29 }, { 29, 14 }, { 28, 23 }, { 29, 36 }, { 36, 14 }, { 23, 31 }, { 31, 24 }, { 36, 16 }, { 26, 54 }, { 54, 0 }, { 28, 31 }, { 31, 25 }, { 33, 27 }, { 27, 30 }, { 30, 33 }, { 36, 17 }, { 33, 28 }, { 35, 29 }, { 29, 31 }, { 31, 35 }, { 38, 30 }, { 30, 32 }, { 32, 38 }, { 31, 33 }, { 33, 35 }, { 97, 32 }, { 34, 26 }, { 45, 29 }, { 35, 45 }, { 34, 37 }, { 37, 26 }, { 38, 33 }, { 36, 34 }, { 55, 35 }, { 33, 55 }, { 52, 36 }, { 36, 39 }, { 39, 52 }, { 29, 39 }, { 37, 40 }, { 40, 26 }, { 38, 55 }, { 44, 34 }, { 36, 44 }, { 29, 42 }, { 42, 39 }, { 44, 37 }, { 37, 43 }, { 43, 40 }, { 40, 41 }, { 41, 26 }, { 47, 57 }, { 57, 42 }, { 42, 47 }, { 41, 54 }, { 49, 42 }, { 42, 45 }, { 45, 49 }, { 43, 41 }, { 44, 43 }, { 46, 50 }, { 50, 44 }, { 44, 46 }, { 50, 43 }, { 38, 56 }, { 56, 55 }, { 36, 46 }, { 57, 39 }, { 50, 41 }, { 45, 48 }, { 48, 49 }, { 49, 47 }, { 35, 48 }, { 53, 47 }, { 49, 53 }, { 48, 66 }, { 66, 49 }, { 52, 46 }, { 46, 51 }, { 51, 50 }, { 50, 54 }, { 52, 51 }, { 57, 58 }, { 58, 52 }, { 52, 57 }, { 53, 57 }, { 55, 48 }, { 56, 65 }, { 65, 67 }, { 67, 56 }, { 51, 54 }, { 55, 69 }, { 69, 48 }, { 67, 55 }, { 65, 38 }, { 32, 65 }, { 58, 60 }, { 60, 52 }, { 70, 57 }, { 53, 70 }, { 60, 61 }, { 61, 52 }, { 57, 59 }, { 59, 58 }, { 59, 60 }, { 66, 70 }, { 53, 66 }, { 61, 51 }, { 70, 59 }, { 61, 54 }, { 63, 60 }, { 60, 62 }, { 62, 63 }, { 61, 72 }, { 72, 54 }, { 59, 62 }, { 63, 61 }, { 62, 64 }, { 64, 63 }, { 64, 61 }, { 64, 74 }, { 74, 61 }, { 70, 75 }, { 75, 59 }, { 68, 55 }, { 67, 68 }, { 69, 66 }, { 83, 65 }, { 32, 83 }, { 68, 69 }, { 71, 67 }, { 65, 71 }, { 76, 66 }, { 69, 76 }, { 71, 68 }, { 75, 62 }, { 73, 78 }, { 78, 71 }, { 71, 73 }, { 77, 62 }, { 75, 77 }, { 71, 80 }, { 80, 68 }, { 76, 84 }, { 84, 66 }, { 78, 82 }, { 82, 71 }, { 72, 79 }, { 79, 54 }, { 65, 73 }, { 74, 72 }, { 77, 64 }, { 74, 81 }, { 81, 72 }, { 84, 70 }, { 84, 75 }, { 73, 83 }, { 83, 78 }, { 85, 64 }, { 77, 85 }, { 80, 86 }, { 86, 69 }, { 69, 80 }, { 85, 74 }, { 84, 89 }, { 89, 75 }, { 86, 76 }, { 88, 78 }, { 83, 88 }, { 81, 79 }, { 88, 82 }, { 81, 93 }, { 93, 79 }, { 82, 80 }, { 87, 80 }, { 82, 87 }, { 89, 96 }, { 96, 75 }, { 91, 80 }, { 87, 91 }, { 85, 81 }, { 91, 86 }, { 93, 85 }, { 77, 93 }, { 86, 90 }, { 90, 76 }, { 90, 84 }, { 88, 87 }, { 92, 87 }, { 88, 92 }, { 97, 83 }, { 96, 77 }, { 90, 89 }, { 94, 86 }, { 91, 94 }, { 94, 95 }, { 95, 86 }, { 99, 87 }, { 92, 99 }, { 97, 99 }, { 92, 97 }, { 83, 92 }, { 95, 90 }, { 95, 98 }, { 98, 90 }, { 91, 99 }, { 99, 94 }, { 98, 89 }, { 96, 93 }, { 94, 98 }, { 98, 96 }, { 99, 98 }, { 99, 96 } }; testOnGraph(edges); } /** * Random not planar graph on 100 vertices */ @Test public void testPlanarity31() { int[][] edges = new int[][] { { 0, 83 }, { 1, 13 }, { 2, 97 }, { 3, 4 }, { 4, 53 }, { 5, 7 }, { 6, 65 }, { 7, 16 }, { 8, 46 }, { 9, 5 }, { 10, 92 }, { 11, 16 }, { 12, 84 }, { 13, 88 }, { 14, 41 }, { 15, 21 }, { 16, 54 }, { 17, 84 }, { 18, 82 }, { 19, 38 }, { 20, 24 }, { 21, 59 }, { 22, 36 }, { 23, 8 }, { 24, 38 }, { 25, 59 }, { 26, 80 }, { 27, 18 }, { 28, 62 }, { 29, 80 }, { 30, 20 }, { 31, 2 }, { 32, 69 }, { 33, 10 }, { 34, 84 }, { 35, 60 }, { 36, 57 }, { 37, 0 }, { 38, 46 }, { 39, 8 }, { 40, 33 }, { 41, 6 }, { 42, 61 }, { 43, 70 }, { 44, 71 }, { 45, 87 }, { 46, 62 }, { 47, 71 }, { 48, 80 }, { 49, 24 }, { 50, 58 }, { 51, 11 }, { 52, 23 }, { 53, 66 }, { 54, 81 }, { 55, 59 }, { 56, 13 }, { 57, 83 }, { 58, 79 }, { 59, 99 }, { 60, 58 }, { 61, 0 }, { 62, 32 }, { 63, 36 }, { 64, 20 }, { 65, 82 }, { 66, 22 }, { 67, 21 }, { 68, 88 }, { 69, 23 }, { 70, 99 }, { 71, 93 }, { 72, 81 }, { 73, 8 }, { 74, 54 }, { 75, 15 }, { 76, 9 }, { 77, 50 }, { 78, 27 }, { 79, 6 }, { 80, 0 }, { 81, 29 }, { 82, 99 }, { 83, 6 }, { 84, 48 }, { 85, 37 }, { 86, 11 }, { 87, 39 }, { 88, 40 }, { 89, 36 }, { 90, 32 }, { 91, 73 }, { 92, 97 }, { 93, 10 }, { 94, 67 }, { 95, 85 }, { 96, 30 }, { 97, 93 }, { 98, 3 }, { 99, 80 }, { 0, 4 }, { 1, 64 }, { 2, 92 }, { 3, 74 }, { 4, 30 }, { 5, 46 }, { 6, 76 }, { 7, 75 }, { 8, 72 }, { 9, 79 }, { 10, 57 }, { 11, 42 }, { 12, 58 }, { 13, 12 }, { 14, 46 }, { 15, 74 }, { 16, 96 }, { 17, 89 }, { 18, 75 }, { 19, 50 } }; testOnGraph(edges); } /** * Triangulation of 150 points */ @Test public void testPlanarity32() { int[][] edges = new int[][] { { 1, 0 }, { 0, 5 }, { 5, 1 }, { 0, 2 }, { 2, 4 }, { 4, 0 }, { 1, 3 }, { 3, 7 }, { 7, 1 }, { 13, 16 }, { 16, 4 }, { 4, 13 }, { 6, 2 }, { 5, 3 }, { 10, 4 }, { 2, 10 }, { 5, 8 }, { 8, 3 }, { 6, 10 }, { 0, 9 }, { 9, 5 }, { 12, 3 }, { 8, 12 }, { 7, 11 }, { 11, 1 }, { 27, 5 }, { 9, 27 }, { 12, 7 }, { 10, 13 }, { 16, 17 }, { 17, 4 }, { 20, 6 }, { 24, 7 }, { 12, 24 }, { 14, 27 }, { 9, 14 }, { 0, 14 }, { 7, 15 }, { 15, 11 }, { 21, 16 }, { 13, 21 }, { 27, 8 }, { 20, 25 }, { 25, 10 }, { 10, 20 }, { 17, 18 }, { 18, 0 }, { 0, 17 }, { 18, 14 }, { 15, 24 }, { 24, 11 }, { 18, 22 }, { 22, 29 }, { 29, 18 }, { 10, 21 }, { 19, 12 }, { 8, 19 }, { 21, 22 }, { 22, 16 }, { 49, 12 }, { 19, 49 }, { 22, 17 }, { 27, 19 }, { 26, 10 }, { 25, 26 }, { 28, 14 }, { 18, 28 }, { 21, 23 }, { 23, 22 }, { 52, 20 }, { 26, 23 }, { 21, 26 }, { 26, 37 }, { 37, 23 }, { 80, 24 }, { 24, 73 }, { 73, 80 }, { 43, 22 }, { 23, 43 }, { 22, 30 }, { 30, 29 }, { 31, 19 }, { 27, 31 }, { 34, 14 }, { 28, 34 }, { 34, 27 }, { 29, 28 }, { 30, 36 }, { 36, 29 }, { 33, 26 }, { 25, 33 }, { 36, 28 }, { 32, 26 }, { 33, 32 }, { 40, 19 }, { 31, 40 }, { 33, 46 }, { 46, 32 }, { 38, 31 }, { 27, 38 }, { 32, 37 }, { 37, 43 }, { 35, 25 }, { 20, 35 }, { 34, 38 }, { 52, 56 }, { 56, 35 }, { 35, 52 }, { 45, 31 }, { 38, 45 }, { 35, 33 }, { 42, 28 }, { 36, 42 }, { 43, 47 }, { 47, 22 }, { 42, 34 }, { 44, 59 }, { 59, 42 }, { 42, 44 }, { 36, 39 }, { 39, 42 }, { 30, 39 }, { 41, 19 }, { 40, 41 }, { 46, 37 }, { 45, 40 }, { 42, 45 }, { 45, 34 }, { 41, 49 }, { 47, 30 }, { 49, 24 }, { 47, 39 }, { 39, 44 }, { 54, 46 }, { 33, 54 }, { 51, 43 }, { 37, 51 }, { 60, 40 }, { 45, 60 }, { 47, 44 }, { 45, 57 }, { 57, 60 }, { 46, 53 }, { 53, 37 }, { 48, 44 }, { 47, 48 }, { 51, 47 }, { 48, 59 }, { 35, 54 }, { 42, 57 }, { 41, 50 }, { 50, 49 }, { 63, 49 }, { 49, 61 }, { 61, 63 }, { 62, 48 }, { 48, 51 }, { 51, 62 }, { 60, 50 }, { 41, 60 }, { 53, 51 }, { 65, 51 }, { 53, 65 }, { 54, 53 }, { 55, 64 }, { 64, 54 }, { 54, 55 }, { 35, 55 }, { 56, 58 }, { 58, 55 }, { 55, 56 }, { 58, 64 }, { 119, 52 }, { 64, 53 }, { 61, 50 }, { 60, 61 }, { 70, 56 }, { 52, 70 }, { 59, 57 }, { 70, 58 }, { 62, 59 }, { 69, 74 }, { 74, 59 }, { 59, 69 }, { 51, 68 }, { 68, 62 }, { 63, 24 }, { 65, 68 }, { 60, 67 }, { 67, 61 }, { 65, 66 }, { 66, 68 }, { 73, 63 }, { 63, 67 }, { 67, 73 }, { 64, 65 }, { 68, 69 }, { 69, 62 }, { 64, 88 }, { 88, 65 }, { 70, 64 }, { 59, 71 }, { 71, 57 }, { 78, 88 }, { 64, 78 }, { 71, 72 }, { 72, 57 }, { 83, 68 }, { 66, 83 }, { 72, 60 }, { 70, 78 }, { 75, 59 }, { 74, 75 }, { 79, 69 }, { 68, 79 }, { 76, 60 }, { 72, 76 }, { 119, 70 }, { 75, 81 }, { 81, 71 }, { 71, 75 }, { 76, 77 }, { 77, 60 }, { 80, 11 }, { 81, 84 }, { 84, 85 }, { 85, 81 }, { 90, 74 }, { 74, 79 }, { 79, 90 }, { 77, 67 }, { 82, 67 }, { 77, 82 }, { 74, 81 }, { 86, 67 }, { 82, 86 }, { 88, 66 }, { 85, 71 }, { 83, 79 }, { 85, 72 }, { 85, 76 }, { 95, 78 }, { 78, 93 }, { 93, 95 }, { 86, 73 }, { 90, 81 }, { 76, 100 }, { 100, 77 }, { 83, 96 }, { 96, 79 }, { 85, 100 }, { 88, 83 }, { 100, 101 }, { 101, 77 }, { 90, 85 }, { 84, 90 }, { 87, 73 }, { 86, 87 }, { 87, 80 }, { 90, 100 }, { 87, 89 }, { 89, 80 }, { 88, 92 }, { 92, 97 }, { 97, 88 }, { 86, 91 }, { 91, 87 }, { 101, 82 }, { 118, 89 }, { 89, 98 }, { 98, 118 }, { 78, 92 }, { 91, 89 }, { 95, 92 }, { 91, 98 }, { 88, 94 }, { 94, 83 }, { 94, 96 }, { 119, 78 }, { 96, 90 }, { 95, 97 }, { 97, 94 }, { 99, 93 }, { 78, 99 }, { 105, 90 }, { 96, 105 }, { 104, 110 }, { 110, 97 }, { 97, 104 }, { 86, 98 }, { 99, 95 }, { 99, 104 }, { 104, 95 }, { 118, 80 }, { 99, 103 }, { 103, 104 }, { 101, 102 }, { 102, 82 }, { 127, 97 }, { 110, 127 }, { 102, 86 }, { 100, 107 }, { 107, 101 }, { 102, 98 }, { 104, 109 }, { 109, 110 }, { 107, 117 }, { 117, 106 }, { 106, 107 }, { 78, 103 }, { 105, 113 }, { 113, 90 }, { 108, 103 }, { 78, 108 }, { 111, 113 }, { 105, 111 }, { 97, 120 }, { 120, 94 }, { 101, 106 }, { 106, 102 }, { 116, 102 }, { 106, 116 }, { 103, 109 }, { 102, 112 }, { 112, 98 }, { 108, 109 }, { 94, 115 }, { 115, 96 }, { 108, 110 }, { 115, 105 }, { 119, 108 }, { 113, 100 }, { 111, 114 }, { 114, 113 }, { 123, 107 }, { 100, 123 }, { 113, 123 }, { 117, 112 }, { 112, 116 }, { 116, 117 }, { 105, 114 }, { 115, 120 }, { 120, 122 }, { 122, 115 }, { 115, 114 }, { 122, 129 }, { 129, 115 }, { 119, 124 }, { 124, 108 }, { 121, 112 }, { 117, 121 }, { 124, 126 }, { 126, 108 }, { 112, 118 }, { 129, 114 }, { 149, 118 }, { 118, 134 }, { 134, 149 }, { 123, 131 }, { 131, 107 }, { 126, 110 }, { 121, 118 }, { 129, 113 }, { 121, 125 }, { 125, 118 }, { 126, 127 }, { 131, 117 }, { 127, 120 }, { 143, 117 }, { 131, 143 }, { 137, 124 }, { 124, 139 }, { 139, 137 }, { 127, 122 }, { 127, 133 }, { 133, 140 }, { 140, 127 }, { 125, 134 }, { 132, 124 }, { 137, 132 }, { 128, 123 }, { 113, 128 }, { 133, 126 }, { 126, 132 }, { 132, 133 }, { 128, 131 }, { 130, 113 }, { 129, 130 }, { 130, 128 }, { 140, 122 }, { 138, 128 }, { 130, 138 }, { 129, 138 }, { 135, 121 }, { 117, 135 }, { 135, 125 }, { 132, 140 }, { 119, 139 }, { 138, 131 }, { 135, 134 }, { 149, 80 }, { 148, 131 }, { 138, 148 }, { 135, 136 }, { 136, 134 }, { 142, 129 }, { 122, 142 }, { 136, 149 }, { 137, 140 }, { 143, 135 }, { 141, 137 }, { 139, 141 }, { 142, 138 }, { 140, 142 }, { 145, 137 }, { 141, 145 }, { 144, 140 }, { 137, 144 }, { 147, 139 }, { 142, 148 }, { 144, 142 }, { 143, 136 }, { 144, 148 }, { 143, 149 }, { 145, 144 }, { 147, 144 }, { 145, 147 }, { 147, 141 }, { 131, 146 }, { 146, 143 }, { 147, 148 }, { 149, 146 }, { 146, 148 }, { 148, 149 } }; testOnGraph(edges); } /** * Random not planar graph on 150 vertices */ @Test public void testPlanarity33() { int[][] edges = new int[][] { { 0, 113 }, { 1, 107 }, { 2, 95 }, { 3, 77 }, { 4, 49 }, { 5, 61 }, { 6, 25 }, { 7, 3 }, { 8, 5 }, { 9, 129 }, { 10, 14 }, { 11, 92 }, { 12, 135 }, { 13, 56 }, { 14, 145 }, { 15, 17 }, { 16, 110 }, { 17, 39 }, { 18, 90 }, { 19, 65 }, { 20, 121 }, { 21, 64 }, { 22, 75 }, { 23, 65 }, { 24, 91 }, { 25, 79 }, { 26, 83 }, { 27, 75 }, { 28, 87 }, { 29, 32 }, { 30, 69 }, { 31, 4 }, { 32, 75 }, { 33, 112 }, { 34, 115 }, { 35, 23 }, { 36, 86 }, { 37, 3 }, { 38, 122 }, { 39, 104 }, { 40, 52 }, { 41, 62 }, { 42, 49 }, { 43, 32 }, { 44, 79 }, { 45, 125 }, { 46, 48 }, { 47, 120 }, { 48, 5 }, { 49, 1 }, { 50, 60 }, { 51, 74 }, { 52, 55 }, { 53, 130 }, { 54, 0 }, { 55, 28 }, { 56, 29 }, { 57, 80 }, { 58, 60 }, { 59, 92 }, { 60, 7 }, { 61, 93 }, { 62, 25 }, { 63, 143 }, { 64, 88 }, { 65, 52 }, { 66, 144 }, { 67, 50 }, { 68, 1 }, { 69, 68 }, { 70, 35 }, { 71, 54 }, { 72, 67 }, { 73, 87 }, { 74, 97 }, { 75, 113 }, { 76, 49 }, { 77, 54 }, { 78, 29 }, { 79, 91 }, { 80, 3 }, { 81, 139 }, { 82, 17 }, { 83, 111 }, { 84, 66 }, { 85, 20 }, { 86, 76 }, { 87, 29 }, { 88, 6 }, { 89, 4 }, { 90, 131 }, { 91, 85 }, { 92, 19 }, { 93, 133 }, { 94, 130 }, { 95, 59 }, { 96, 120 }, { 97, 59 }, { 98, 79 }, { 99, 90 }, { 100, 25 }, { 101, 54 }, { 102, 24 }, { 103, 138 }, { 104, 137 }, { 105, 88 }, { 106, 144 }, { 107, 114 }, { 108, 75 }, { 109, 49 }, { 110, 114 }, { 111, 6 }, { 112, 98 }, { 113, 132 }, { 114, 124 }, { 115, 84 }, { 116, 28 }, { 117, 32 }, { 118, 120 }, { 119, 101 }, { 120, 85 }, { 121, 64 }, { 122, 36 }, { 123, 133 }, { 124, 129 }, { 125, 18 }, { 126, 45 }, { 127, 125 }, { 128, 137 }, { 129, 144 }, { 130, 149 }, { 131, 34 }, { 132, 90 }, { 133, 66 }, { 134, 89 }, { 135, 75 }, { 136, 51 }, { 137, 83 }, { 138, 129 }, { 139, 109 }, { 140, 13 }, { 141, 118 }, { 142, 114 }, { 143, 56 }, { 144, 97 }, { 145, 137 }, { 146, 72 }, { 147, 68 }, { 148, 80 }, { 149, 84 }, { 0, 63 }, { 1, 32 }, { 2, 18 }, { 3, 12 }, { 4, 130 }, { 5, 101 }, { 6, 98 }, { 7, 72 }, { 8, 48 }, { 9, 24 }, { 10, 81 }, { 11, 47 }, { 12, 126 }, { 13, 125 }, { 14, 2 } }; testOnGraph(edges); } /** * Triangulation of 200 points */ @Test public void testPlanarity34() { int[][] edges = new int[][] { { 9, 13 }, { 13, 10 }, { 10, 9 }, { 1, 2 }, { 2, 0 }, { 0, 1 }, { 0, 3 }, { 3, 4 }, { 4, 0 }, { 2, 12 }, { 12, 0 }, { 7, 2 }, { 1, 7 }, { 7, 12 }, { 4, 5 }, { 5, 0 }, { 8, 4 }, { 3, 8 }, { 5, 6 }, { 6, 0 }, { 8, 11 }, { 11, 4 }, { 12, 3 }, { 10, 4 }, { 4, 9 }, { 21, 7 }, { 1, 21 }, { 20, 6 }, { 6, 15 }, { 15, 20 }, { 10, 5 }, { 12, 17 }, { 17, 3 }, { 13, 16 }, { 16, 10 }, { 8, 17 }, { 17, 11 }, { 17, 29 }, { 29, 11 }, { 11, 9 }, { 55, 1 }, { 16, 5 }, { 19, 24 }, { 24, 18 }, { 18, 19 }, { 14, 6 }, { 5, 14 }, { 13, 22 }, { 22, 16 }, { 24, 6 }, { 14, 24 }, { 16, 19 }, { 19, 5 }, { 5, 18 }, { 18, 14 }, { 9, 22 }, { 29, 9 }, { 21, 27 }, { 27, 7 }, { 27, 12 }, { 23, 15 }, { 6, 23 }, { 31, 20 }, { 15, 31 }, { 22, 19 }, { 27, 17 }, { 22, 32 }, { 32, 19 }, { 31, 25 }, { 25, 20 }, { 23, 33 }, { 33, 34 }, { 34, 23 }, { 24, 23 }, { 27, 30 }, { 30, 17 }, { 26, 23 }, { 24, 26 }, { 32, 36 }, { 36, 19 }, { 26, 33 }, { 87, 25 }, { 25, 28 }, { 28, 87 }, { 29, 22 }, { 1, 39 }, { 39, 21 }, { 29, 32 }, { 27, 40 }, { 40, 30 }, { 36, 24 }, { 34, 47 }, { 47, 31 }, { 31, 34 }, { 30, 29 }, { 21, 38 }, { 38, 27 }, { 31, 28 }, { 46, 65 }, { 65, 34 }, { 34, 46 }, { 36, 41 }, { 41, 24 }, { 27, 35 }, { 35, 40 }, { 34, 15 }, { 44, 30 }, { 40, 44 }, { 32, 42 }, { 42, 36 }, { 41, 26 }, { 44, 29 }, { 41, 46 }, { 46, 26 }, { 37, 51 }, { 51, 38 }, { 38, 37 }, { 38, 35 }, { 21, 37 }, { 38, 43 }, { 43, 35 }, { 33, 46 }, { 39, 55 }, { 55, 37 }, { 37, 39 }, { 29, 53 }, { 53, 32 }, { 54, 28 }, { 31, 54 }, { 45, 60 }, { 60, 42 }, { 42, 45 }, { 42, 49 }, { 49, 36 }, { 43, 40 }, { 43, 52 }, { 52, 57 }, { 57, 43 }, { 44, 50 }, { 50, 29 }, { 56, 44 }, { 40, 56 }, { 32, 45 }, { 52, 48 }, { 48, 51 }, { 51, 52 }, { 49, 41 }, { 43, 48 }, { 47, 54 }, { 49, 58 }, { 58, 41 }, { 50, 53 }, { 38, 48 }, { 60, 49 }, { 68, 73 }, { 73, 54 }, { 54, 68 }, { 53, 45 }, { 70, 44 }, { 56, 70 }, { 43, 56 }, { 53, 60 }, { 55, 62 }, { 62, 37 }, { 58, 66 }, { 66, 41 }, { 78, 59 }, { 59, 69 }, { 69, 78 }, { 61, 64 }, { 64, 65 }, { 65, 61 }, { 51, 59 }, { 59, 52 }, { 56, 63 }, { 63, 70 }, { 67, 37 }, { 62, 67 }, { 74, 44 }, { 70, 74 }, { 60, 79 }, { 79, 58 }, { 58, 60 }, { 57, 63 }, { 63, 43 }, { 161, 55 }, { 41, 61 }, { 61, 46 }, { 65, 47 }, { 59, 57 }, { 67, 51 }, { 71, 54 }, { 47, 71 }, { 72, 74 }, { 70, 72 }, { 69, 51 }, { 67, 69 }, { 74, 50 }, { 73, 28 }, { 66, 61 }, { 65, 71 }, { 75, 65 }, { 64, 75 }, { 61, 75 }, { 66, 80 }, { 80, 61 }, { 78, 57 }, { 55, 81 }, { 81, 62 }, { 78, 86 }, { 86, 57 }, { 77, 68 }, { 54, 77 }, { 74, 85 }, { 85, 50 }, { 81, 67 }, { 85, 53 }, { 81, 69 }, { 71, 77 }, { 73, 87 }, { 53, 79 }, { 86, 63 }, { 79, 66 }, { 82, 83 }, { 83, 77 }, { 77, 82 }, { 75, 76 }, { 76, 65 }, { 63, 84 }, { 84, 70 }, { 76, 71 }, { 71, 82 }, { 76, 82 }, { 72, 84 }, { 84, 74 }, { 83, 68 }, { 75, 80 }, { 80, 90 }, { 90, 75 }, { 79, 88 }, { 88, 66 }, { 81, 99 }, { 99, 69 }, { 90, 76 }, { 73, 83 }, { 83, 87 }, { 84, 92 }, { 92, 74 }, { 99, 78 }, { 53, 89 }, { 89, 79 }, { 192, 25 }, { 87, 192 }, { 85, 89 }, { 88, 80 }, { 55, 112 }, { 112, 81 }, { 88, 90 }, { 86, 84 }, { 92, 85 }, { 86, 98 }, { 98, 84 }, { 90, 82 }, { 111, 92 }, { 92, 106 }, { 106, 111 }, { 90, 97 }, { 97, 82 }, { 88, 101 }, { 101, 109 }, { 109, 88 }, { 89, 93 }, { 93, 79 }, { 78, 91 }, { 91, 86 }, { 97, 83 }, { 148, 87 }, { 87, 94 }, { 94, 148 }, { 92, 89 }, { 91, 95 }, { 95, 86 }, { 93, 101 }, { 101, 79 }, { 99, 91 }, { 95, 96 }, { 96, 86 }, { 83, 94 }, { 97, 104 }, { 104, 83 }, { 98, 92 }, { 96, 98 }, { 98, 106 }, { 99, 100 }, { 100, 91 }, { 97, 103 }, { 103, 104 }, { 96, 100 }, { 100, 113 }, { 113, 96 }, { 111, 89 }, { 124, 97 }, { 90, 124 }, { 95, 100 }, { 96, 110 }, { 110, 98 }, { 81, 102 }, { 102, 105 }, { 105, 81 }, { 109, 90 }, { 108, 94 }, { 83, 108 }, { 118, 105 }, { 105, 112 }, { 112, 118 }, { 105, 99 }, { 99, 118 }, { 118, 122 }, { 122, 99 }, { 129, 148 }, { 148, 117 }, { 117, 129 }, { 104, 108 }, { 148, 108 }, { 108, 117 }, { 93, 107 }, { 107, 101 }, { 111, 93 }, { 107, 109 }, { 116, 93 }, { 111, 116 }, { 127, 109 }, { 109, 123 }, { 123, 127 }, { 110, 106 }, { 125, 104 }, { 103, 125 }, { 111, 115 }, { 115, 116 }, { 109, 119 }, { 119, 123 }, { 110, 113 }, { 113, 114 }, { 114, 110 }, { 110, 121 }, { 121, 106 }, { 131, 110 }, { 114, 131 }, { 102, 112 }, { 106, 115 }, { 55, 126 }, { 126, 112 }, { 116, 107 }, { 122, 100 }, { 116, 119 }, { 119, 107 }, { 122, 113 }, { 122, 114 }, { 104, 117 }, { 124, 103 }, { 115, 120 }, { 120, 116 }, { 121, 115 }, { 109, 124 }, { 115, 130 }, { 130, 120 }, { 120, 119 }, { 135, 119 }, { 120, 135 }, { 121, 130 }, { 128, 117 }, { 104, 128 }, { 122, 131 }, { 126, 118 }, { 131, 121 }, { 126, 133 }, { 133, 118 }, { 124, 125 }, { 146, 118 }, { 133, 146 }, { 125, 128 }, { 137, 148 }, { 129, 137 }, { 127, 124 }, { 130, 135 }, { 127, 136 }, { 136, 124 }, { 161, 126 }, { 132, 125 }, { 124, 132 }, { 135, 123 }, { 128, 129 }, { 134, 137 }, { 137, 128 }, { 128, 134 }, { 139, 123 }, { 135, 139 }, { 130, 131 }, { 131, 135 }, { 132, 134 }, { 134, 125 }, { 122, 138 }, { 138, 131 }, { 146, 122 }, { 136, 132 }, { 139, 127 }, { 135, 140 }, { 140, 157 }, { 157, 135 }, { 136, 134 }, { 138, 140 }, { 140, 131 }, { 144, 153 }, { 153, 136 }, { 136, 144 }, { 139, 136 }, { 161, 133 }, { 139, 143 }, { 143, 136 }, { 137, 142 }, { 142, 148 }, { 140, 141 }, { 141, 157 }, { 146, 138 }, { 135, 160 }, { 160, 139 }, { 138, 141 }, { 134, 142 }, { 145, 159 }, { 159, 144 }, { 144, 145 }, { 143, 144 }, { 153, 142 }, { 134, 153 }, { 139, 145 }, { 145, 143 }, { 191, 192 }, { 192, 148 }, { 148, 191 }, { 138, 147 }, { 147, 149 }, { 149, 138 }, { 146, 147 }, { 149, 141 }, { 133, 151 }, { 151, 146 }, { 167, 155 }, { 155, 156 }, { 156, 167 }, { 153, 163 }, { 163, 150 }, { 150, 153 }, { 149, 152 }, { 152, 155 }, { 155, 149 }, { 146, 152 }, { 152, 147 }, { 150, 148 }, { 142, 150 }, { 159, 153 }, { 151, 156 }, { 156, 146 }, { 139, 154 }, { 154, 145 }, { 163, 148 }, { 157, 160 }, { 154, 158 }, { 158, 145 }, { 156, 152 }, { 167, 149 }, { 161, 151 }, { 149, 164 }, { 164, 141 }, { 160, 154 }, { 162, 171 }, { 171, 158 }, { 158, 162 }, { 158, 159 }, { 164, 157 }, { 159, 163 }, { 160, 162 }, { 162, 154 }, { 165, 148 }, { 163, 165 }, { 161, 174 }, { 174, 151 }, { 161, 173 }, { 173, 174 }, { 164, 166 }, { 166, 157 }, { 172, 148 }, { 165, 172 }, { 171, 159 }, { 166, 160 }, { 166, 175 }, { 175, 160 }, { 181, 161 }, { 169, 163 }, { 159, 169 }, { 164, 168 }, { 168, 170 }, { 170, 164 }, { 170, 166 }, { 165, 169 }, { 169, 172 }, { 167, 168 }, { 164, 167 }, { 175, 162 }, { 156, 174 }, { 174, 176 }, { 176, 156 }, { 180, 184 }, { 184, 172 }, { 172, 180 }, { 170, 178 }, { 178, 166 }, { 171, 169 }, { 176, 167 }, { 180, 169 }, { 171, 180 }, { 187, 177 }, { 177, 186 }, { 186, 187 }, { 168, 176 }, { 176, 170 }, { 175, 171 }, { 181, 173 }, { 177, 178 }, { 178, 186 }, { 176, 179 }, { 179, 170 }, { 184, 191 }, { 191, 172 }, { 177, 175 }, { 166, 177 }, { 174, 188 }, { 188, 176 }, { 175, 185 }, { 185, 171 }, { 179, 182 }, { 182, 170 }, { 181, 174 }, { 182, 183 }, { 183, 170 }, { 188, 179 }, { 181, 188 }, { 183, 178 }, { 199, 181 }, { 183, 186 }, { 188, 199 }, { 199, 179 }, { 185, 193 }, { 193, 171 }, { 187, 189 }, { 189, 185 }, { 185, 187 }, { 193, 180 }, { 186, 189 }, { 187, 175 }, { 199, 182 }, { 183, 199 }, { 199, 186 }, { 189, 195 }, { 195, 185 }, { 190, 184 }, { 180, 190 }, { 186, 195 }, { 194, 184 }, { 190, 194 }, { 193, 190 }, { 192, 194 }, { 194, 191 }, { 193, 194 }, { 194, 198 }, { 185, 196 }, { 196, 198 }, { 198, 185 }, { 195, 196 }, { 185, 197 }, { 197, 193 }, { 198, 199 }, { 197, 198 }, { 198, 193 }, { 195, 198 }, { 195, 199 } }; testOnGraph(edges); } /** * Random not planar graph on 200 vertices */ @Test public void testPlanarity35() { int[][] edges = new int[][] { { 0, 170 }, { 1, 19 }, { 2, 55 }, { 3, 195 }, { 4, 140 }, { 5, 157 }, { 6, 35 }, { 7, 87 }, { 8, 103 }, { 9, 138 }, { 10, 99 }, { 11, 26 }, { 12, 23 }, { 13, 138 }, { 14, 72 }, { 15, 75 }, { 16, 179 }, { 17, 121 }, { 18, 195 }, { 19, 156 }, { 20, 118 }, { 21, 167 }, { 22, 188 }, { 23, 94 }, { 24, 182 }, { 25, 139 }, { 26, 13 }, { 27, 25 }, { 28, 138 }, { 29, 51 }, { 30, 189 }, { 31, 13 }, { 32, 145 }, { 33, 163 }, { 34, 72 }, { 35, 199 }, { 36, 69 }, { 37, 119 }, { 38, 196 }, { 39, 35 }, { 40, 79 }, { 41, 55 }, { 42, 14 }, { 43, 195 }, { 44, 188 }, { 45, 118 }, { 46, 51 }, { 47, 105 }, { 48, 132 }, { 49, 191 }, { 50, 139 }, { 51, 15 }, { 52, 123 }, { 53, 157 }, { 54, 15 }, { 55, 64 }, { 56, 137 }, { 57, 12 }, { 58, 179 }, { 59, 138 }, { 60, 164 }, { 61, 137 }, { 62, 174 }, { 63, 27 }, { 64, 61 }, { 65, 139 }, { 66, 63 }, { 67, 133 }, { 68, 8 }, { 69, 21 }, { 70, 138 }, { 71, 48 }, { 72, 77 }, { 73, 82 }, { 74, 104 }, { 75, 170 }, { 76, 155 }, { 77, 195 }, { 78, 5 }, { 79, 69 }, { 80, 60 }, { 81, 127 }, { 82, 34 }, { 83, 117 }, { 84, 164 }, { 85, 0 }, { 86, 66 }, { 87, 148 }, { 88, 35 }, { 89, 166 }, { 90, 138 }, { 91, 76 }, { 92, 130 }, { 93, 82 }, { 94, 26 }, { 95, 121 }, { 96, 106 }, { 97, 185 }, { 98, 152 }, { 99, 169 }, { 100, 7 }, { 101, 59 }, { 102, 180 }, { 103, 184 }, { 104, 196 }, { 105, 103 }, { 106, 93 }, { 107, 124 }, { 108, 1 }, { 109, 173 }, { 110, 66 }, { 111, 130 }, { 112, 47 }, { 113, 7 }, { 114, 157 }, { 115, 35 }, { 116, 187 }, { 117, 69 }, { 118, 66 }, { 119, 174 }, { 120, 73 }, { 121, 75 }, { 122, 118 }, { 123, 3 }, { 124, 40 }, { 125, 122 }, { 126, 171 }, { 127, 137 }, { 128, 19 }, { 129, 176 }, { 130, 175 }, { 131, 189 }, { 132, 104 }, { 133, 92 }, { 134, 123 }, { 135, 169 }, { 136, 57 }, { 137, 34 }, { 138, 137 }, { 139, 108 }, { 140, 40 }, { 141, 39 }, { 142, 151 }, { 143, 171 }, { 144, 80 }, { 145, 130 }, { 146, 33 }, { 147, 92 }, { 148, 118 }, { 149, 183 }, { 150, 155 }, { 151, 26 }, { 152, 191 }, { 153, 17 }, { 154, 27 }, { 155, 90 }, { 156, 74 }, { 157, 52 }, { 158, 21 }, { 159, 17 }, { 160, 134 }, { 161, 116 }, { 162, 8 }, { 163, 104 }, { 164, 14 }, { 165, 85 }, { 166, 70 }, { 167, 69 }, { 168, 105 }, { 169, 72 }, { 170, 73 }, { 171, 159 }, { 172, 119 }, { 173, 30 }, { 174, 32 }, { 175, 125 }, { 176, 92 }, { 177, 12 }, { 178, 105 }, { 179, 92 }, { 180, 185 }, { 181, 3 }, { 182, 128 }, { 183, 44 }, { 184, 80 }, { 185, 67 }, { 186, 46 }, { 187, 5 }, { 188, 173 }, { 189, 108 }, { 190, 77 }, { 191, 67 }, { 192, 53 }, { 193, 9 }, { 194, 31 }, { 195, 123 }, { 196, 158 }, { 197, 113 }, { 198, 23 }, { 199, 129 }, { 0, 197 }, { 1, 155 }, { 2, 151 }, { 3, 24 }, { 4, 127 }, { 5, 85 }, { 6, 72 }, { 7, 85 }, { 8, 23 }, { 9, 169 }, { 10, 80 }, { 11, 56 }, { 12, 54 }, { 13, 29 }, { 14, 198 }, { 15, 127 }, { 16, 169 }, { 17, 54 }, { 18, 17 }, { 19, 113 } }; testOnGraph(edges); } /** * Triangulation of 300 points */ @Test public void testPlanarity36() { int[][] edges = new int[][] { { 1, 0 }, { 0, 3 }, { 3, 1 }, { 0, 2 }, { 2, 5 }, { 5, 0 }, { 11, 24 }, { 24, 9 }, { 9, 11 }, { 6, 14 }, { 14, 5 }, { 5, 6 }, { 7, 2 }, { 0, 7 }, { 4, 27 }, { 27, 0 }, { 0, 4 }, { 5, 3 }, { 2, 8 }, { 8, 18 }, { 18, 2 }, { 2, 6 }, { 1, 10 }, { 7, 8 }, { 123, 4 }, { 18, 6 }, { 0, 21 }, { 21, 7 }, { 9, 3 }, { 5, 9 }, { 21, 8 }, { 15, 1 }, { 3, 15 }, { 10, 13 }, { 40, 24 }, { 24, 33 }, { 33, 40 }, { 5, 11 }, { 12, 10 }, { 1, 12 }, { 14, 19 }, { 19, 5 }, { 15, 22 }, { 22, 12 }, { 12, 15 }, { 29, 43 }, { 43, 46 }, { 46, 29 }, { 5, 17 }, { 17, 11 }, { 12, 20 }, { 20, 10 }, { 18, 14 }, { 16, 13 }, { 10, 16 }, { 23, 26 }, { 26, 20 }, { 20, 23 }, { 19, 17 }, { 17, 24 }, { 19, 25 }, { 25, 17 }, { 39, 18 }, { 18, 38 }, { 38, 39 }, { 3, 22 }, { 21, 18 }, { 26, 10 }, { 26, 29 }, { 29, 10 }, { 14, 31 }, { 31, 19 }, { 37, 21 }, { 21, 27 }, { 27, 37 }, { 22, 34 }, { 34, 12 }, { 12, 23 }, { 29, 16 }, { 17, 36 }, { 36, 24 }, { 40, 9 }, { 17, 28 }, { 28, 36 }, { 25, 28 }, { 30, 34 }, { 22, 30 }, { 41, 43 }, { 29, 41 }, { 31, 25 }, { 42, 21 }, { 37, 42 }, { 3, 30 }, { 29, 32 }, { 32, 41 }, { 34, 23 }, { 26, 32 }, { 23, 32 }, { 31, 35 }, { 35, 25 }, { 35, 28 }, { 4, 62 }, { 62, 27 }, { 40, 51 }, { 51, 9 }, { 54, 64 }, { 64, 34 }, { 34, 54 }, { 51, 3 }, { 42, 18 }, { 34, 32 }, { 31, 44 }, { 44, 35 }, { 36, 33 }, { 52, 35 }, { 35, 48 }, { 48, 52 }, { 36, 40 }, { 46, 16 }, { 14, 39 }, { 39, 31 }, { 39, 44 }, { 64, 68 }, { 68, 71 }, { 71, 64 }, { 44, 48 }, { 42, 38 }, { 3, 47 }, { 47, 30 }, { 42, 53 }, { 53, 38 }, { 45, 34 }, { 30, 45 }, { 46, 13 }, { 52, 28 }, { 53, 39 }, { 45, 54 }, { 28, 55 }, { 55, 36 }, { 41, 56 }, { 56, 43 }, { 45, 47 }, { 47, 59 }, { 59, 45 }, { 40, 49 }, { 49, 50 }, { 50, 40 }, { 64, 32 }, { 32, 61 }, { 61, 41 }, { 53, 44 }, { 36, 49 }, { 37, 60 }, { 60, 42 }, { 50, 51 }, { 53, 48 }, { 51, 47 }, { 52, 57 }, { 57, 28 }, { 51, 59 }, { 56, 58 }, { 58, 43 }, { 52, 53 }, { 53, 70 }, { 70, 52 }, { 55, 49 }, { 55, 63 }, { 63, 49 }, { 63, 50 }, { 58, 46 }, { 63, 67 }, { 67, 50 }, { 60, 75 }, { 75, 53 }, { 53, 60 }, { 57, 86 }, { 86, 55 }, { 55, 57 }, { 65, 46 }, { 58, 65 }, { 59, 54 }, { 59, 68 }, { 68, 54 }, { 61, 69 }, { 69, 72 }, { 72, 61 }, { 61, 56 }, { 60, 62 }, { 62, 78 }, { 78, 60 }, { 27, 60 }, { 70, 57 }, { 66, 77 }, { 77, 65 }, { 65, 66 }, { 67, 51 }, { 67, 85 }, { 85, 51 }, { 4, 101 }, { 101, 62 }, { 64, 61 }, { 64, 74 }, { 74, 69 }, { 69, 64 }, { 72, 56 }, { 77, 46 }, { 58, 66 }, { 85, 59 }, { 97, 70 }, { 70, 79 }, { 79, 97 }, { 92, 57 }, { 70, 92 }, { 72, 58 }, { 76, 77 }, { 77, 73 }, { 73, 76 }, { 85, 87 }, { 87, 59 }, { 70, 75 }, { 75, 79 }, { 71, 74 }, { 172, 46 }, { 77, 172 }, { 72, 80 }, { 80, 58 }, { 68, 81 }, { 81, 71 }, { 73, 66 }, { 58, 73 }, { 80, 73 }, { 74, 80 }, { 72, 74 }, { 96, 78 }, { 78, 95 }, { 95, 96 }, { 86, 63 }, { 78, 75 }, { 74, 83 }, { 83, 80 }, { 82, 71 }, { 81, 82 }, { 86, 67 }, { 80, 88 }, { 88, 73 }, { 62, 99 }, { 99, 78 }, { 83, 88 }, { 94, 104 }, { 104, 105 }, { 105, 94 }, { 82, 74 }, { 75, 89 }, { 89, 79 }, { 82, 83 }, { 87, 68 }, { 88, 76 }, { 87, 81 }, { 84, 77 }, { 76, 84 }, { 94, 84 }, { 84, 88 }, { 88, 94 }, { 81, 91 }, { 91, 82 }, { 93, 102 }, { 102, 87 }, { 87, 93 }, { 67, 90 }, { 90, 85 }, { 86, 90 }, { 91, 83 }, { 98, 88 }, { 83, 98 }, { 92, 86 }, { 90, 112 }, { 112, 85 }, { 89, 97 }, { 85, 93 }, { 96, 75 }, { 91, 98 }, { 105, 84 }, { 92, 97 }, { 97, 118 }, { 118, 92 }, { 118, 86 }, { 102, 81 }, { 96, 89 }, { 102, 91 }, { 91, 113 }, { 113, 98 }, { 100, 109 }, { 109, 96 }, { 96, 100 }, { 86, 111 }, { 111, 90 }, { 99, 95 }, { 90, 103 }, { 103, 112 }, { 99, 100 }, { 100, 95 }, { 98, 104 }, { 104, 88 }, { 101, 106 }, { 106, 107 }, { 107, 101 }, { 110, 96 }, { 109, 110 }, { 108, 99 }, { 99, 101 }, { 101, 108 }, { 107, 108 }, { 102, 113 }, { 115, 77 }, { 84, 115 }, { 112, 93 }, { 123, 101 }, { 111, 116 }, { 116, 103 }, { 103, 111 }, { 108, 100 }, { 105, 115 }, { 120, 105 }, { 104, 120 }, { 110, 89 }, { 108, 109 }, { 110, 119 }, { 119, 89 }, { 123, 106 }, { 119, 97 }, { 106, 121 }, { 121, 107 }, { 121, 108 }, { 112, 102 }, { 121, 109 }, { 125, 112 }, { 112, 114 }, { 114, 125 }, { 136, 140 }, { 140, 120 }, { 120, 136 }, { 113, 117 }, { 117, 98 }, { 118, 111 }, { 117, 104 }, { 140, 172 }, { 172, 115 }, { 115, 140 }, { 125, 131 }, { 131, 112 }, { 103, 114 }, { 103, 122 }, { 122, 114 }, { 120, 115 }, { 131, 102 }, { 116, 122 }, { 117, 128 }, { 128, 104 }, { 119, 129 }, { 129, 97 }, { 111, 122 }, { 118, 138 }, { 138, 111 }, { 119, 126 }, { 126, 129 }, { 130, 121 }, { 121, 124 }, { 124, 130 }, { 122, 127 }, { 127, 114 }, { 110, 126 }, { 121, 123 }, { 123, 124 }, { 130, 109 }, { 132, 102 }, { 131, 132 }, { 128, 136 }, { 136, 104 }, { 130, 110 }, { 129, 118 }, { 144, 159 }, { 159, 132 }, { 132, 144 }, { 113, 128 }, { 111, 135 }, { 135, 122 }, { 123, 137 }, { 137, 124 }, { 127, 125 }, { 130, 126 }, { 135, 145 }, { 145, 147 }, { 147, 135 }, { 126, 133 }, { 133, 129 }, { 133, 134 }, { 134, 129 }, { 134, 118 }, { 130, 141 }, { 141, 126 }, { 181, 123 }, { 132, 113 }, { 137, 139 }, { 139, 124 }, { 132, 128 }, { 135, 127 }, { 134, 138 }, { 147, 127 }, { 139, 130 }, { 135, 143 }, { 143, 145 }, { 149, 130 }, { 139, 149 }, { 147, 125 }, { 150, 133 }, { 133, 142 }, { 142, 150 }, { 138, 135 }, { 141, 146 }, { 146, 126 }, { 138, 143 }, { 156, 172 }, { 140, 156 }, { 126, 142 }, { 123, 173 }, { 173, 137 }, { 148, 136 }, { 128, 148 }, { 125, 157 }, { 157, 131 }, { 150, 134 }, { 137, 149 }, { 150, 158 }, { 158, 134 }, { 162, 164 }, { 164, 136 }, { 136, 162 }, { 131, 144 }, { 146, 142 }, { 132, 148 }, { 177, 151 }, { 151, 170 }, { 170, 177 }, { 147, 157 }, { 158, 138 }, { 146, 152 }, { 152, 154 }, { 154, 146 }, { 149, 141 }, { 151, 145 }, { 143, 151 }, { 149, 160 }, { 160, 141 }, { 148, 162 }, { 153, 158 }, { 158, 170 }, { 170, 153 }, { 138, 153 }, { 153, 143 }, { 141, 152 }, { 151, 147 }, { 173, 149 }, { 157, 144 }, { 142, 154 }, { 154, 155 }, { 155, 142 }, { 153, 151 }, { 155, 150 }, { 160, 152 }, { 155, 163 }, { 163, 150 }, { 160, 174 }, { 174, 152 }, { 157, 161 }, { 161, 144 }, { 159, 148 }, { 161, 165 }, { 165, 168 }, { 168, 161 }, { 158, 166 }, { 166, 170 }, { 174, 154 }, { 159, 168 }, { 168, 148 }, { 164, 156 }, { 140, 164 }, { 163, 166 }, { 166, 150 }, { 154, 163 }, { 161, 159 }, { 173, 160 }, { 169, 188 }, { 188, 164 }, { 164, 169 }, { 157, 165 }, { 157, 167 }, { 167, 165 }, { 168, 162 }, { 175, 156 }, { 164, 175 }, { 147, 167 }, { 173, 178 }, { 178, 160 }, { 167, 185 }, { 185, 165 }, { 176, 154 }, { 174, 176 }, { 168, 187 }, { 187, 162 }, { 177, 147 }, { 162, 169 }, { 167, 177 }, { 177, 191 }, { 191, 167 }, { 171, 169 }, { 162, 171 }, { 183, 172 }, { 172, 175 }, { 175, 183 }, { 194, 169 }, { 171, 194 }, { 184, 166 }, { 163, 184 }, { 163, 176 }, { 176, 184 }, { 165, 179 }, { 179, 168 }, { 187, 171 }, { 179, 180 }, { 180, 195 }, { 195, 179 }, { 184, 189 }, { 189, 166 }, { 178, 192 }, { 192, 160 }, { 179, 187 }, { 192, 174 }, { 178, 181 }, { 181, 199 }, { 199, 178 }, { 181, 173 }, { 174, 184 }, { 197, 217 }, { 217, 183 }, { 183, 197 }, { 180, 182 }, { 182, 195 }, { 165, 180 }, { 189, 170 }, { 185, 180 }, { 285, 181 }, { 188, 175 }, { 183, 188 }, { 188, 197 }, { 185, 182 }, { 193, 170 }, { 189, 193 }, { 185, 186 }, { 186, 182 }, { 192, 203 }, { 203, 174 }, { 186, 222 }, { 222, 182 }, { 170, 190 }, { 190, 177 }, { 187, 196 }, { 196, 171 }, { 190, 191 }, { 190, 193 }, { 193, 201 }, { 201, 190 }, { 190, 205 }, { 205, 191 }, { 191, 185 }, { 185, 202 }, { 202, 186 }, { 212, 192 }, { 192, 199 }, { 199, 212 }, { 195, 187 }, { 206, 188 }, { 188, 194 }, { 194, 206 }, { 203, 224 }, { 224, 174 }, { 193, 200 }, { 200, 204 }, { 204, 193 }, { 206, 197 }, { 208, 217 }, { 197, 208 }, { 195, 196 }, { 184, 207 }, { 207, 189 }, { 196, 198 }, { 198, 171 }, { 189, 200 }, { 198, 194 }, { 202, 222 }, { 198, 216 }, { 216, 194 }, { 195, 215 }, { 215, 196 }, { 201, 205 }, { 191, 202 }, { 181, 209 }, { 209, 199 }, { 228, 202 }, { 202, 214 }, { 214, 228 }, { 203, 213 }, { 213, 224 }, { 202, 211 }, { 211, 214 }, { 206, 208 }, { 204, 201 }, { 220, 189 }, { 207, 220 }, { 204, 210 }, { 210, 234 }, { 234, 204 }, { 209, 212 }, { 205, 211 }, { 211, 191 }, { 189, 210 }, { 210, 200 }, { 201, 218 }, { 218, 205 }, { 217, 172 }, { 234, 201 }, { 222, 195 }, { 218, 211 }, { 203, 212 }, { 212, 213 }, { 181, 247 }, { 247, 209 }, { 215, 198 }, { 223, 172 }, { 217, 223 }, { 225, 184 }, { 184, 224 }, { 224, 225 }, { 220, 210 }, { 209, 236 }, { 236, 212 }, { 243, 215 }, { 215, 229 }, { 229, 243 }, { 236, 213 }, { 216, 206 }, { 218, 221 }, { 221, 211 }, { 216, 219 }, { 219, 206 }, { 221, 214 }, { 219, 208 }, { 232, 217 }, { 208, 232 }, { 195, 227 }, { 227, 215 }, { 226, 214 }, { 221, 226 }, { 219, 231 }, { 231, 232 }, { 232, 219 }, { 210, 233 }, { 233, 238 }, { 238, 210 }, { 220, 233 }, { 222, 227 }, { 228, 240 }, { 240, 249 }, { 249, 228 }, { 227, 229 }, { 207, 225 }, { 225, 220 }, { 215, 230 }, { 230, 198 }, { 239, 241 }, { 241, 225 }, { 225, 239 }, { 218, 226 }, { 226, 228 }, { 236, 224 }, { 228, 222 }, { 236, 252 }, { 252, 224 }, { 230, 216 }, { 235, 242 }, { 242, 246 }, { 246, 235 }, { 249, 222 }, { 234, 218 }, { 230, 231 }, { 231, 216 }, { 240, 218 }, { 234, 240 }, { 238, 240 }, { 234, 238 }, { 240, 226 }, { 232, 223 }, { 246, 232 }, { 232, 235 }, { 243, 230 }, { 230, 250 }, { 250, 231 }, { 237, 241 }, { 241, 245 }, { 245, 237 }, { 231, 235 }, { 220, 237 }, { 237, 233 }, { 245, 233 }, { 224, 239 }, { 231, 242 }, { 255, 222 }, { 249, 255 }, { 220, 241 }, { 247, 236 }, { 255, 227 }, { 246, 223 }, { 245, 253 }, { 253, 233 }, { 247, 248 }, { 248, 236 }, { 253, 259 }, { 259, 233 }, { 250, 242 }, { 243, 244 }, { 244, 230 }, { 251, 252 }, { 252, 248 }, { 248, 251 }, { 244, 250 }, { 256, 223 }, { 246, 256 }, { 255, 229 }, { 259, 238 }, { 229, 254 }, { 254, 243 }, { 242, 256 }, { 252, 258 }, { 258, 224 }, { 258, 239 }, { 229, 262 }, { 262, 254 }, { 254, 244 }, { 247, 251 }, { 250, 257 }, { 257, 242 }, { 258, 241 }, { 241, 260 }, { 260, 245 }, { 285, 247 }, { 267, 238 }, { 259, 267 }, { 253, 261 }, { 261, 259 }, { 267, 240 }, { 251, 263 }, { 263, 252 }, { 254, 257 }, { 257, 244 }, { 255, 262 }, { 258, 260 }, { 284, 255 }, { 255, 274 }, { 274, 284 }, { 257, 273 }, { 273, 242 }, { 266, 245 }, { 260, 266 }, { 240, 264 }, { 264, 249 }, { 285, 251 }, { 269, 281 }, { 281, 266 }, { 266, 269 }, { 249, 274 }, { 266, 253 }, { 263, 258 }, { 264, 274 }, { 267, 264 }, { 262, 276 }, { 276, 254 }, { 258, 265 }, { 265, 260 }, { 283, 265 }, { 265, 278 }, { 278, 283 }, { 263, 268 }, { 268, 258 }, { 264, 272 }, { 272, 275 }, { 275, 264 }, { 276, 257 }, { 266, 261 }, { 285, 263 }, { 281, 261 }, { 268, 271 }, { 271, 258 }, { 267, 272 }, { 277, 267 }, { 259, 277 }, { 260, 269 }, { 268, 270 }, { 270, 271 }, { 261, 277 }, { 280, 282 }, { 282, 273 }, { 273, 280 }, { 271, 265 }, { 263, 270 }, { 271, 278 }, { 279, 280 }, { 280, 276 }, { 276, 279 }, { 283, 260 }, { 270, 285 }, { 285, 286 }, { 286, 270 }, { 282, 242 }, { 275, 290 }, { 290, 274 }, { 274, 275 }, { 284, 262 }, { 292, 277 }, { 277, 289 }, { 289, 292 }, { 282, 256 }, { 280, 257 }, { 281, 277 }, { 281, 289 }, { 292, 267 }, { 283, 269 }, { 291, 295 }, { 295, 284 }, { 284, 291 }, { 284, 276 }, { 297, 270 }, { 286, 297 }, { 284, 279 }, { 292, 272 }, { 295, 279 }, { 292, 275 }, { 278, 288 }, { 288, 283 }, { 283, 293 }, { 293, 269 }, { 295, 280 }, { 271, 287 }, { 287, 278 }, { 270, 287 }, { 287, 288 }, { 288, 296 }, { 296, 283 }, { 293, 281 }, { 297, 287 }, { 290, 291 }, { 291, 274 }, { 282, 295 }, { 293, 289 }, { 291, 294 }, { 294, 299 }, { 299, 291 }, { 289, 298 }, { 298, 299 }, { 299, 289 }, { 275, 294 }, { 294, 290 }, { 292, 294 }, { 293, 298 }, { 287, 296 }, { 296, 293 }, { 295, 299 }, { 296, 297 }, { 297, 298 }, { 298, 296 }, { 299, 292 } }; testOnGraph(edges); } /** * Random not planar graph on 300 vertices */ @Test public void testPlanarity37() { int[][] edges = new int[][] { { 0, 133 }, { 1, 227 }, { 2, 88 }, { 3, 200 }, { 4, 26 }, { 5, 16 }, { 6, 157 }, { 7, 272 }, { 8, 97 }, { 9, 259 }, { 10, 276 }, { 11, 106 }, { 12, 239 }, { 13, 156 }, { 14, 18 }, { 15, 292 }, { 16, 9 }, { 17, 171 }, { 18, 152 }, { 19, 24 }, { 20, 37 }, { 21, 255 }, { 22, 162 }, { 23, 54 }, { 24, 254 }, { 25, 117 }, { 26, 273 }, { 27, 209 }, { 28, 81 }, { 29, 143 }, { 30, 219 }, { 31, 46 }, { 32, 285 }, { 33, 19 }, { 34, 263 }, { 35, 48 }, { 36, 225 }, { 37, 254 }, { 38, 138 }, { 39, 251 }, { 40, 132 }, { 41, 251 }, { 42, 36 }, { 43, 71 }, { 44, 250 }, { 45, 176 }, { 46, 285 }, { 47, 240 }, { 48, 235 }, { 49, 96 }, { 50, 243 }, { 51, 151 }, { 52, 139 }, { 53, 207 }, { 54, 84 }, { 55, 128 }, { 56, 296 }, { 57, 81 }, { 58, 60 }, { 59, 116 }, { 60, 206 }, { 61, 97 }, { 62, 207 }, { 63, 49 }, { 64, 263 }, { 65, 10 }, { 66, 88 }, { 67, 182 }, { 68, 204 }, { 69, 99 }, { 70, 30 }, { 71, 296 }, { 72, 39 }, { 73, 183 }, { 74, 236 }, { 75, 299 }, { 76, 200 }, { 77, 280 }, { 78, 220 }, { 79, 216 }, { 80, 252 }, { 81, 295 }, { 82, 85 }, { 83, 28 }, { 84, 72 }, { 85, 165 }, { 86, 281 }, { 87, 182 }, { 88, 250 }, { 89, 288 }, { 90, 174 }, { 91, 47 }, { 92, 36 }, { 93, 168 }, { 94, 153 }, { 95, 231 }, { 96, 274 }, { 97, 217 }, { 98, 77 }, { 99, 3 }, { 100, 123 }, { 101, 67 }, { 102, 178 }, { 103, 246 }, { 104, 127 }, { 105, 126 }, { 106, 278 }, { 107, 297 }, { 108, 23 }, { 109, 168 }, { 110, 129 }, { 111, 216 }, { 112, 190 }, { 113, 95 }, { 114, 129 }, { 115, 275 }, { 116, 260 }, { 117, 176 }, { 118, 74 }, { 119, 181 }, { 120, 227 }, { 121, 190 }, { 122, 224 }, { 123, 109 }, { 124, 101 }, { 125, 113 }, { 126, 259 }, { 127, 279 }, { 128, 169 }, { 129, 156 }, { 130, 159 }, { 131, 245 }, { 132, 34 }, { 133, 34 }, { 134, 132 }, { 135, 95 }, { 136, 225 }, { 137, 189 }, { 138, 1 }, { 139, 56 }, { 140, 195 }, { 141, 219 }, { 142, 269 }, { 143, 108 }, { 144, 150 }, { 145, 202 }, { 146, 257 }, { 147, 268 }, { 148, 151 }, { 149, 227 }, { 150, 106 }, { 151, 86 }, { 152, 126 }, { 153, 128 }, { 154, 226 }, { 155, 278 }, { 156, 122 }, { 157, 256 }, { 158, 170 }, { 159, 232 }, { 160, 263 }, { 161, 184 }, { 162, 38 }, { 163, 287 }, { 164, 273 }, { 165, 51 }, { 166, 116 }, { 167, 195 }, { 168, 139 }, { 169, 294 }, { 170, 111 }, { 171, 53 }, { 172, 261 }, { 173, 6 }, { 174, 220 }, { 175, 237 }, { 176, 137 }, { 177, 263 }, { 178, 24 }, { 179, 188 }, { 180, 215 }, { 181, 51 }, { 182, 286 }, { 183, 227 }, { 184, 200 }, { 185, 205 }, { 186, 85 }, { 187, 247 }, { 188, 274 }, { 189, 60 }, { 190, 149 }, { 191, 228 }, { 192, 266 }, { 193, 115 }, { 194, 150 }, { 195, 162 }, { 196, 296 }, { 197, 147 }, { 198, 209 }, { 199, 187 }, { 200, 70 }, { 201, 207 }, { 202, 35 }, { 203, 155 }, { 204, 296 }, { 205, 12 }, { 206, 33 }, { 207, 293 }, { 208, 196 }, { 209, 167 }, { 210, 166 }, { 211, 267 }, { 212, 10 }, { 213, 224 }, { 214, 245 }, { 215, 245 }, { 216, 125 }, { 217, 32 }, { 218, 55 }, { 219, 291 }, { 220, 157 }, { 221, 90 }, { 222, 77 }, { 223, 185 }, { 224, 84 }, { 225, 183 }, { 226, 23 }, { 227, 185 }, { 228, 123 }, { 229, 57 }, { 230, 211 }, { 231, 1 }, { 232, 114 }, { 233, 262 }, { 234, 129 }, { 235, 14 }, { 236, 296 }, { 237, 251 }, { 238, 205 }, { 239, 172 }, { 240, 106 }, { 241, 148 }, { 242, 183 }, { 243, 284 }, { 244, 299 }, { 245, 177 }, { 246, 196 }, { 247, 52 }, { 248, 25 }, { 249, 264 }, { 250, 157 }, { 251, 90 }, { 252, 71 }, { 253, 293 }, { 254, 65 }, { 255, 101 }, { 256, 286 }, { 257, 115 }, { 258, 65 }, { 259, 225 }, { 260, 135 }, { 261, 122 }, { 262, 131 }, { 263, 94 }, { 264, 247 }, { 265, 26 }, { 266, 53 }, { 267, 295 }, { 268, 142 }, { 269, 0 }, { 270, 7 }, { 271, 100 }, { 272, 273 }, { 273, 135 }, { 274, 179 }, { 275, 239 }, { 276, 271 }, { 277, 11 }, { 278, 130 }, { 279, 180 }, { 280, 52 }, { 281, 22 }, { 282, 172 }, { 283, 165 }, { 284, 17 }, { 285, 147 }, { 286, 102 }, { 287, 146 }, { 288, 92 }, { 289, 231 }, { 290, 96 }, { 291, 280 }, { 292, 191 }, { 293, 142 }, { 294, 125 }, { 295, 9 }, { 296, 172 }, { 297, 52 }, { 298, 263 }, { 299, 169 }, { 0, 236 }, { 1, 290 }, { 2, 4 }, { 3, 168 }, { 4, 63 }, { 5, 201 }, { 6, 86 }, { 7, 176 }, { 8, 81 }, { 9, 102 }, { 10, 28 }, { 11, 101 }, { 12, 209 }, { 13, 255 }, { 14, 170 }, { 15, 36 }, { 16, 222 }, { 17, 77 }, { 18, 36 }, { 19, 17 }, { 20, 14 }, { 21, 79 }, { 22, 240 }, { 23, 245 }, { 24, 12 }, { 25, 208 }, { 26, 108 }, { 27, 4 }, { 28, 238 }, { 29, 26 } }; testOnGraph(edges); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/000077500000000000000000000000001402514743400263275ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/AlphaCentralityTest.java000066400000000000000000000265471402514743400331340ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit tests for AlphaCentrality * * @author Dimitrios Michail * @author Pratik Tibrewal */ public class AlphaCentralityTest { @Test public void testGraph2Nodes() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addEdge("1", "2"); g.addEdge("2", "1"); VertexScoringAlgorithm pr = new AlphaCentrality<>(g); assertEquals(pr.getVertexScore("1"), pr.getVertexScore("2"), 0.0001); } @Test public void testGraph3Nodes() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addEdge("1", "2"); g.addEdge("2", "3"); g.addEdge("3", "1"); VertexScoringAlgorithm pr = new AlphaCentrality<>(g); assertEquals(pr.getVertexScore("1"), pr.getVertexScore("2"), 0.0001); assertEquals(pr.getVertexScore("1"), pr.getVertexScore("3"), 0.0001); } @Test public void testGraph1() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("A", "E"); g.addEdge("A", "F"); g.addEdge("B", "E"); g.addEdge("B", "F"); g.addEdge("C", "E"); g.addEdge("D", "E"); g.addEdge("1", "E"); g.addEdge("1", "F"); g.addEdge("E", "1"); g.addEdge("2", "E"); g.addEdge("2", "F"); g.addEdge("3", "F"); g.addEdge("F", "3"); g.addEdge("4", "F"); g.addEdge("E", "4"); g.addEdge("4", "5"); g.addEdge("E", "F"); VertexScoringAlgorithm pr = new AlphaCentrality<>(g, 0.85); assertEquals(pr.getVertexScore("A"), 1.0000, 0.5); assertEquals(pr.getVertexScore("B"), 1.0000, 0.5); assertEquals(pr.getVertexScore("C"), 1.0000, 0.5); assertEquals(pr.getVertexScore("D"), 1.0000, 0.5); assertEquals(pr.getVertexScore("E"), 22.000, 0.5); assertEquals(pr.getVertexScore("F"), 204.00, 0.5); assertEquals(pr.getVertexScore("1"), 20.000, 0.5); assertEquals(pr.getVertexScore("2"), 1.0000, 0.5); assertEquals(pr.getVertexScore("3"), 174.00, 0.5); assertEquals(pr.getVertexScore("4"), 20.000, 0.5); assertEquals(pr.getVertexScore("5"), 18.000, 0.5); } @Test public void testGraph2() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("A", "E"); g.addEdge("A", "F"); g.addEdge("B", "E"); g.addEdge("B", "F"); g.addEdge("C", "E"); g.addEdge("D", "E"); g.addEdge("1", "E"); g.addEdge("1", "F"); g.addEdge("E", "1"); g.addEdge("2", "E"); g.addEdge("2", "F"); g.addEdge("3", "F"); g.addEdge("F", "3"); g.addEdge("4", "F"); g.addEdge("E", "4"); g.addEdge("4", "5"); g.addEdge("E", "F"); VertexScoringAlgorithm pr = new AlphaCentrality<>(g, 0.15); assertEquals(pr.getVertexScore("A"), 1.0000, 0.05); assertEquals(pr.getVertexScore("B"), 1.0000, 0.05); assertEquals(pr.getVertexScore("C"), 1.0000, 0.05); assertEquals(pr.getVertexScore("D"), 1.0000, 0.05); assertEquals(pr.getVertexScore("E"), 1.9400, 0.05); assertEquals(pr.getVertexScore("F"), 2.3300, 0.05); assertEquals(pr.getVertexScore("1"), 1.2900, 0.05); assertEquals(pr.getVertexScore("2"), 1.0000, 0.05); assertEquals(pr.getVertexScore("3"), 1.3500, 0.05); assertEquals(pr.getVertexScore("4"), 1.2900, 0.05); assertEquals(pr.getVertexScore("5"), 1.1900, 0.05); } @Test public void testGraph3() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("A", "E"); g.addEdge("A", "F"); g.addEdge("B", "E"); g.addEdge("B", "F"); g.addEdge("C", "E"); g.addEdge("D", "E"); g.addEdge("1", "E"); g.addEdge("1", "F"); g.addEdge("E", "1"); g.addEdge("2", "E"); g.addEdge("2", "F"); g.addEdge("3", "F"); g.addEdge("F", "3"); g.addEdge("4", "F"); g.addEdge("E", "4"); g.addEdge("4", "5"); g.addEdge("E", "F"); Map exogenousfactormap = new HashMap<>(); for (String v : g.vertexSet()) { exogenousfactormap.put(v, 1.0); } exogenousfactormap.put("4", 2.0); ToDoubleFunction exogenousFactorFunction = (v) -> exogenousfactormap.get(v); VertexScoringAlgorithm pr = new AlphaCentrality<>(g, 0.15, exogenousFactorFunction); assertEquals(pr.getVertexScore("A"), 1.0000, 0.005); assertEquals(pr.getVertexScore("B"), 1.0000, 0.005); assertEquals(pr.getVertexScore("C"), 1.0000, 0.005); assertEquals(pr.getVertexScore("D"), 1.0000, 0.005); assertEquals(pr.getVertexScore("E"), 1.9400, 0.005); assertEquals(pr.getVertexScore("F"), 2.4800, 0.005); assertEquals(pr.getVertexScore("1"), 1.2900, 0.005); assertEquals(pr.getVertexScore("2"), 1.0000, 0.005); assertEquals(pr.getVertexScore("3"), 1.3700, 0.005); assertEquals(pr.getVertexScore("4"), 2.2900, 0.005); assertEquals(pr.getVertexScore("5"), 1.3400, 0.005); } @Test public void testWeightedGraph1() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.setEdgeWeight(g.addEdge("center", "a"), 75.0); g.setEdgeWeight(g.addEdge("center", "b"), 20.0); g.setEdgeWeight(g.addEdge("center", "c"), 5.0); VertexScoringAlgorithm pr = new AlphaCentrality<>(g, 0.85, 1.0, 100, 0.0001); assertEquals(pr.getVertexScore("center"), 1.0000, 0.0001); assertEquals(pr.getVertexScore("a"), 64.7500, 0.0001); assertEquals(pr.getVertexScore("b"), 18.0000, 0.0001); assertEquals(pr.getVertexScore("c"), 5.2500, 0.0001); } @Test public void testweightedGraph2() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.setEdgeWeight(g.addEdge("center", "a"), 1.0); g.setEdgeWeight(g.addEdge("center", "b"), 1.0); g.setEdgeWeight(g.addEdge("center", "c"), 1.0); VertexScoringAlgorithm pr = new AlphaCentrality<>(g, 0.85, 1.0, 100, 0.0001); assertEquals(pr.getVertexScore("center"), 1.0000, 0.0001); assertEquals(pr.getVertexScore("a"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("b"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("c"), 1.8500, 0.0001); } @Test public void testUnweightedGraph2() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addEdge("center", "a"); g.addEdge("center", "b"); g.addEdge("center", "c"); VertexScoringAlgorithm pr = new AlphaCentrality<>(g, 0.85, 1.0, 100, 0.0001); assertEquals(pr.getVertexScore("center"), 1.0000, 0.0001); assertEquals(pr.getVertexScore("a"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("b"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("c"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("d"), 1.0000, 0.0001); } @Test public void testEmptyGraph() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); VertexScoringAlgorithm pr = new AlphaCentrality<>(g, 0.85, 1.0, 100, 0.0001); assertTrue(pr.getScores().isEmpty()); } @Test(expected = IllegalArgumentException.class) public void testNonExistantVertex() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addEdge("center", "a"); g.addEdge("center", "b"); g.addEdge("center", "c"); VertexScoringAlgorithm pr = new AlphaCentrality<>(g, 0.85, 1.0, 100, 0.0001); pr.getVertexScore("unknown"); } @Test(expected = IllegalArgumentException.class) public void testBadParameters1() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); new AlphaCentrality<>(g, 1.25, 1.0, 100, 0.0001); } @Test(expected = IllegalArgumentException.class) public void testBadParameters2() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); new AlphaCentrality<>(g, 0.85, 1.0, 0, 0.0001); } @Test(expected = IllegalArgumentException.class) public void testBadParameters3() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); new AlphaCentrality<>(g, 0.85, 1.0, 100, 0.0); } } BetweennessCentralityTest.java000066400000000000000000000430271402514743400343020ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/* * (C) Copyright 2017-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.scoring.BetweennessCentrality.OverflowStrategy; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.experimental.categories.Category; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class BetweennessCentralityTest { @Test(expected = NullPointerException.class) public void testNullGraph() { Graph g = null; VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); bc.getScores(); } @Test public void testEmptyGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertTrue(scores.isEmpty()); } @Test public void testEmptyGraphNormalized() { Graph g = new SimpleGraph<>(DefaultEdge.class); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g, true); Map scores = bc.getScores(); assertTrue(scores.isEmpty()); } @Test public void testSingletonGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(0); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertEquals(0.0, scores.get(0), 0.0); } @Test public void testSingletonGraphNormalized() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(0); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g, true); Map scores = bc.getScores(); assertEquals(0.0, scores.get(0), 0.0); } @Test public void testK2Graph() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(0); g.addVertex(1); g.addEdge(0, 1); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertEquals(0.0, scores.get(0), 0.0); assertEquals(0.0, scores.get(1), 0.0); } @Test public void testK2GraphNormalized() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(0); g.addVertex(1); g.addEdge(0, 1); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g, true); Map scores = bc.getScores(); assertEquals(0.0, scores.get(0), 0.0); assertEquals(0.0, scores.get(1), 0.0); } @Test public void testUnweighted1() { Graph g = createUnweighted1(); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertGraph1(scores); } @Test public void testAsWeighted1() { Graph g = new AsWeightedGraph<>(createUnweighted1(), new HashMap<>()); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertGraph1(scores); } @Test public void testNormalization() { Graph g = new AsWeightedGraph<>(createUnweighted1(), new HashMap<>()); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g, true); Map scores = new HashMap<>(bc.getScores()); int n = g.vertexSet().size(); scores.forEach((v, score) -> scores.put(v, score * ((n - 1) * (n - 2)))); assertGraph1(scores); } @Test public void testUnweighted2() { Graph g = createUnweighted2(); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertGraph2(scores); } @Test public void testUnweighted3() { Graph g = createUnweighted3(); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertGraph3(scores); } @Test public void testUnweighted4() { Graph g = createUnweighted4(); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertGraph4(scores); } @Test public void testWeighted5() { Graph g = createWeighted5(); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertGraph5(scores); } @Test public void testWeighted6() { Graph g = createWeighted6(); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); Map scores = bc.getScores(); assertGraph6(scores); } @Test public void testStar() { testStar(5); testStar(12); } private void testStar(int order) { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator generator = new StarGraphGenerator<>(order); Map resultMap = new HashMap<>(); generator.generateGraph(g, resultMap); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); assertStar(bc.getScores(), resultMap.get(StarGraphGenerator.CENTER_VERTEX), order); } private void assertStar(Map scores, Integer center, int order) { for (Integer v : scores.keySet()) { if (v.equals(center)) { assertEquals((order - 2) * (order - 1) / 2, scores.get(v), 0.0); } else { assertEquals(0.0, scores.get(v), 0.0); } } } @Test public void testLinear() { testLinear(5); testLinear(12); testLinear(37); } private void testLinear(int order) { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator generator = new LinearGraphGenerator<>(order); Map resultMap = new HashMap<>(); generator.generateGraph(g, resultMap); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); if (order == 5) { assertLinear5(bc.getScores()); } else { assertLinear(bc.getScores(), order); } } private void assertLinear5(Map scores) { for (Integer v : scores.keySet()) { if (v.equals(0) || v.equals(4)) { assertEquals(0.0, scores.get(v), 0.0); } else if (v.equals(1) || v.equals(3)) { assertEquals(3.0, scores.get(v), 0.0); } else if (v.equals(2)) { assertEquals(4.0, scores.get(v), 0.0); } else { throw new IllegalArgumentException("Unexpected vertex " + v); } } } private void assertLinear(Map scores, int order) { for (int i = 0; i < order / 2; i++) { assertEquals(scores.get(i), scores.get(order - i - 1), 0.0); } } @Test public void testRing() { testRing(5); testRing(12); testRing(37); } private void testRing(int order) { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator generator = new RingGraphGenerator<>(order); Map resultMap = new HashMap<>(); generator.generateGraph(g, resultMap); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g); if (order == 5) { assertRing5(bc.getScores()); } else { assertRing(bc.getScores(), order); } } private void assertRing5(Map scores) { for (Integer v : scores.keySet()) { assertEquals(1.0, scores.get(v), 0.0); } } private void assertRing(Map scores, int order) { for (int i = 0; i < order - 1; i++) { assertEquals(scores.get(i), scores.get(i + 1), 0.0); } } private void assertGraph3(Map scores) { assertEquals(0.0, scores.get(1), 0.0); assertEquals(1.5, scores.get(2), 0.0); assertEquals(1.0, scores.get(3), 0.0); assertEquals(4.5, scores.get(4), 0.0); assertEquals(3.0, scores.get(5), 0.0); assertEquals(0.0, scores.get(6), 0.0); } private void assertGraph4(Map scores) { assertEquals(0.0, scores.get(1), 0.0); assertEquals(3.5, scores.get(2), 0.0); assertEquals(1.0, scores.get(3), 0.0); assertEquals(1.0, scores.get(4), 0.0); assertEquals(0.5, scores.get(5), 0.0); } private void assertGraph5(Map scores) { assertEquals(0.0, scores.get("A"), 0.0); assertEquals(3.0, scores.get("B"), 0.0); assertEquals(6.0, scores.get("C"), 0.0); assertEquals(10.0, scores.get("D"), 0.0); assertEquals(5.0, scores.get("E"), 0.0); assertEquals(5.0, scores.get("F"), 0.0); assertEquals(1.0, scores.get("G"), 0.0); } private void assertGraph1(Map scores) { assertEquals(3.0, scores.get(1), 0.0); assertEquals(0.0, scores.get(2), 0.0); assertEquals(3.0, scores.get(3), 0.0); assertEquals(15.0, scores.get(4), 0.0); assertEquals(6.0, scores.get(5), 0.0); assertEquals(6.0, scores.get(6), 0.0); assertEquals(7.0, scores.get(7), 0.0); assertEquals(0.0, scores.get(8), 0.0); assertEquals(0.0, scores.get(9), 0.0); } private void assertGraph2(Map scores) { assertEquals(43.0, scores.get(0), 0.0); assertEquals(25.0, scores.get(1), 0.0); assertEquals(70.0, scores.get(2), 0.0); assertEquals(40.0, scores.get(3), 0.0); assertEquals(13.0, scores.get(4), 0.0); assertEquals(0.0, scores.get(5), 0.0); assertEquals(0.0, scores.get(6), 0.0); assertEquals(36.0, scores.get(7), 0.0); assertEquals(0.0, scores.get(8), 0.0); assertEquals(0.0, scores.get(9), 0.0); assertEquals(0.0, scores.get(10), 0.0); assertEquals(0.0, scores.get(11), 0.0); assertEquals(0.0, scores.get(12), 0.0); assertEquals(0.0, scores.get(13), 0.0); assertEquals(0.0, scores.get(14), 0.0); } private void assertGraph6(Map scores) { assertEquals(0.0, scores.get(0), 0.0); assertEquals(1.0, scores.get(1), 0.0); assertEquals(0.0, scores.get(2), 0.0); } private Graph createUnweighted1() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addVertex(6); g.addVertex(7); g.addVertex(8); g.addVertex(9); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(1, 4); g.addEdge(2, 3); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(4, 6); g.addEdge(5, 6); g.addEdge(5, 7); g.addEdge(5, 8); g.addEdge(6, 7); g.addEdge(6, 8); g.addEdge(7, 8); g.addEdge(7, 9); return g; } private Graph createUnweighted2() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(0); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addVertex(6); g.addVertex(7); g.addVertex(8); g.addVertex(9); g.addVertex(10); g.addVertex(11); g.addVertex(12); g.addVertex(13); g.addVertex(14); g.addEdge(0, 1); g.addEdge(0, 2); g.addEdge(0, 5); g.addEdge(1, 6); g.addEdge(1, 9); g.addEdge(2, 3); g.addEdge(2, 4); g.addEdge(2, 10); g.addEdge(2, 14); g.addEdge(3, 7); g.addEdge(4, 11); g.addEdge(7, 8); g.addEdge(7, 12); g.addEdge(7, 13); return g; } private Graph createUnweighted3() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addVertex(6); g.addEdge(1, 2); g.addEdge(1, 5); g.addEdge(2, 3); g.addEdge(2, 5); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(4, 6); return g; } private Graph createUnweighted4() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(2, 3); g.addEdge(2, 4); g.addEdge(3, 5); g.addEdge(4, 5); return g; } private Graph createWeighted5() { Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("G"); DefaultWeightedEdge e; e = g.addEdge("A", "B"); g.setEdgeWeight(e, 0.7); e = g.addEdge("A", "D"); g.setEdgeWeight(e, 0.3); e = g.addEdge("B", "C"); g.setEdgeWeight(e, 0.9); e = g.addEdge("C", "A"); g.setEdgeWeight(e, 1.3); e = g.addEdge("C", "D"); g.setEdgeWeight(e, 0.57); e = g.addEdge("D", "B"); g.setEdgeWeight(e, 1.0); e = g.addEdge("D", "E"); g.setEdgeWeight(e, 0.8); e = g.addEdge("D", "F"); g.setEdgeWeight(e, 0.2); e = g.addEdge("E", "G"); g.setEdgeWeight(e, 0.4); e = g.addEdge("F", "E"); g.setEdgeWeight(e, 0.6); e = g.addEdge("G", "F"); g.setEdgeWeight(e, 0.2); return g; } private Graph createWeighted6() { Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(0); g.addVertex(1); g.addVertex(2); DefaultWeightedEdge e; e = g.addEdge(2, 1); g.setEdgeWeight(e, 1); e = g.addEdge(1, 0); g.setEdgeWeight(e, 1); e = g.addEdge(2, 0); g.setEdgeWeight(e, 49); return g; } @Test(expected = ArithmeticException.class) public void testOverflow() { final Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); for (int i = 0; i < 3300; i++) g.addVertex(i); for (int i = 0; i < 3290; i++) for (int j = 0; j < 10; j++) g.addEdge(i, i - i % 10 + 10 + j); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g, false, OverflowStrategy.THROW_EXCEPTION_ON_OVERFLOW); bc.getScores(); } @Test @Category(SlowTests.class) public void testIgnoreOverflow() { final Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); for (int i = 0; i < 3300; i++) g.addVertex(i); for (int i = 0; i < 3290; i++) for (int j = 0; j < 10; j++) g.addEdge(i, i - i % 10 + 10 + j); VertexScoringAlgorithm bc = new BetweennessCentrality<>(g, false, OverflowStrategy.IGNORE_OVERFLOW); Map scores = bc.getScores(); assertEquals(scores.get(9), 0d, 1e-9); assertEquals(scores.get(10), Double.NaN, 1e-9); assertEquals(scores.get(3289), Double.NaN, 1e-9); assertEquals(scores.get(3290), 0d, 1e-9); } } ClosenessCentralityTest.java000066400000000000000000000141701402514743400337530ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import static org.junit.Assert.assertEquals; /** * Unit tests for closeness centrality. * * @author Dimitrios Michail */ public class ClosenessCentralityTest { @Test public void testOutgoing() { Graph g = createInstance1(); VertexScoringAlgorithm pr = new ClosenessCentrality<>(g, false, true); assertEquals(4d / 7, pr.getVertexScore("1"), 1e-9); assertEquals(4d / 9, pr.getVertexScore("2"), 1e-9); assertEquals(4d / 8, pr.getVertexScore("3"), 1e-9); assertEquals(4d / 6, pr.getVertexScore("4"), 1e-9); assertEquals(4d / 10, pr.getVertexScore("5"), 1e-9); } @Test public void testIncoming() { Graph g = createInstance1(); VertexScoringAlgorithm pr = new ClosenessCentrality<>(g, true, true); assertEquals(4d / 9, pr.getVertexScore("1"), 1e-9); assertEquals(4d / 10, pr.getVertexScore("2"), 1e-9); assertEquals(4d / 5, pr.getVertexScore("3"), 1e-9); assertEquals(4d / 7, pr.getVertexScore("4"), 1e-9); assertEquals(4d / 9, pr.getVertexScore("5"), 1e-9); } @Test public void testIncomingNoNormalization() { Graph g = createInstance1(); VertexScoringAlgorithm pr = new ClosenessCentrality<>(g, true, false); assertEquals(1d / 9, pr.getVertexScore("1"), 1e-9); assertEquals(1d / 10, pr.getVertexScore("2"), 1e-9); assertEquals(1d / 5, pr.getVertexScore("3"), 1e-9); assertEquals(1d / 7, pr.getVertexScore("4"), 1e-9); assertEquals(1d / 9, pr.getVertexScore("5"), 1e-9); } @Test public void testUndirected() { Graph g = new AsUndirectedGraph<>(createInstance1()); VertexScoringAlgorithm pr1 = new ClosenessCentrality<>(g, true, true); VertexScoringAlgorithm pr2 = new ClosenessCentrality<>(g, false, true); assertEquals(4d / 5, pr1.getVertexScore("1"), 1e-9); assertEquals(4d / 5, pr2.getVertexScore("1"), 1e-9); assertEquals(4d / 6, pr1.getVertexScore("2"), 1e-9); assertEquals(4d / 6, pr2.getVertexScore("2"), 1e-9); assertEquals(4d / 4, pr1.getVertexScore("3"), 1e-9); assertEquals(4d / 4, pr2.getVertexScore("3"), 1e-9); assertEquals(4d / 5, pr1.getVertexScore("4"), 1e-9); assertEquals(4d / 5, pr2.getVertexScore("4"), 1e-9); assertEquals(4d / 6, pr1.getVertexScore("5"), 1e-9); assertEquals(4d / 6, pr2.getVertexScore("5"), 1e-9); } @Test public void testNegativeWeights() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("1", "2"); DefaultWeightedEdge e13 = g.addEdge("1", "3"); g.addEdge("2", "3"); g.addEdge("3", "4"); g.addEdge("4", "1"); g.addEdge("4", "5"); g.addEdge("5", "3"); g.setEdgeWeight(e13, -1d); VertexScoringAlgorithm pr = new ClosenessCentrality<>(g, false, true); assertEquals(4d / 1, pr.getVertexScore("1"), 1e-9); assertEquals(4d / 9, pr.getVertexScore("2"), 1e-9); assertEquals(4d / 8, pr.getVertexScore("3"), 1e-9); assertEquals(4d / 4, pr.getVertexScore("4"), 1e-9); assertEquals(4d / 10, pr.getVertexScore("5"), 1e-9); } @Test public void testDisconnectedOutgoing() { Graph g = createInstance1(); g.addVertex("6"); VertexScoringAlgorithm pr = new ClosenessCentrality<>(g, false, true); assertEquals(0d, pr.getVertexScore("1"), 1e-9); assertEquals(0d, pr.getVertexScore("2"), 1e-9); assertEquals(0d, pr.getVertexScore("3"), 1e-9); assertEquals(0d, pr.getVertexScore("4"), 1e-9); assertEquals(0d, pr.getVertexScore("5"), 1e-9); assertEquals(0d, pr.getVertexScore("6"), 1e-9); } @Test public void testSingletonWithNormalize() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); VertexScoringAlgorithm pr = new ClosenessCentrality<>(g, false, true); assertEquals(Double.NaN, pr.getVertexScore("1"), 1e-9); } @Test public void testSingletonWithoutNormalize() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); VertexScoringAlgorithm pr = new ClosenessCentrality<>(g, false, false); assertEquals(Double.POSITIVE_INFINITY, pr.getVertexScore("1"), 1e-9); } private Graph createInstance1() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("1", "2"); g.addEdge("1", "3"); g.addEdge("2", "3"); g.addEdge("3", "4"); g.addEdge("4", "1"); g.addEdge("4", "5"); g.addEdge("5", "3"); return g; } } ClusteringCoefficientTest.java000066400000000000000000000300261402514743400342320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; /** * Tests for {@link ClusteringCoefficient} * * @author Alexandru Valeanu */ public class ClusteringCoefficientTest { @Test public void testUndirectedClusteringCoefficient() { Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int i = 1; i <= 8; i++) { graph.addVertex(i); } graph.addEdge(1, 2); graph.addEdge(1, 3); graph.addEdge(2, 3); graph.addEdge(2, 4); graph.addEdge(3, 4); graph.addEdge(4, 5); graph.addEdge(4, 6); graph.addEdge(5, 7); graph.addEdge(6, 7); graph.addEdge(2, 8); graph.addEdge(8, 5); ClusteringCoefficient clusteringCoefficient = new ClusteringCoefficient<>(graph); assertEquals(1, clusteringCoefficient.getVertexScore(1), 0.0); assertEquals(0.333333333, clusteringCoefficient.getVertexScore(2), 0.0001); assertEquals(0.666666666, clusteringCoefficient.getVertexScore(3), 0.0001); assertEquals(0.166666666, clusteringCoefficient.getVertexScore(4), 0.0001); assertEquals(0, clusteringCoefficient.getVertexScore(5), 0.0); assertEquals(0, clusteringCoefficient.getVertexScore(6), 0.0); assertEquals(0, clusteringCoefficient.getVertexScore(7), 0.0); assertEquals(0, clusteringCoefficient.getVertexScore(8), 0.0); } @Test public void testUndirected2ClusteringCoefficient() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addVertex("D"); graph.addEdge("A", "B"); graph.addEdge("A", "C"); graph.addEdge("A", "D"); graph.addEdge("B", "C"); ClusteringCoefficient clusteringCoefficient = new ClusteringCoefficient<>(graph); assertEquals(1.0 / 3.0, clusteringCoefficient.getVertexScore("A"), 0.001); } @Test public void testOneNodeClusteringCoefficient() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex("A"); ClusteringCoefficient clusteringCoefficient = new ClusteringCoefficient<>(graph); assertEquals(0, clusteringCoefficient.getAverageClusteringCoefficient(), 0.0); } @Test public void testTwoConectedNodesClusteringCoefficient() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addEdge("A", "B"); ClusteringCoefficient clusteringCoefficient = new ClusteringCoefficient<>(graph); assertEquals(0, clusteringCoefficient.getAverageClusteringCoefficient(), 0.0); } @Test(expected = NullPointerException.class) public void testNullGraphClusteringCoefficient() { ClusteringCoefficient clusteringCoefficient = new ClusteringCoefficient<>(null); } @Test public void testCompleteGraphClusteringCoefficient() { Graph graph = new SimpleGraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator completeGraphGenerator = new CompleteGraphGenerator<>(100); completeGraphGenerator.generateGraph(graph); ClusteringCoefficient clusteringCoefficient = new ClusteringCoefficient<>(graph); assertEquals(1, clusteringCoefficient.getAverageClusteringCoefficient(), 0.0); } @Test public void testStarGraphClusteringCoefficient() { Graph graph = new SimpleGraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); StarGraphGenerator starGraphGenerator = new StarGraphGenerator<>(100); starGraphGenerator.generateGraph(graph); ClusteringCoefficient clusteringCoefficient = new ClusteringCoefficient<>(graph); assertEquals(0, clusteringCoefficient.getAverageClusteringCoefficient(), 0.0); } @Test public void testTriangleDirectedGraphClusteringCoefficient() { Graph directedGraph = new SimpleDirectedGraph<>(DefaultEdge.class); String node1 = "0"; String node2 = "1"; String node3 = "2"; directedGraph.addVertex(node1); directedGraph.addVertex(node2); directedGraph.addVertex(node3); directedGraph.addEdge(node1, node2); directedGraph.addEdge(node2, node1); directedGraph.addEdge(node2, node3); directedGraph.addEdge(node3, node2); directedGraph.addEdge(node3, node1); directedGraph.addEdge(node1, node3); assertEquals( 1, new ClusteringCoefficient<>(directedGraph).getAverageClusteringCoefficient(), 0.0); } @Test public void testSpecial1DirectedGraphClusteringCoefficient() { Graph directedGraph = new SimpleDirectedGraph<>(DefaultEdge.class); String node1 = "0"; String node2 = "1"; String node3 = "2"; String node4 = "3"; directedGraph.addVertex(node1); directedGraph.addVertex(node2); directedGraph.addVertex(node3); directedGraph.addVertex(node4); directedGraph.addEdge(node1, node2); directedGraph.addEdge(node2, node3); directedGraph.addEdge(node2, node4); directedGraph.addEdge(node3, node1); directedGraph.addEdge(node3, node4); directedGraph.addEdge(node4, node1); assertEquals( 0.5, new ClusteringCoefficient<>(directedGraph).getAverageClusteringCoefficient(), 0.0); } @Test public void testSpecial2DirectedGraphClusteringCoefficient() { Graph directedGraph = new SimpleDirectedGraph<>(DefaultEdge.class); String node1 = "0"; String node2 = "1"; String node3 = "2"; String node4 = "3"; directedGraph.addVertex(node1); directedGraph.addVertex(node2); directedGraph.addVertex(node3); directedGraph.addVertex(node4); directedGraph.addEdge(node2, node1); directedGraph.addEdge(node2, node4); directedGraph.addEdge(node3, node1); directedGraph.addEdge(node3, node2); directedGraph.addEdge(node4, node3); assertEquals( 0.4167, new ClusteringCoefficient<>(directedGraph).getAverageClusteringCoefficient(), 0.01); } @Test public void testTriangleNonCompleteDirectedGraphClusteringCoefficient() { Graph directedGraph = new SimpleDirectedGraph<>(DefaultEdge.class); String node1 = "0"; String node2 = "1"; String node3 = "2"; directedGraph.addVertex(node1); directedGraph.addVertex(node2); directedGraph.addVertex(node3); directedGraph.addEdge(node1, node2); directedGraph.addEdge(node2, node1); directedGraph.addEdge(node2, node3); directedGraph.addEdge(node3, node2); directedGraph.addEdge(node1, node3); ClusteringCoefficient clusteringCoefficient = new ClusteringCoefficient<>(directedGraph); assertEquals(0.833, clusteringCoefficient.getAverageClusteringCoefficient(), 0.01); } @Test public void testTriangleGraphClusteringCoefficient() { Graph graph = new SimpleGraph<>(DefaultEdge.class); String node1 = "0"; String node2 = "1"; String node3 = "2"; graph.addVertex(node1); graph.addVertex(node2); graph.addVertex(node3); graph.addEdge(node1, node2); graph.addEdge(node2, node3); graph.addEdge(node3, node1); assertEquals(1, new ClusteringCoefficient<>(graph).getAverageClusteringCoefficient(), 0.0); } @Test public void testSpecial1UndirectedGraphClusteringCoefficient() { Graph graph = new SimpleGraph<>(DefaultEdge.class); String node1 = "0"; String node2 = "1"; String node3 = "2"; String node4 = "3"; String node5 = "4"; String node6 = "5"; String node7 = "6"; graph.addVertex(node1); graph.addVertex(node2); graph.addVertex(node3); graph.addVertex(node4); graph.addVertex(node5); graph.addVertex(node6); graph.addVertex(node7); graph.addEdge(node1, node2); graph.addEdge(node1, node3); graph.addEdge(node1, node4); graph.addEdge(node1, node5); graph.addEdge(node1, node6); graph.addEdge(node1, node7); graph.addEdge(node2, node3); graph.addEdge(node3, node4); graph.addEdge(node4, node5); graph.addEdge(node5, node6); graph.addEdge(node6, node7); graph.addEdge(node7, node2); ClusteringCoefficient clusteringCoefficient = new ClusteringCoefficient<>(graph); assertEquals(0.4, clusteringCoefficient.getVertexScore(node1), 0.0); assertEquals(0.667, clusteringCoefficient.getVertexScore(node3), 0.001); } @Test public void testSpecial2UndirectedGraphClusteringCoefficient() { Graph graph = new SimpleGraph<>(DefaultEdge.class); String node1 = "0"; String node2 = "1"; String node3 = "2"; String node4 = "3"; String node5 = "4"; String node6 = "5"; String node7 = "6"; graph.addVertex(node1); graph.addVertex(node2); graph.addVertex(node3); graph.addVertex(node4); graph.addVertex(node5); graph.addVertex(node6); graph.addVertex(node7); graph.addEdge(node1, node2); graph.addEdge(node2, node3); graph.addEdge(node3, node1); graph.addEdge(node1, node4); graph.addEdge(node4, node5); graph.addEdge(node5, node1); graph.addEdge(node1, node6); graph.addEdge(node6, node7); graph.addEdge(node7, node1); assertEquals( 0.8857, new ClusteringCoefficient<>(graph).getAverageClusteringCoefficient(), 0.01); } @Test public void testSpecial3UndirectedGraphClusteringCoefficient() { Graph graph = new SimpleGraph<>(DefaultEdge.class); String node1 = "0"; String node2 = "1"; String node3 = "2"; String node4 = "3"; String node5 = "4"; String node6 = "5"; graph.addVertex(node1); graph.addVertex(node2); graph.addVertex(node3); graph.addVertex(node4); graph.addVertex(node5); graph.addVertex(node6); graph.addEdge(node1, node2); graph.addEdge(node2, node3); graph.addEdge(node3, node1); graph.addEdge(node1, node4); graph.addEdge(node2, node5); graph.addEdge(node3, node6); assertEquals(0.333, new ClusteringCoefficient<>(graph).getVertexScore(node1), 0.01); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/CorenessTest.java000066400000000000000000000113241402514743400316140ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Unit tests for {@link Coreness}. * * @author Dimitrios Michail */ public class CorenessTest { @Test public void testGraph() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h")); g.addEdge("b", "c"); g.addEdge("c", "d"); g.addEdge("c", "e"); g.addEdge("e", "f"); g.addEdge("e", "g"); g.addEdge("e", "h"); g.addEdge("f", "g"); g.addEdge("f", "h"); g.addEdge("g", "h"); Coreness pr = new Coreness(g); assertEquals(Integer.valueOf(0), pr.getVertexScore("a")); assertEquals(Integer.valueOf(1), pr.getVertexScore("b")); assertEquals(Integer.valueOf(1), pr.getVertexScore("c")); assertEquals(Integer.valueOf(1), pr.getVertexScore("d")); assertEquals(Integer.valueOf(3), pr.getVertexScore("e")); assertEquals(Integer.valueOf(3), pr.getVertexScore("f")); assertEquals(Integer.valueOf(3), pr.getVertexScore("g")); assertEquals(Integer.valueOf(3), pr.getVertexScore("h")); assertEquals(3, pr.getDegeneracy()); } @Test public void testAnotherGraph() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); Graphs .addAllVertices( g, Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k")); g.addEdge("b", "c"); g.addEdge("c", "d"); g.addEdge("c", "e"); g.addEdge("e", "f"); g.addEdge("e", "g"); g.addEdge("e", "h"); g.addEdge("f", "g"); g.addEdge("f", "h"); g.addEdge("f", "i"); g.addEdge("g", "h"); g.addEdge("i", "j"); g.addEdge("i", "k"); g.addEdge("j", "k"); Coreness pr = new Coreness(g); assertEquals(Integer.valueOf(0), pr.getVertexScore("a")); assertEquals(Integer.valueOf(1), pr.getVertexScore("b")); assertEquals(Integer.valueOf(1), pr.getVertexScore("c")); assertEquals(Integer.valueOf(1), pr.getVertexScore("d")); assertEquals(Integer.valueOf(3), pr.getVertexScore("e")); assertEquals(Integer.valueOf(3), pr.getVertexScore("f")); assertEquals(Integer.valueOf(3), pr.getVertexScore("g")); assertEquals(Integer.valueOf(3), pr.getVertexScore("h")); assertEquals(Integer.valueOf(2), pr.getVertexScore("i")); assertEquals(Integer.valueOf(2), pr.getVertexScore("j")); assertEquals(Integer.valueOf(2), pr.getVertexScore("k")); assertEquals(3, pr.getDegeneracy()); } @Test public void testSingletonGraph() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList("a")); Coreness pr = new Coreness(g); assertEquals(Integer.valueOf(0), pr.getVertexScore("a")); assertEquals(0, pr.getDegeneracy()); } @Test public void testEmptyGraph() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); VertexScoringAlgorithm pr = new Coreness<>(g); assertTrue(pr.getScores().isEmpty()); } @Test public void testNonExistantVertex() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("a"); VertexScoringAlgorithm pr = new Coreness<>(g); try { pr.getVertexScore("unknown"); fail("No!"); } catch (IllegalArgumentException e) { } } @Test public void testBadParameters() { try { new Coreness<>(null); fail("No!"); } catch (NullPointerException e) { } } } EdgeBetweennessCentralityTest.java000066400000000000000000000171421402514743400350660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import static org.junit.Assert.assertEquals; import java.util.List; import org.jgrapht.Graph; import org.jgrapht.alg.scoring.EdgeBetweennessCentrality.OverflowStrategy; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Unit tests for {@link EdgeBetweennessCentrality} * * @author Dimitrios Michail */ public class EdgeBetweennessCentralityTest { @Test public void testUndirectedGraph1() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createStringSupplier()).buildGraph(); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("G"); g.addVertex("H"); g.addEdge("A", "B"); g.addEdge("A", "C"); g.addEdge("A", "D"); g.addEdge("B", "C"); g.addEdge("B", "D"); g.addEdge("C", "D"); DefaultEdge e_D_E = g.addEdge("D", "E"); g.addEdge("E", "F"); DefaultEdge e_E_G = g.addEdge("E", "G"); g.addEdge("F", "G"); g.addEdge("F", "H"); g.addEdge("G", "H"); EdgeBetweennessCentrality ebc = new EdgeBetweennessCentrality<>(g); assertEquals(16.0, ebc.getEdgeScore(e_D_E), 1e-9); assertEquals(7.5, ebc.getEdgeScore(e_E_G), 1e-9); } @Test public void testUndirectedGraph2() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 1; i < 15; i++) { g.addVertex(i); } g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 3); DefaultEdge e3_7 = g.addEdge(3, 7); g.addEdge(4, 6); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 7); DefaultEdge e7_8 = g.addEdge(7, 8); g.addEdge(8, 9); g.addEdge(8, 12); g.addEdge(9, 10); DefaultEdge e9_11 = g.addEdge(9, 11); g.addEdge(12, 13); g.addEdge(12, 14); g.addEdge(10, 11); DefaultEdge e13_14 = g.addEdge(13, 14); EdgeBetweennessCentrality ebc = new EdgeBetweennessCentrality<>(g); assertEquals(33.0, ebc.getEdgeScore(e3_7), 1e-9); assertEquals(49.0, ebc.getEdgeScore(e7_8), 1e-9); assertEquals(12.0, ebc.getEdgeScore(e9_11), 1e-9); assertEquals(1.0, ebc.getEdgeScore(e13_14), 1e-9); } @Test public void testDirectedGraph3() { Graph g = GraphTypeBuilder .directed().allowingMultipleEdges(false).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 1; i < 15; i++) { g.addVertex(i); } g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 3); DefaultEdge e3_7 = g.addEdge(3, 7); g.addEdge(4, 6); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 7); DefaultEdge e7_8 = g.addEdge(7, 8); g.addEdge(8, 9); g.addEdge(8, 12); g.addEdge(9, 10); DefaultEdge e9_11 = g.addEdge(9, 11); g.addEdge(12, 13); g.addEdge(12, 14); g.addEdge(10, 11); DefaultEdge e13_14 = g.addEdge(13, 14); EdgeBetweennessCentrality ebc = new EdgeBetweennessCentrality<>(g); assertEquals(24.0, ebc.getEdgeScore(e3_7), 1e-9); assertEquals(49.0, ebc.getEdgeScore(e7_8), 1e-9); assertEquals(9.0, ebc.getEdgeScore(e9_11), 1e-9); assertEquals(1.0, ebc.getEdgeScore(e13_14), 1e-9); } @Test public void testDirectedGraph3Subset() { Graph g = GraphTypeBuilder .directed().allowingMultipleEdges(false).allowingSelfLoops(true).weighted(false) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); for (int i = 1; i < 15; i++) { g.addVertex(i); } g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(2, 3); DefaultEdge e3_7 = g.addEdge(3, 7); g.addEdge(4, 6); g.addEdge(4, 5); g.addEdge(5, 6); g.addEdge(6, 7); DefaultEdge e7_8 = g.addEdge(7, 8); g.addEdge(8, 9); g.addEdge(8, 12); g.addEdge(9, 10); DefaultEdge e9_11 = g.addEdge(9, 11); g.addEdge(12, 13); g.addEdge(12, 14); g.addEdge(10, 11); DefaultEdge e13_14 = g.addEdge(13, 14); EdgeBetweennessCentrality ebc = new EdgeBetweennessCentrality<>( g, OverflowStrategy.THROW_EXCEPTION_ON_OVERFLOW, List.of(1, 2, 4, 11)); assertEquals(16.0, ebc.getEdgeScore(e3_7), 1e-9); assertEquals(21.0, ebc.getEdgeScore(e7_8), 1e-9); assertEquals(3.0, ebc.getEdgeScore(e9_11), 1e-9); assertEquals(0.0, ebc.getEdgeScore(e13_14), 1e-9); } @Test public void testUndirectedGraphWithWeights() { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(true).weighted(true) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createStringSupplier()).buildGraph(); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("G"); g.addVertex("H"); g.addEdge("A", "B"); g.addEdge("A", "C"); g.addEdge("A", "D"); g.addEdge("B", "C"); g.addEdge("B", "D"); g.addEdge("C", "D"); DefaultEdge e_D_E = g.addEdge("D", "E"); g.setEdgeWeight(e_D_E, 1000.0); // very large DefaultEdge e_D_F = g.addEdge("D", "F"); g.addEdge("E", "F"); DefaultEdge e_E_G = g.addEdge("E", "G"); DefaultEdge e_F_G = g.addEdge("F", "G"); g.addEdge("F", "H"); g.addEdge("G", "H"); EdgeBetweennessCentrality ebc = new EdgeBetweennessCentrality<>(g); assertEquals(0.0, ebc.getEdgeScore(e_D_E), 1e-9); assertEquals(16.0, ebc.getEdgeScore(e_D_F), 1e-9); assertEquals(1.5, ebc.getEdgeScore(e_E_G), 1e-9); assertEquals(5.0, ebc.getEdgeScore(e_F_G), 1e-9); } } EigenvectorCentralityTest.java000066400000000000000000000130531402514743400342660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import static org.junit.Assert.assertEquals; import org.jgrapht.alg.interfaces.VertexScoringAlgorithm; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.DirectedPseudograph; import org.jgrapht.graph.DirectedWeightedPseudograph; import org.junit.Test; /** * Unit tests for eigenvector centrality * * @author Sebastiano Vigna */ public class EigenvectorCentralityTest { @Test public void testGraph2Nodes() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addEdge("1", "2"); g.addEdge("2", "1"); final VertexScoringAlgorithm pr = new EigenvectorCentrality<>(g); assertEquals(pr.getVertexScore("1"), 1 / Math.sqrt(2), 0.0001); assertEquals(pr.getVertexScore("2"), 1 / Math.sqrt(2), 0.0001); } @Test public void testGraph3Nodes() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addEdge("1", "2"); g.addEdge("2", "3"); g.addEdge("3", "1"); final VertexScoringAlgorithm pr = new EigenvectorCentrality<>(g); assertEquals(pr.getVertexScore("1"), 1 / Math.sqrt(3), 0.0001); assertEquals(pr.getVertexScore("2"), 1 / Math.sqrt(3), 0.0001); assertEquals(pr.getVertexScore("3"), 1 / Math.sqrt(3), 0.0001); } @Test public void testGraph1() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("0"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addEdge("0", "1"); g.addEdge("0", "2"); g.addEdge("1", "3"); g.addEdge("1", "2"); g.addEdge("2", "1"); g.addEdge("2", "4"); g.addEdge("2", "3"); g.addEdge("3", "1"); g.addEdge("3", "2"); g.addEdge("3", "3"); g.addEdge("4", "2"); g.addEdge("4", "0"); final VertexScoringAlgorithm pr = new EigenvectorCentrality<>(g); assertEquals(pr.getVertexScore("0"), 0.08032022089204849, 0.001); assertEquals(pr.getVertexScore("1"), 0.48765632797141506, 0.001); assertEquals(pr.getVertexScore("2"), 0.5453987490787013, 0.001); assertEquals(pr.getVertexScore("3"), 0.6437087676602127, 0.001); assertEquals(pr.getVertexScore("4"), 0.20956906939251885, 0.001); } @Test public void testWeightedGraph1() { final DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.setEdgeWeight(g.addEdge("a", "b"), 1. / 2); g.setEdgeWeight(g.addEdge("a", "c"), 1. / 3); g.setEdgeWeight(g.addEdge("b", "a"), 1); g.setEdgeWeight(g.addEdge("b", "b"), 2); g.setEdgeWeight(g.addEdge("b", "d"), 1. / 4); g.setEdgeWeight(g.addEdge("c", "a"), 1); g.setEdgeWeight(g.addEdge("c", "d"), 3); g.setEdgeWeight(g.addEdge("d", "b"), 1. / 5); g.setEdgeWeight(g.addEdge("d", "d"), 1); final VertexScoringAlgorithm pr = new EigenvectorCentrality<>(g); assertEquals(pr.getVertexScore("a"), 0.400610775759173, 0.0001); assertEquals(pr.getVertexScore("b"), 0.863882834704165, 0.0001); assertEquals(pr.getVertexScore("c"), 0.0580276877361552, 0.0001); assertEquals(pr.getVertexScore("d"), 0.299750298600000, 0.0001); } @Test(expected = IllegalArgumentException.class) public void testNonExistantVertex() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addEdge("center", "a"); g.addEdge("center", "b"); g.addEdge("center", "c"); final VertexScoringAlgorithm pr = new EigenvectorCentrality<>(g); pr.getVertexScore("unknown"); } @Test(expected = IllegalArgumentException.class) public void testBadParameters1() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); new EigenvectorCentrality<>(g, -1); } @Test(expected = IllegalArgumentException.class) public void testBadParameters2() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); new EigenvectorCentrality<>(g, 1, 0); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/HarmonicCentralityTest.java000066400000000000000000000162601402514743400336360ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import static org.junit.Assert.assertEquals; /** * Unit tests for harmonic centrality. * * @author Dimitrios Michail */ public class HarmonicCentralityTest { @Test public void testOutgoing() { Graph g = createInstance1(); VertexScoringAlgorithm pr = new HarmonicCentrality<>(g, false, true); assertEquals((1d / 1 + 1d / 1 + 1d / 2 + 1d / 3) / 4, pr.getVertexScore("1"), 1e-9); assertEquals((1d / 3 + 1d / 1 + 1d / 2 + 1d / 3) / 4, pr.getVertexScore("2"), 1e-9); assertEquals((1d / 2 + 1d / 3 + 1d / 1 + 1d / 2) / 4, pr.getVertexScore("3"), 1e-9); assertEquals((1d / 1 + 1d / 2 + 1d / 2 + 1d / 1) / 4, pr.getVertexScore("4"), 1e-9); assertEquals((1d / 3 + 1d / 4 + 1d / 1 + 1d / 2) / 4, pr.getVertexScore("5"), 1e-9); } @Test public void testIncoming() { Graph g = createInstance1(); VertexScoringAlgorithm pr = new HarmonicCentrality<>(g, true, true); assertEquals((1d / 3 + 1d / 2 + 1d / 1 + 1d / 3) / 4, pr.getVertexScore("1"), 1e-9); assertEquals((1d / 1 + 1d / 3 + 1d / 2 + 1d / 4) / 4, pr.getVertexScore("2"), 1e-9); assertEquals((1d / 1 + 1d / 1 + 1d / 2 + 1d / 1) / 4, pr.getVertexScore("3"), 1e-9); assertEquals((1d / 2 + 1d / 2 + 1d / 1 + 1d / 2) / 4, pr.getVertexScore("4"), 1e-9); assertEquals((1d / 3 + 1d / 3 + 1d / 2 + 1d / 1) / 4, pr.getVertexScore("5"), 1e-9); } @Test public void testIncomingNoNormalization() { Graph g = createInstance1(); VertexScoringAlgorithm pr = new HarmonicCentrality<>(g, true, false); assertEquals((1d / 3 + 1d / 2 + 1d / 1 + 1d / 3), pr.getVertexScore("1"), 1e-9); assertEquals((1d / 1 + 1d / 3 + 1d / 2 + 1d / 4), pr.getVertexScore("2"), 1e-9); assertEquals((1d / 1 + 1d / 1 + 1d / 2 + 1d / 1), pr.getVertexScore("3"), 1e-9); assertEquals((1d / 2 + 1d / 2 + 1d / 1 + 1d / 2), pr.getVertexScore("4"), 1e-9); assertEquals((1d / 3 + 1d / 3 + 1d / 2 + 1d / 1), pr.getVertexScore("5"), 1e-9); } @Test public void testUndirected() { Graph g = new AsUndirectedGraph<>(createInstance1()); VertexScoringAlgorithm pr1 = new HarmonicCentrality<>(g, true, true); VertexScoringAlgorithm pr2 = new HarmonicCentrality<>(g, false, true); assertEquals((1d / 1 + 1d / 1 + 1d / 1 + 1d / 2) / 4, pr1.getVertexScore("1"), 1e-9); assertEquals((1d / 1 + 1d / 1 + 1d / 1 + 1d / 2) / 4, pr2.getVertexScore("1"), 1e-9); assertEquals((1d / 1 + 1d / 1 + 1d / 2 + 1d / 2) / 4, pr1.getVertexScore("2"), 1e-9); assertEquals((1d / 1 + 1d / 1 + 1d / 2 + 1d / 2) / 4, pr2.getVertexScore("2"), 1e-9); assertEquals((1d / 1 + 1d / 1 + 1d / 1 + 1d / 1) / 4, pr1.getVertexScore("3"), 1e-9); assertEquals((1d / 1 + 1d / 1 + 1d / 1 + 1d / 1) / 4, pr2.getVertexScore("3"), 1e-9); assertEquals((1d / 1 + 1d / 2 + 1d / 1 + 1d / 1) / 4, pr1.getVertexScore("4"), 1e-9); assertEquals((1d / 1 + 1d / 2 + 1d / 1 + 1d / 1) / 4, pr2.getVertexScore("4"), 1e-9); assertEquals((1d / 2 + 1d / 2 + 1d / 1 + 1d / 1) / 4, pr1.getVertexScore("5"), 1e-9); assertEquals((1d / 2 + 1d / 2 + 1d / 1 + 1d / 1) / 4, pr2.getVertexScore("5"), 1e-9); } @Test public void testNegativeWeights() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("1", "2"); DefaultWeightedEdge e13 = g.addEdge("1", "3"); g.addEdge("2", "3"); g.addEdge("3", "4"); g.addEdge("4", "1"); g.addEdge("4", "5"); g.addEdge("5", "3"); g.setEdgeWeight(e13, -1d); VertexScoringAlgorithm pr = new HarmonicCentrality<>(g, false, true); assertEquals(Double.POSITIVE_INFINITY, pr.getVertexScore("1"), 1e-9); assertEquals((1d / 3 + 1d / 1 + 1d / 2 + 1d / 3) / 4, pr.getVertexScore("2"), 1e-9); assertEquals((1d / 2 + 1d / 3 + 1d / 1 + 1d / 2) / 4, pr.getVertexScore("3"), 1e-9); assertEquals(Double.POSITIVE_INFINITY, pr.getVertexScore("4"), 1e-9); assertEquals((1d / 3 + 1d / 4 + 1d / 1 + 1d / 2) / 4, pr.getVertexScore("5"), 1e-9); } @Test public void testDisconnectedOutgoing() { Graph g = createInstance1(); g.addVertex("6"); VertexScoringAlgorithm pr = new HarmonicCentrality<>(g, false, true); assertEquals((1d / 1 + 1d / 1 + 1d / 2 + 1d / 3) / 5, pr.getVertexScore("1"), 1e-9); assertEquals((1d / 3 + 1d / 1 + 1d / 2 + 1d / 3) / 5, pr.getVertexScore("2"), 1e-9); assertEquals((1d / 2 + 1d / 3 + 1d / 1 + 1d / 2) / 5, pr.getVertexScore("3"), 1e-9); assertEquals((1d / 1 + 1d / 2 + 1d / 2 + 1d / 1) / 5, pr.getVertexScore("4"), 1e-9); assertEquals((1d / 3 + 1d / 4 + 1d / 1 + 1d / 2) / 5, pr.getVertexScore("5"), 1e-9); assertEquals(0d, pr.getVertexScore("6"), 1e-9); } @Test public void testSingletonWithNormalize() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); VertexScoringAlgorithm pr = new HarmonicCentrality<>(g, false, true); assertEquals(0d, pr.getVertexScore("1"), 1e-9); } @Test public void testSingletonWithoutNormalize() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); VertexScoringAlgorithm pr = new HarmonicCentrality<>(g, false, false); assertEquals(0d, pr.getVertexScore("1"), 1e-9); } private Graph createInstance1() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("1", "2"); g.addEdge("1", "3"); g.addEdge("2", "3"); g.addEdge("3", "4"); g.addEdge("4", "1"); g.addEdge("4", "5"); g.addEdge("5", "3"); return g; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/KatzCentralityTest.java000066400000000000000000000305141402514743400330050ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.HashMap; import java.util.Map; import java.util.function.ToDoubleFunction; import org.jgrapht.alg.interfaces.VertexScoringAlgorithm; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.DirectedPseudograph; import org.jgrapht.graph.DirectedWeightedPseudograph; import org.junit.Test; /** * Unit tests for KatzCentrality * * @author Dimitrios Michail * @author Pratik Tibrewal */ public class KatzCentralityTest { @Test public void testGraph2Nodes() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addEdge("1", "2"); g.addEdge("2", "1"); final VertexScoringAlgorithm pr = new KatzCentrality<>(g); assertEquals(pr.getVertexScore("1"), pr.getVertexScore("2"), 0.0001); } @Test public void testExogenousFactor() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addEdge("1", "2"); g.addEdge("2", "1"); final VertexScoringAlgorithm pr = new KatzCentrality<>(g, 0.5, x -> x.equals("1") ? .5 : 1); assertEquals(4. / 3, pr.getVertexScore("1"), 0.0001); assertEquals(5. / 3, pr.getVertexScore("2"), 0.0001); } @Test public void testGraph3Nodes() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addEdge("1", "2"); g.addEdge("2", "3"); g.addEdge("3", "1"); final VertexScoringAlgorithm pr = new KatzCentrality<>(g); assertEquals(pr.getVertexScore("1"), pr.getVertexScore("2"), 0.0001); assertEquals(pr.getVertexScore("1"), pr.getVertexScore("3"), 0.0001); } @Test public void testGraph1() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("A", "E"); g.addEdge("A", "F"); g.addEdge("B", "E"); g.addEdge("B", "F"); g.addEdge("C", "E"); g.addEdge("D", "E"); g.addEdge("1", "E"); g.addEdge("1", "F"); g.addEdge("E", "1"); g.addEdge("2", "E"); g.addEdge("2", "F"); g.addEdge("3", "F"); g.addEdge("F", "3"); g.addEdge("4", "F"); g.addEdge("E", "4"); g.addEdge("4", "5"); g.addEdge("E", "F"); final VertexScoringAlgorithm pr = new KatzCentrality<>(g, 0.85); assertEquals(pr.getVertexScore("A"), 1.0000, 0.5); assertEquals(pr.getVertexScore("B"), 1.0000, 0.5); assertEquals(pr.getVertexScore("C"), 1.0000, 0.5); assertEquals(pr.getVertexScore("D"), 1.0000, 0.5); assertEquals(pr.getVertexScore("E"), 22.000, 0.5); assertEquals(pr.getVertexScore("F"), 204.00, 0.5); assertEquals(pr.getVertexScore("1"), 20.000, 0.5); assertEquals(pr.getVertexScore("2"), 1.0000, 0.5); assertEquals(pr.getVertexScore("3"), 174.00, 0.5); assertEquals(pr.getVertexScore("4"), 20.000, 0.5); assertEquals(pr.getVertexScore("5"), 18.000, 0.5); } @Test public void testGraph2() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("A", "E"); g.addEdge("A", "F"); g.addEdge("B", "E"); g.addEdge("B", "F"); g.addEdge("C", "E"); g.addEdge("D", "E"); g.addEdge("1", "E"); g.addEdge("1", "F"); g.addEdge("E", "1"); g.addEdge("2", "E"); g.addEdge("2", "F"); g.addEdge("3", "F"); g.addEdge("F", "3"); g.addEdge("4", "F"); g.addEdge("E", "4"); g.addEdge("4", "5"); g.addEdge("E", "F"); final VertexScoringAlgorithm pr = new KatzCentrality<>(g, 0.15); assertEquals(pr.getVertexScore("A"), 1.0000, 0.05); assertEquals(pr.getVertexScore("B"), 1.0000, 0.05); assertEquals(pr.getVertexScore("C"), 1.0000, 0.05); assertEquals(pr.getVertexScore("D"), 1.0000, 0.05); assertEquals(pr.getVertexScore("E"), 1.9400, 0.05); assertEquals(pr.getVertexScore("F"), 2.3300, 0.05); assertEquals(pr.getVertexScore("1"), 1.2900, 0.05); assertEquals(pr.getVertexScore("2"), 1.0000, 0.05); assertEquals(pr.getVertexScore("3"), 1.3500, 0.05); assertEquals(pr.getVertexScore("4"), 1.2900, 0.05); assertEquals(pr.getVertexScore("5"), 1.1900, 0.05); } @Test public void testGraph3() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("A", "E"); g.addEdge("A", "F"); g.addEdge("B", "E"); g.addEdge("B", "F"); g.addEdge("C", "E"); g.addEdge("D", "E"); g.addEdge("1", "E"); g.addEdge("1", "F"); g.addEdge("E", "1"); g.addEdge("2", "E"); g.addEdge("2", "F"); g.addEdge("3", "F"); g.addEdge("F", "3"); g.addEdge("4", "F"); g.addEdge("E", "4"); g.addEdge("4", "5"); g.addEdge("E", "F"); final Map exogenousfactormap = new HashMap<>(); for (final String v : g.vertexSet()) { exogenousfactormap.put(v, 1.0); } exogenousfactormap.put("4", 2.0); final ToDoubleFunction exogenousFactorFunction = (v) -> exogenousfactormap.get(v); final VertexScoringAlgorithm pr = new KatzCentrality<>(g, 0.15, exogenousFactorFunction); assertEquals(pr.getVertexScore("A"), 1.0000, 0.005); assertEquals(pr.getVertexScore("B"), 1.0000, 0.005); assertEquals(pr.getVertexScore("C"), 1.0000, 0.005); assertEquals(pr.getVertexScore("D"), 1.0000, 0.005); assertEquals(pr.getVertexScore("E"), 1.9400, 0.005); assertEquals(pr.getVertexScore("F"), 2.4800, 0.005); assertEquals(pr.getVertexScore("1"), 1.2900, 0.005); assertEquals(pr.getVertexScore("2"), 1.0000, 0.005); assertEquals(pr.getVertexScore("3"), 1.3700, 0.005); assertEquals(pr.getVertexScore("4"), 2.2900, 0.005); assertEquals(pr.getVertexScore("5"), 1.3400, 0.005); } @Test public void testWeightedGraph1() { final DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.setEdgeWeight(g.addEdge("center", "a"), 75.0); g.setEdgeWeight(g.addEdge("center", "b"), 20.0); g.setEdgeWeight(g.addEdge("center", "c"), 5.0); final VertexScoringAlgorithm pr = new KatzCentrality<>(g, 0.85, 100, 0.0001); assertEquals(pr.getVertexScore("center"), 1.0000, 0.0001); assertEquals(pr.getVertexScore("a"), 64.7500, 0.0001); assertEquals(pr.getVertexScore("b"), 18.0000, 0.0001); assertEquals(pr.getVertexScore("c"), 5.2500, 0.0001); } @Test public void testweightedGraph2() { final DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.setEdgeWeight(g.addEdge("center", "a"), 1.0); g.setEdgeWeight(g.addEdge("center", "b"), 1.0); g.setEdgeWeight(g.addEdge("center", "c"), 1.0); final VertexScoringAlgorithm pr = new KatzCentrality<>(g, 0.85, 100, 0.0001); assertEquals(pr.getVertexScore("center"), 1.0000, 0.0001); assertEquals(pr.getVertexScore("a"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("b"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("c"), 1.8500, 0.0001); } @Test public void testUnweightedGraph2() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addEdge("center", "a"); g.addEdge("center", "b"); g.addEdge("center", "c"); final VertexScoringAlgorithm pr = new KatzCentrality<>(g, 0.85, 100, 0.0001); assertEquals(pr.getVertexScore("center"), 1.0000, 0.0001); assertEquals(pr.getVertexScore("a"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("b"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("c"), 1.8500, 0.0001); assertEquals(pr.getVertexScore("d"), 1.0000, 0.0001); } @Test public void testEmptyGraph() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); final VertexScoringAlgorithm pr = new KatzCentrality<>(g, 0.85, 100, 0.0001); assertTrue(pr.getScores().isEmpty()); } @Test(expected = IllegalArgumentException.class) public void testNonExistantVertex() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addEdge("center", "a"); g.addEdge("center", "b"); g.addEdge("center", "c"); final VertexScoringAlgorithm pr = new KatzCentrality<>(g, 0.85, 100, 0.0001); pr.getVertexScore("unknown"); } @Test(expected = IllegalArgumentException.class) public void testBadParameters1() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); new KatzCentrality<>(g, -1, 100, 0.0001); } @Test(expected = IllegalArgumentException.class) public void testBadParameters2() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); new KatzCentrality<>(g, 0.85, 0, 0.0001); } @Test(expected = IllegalArgumentException.class) public void testBadParameters3() { final DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); new KatzCentrality<>(g, 0.85, 100, 0.0); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/scoring/PageRankTest.java000066400000000000000000000224641402514743400315320ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.scoring; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import static org.junit.Assert.*; /** * Unit tests for PageRank * * @author Dimitrios Michail */ public class PageRankTest { @Test public void testGraph2Nodes() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addEdge("1", "2"); g.addEdge("2", "1"); VertexScoringAlgorithm pr = new PageRank<>(g); assertEquals(pr.getVertexScore("1"), pr.getVertexScore("2"), 0.0001); } @Test public void testGraph3Nodes() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addEdge("1", "2"); g.addEdge("2", "3"); g.addEdge("3", "1"); VertexScoringAlgorithm pr = new PageRank<>(g); assertEquals(pr.getVertexScore("1"), pr.getVertexScore("2"), 0.0001); assertEquals(pr.getVertexScore("1"), pr.getVertexScore("3"), 0.0001); } @Test public void testGraphWikipedia() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("B", "C"); g.addEdge("C", "B"); g.addEdge("D", "A"); g.addEdge("D", "B"); g.addEdge("E", "D"); g.addEdge("E", "B"); g.addEdge("E", "F"); g.addEdge("F", "B"); g.addEdge("F", "E"); g.addEdge("1", "B"); g.addEdge("1", "E"); g.addEdge("2", "B"); g.addEdge("2", "E"); g.addEdge("3", "B"); g.addEdge("3", "E"); g.addEdge("4", "E"); g.addEdge("5", "E"); VertexScoringAlgorithm pr = new PageRank<>(g); assertEquals(pr.getVertexScore("A"), 0.03278, 0.0001); assertEquals(pr.getVertexScore("B"), 0.38435, 0.0001); assertEquals(pr.getVertexScore("C"), 0.34295, 0.0001); assertEquals(pr.getVertexScore("D"), 0.03908, 0.0001); assertEquals(pr.getVertexScore("E"), 0.08088, 0.0001); assertEquals(pr.getVertexScore("F"), 0.03908, 0.0001); assertEquals(pr.getVertexScore("1"), 0.01616, 0.0001); assertEquals(pr.getVertexScore("2"), 0.01616, 0.0001); assertEquals(pr.getVertexScore("3"), 0.01616, 0.0001); assertEquals(pr.getVertexScore("4"), 0.01616, 0.0001); assertEquals(pr.getVertexScore("5"), 0.01616, 0.0001); } @Test public void testUndirectedGraphWikipedia() { Pseudograph g = new Pseudograph<>(DefaultEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.addVertex("E"); g.addVertex("F"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addEdge("B", "C"); g.addEdge("C", "B"); g.addEdge("D", "A"); g.addEdge("D", "B"); g.addEdge("E", "D"); g.addEdge("E", "B"); g.addEdge("E", "F"); g.addEdge("F", "B"); g.addEdge("F", "E"); g.addEdge("1", "B"); g.addEdge("1", "E"); g.addEdge("2", "B"); g.addEdge("2", "E"); g.addEdge("3", "B"); g.addEdge("3", "E"); g.addEdge("4", "E"); g.addEdge("5", "E"); VertexScoringAlgorithm pr = new PageRank<>(g); assertEquals(pr.getVertexScore("A"), 0.0404, 0.0001); assertEquals(pr.getVertexScore("B"), 0.2152, 0.0001); assertEquals(pr.getVertexScore("C"), 0.0593, 0.0001); assertEquals(pr.getVertexScore("D"), 0.0945, 0.0001); assertEquals(pr.getVertexScore("E"), 0.2511, 0.0001); assertEquals(pr.getVertexScore("F"), 0.0839, 0.0001); assertEquals(pr.getVertexScore("1"), 0.0602, 0.0001); assertEquals(pr.getVertexScore("2"), 0.0602, 0.0001); assertEquals(pr.getVertexScore("3"), 0.0602, 0.0001); assertEquals(pr.getVertexScore("4"), 0.0373, 0.0001); assertEquals(pr.getVertexScore("5"), 0.0373, 0.0001); } @Test public void testWeightedGraph1() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.setEdgeWeight(g.addEdge("center", "a"), 75.0); g.setEdgeWeight(g.addEdge("center", "b"), 20.0); g.setEdgeWeight(g.addEdge("center", "c"), 5.0); VertexScoringAlgorithm pr = new PageRank<>(g, 0.85, 100, 0.0001); assertEquals(pr.getVertexScore("center"), 0.2061, 0.0001); assertEquals(pr.getVertexScore("a"), 0.3376, 0.0001); assertEquals(pr.getVertexScore("b"), 0.2412, 0.0001); assertEquals(pr.getVertexScore("c"), 0.2149, 0.0001); } @Test public void testUnweightedGraph1() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.setEdgeWeight(g.addEdge("center", "a"), 1.0); g.setEdgeWeight(g.addEdge("center", "b"), 1.0); g.setEdgeWeight(g.addEdge("center", "c"), 1.0); VertexScoringAlgorithm pr = new PageRank<>(g, 0.85, 100, 0.0001); assertEquals(pr.getVertexScore("center"), 0.2061, 0.0001); assertEquals(pr.getVertexScore("a"), 0.2646, 0.0001); assertEquals(pr.getVertexScore("b"), 0.2646, 0.0001); assertEquals(pr.getVertexScore("c"), 0.2646, 0.0001); // for (String v : g.vertexSet()) { // System.out.println("pagerank(" + v + ") = " + pr.getVertexScore(v)); // } } @Test public void testUnweightedGraph2() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addEdge("center", "a"); g.addEdge("center", "b"); g.addEdge("center", "c"); VertexScoringAlgorithm pr = new PageRank<>(g, 0.85, 100, 0.0001); assertEquals(pr.getVertexScore("center"), 0.1709, 0.0001); assertEquals(pr.getVertexScore("a"), 0.21937, 0.0001); assertEquals(pr.getVertexScore("b"), 0.21937, 0.0001); assertEquals(pr.getVertexScore("c"), 0.21937, 0.0001); assertEquals(pr.getVertexScore("d"), 0.1709, 0.0001); // for (String v : g.vertexSet()) { // System.out.println("pagerank(" + v + ") = " + pr.getVertexScore(v)); // } } @Test public void testEmptyGraph() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); VertexScoringAlgorithm pr = new PageRank<>(g, 0.85, 100, 0.0001); assertTrue(pr.getScores().isEmpty()); } @Test public void testNonExistantVertex() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("center"); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addEdge("center", "a"); g.addEdge("center", "b"); g.addEdge("center", "c"); VertexScoringAlgorithm pr = new PageRank<>(g, 0.85, 100, 0.0001); try { pr.getVertexScore("unknown"); fail("No!"); } catch (IllegalArgumentException e) { } } @Test public void testBadParameters() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); try { new PageRank<>(g, 1.1, 100, 0.0001); fail("No!"); } catch (IllegalArgumentException e) { } try { new PageRank<>(g, 0.85, 0, 0.0001); fail("No!"); } catch (IllegalArgumentException e) { } try { new PageRank<>(g, 0.85, 100, 0.0); fail("No!"); } catch (IllegalArgumentException e) { } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/000077500000000000000000000000001402514743400274135ustar00rootroot00000000000000ALTAdmissibleHeuristicTest.java000066400000000000000000000133361402514743400353420ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.alg.util.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Dimitrios Michail */ public class ALTAdmissibleHeuristicTest { @Test public void testRandom() { final int tests = 3; final int n = 30; final double p = 0.35; final int landmarksCount = 2; Random rng = new Random(47); List>> graphs = new ArrayList<>(); graphs .add( () -> new DirectedWeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER)); graphs .add( () -> new WeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER)); for (Supplier> gSupplier : graphs) { GraphGenerator gen = new GnpRandomGraphGenerator<>(n, p, rng, true); for (int i = 0; i < tests; i++) { Graph g = gSupplier.get(); gen.generateGraph(g); // assign random weights for (DefaultWeightedEdge e : g.edgeSet()) { g.setEdgeWeight(e, rng.nextDouble()); } // pick random landmarks Integer[] allVertices = g.vertexSet().toArray(new Integer[0]); Set landmarks = new HashSet<>(); while (landmarks.size() < landmarksCount) { landmarks.add(allVertices[rng.nextInt(n)]); } AStarAdmissibleHeuristic h = new ALTAdmissibleHeuristic<>(g, landmarks); ShortestPathAlgorithm sp1 = new DijkstraShortestPath<>(g); ShortestPathAlgorithm sp2 = new AStarShortestPath<>(g, h); for (Integer v : g.vertexSet()) { for (Integer u : g.vertexSet()) { GraphPath p1 = sp1.getPath(v, u); GraphPath p2 = sp2.getPath(v, u); assertEquals(p1.getWeight(), p2.getWeight(), 1e-9); } } } } } @Test public void testRandomAdmissible() { final int tests = 3; final int n = 35; final double p = 0.3; Random rng = new Random(33); List>> graphs = new ArrayList<>(); graphs .add( () -> new DirectedWeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER)); graphs .add( () -> new WeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER)); Comparator comparator = new ToleranceDoubleComparator(); for (Supplier> gSupplier : graphs) { GraphGenerator gen = new GnpRandomGraphGenerator<>(n, p, rng, true); for (int i = 0; i < tests; i++) { Graph g = gSupplier.get(); gen.generateGraph(g); // assign random weights for (DefaultWeightedEdge e : g.edgeSet()) { g.setEdgeWeight(e, rng.nextDouble()); } for (Integer l : g.vertexSet()) { AStarAdmissibleHeuristic h = new ALTAdmissibleHeuristic<>(g, Collections.singleton(l)); for (Integer v : g.vertexSet()) { ShortestPathAlgorithm sp = new DijkstraShortestPath<>(g); SingleSourcePaths paths = sp.getPaths(v); for (Integer u : g.vertexSet()) { GraphPath path = paths.getPath(u); // System.out.println(h.getCostEstimate(v, u) + " <= " + // path.getWeight()); assertTrue( comparator.compare(h.getCostEstimate(v, u), path.getWeight()) <= 0); } } } } } } } AStarShortestPathTest.java000066400000000000000000000076221402514743400344310ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2015-2021, by Joris Kinable, Jon Robison, Thomas Breitbart and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import static org.junit.Assert.*; /** * Test class for AStarShortestPath implementation * * @author Joris Kinable */ public class AStarShortestPathTest extends BaseHeuristicSearchTest { /** * Test on a graph with a path from the source node to the target node. */ @Test public void testLabyrinth1() { this.readLabyrinth(labyrinth1); AStarShortestPath aStarShortestPath = new AStarShortestPath<>(graph, new ManhattanDistance()); GraphPath path = aStarShortestPath.getPath(sourceNode, targetNode); assertNotNull(path); assertEquals((int) path.getWeight(), 47); assertEquals(path.getEdgeList().size(), 47); assertEquals(path.getLength() + 1, 48); AStarShortestPath aStarShortestPath2 = new AStarShortestPath<>(graph, new EuclideanDistance()); GraphPath path2 = aStarShortestPath2.getPath(sourceNode, targetNode); assertNotNull(path2); assertEquals((int) path2.getWeight(), 47); assertEquals(path2.getEdgeList().size(), 47); } /** * Test on a graph where there is no path from the source node to the target node. */ @Test public void testLabyrinth2() { this.readLabyrinth(labyrinth2); AStarShortestPath aStarShortestPath = new AStarShortestPath<>(graph, new ManhattanDistance()); GraphPath path = aStarShortestPath.getPath(sourceNode, targetNode); assertNull(path); } /** * This test verifies whether multigraphs are processed correctly. In a multigraph, there are * multiple edges between the same vertex pair. Each of these edges can have a different cost. * Here we create a simple multigraph A-B-C with multiple edges between (A,B) and (B,C) and * query the shortest path, which is simply the cheapest edge between (A,B) plus the cheapest * edge between (B,C). The admissible heuristic in this test is not important. */ @Test public void testMultiGraph() { Graph multigraph = getMultigraph(); AStarShortestPath aStarShortestPath = new AStarShortestPath<>(multigraph, new ManhattanDistance()); GraphPath path = aStarShortestPath.getPath(n1, n3); assertNotNull(path); assertEquals((int) path.getWeight(), 6); assertEquals(path.getEdgeList().size(), 2); } @Test public void testInconsistentHeuristic() { Graph g = getInconsistentHeuristicTestGraph(); AStarAdmissibleHeuristic h = getInconsistentHeuristic(); AStarShortestPath alg = new AStarShortestPath<>(g, h); // shortest path from 3 to 2 is 3->0->1->2 with weight 0.9641320715228003 assertEquals(0.9641320715228003, alg.getPath(3, 2).getWeight(), 1e-9); } } AllDirectedPathsTest.java000066400000000000000000000207351402514743400342220ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Vera-Licona Research Group and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Test cases for the AllDirectedPaths algorithm. * * @author Andrew Gainer-Dewar, Google LLC **/ public class AllDirectedPathsTest { private static final String I1 = "I1"; private static final String I2 = "I2"; private static final String A = "A"; private static final String B = "B"; private static final String C = "C"; private static final String D = "D"; private static final String E = "E"; private static final String F = "F"; private static final String O1 = "O1"; private static final String O2 = "O2"; @Test public void testSmallExampleGraph() { AllDirectedPaths pathFindingAlg = new AllDirectedPaths<>(toyGraph()); Set sources = new HashSet<>(); sources.add(I1); sources.add(I2); Set targets = new HashSet<>(); targets.add(O1); targets.add(O2); List> allPaths = pathFindingAlg.getAllPaths(sources, targets, true, null); assertEquals("Toy network should have correct number of simple paths", 7, allPaths.size()); } @Test public void testTrivialPaths() { // Verify fix for http://github.com/jgrapht/jgrapht/issues/234. AllDirectedPaths pathFindingAlg = new AllDirectedPaths<>(toyGraph()); Set sources = new HashSet<>(); sources.add(I1); Set targets = new HashSet<>(); targets.add(I1); targets.add(A); List> allPaths = pathFindingAlg.getAllPaths(sources, targets, true, 1); assertEquals( "Toy network should have correct number of trivial simple paths", 2, allPaths.size()); assertEquals(Arrays.asList(I1), allPaths.get(0).getVertexList()); assertEquals(Arrays.asList(I1, A), allPaths.get(1).getVertexList()); } @Test public void testLengthOnePaths() { // Verify fix for http://github.com/jgrapht/jgrapht/issues/441. DefaultDirectedGraph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addEdge("B", "A"); AllDirectedPaths all = new AllDirectedPaths<>(graph); List> allPaths = all.getAllPaths(graph.vertexSet(), graph.vertexSet(), true, graph.edgeSet().size()); assertEquals(3, allPaths.size()); assertEquals(Arrays.asList("A"), allPaths.get(0).getVertexList()); assertEquals(Arrays.asList("B"), allPaths.get(1).getVertexList()); assertEquals(Arrays.asList("B", "A"), allPaths.get(2).getVertexList()); } @Test public void testPathWeights() { // Verify fix for https://github.com/jgrapht/jgrapht/issues/617. SimpleDirectedWeightedGraph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addVertex("D"); graph.setEdgeWeight(graph.addEdge("A", "B"), 1.2); graph.setEdgeWeight(graph.addEdge("A", "C"), 0); graph.setEdgeWeight(graph.addEdge("A", "D"), -1); graph.setEdgeWeight(graph.addEdge("B", "C"), 2); graph.setEdgeWeight(graph.addEdge("B", "D"), 1); graph.setEdgeWeight(graph.addEdge("C", "D"), 0.5); AllDirectedPaths all = new AllDirectedPaths<>(graph); List> allPaths = all.getAllPaths("A", "D", true, 2); allPaths.sort(Comparator.comparing(GraphPath::getWeight)); assertEquals( "Example weighted graph has 3 paths of length no greater than 2", 3, allPaths.size()); ; assertEquals(Arrays.asList("A", "D"), allPaths.get(0).getVertexList()); assertEquals(-1, allPaths.get(0).getWeight(), 0); assertEquals(Arrays.asList("A", "C", "D"), allPaths.get(1).getVertexList()); assertEquals(0.5, allPaths.get(1).getWeight(), 0); assertEquals(Arrays.asList("A", "B", "D"), allPaths.get(2).getVertexList()); assertEquals(2.2, allPaths.get(2).getWeight(), 0); } @Test public void testCycleBehavior() { Graph toyGraph = toyGraph(); toyGraph.addEdge(D, A); AllDirectedPaths pathFindingAlg = new AllDirectedPaths<>(toyGraph); Set sources = new HashSet<>(); sources.add(I1); sources.add(I2); Set targets = new HashSet<>(); targets.add(O1); targets.add(O2); List> allPathsWithoutCycle = pathFindingAlg.getAllPaths(sources, targets, true, 8); List> allPathsWithCycle = pathFindingAlg.getAllPaths(sources, targets, false, 8); assertEquals( "Toy network with cycle should have correct number of paths with cycle", 13, allPathsWithCycle.size()); assertEquals( "Toy network with cycle should have correct number of simple paths", 7, allPathsWithoutCycle.size()); } @Test public void testMustBoundIfNonSimplePaths() { // Goofy hack to test for an exception AllDirectedPaths pathFindingAlg = new AllDirectedPaths<>(toyGraph()); Set sources = Collections.singleton(I1); Set targets = Collections.singleton(O1); try { pathFindingAlg.getAllPaths(sources, targets, false, null); fail("Expected an IllegalArgumentException"); } catch (IllegalArgumentException e) { // This is the expected outcome, so the test passes } } @Test public void testZeroLengthPaths() { // Verify fix for https://github.com/jgrapht/jgrapht/issues/640. DefaultDirectedGraph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("a"); graph.addVertex("b"); graph.addEdge("a", "b"); List> paths = new AllDirectedPaths<>(graph) .getAllPaths(graph.vertexSet(), graph.vertexSet(), false, 0); Assert.assertFalse("We should find at least some paths!", paths.isEmpty()); paths .forEach( path -> Assert .assertEquals( String .format( "The path %s has length %d even though we requested only paths of length 0", path, path.getLength()), 0, path.getLength())); } private static Graph toyGraph() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex(I1); graph.addVertex(I2); graph.addVertex(A); graph.addVertex(B); graph.addVertex(C); graph.addVertex(D); graph.addVertex(E); graph.addVertex(F); graph.addVertex(O1); graph.addVertex(O2); graph.addEdge(I1, A); graph.addEdge(I1, B); graph.addEdge(I2, B); graph.addEdge(I2, C); graph.addEdge(A, B); graph.addEdge(A, D); graph.addEdge(A, E); graph.addEdge(B, E); graph.addEdge(C, B); graph.addEdge(C, F); graph.addEdge(D, E); graph.addEdge(E, O1); graph.addEdge(F, O2); return graph; } } AllPairsShortestPathsTest.java000066400000000000000000000102521402514743400353020ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.assertEquals; /** * @author Dimitrios Michail */ public class AllPairsShortestPathsTest { @Test public void testRandomFixedSeed() { final long seed = 47; Random rng = new Random(seed); testAllPairsShortestPaths(rng); } @Test public void testRandomFixedSeed8() { final long seed = 8; Random rng = new Random(seed); testAllPairsShortestPaths(rng); } @Test public void testRandomFixedSeed13() { final long seed = 13; Random rng = new Random(seed); testAllPairsShortestPaths(rng); } @Test public void testRandomFixedSeed17() { final long seed = 17; Random rng = new Random(seed); testAllPairsShortestPaths(rng); } private void testAllPairsShortestPaths(Random rng) { final int tests = 5; final int n = 20; final double p = 0.35; final int landmarksCount = 2; List, ShortestPathAlgorithm>> algs = new ArrayList<>(); algs.add((g) -> new DijkstraShortestPath<>(g)); algs.add((g) -> new BidirectionalDijkstraShortestPath<>(g)); algs.add((g) -> new AStarShortestPath<>(g, (u, t) -> 0d)); algs.add((g) -> { Integer[] vertices = g.vertexSet().toArray(new Integer[0]); Set landmarks = new HashSet<>(); while (landmarks.size() < landmarksCount) { landmarks.add(vertices[rng.nextInt(g.vertexSet().size())]); } return new AStarShortestPath<>(g, new ALTAdmissibleHeuristic<>(g, landmarks)); }); GraphGenerator gen = new GnpRandomGraphGenerator<>(n, p, rng, true); for (int i = 0; i < tests; i++) { Graph g = new DirectedWeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); gen.generateGraph(g); // assign random weights for (DefaultWeightedEdge e : g.edgeSet()) { g.setEdgeWeight(e, rng.nextDouble()); } double[][] dist = new double[n][n]; int j = 0; for (Function, ShortestPathAlgorithm> spProvider : algs) { ShortestPathAlgorithm alg = spProvider.apply(g); for (Integer v : g.vertexSet()) { for (Integer u : g.vertexSet()) { GraphPath path = alg.getPath(v, u); double d; if (path == null) { d = Double.POSITIVE_INFINITY; } else { d = path.getWeight(); } if (j == 0) { dist[v][u] = d; } else { assertEquals(dist[v][u], d, 1e-9); } } } j++; } } } } BFSShortestPathTest.java000066400000000000000000000063771402514743400340370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2018-2021, by Karri Sai Satish Kumar Reddy and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; public class BFSShortestPathTest { // ~ Static fields/initializers --------------------------------------------- static final String V1 = "v1"; static final String V2 = "v2"; static final String V3 = "v3"; static final String V4 = "v4"; static final String V5 = "v5"; // ~ Instance fields -------------------------------------------------------- DefaultEdge e12; DefaultEdge e13; DefaultEdge e35; DefaultEdge e24; DefaultEdge e45; protected Graph create() { Graph g; g = new DefaultDirectedGraph<>(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); e12 = Graphs.addEdgeWithVertices(g, V1, V2); e13 = Graphs.addEdgeWithVertices(g, V1, V3); e24 = Graphs.addEdgeWithVertices(g, V2, V4); e35 = Graphs.addEdgeWithVertices(g, V3, V5); e45 = Graphs.addEdgeWithVertices(g, V4, V5); return g; } @Test public void testPathBetween() { GraphPath path; Graph g = create(); path = BFSShortestPath.findPathBetween(g, V1, V2); assertEquals(Arrays.asList(e12), path.getEdgeList()); path = BFSShortestPath.findPathBetween(g, V1, V4); assertEquals(Arrays.asList(e12, e24), path.getEdgeList()); path = BFSShortestPath.findPathBetween(g, V1, V5); assertEquals(Arrays.asList(e13, e35), path.getEdgeList()); path = BFSShortestPath.findPathBetween(g, V4, V3); assertNull(path); } @Test public void testAllPaths() { List path; Graph g = create(); SingleSourcePaths tree = new BFSShortestPath<>(g).getPaths(V1); path = tree.getPath(V1).getEdgeList(); assertEquals(Arrays.asList(), path); path = tree.getPath(V2).getEdgeList(); assertEquals(Arrays.asList(e12), path); path = tree.getPath(V3).getEdgeList(); assertEquals(Arrays.asList(e13), path); path = tree.getPath(V4).getEdgeList(); assertEquals(Arrays.asList(e12, e24), path); path = tree.getPath(V5).getEdgeList(); assertEquals(Arrays.asList(e13, e35), path); } } BaseHeuristicSearchTest.java000066400000000000000000000210071402514743400347170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Joris Kinable, Jon Robison, Thomas Breitbart and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; /** * Base test class for the heuristic search algorithms. * * @author Joris Kinable * @author Jon Robison * @author Thomas Breitbart */ public class BaseHeuristicSearchTest { protected final String[] labyrinth1 = { ". . . . . . . . . . . . . . . . . . . . . ####. . . . . . .", ". . . . . . . . . . . . . . . . . . . . . ####. . . . . . .", ". . . . . . . . . . . . . . . . . . . . . ####. . . . . . .", ". . . ####. . . . . . . . . . . . . . . . ####. . . . . . .", ". . . ####. . . . . . . . ####. . . . . . ####T . . . . . .", ". . . ####. . . . . . . . ####. . . . . . ##########. . . .", ". . . ####. . . . . . . . ####. . . . . . ##########. . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . . . . . . . . . . . ####. . . . . . . . . . . . . . .", ". . . . . . . . . . . . . ####. . . . . . . . . . . . . . .", "S . . . . . . . . . . . . ####. . . . . . . . . . . . . . ." }; protected final String[] labyrinth2 = { // Target node is unreachable ". . . . . . . . . . . . . . . . . . . . . ####. . . . . . .", ". . . . . . . . . . . . . . . . . . . . . ####. . . . . . .", ". . . . . . . . . . . . . . . . . . . . . ####. . . . . . .", ". . . ####. . . . . . . . . . . . . . . . ####### . . . . .", ". . . ####. . . . . . . . ####. . . . . . ####T## . . . . .", ". . . ####. . . . . . . . ####. . . . . . ##########. . . .", ". . . ####. . . . . . . . ####. . . . . . ##########. . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . ####. . . . . . . . ####. . . . . . . . . . . . . . .", ". . . . . . . . . . . . . ####. . . . . . . . . . . . . . .", ". . . . . . . . . . . . . ####. . . . . . . . . . . . . . .", "S . . . . . . . . . . . . ####. . . . . . . . . . . . . . ." }; protected Graph graph; protected Node sourceNode; protected Node targetNode; protected Node n1; protected Node n3; protected void readLabyrinth(String[] labyrinth) { graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); // Create the nodes Node[][] nodes = new Node[labyrinth.length][labyrinth[0].length()]; for (int i = 0; i < labyrinth.length; i++) { for (int j = 0; j < labyrinth[0].length(); j++) { if (labyrinth[i].charAt(j) == '#' || labyrinth[i].charAt(j) == ' ') continue; nodes[i][j] = new Node(i, j / 2); graph.addVertex(nodes[i][j]); if (labyrinth[i].charAt(j) == 'S') sourceNode = nodes[i][j]; else if (labyrinth[i].charAt(j) == 'T') targetNode = nodes[i][j]; } } // Create the edges // a. Horizontal edges for (int i = 0; i < labyrinth.length; i++) { for (int j = 0; j < labyrinth[0].length() - 2; j++) { if (nodes[i][j] == null || nodes[i][j + 2] == null) continue; Graphs.addEdge(graph, nodes[i][j], nodes[i][j + 2], 1); } } // b. Vertical edges for (int i = 0; i < labyrinth.length - 1; i++) { for (int j = 0; j < labyrinth[0].length(); j++) { if (nodes[i][j] == null || nodes[i + 1][j] == null) continue; Graphs.addEdge(graph, nodes[i][j], nodes[i + 1][j], 1); } } } protected Graph getMultigraph() { WeightedMultigraph multigraph = new WeightedMultigraph<>(DefaultWeightedEdge.class); n1 = new Node(0, 0); multigraph.addVertex(n1); Node n2 = new Node(1, 0); multigraph.addVertex(n2); n3 = new Node(2, 0); multigraph.addVertex(n3); Graphs.addEdge(multigraph, n1, n2, 5.0); Graphs.addEdge(multigraph, n1, n2, 4.0); Graphs.addEdge(multigraph, n1, n2, 8.0); Graphs.addEdge(multigraph, n2, n3, 7.0); Graphs.addEdge(multigraph, n2, n3, 9); Graphs.addEdge(multigraph, n2, n3, 2); return multigraph; } protected Graph getInconsistentHeuristicTestGraph() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.setEdgeWeight(graph.addEdge(0, 1), 0.5822723681370429); graph.setEdgeWeight(graph.addEdge(0, 3), 0.8512429683406786); graph.setEdgeWeight(graph.addEdge(3, 0), 0.22867383417976428); graph.setEdgeWeight(graph.addEdge(1, 2), 0.1531858692059932); graph.setEdgeWeight(graph.addEdge(3, 1), 0.9639222864568235); graph.setEdgeWeight(graph.addEdge(2, 2), 0.23262564370920258); graph.setEdgeWeight(graph.addEdge(2, 2), 0.6166416559599189); graph.setEdgeWeight(graph.addEdge(3, 3), 0.6088954021459719); graph.setEdgeWeight(graph.addEdge(3, 3), 0.2476189990121238); return graph; } protected AStarAdmissibleHeuristic getInconsistentHeuristic() { return (s, t) -> { if (s == 0 && t == 1) { // actual = 0.5822723681370429 return 0.5822723681370429; } if (s == 3 && t == 1) { // actual = 0.8109462023168071 return 0.8109462023168071; } if (s == 3 && t == 2) { // actual = 0.9641320715228003 return 0.9639222864568235; } if (s == 0 && t == 2) { // actual = 0.7354582373430361 return 0.7354582373430361; } // all other zero return 0d; }; } public static class Node { public final int x; public final int y; Node(int x, int y) { this.x = x; this.y = y; } public String toString() { return "(" + x + "," + y + ")"; } } public static class ManhattanDistance implements AStarAdmissibleHeuristic { @Override public double getCostEstimate(Node sourceVertex, Node targetVertex) { return Math.abs(sourceVertex.x - targetVertex.x) + Math.abs(sourceVertex.y - targetVertex.y); } @Override public boolean isConsistent(Graph graph) { return true; } } public static class EuclideanDistance implements AStarAdmissibleHeuristic { @Override public double getCostEstimate(Node sourceVertex, Node targetVertex) { return Math .sqrt( Math.pow(sourceVertex.x - targetVertex.x, 2) + Math.pow(sourceVertex.y - targetVertex.y, 2)); } @Override public boolean isConsistent(Graph graph) { return true; } } } BaseKShortestPathTest.java000066400000000000000000000063071402514743400344030ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.graph.*; /** * Base test for K shortest paths algorithms. Currently extended by {@link YenShortestPathIterator} * and {@link YenKShortestPath}. */ class BaseKShortestPathTest { protected final int[][] simpleGraph1 = { { 1, 2, 2 }, { 2, 3, 20 }, { 3, 4, 14 }, { 1, 5, 13 }, { 2, 6, 27 }, { 3, 7, 14 }, { 4, 8, 15 }, { 5, 6, 9 }, { 6, 7, 10 }, { 7, 8, 25 }, { 5, 9, 15 }, { 6, 10, 20 }, { 7, 11, 12 }, { 8, 12, 7 }, { 9, 10, 18 }, { 10, 11, 8 }, { 11, 12, 11 } }; protected final int[][] simpleGraph2 = { { 1, 2, 5 }, { 1, 3, 6 }, { 2, 3, 7 }, { 2, 4, 8 }, { 3, 4, 9 } }; protected final int[][] simpleGraph3 = { { 0, 1, 6 }, { 2, 0, 9 }, { 4, 0, 4 }, { 0, 5, 6 }, { 0, 6, 5 }, { 2, 1, 1 }, { 1, 4, 9 }, { 4, 1, 2 }, { 1, 5, 7 }, { 1, 6, 5 }, { 2, 4, 1 }, { 2, 5, 0 }, { 3, 4, 4 }, { 4, 3, 4 }, { 4, 5, 6 }, { 5, 4, 8 }, { 4, 6, 3 }, { 6, 5, 0 } }; protected final int[][] simpleGraph4 = { { 1, 2, 5 }, { 2, 3, 8 }, { 2, 4, 7 }, { 1, 4, 6 }, { 4, 3, 9 } }; protected final int[][] cyclicGraph1 = { { 1, 2, 1 }, { 2, 1, 1 } }; protected final int[][] cyclicGraph2 = { { 1, 2, 1 }, { 2, 3, 1 }, { 3, 4, 1 }, { 4, 1, 1 }, { 1, 5, 2 }, { 5, 6, 2 }, { 6, 7, 2 }, { 7, 1, 2 }, { 3, 6, 2 }, { 6, 3, 2 } }; protected final int[][] cyclicGraph3 = { { 1, 2, 1 }, { 2, 3, 1 }, { 3, 4, 1 }, { 4, 3, 1 }, { 4, 5, 1 }, { 5, 4, 1 } }; protected final int[][] notShortestPathEdgesGraph = { { 1, 2, 1 }, { 1, 3, 3 }, { 1, 4, 4 }, { 1, 5, 5 }, { 1, 6, 6 }, { 1, 7, 7 }, { 1, 8, 8 }, { 1, 9, 9 } }; protected final int[][] pseudograph1 = { { 1, 2, 3 }, { 1, 4, 2 }, { 1, 5, 4 }, { 2, 3, 4 }, { 2, 2, 0 }, { 3, 5, 3 }, { 4, 1, 0 }, { 4, 3, 2 }, { 4, 4, 0 }, { 4, 6, 0 }, { 5, 3, 2 }, { 5, 6, 2 } }; protected final int[][] pseudograph2 = { { 1, 1, 0 }, { 1, 1, 1 }, { 1, 2, 2 }, { 1, 2, 3 }, { 1, 2, 4 }, { 2, 2, 5 }, { 2, 3, 6 }, { 2, 3, 7 }, { 3, 3, 8 }, { 3, 4, 9 }, { 3, 4, 10 }, { 4, 4, 11 } }; protected final int[][] pseudograph3 = { { 1, 2, 1 }, { 1, 2, 2 }, { 1, 2, 3 }, { 2, 3, 4 }, { 2, 3, 5 } }; protected final int[][] pseudograph4 = { { 1, 2, 1 }, { 2, 3, 1 }, { 3, 4, 1 }, { 1, 4, 7 }, { 2, 4, 7 }, { 3, 4, 7 } }; protected void readGraph(Graph graph, int[][] representation) { for (int[] ints : representation) { Graphs.addEdgeWithVertices(graph, ints[0], ints[1], ints[2]); } } } BaseManyToManyShortestPathsTest.java000066400000000000000000000465261402514743400364370ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Base test for many-to-many shortest paths algorithms. Currently extended by * {@link CHManyToManyShortestPathsTest}, {@link DijkstraManyToManyShortestPathsTest} and * {@link DefaultManyToManyShortestPathsTest}. * * @author Semen Chudakov */ public abstract class BaseManyToManyShortestPathsTest { /** * Seed for random numbers generator used in tests. */ protected static final long SEED = 17L; /** * Provides implementation of * {@link org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths} * to be tested. * * @param graph a graph * @return algorithm implementation */ protected abstract ManyToManyShortestPathsAlgorithm getAlgorithm( Graph graph); /** * Tests provided algorithm on an empty graph to ensure no exception is thrown. */ protected void testEmptyGraph() { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); ManyToManyShortestPathsAlgorithm algorithm = getAlgorithm(graph); algorithm.getManyToManyPaths(Collections.emptySet(), Collections.emptySet()); } /** * Checks that provided implementation throws exception when source vertices set is null. */ protected void testSourcesIsNull() { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); ManyToManyShortestPathsAlgorithm algorithm = getAlgorithm(graph); algorithm.getManyToManyPaths(null, Collections.emptySet()); } /** * Checks that provided implementation throws exception when target vertices set is null. */ protected void testTargetsIsNull() { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); ManyToManyShortestPathsAlgorithm algorithm = getAlgorithm(graph); algorithm.getManyToManyPaths(Collections.emptySet(), null); } /** * Checks that provided implementation returns {@link Double#POSITIVE_INFINITY} when there is no * path between a source and a target as well as that the returned path is $null$. */ protected void testNoPath() { Graph graph = new DirectedWeightedMultigraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths shortestPaths = getAlgorithm(graph).getManyToManyPaths(Set.of(1), Set.of(2)); assertEquals(Double.POSITIVE_INFINITY, shortestPaths.getWeight(1, 2), 1e-9); assertNull(shortestPaths.getPath(1, 2)); } /** * Test provided algorithm on the graph generated by {@code getSimpleGraph} using disjoint sets * of source and target vertices. */ protected void testDifferentSourcesAndTargetsSimpleGraph() { ManyToManyShortestPathsAlgorithm algorithm = getAlgorithm(getSimpleGraph()); ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths shortestPaths = algorithm.getManyToManyPaths(Set.of(4, 1, 2), Set.of(8, 9, 6)); assertEquals(2.0, shortestPaths.getWeight(4, 8), 1e-9); assertEquals(Arrays.asList(4, 5, 8), shortestPaths.getPath(4, 8).getVertexList()); assertEquals(3.0, shortestPaths.getWeight(4, 9), 1e-9); assertEquals(Arrays.asList(4, 5, 6, 9), shortestPaths.getPath(4, 9).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(4, 6), 1e-9); assertEquals(Arrays.asList(4, 5, 6), shortestPaths.getPath(4, 6).getVertexList()); assertEquals(3.0, shortestPaths.getWeight(1, 8), 1e-9); assertEquals(Arrays.asList(1, 4, 5, 8), shortestPaths.getPath(1, 8).getVertexList()); assertEquals(4.0, shortestPaths.getWeight(1, 9), 1e-9); assertEquals(Arrays.asList(1, 4, 5, 6, 9), shortestPaths.getPath(1, 9).getVertexList()); assertEquals(3.0, shortestPaths.getWeight(1, 6), 1e-9); assertEquals(Arrays.asList(1, 4, 5, 6), shortestPaths.getPath(1, 6).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(2, 8), 1e-9); assertEquals(Arrays.asList(2, 5, 8), shortestPaths.getPath(2, 8).getVertexList()); assertEquals(3.0, shortestPaths.getWeight(2, 9), 1e-9); assertEquals(Arrays.asList(2, 5, 6, 9), shortestPaths.getPath(2, 9).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(2, 6), 1e-9); assertEquals(Arrays.asList(2, 5, 6), shortestPaths.getPath(2, 6).getVertexList()); } /** * Test provided algorithm on the graph generated by {@code getMultigraph} using disjoint sets * of source and target vertices. */ protected void testDifferentSourcesAndTargetsMultigraph() { ManyToManyShortestPathsAlgorithm algorithm = getAlgorithm(getMultigraph()); ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths shortestPaths = algorithm.getManyToManyPaths(Set.of(1, 4), Set.of(2, 5)); assertEquals(1.0, shortestPaths.getWeight(1, 2), 1e-9); assertEquals(Arrays.asList(1, 2), shortestPaths.getPath(1, 2).getVertexList()); assertEquals(32, shortestPaths.getWeight(1, 5), 1e-9); assertEquals(Arrays.asList(1, 2, 3, 4, 5), shortestPaths.getPath(1, 5).getVertexList()); assertEquals(16, shortestPaths.getWeight(4, 2), 1e-9); assertEquals(Arrays.asList(4, 3, 2), shortestPaths.getPath(4, 2).getVertexList()); assertEquals(15, shortestPaths.getWeight(4, 5), 1e-9); assertEquals(Arrays.asList(4, 5), shortestPaths.getPath(4, 5).getVertexList()); } /** * Test provided algorithm on the graph generated by {@code getSimpleGraph} using the same * source and target vertices. */ protected void testSourcesEqualTargetsSimpleGraph() { ManyToManyShortestPathsAlgorithm algorithm = getAlgorithm(getSimpleGraph()); ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths shortestPaths = algorithm.getManyToManyPaths(Set.of(1, 5, 9), Set.of(1, 5, 9)); assertEquals(0.0, shortestPaths.getWeight(1, 1), 1e-9); assertEquals(Collections.singletonList(1), shortestPaths.getPath(1, 1).getVertexList()); assertEquals(0.0, shortestPaths.getWeight(5, 5), 1e-9); assertEquals(Collections.singletonList(5), shortestPaths.getPath(5, 5).getVertexList()); assertEquals(0.0, shortestPaths.getWeight(9, 9), 1e-9); assertEquals(Collections.singletonList(9), shortestPaths.getPath(9, 9).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(1, 5), 1e-9); assertEquals(Arrays.asList(1, 4, 5), shortestPaths.getPath(1, 5).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(5, 1), 1e-9); assertEquals(Arrays.asList(5, 4, 1), shortestPaths.getPath(5, 1).getVertexList()); assertEquals(4.0, shortestPaths.getWeight(1, 9), 1e-9); assertEquals(Arrays.asList(1, 4, 5, 6, 9), shortestPaths.getPath(1, 9).getVertexList()); assertEquals(4.0, shortestPaths.getWeight(9, 1), 1e-9); assertEquals(Arrays.asList(9, 6, 5, 4, 1), shortestPaths.getPath(9, 1).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(5, 9), 1e-9); assertEquals(Arrays.asList(5, 6, 9), shortestPaths.getPath(5, 9).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(9, 5), 1e-9); assertEquals(Arrays.asList(9, 6, 5), shortestPaths.getPath(9, 5).getVertexList()); } /** * Test provided algorithm on the graph generated by {@code getMultigraph} using the same source * and target vertices. */ protected void testSourcesEqualTargetsMultigraph() { ManyToManyShortestPathsAlgorithm algorithm = getAlgorithm(getMultigraph()); ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths shortestPaths = algorithm.getManyToManyPaths(Set.of(2, 4, 6), Set.of(2, 4, 6)); assertEquals(0.0, shortestPaths.getWeight(2, 2), 1e-9); assertEquals(Collections.singletonList(2), shortestPaths.getPath(2, 2).getVertexList()); assertEquals(0.0, shortestPaths.getWeight(4, 4), 1e-9); assertEquals(Collections.singletonList(4), shortestPaths.getPath(4, 4).getVertexList()); assertEquals(0.0, shortestPaths.getWeight(6, 6), 1e-9); assertEquals(Collections.singletonList(6), shortestPaths.getPath(6, 6).getVertexList()); assertEquals(16.0, shortestPaths.getWeight(2, 4), 1e-9); assertEquals(Arrays.asList(2, 3, 4), shortestPaths.getPath(2, 4).getVertexList()); assertEquals(16.0, shortestPaths.getWeight(4, 2), 1e-9); assertEquals(Arrays.asList(4, 3, 2), shortestPaths.getPath(4, 2).getVertexList()); assertEquals(24.0, shortestPaths.getWeight(2, 6), 1e-9); assertEquals(Arrays.asList(2, 1, 6), shortestPaths.getPath(2, 6).getVertexList()); assertEquals(24.0, shortestPaths.getWeight(6, 2), 1e-9); assertEquals(Arrays.asList(6, 1, 2), shortestPaths.getPath(6, 2).getVertexList()); assertEquals(32.0, shortestPaths.getWeight(4, 6), 1e-9); assertEquals(Arrays.asList(4, 5, 6), shortestPaths.getPath(4, 6).getVertexList()); assertEquals(32.0, shortestPaths.getWeight(6, 4), 1e-9); assertEquals(Arrays.asList(6, 5, 4), shortestPaths.getPath(6, 4).getVertexList()); } /** * Tests provided algorithm on randomly generated graphs. * * @param numOfVertices number of vertices in random graphs * @param vertexDegree vertex degree in random graphs * @param numOfSourcesAndTargets number of source and target vertices * @param numOfIterations number of test iterations for each random graph */ protected void testOnRandomGraphs( int numOfVertices, int vertexDegree, int[][] numOfSourcesAndTargets, int numOfIterations) { Random random = new Random(SEED); for (int[] randomVertices : numOfSourcesAndTargets) { for (int i = 0; i < numOfIterations; i++) { Graph graph = generateRandomGraph(numOfVertices, vertexDegree * numOfVertices, random); Set sources = getRandomVertices(graph, randomVertices[0], random); Set targets = getRandomVertices(graph, randomVertices[1], random); testOnGraph(graph, sources, targets); } } } /** * Tests provided algorithm on {@code graph} using {@code sources} and {@code targets}. * * @param graph a test graph instance * @param sources source vertices * @param targets target vertices */ protected void testOnGraph( Graph graph, Set sources, Set targets) { ManyToManyShortestPathsAlgorithm algorithm = getAlgorithm(graph); ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths sourcesToTargetsPaths = algorithm.getManyToManyPaths(sources, targets); ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths sourcesToSourcesPaths = algorithm.getManyToManyPaths(sources, sources); assertCorrectPaths(graph, sourcesToTargetsPaths, sources, targets); assertCorrectPaths(graph, sourcesToSourcesPaths, sources, sources); } /** * Generates a graph instance from the $G(n,M)$ random graphs model with {@code numOfVertices} * vertices and {@code numOfEdges} edges. * * @param numOfVertices number of vertices in a graph * @param numOfEdges number of edges in a graph * @param random random generator * @return random graph */ protected Graph generateRandomGraph( int numOfVertices, int numOfEdges, Random random) { DirectedWeightedPseudograph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); GraphGenerator generator = new GnmRandomGraphGenerator<>(numOfVertices, numOfEdges - numOfVertices + 1, SEED); generator.generateGraph(graph); makeConnected(graph); addEdgeWeights(graph, random); return graph; } /** * Makes {@code graph} connected. * * @param graph a graph */ protected void makeConnected(Graph graph) { Object[] vertices = graph.vertexSet().toArray(); for (int i = 0; i < vertices.length - 1; ++i) { graph.addEdge((Integer) vertices[i], (Integer) vertices[i + 1]); graph.addEdge((Integer) vertices[i + 1], (Integer) vertices[i]); } } /** * Sets weight for every edge in the {@code graph}. * * @param graph a graph * @param random random generator instance */ protected void addEdgeWeights(Graph graph, Random random) { for (DefaultWeightedEdge edge : graph.edgeSet()) { graph.setEdgeWeight(edge, random.nextDouble()); } } /** * Asserts that shortest paths stored in {@code paths} are correct. {@link DijkstraShortestPath} * algorithm is used a certificate of correctness. * * @param graph a graph * @param paths many-to-many shortest paths object * @param sources source vertices * @param targets target vertices */ protected void assertCorrectPaths( Graph graph, ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths paths, Set sources, Set targets) { ShortestPathAlgorithm dijkstra = new DijkstraShortestPath<>(graph); for (Integer source : sources) { ShortestPathAlgorithm.SingleSourcePaths expectedPaths = dijkstra.getPaths(source); for (Integer target : targets) { GraphPath expected = expectedPaths.getPath(target); GraphPath actual = paths.getPath(source, target); assertEquals(expected.getWeight(), actual.getWeight(), 1e-9); assertEquals(expected.getVertexList(), actual.getVertexList()); } } } /** * Generates list of randomly selected vertices from the given {@code graph}. * * @param graph a graph * @param numOfRandomVertices number of vertices to return * @param random random numbers generator * @return list of random vertices */ protected Set getRandomVertices( Graph graph, int numOfRandomVertices, Random random) { Set result = new HashSet<>(numOfRandomVertices); Integer[] graphVertices = graph.vertexSet().toArray(new Integer[0]); for (int i = 0; i < numOfRandomVertices; ++i) { int vertex = random.nextInt(graph.vertexSet().size()); while (result.contains(vertex)) { vertex = graphVertices[random.nextInt(graph.vertexSet().size())]; } result.add(vertex); } return result; } /** * Generates simple graph for test cases. * * @return test graph */ protected Graph getSimpleGraph() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 3); Graphs.addEdgeWithVertices(graph, 1, 4, 1); Graphs.addEdgeWithVertices(graph, 2, 3, 3); Graphs.addEdgeWithVertices(graph, 2, 5, 1); Graphs.addEdgeWithVertices(graph, 3, 6, 1); Graphs.addEdgeWithVertices(graph, 4, 5, 1); Graphs.addEdgeWithVertices(graph, 4, 7, 1); Graphs.addEdgeWithVertices(graph, 5, 6, 1); Graphs.addEdgeWithVertices(graph, 5, 8, 1); Graphs.addEdgeWithVertices(graph, 6, 9, 1); Graphs.addEdgeWithVertices(graph, 7, 8, 3); Graphs.addEdgeWithVertices(graph, 8, 9, 3); return graph; } /** * Generates multigraph for test cases. * * @return test graph */ protected Graph getMultigraph() { Graph graph = new DirectedWeightedMultigraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 1); Graphs.addEdgeWithVertices(graph, 1, 2, 2); Graphs.addEdgeWithVertices(graph, 2, 1, 3); Graphs.addEdgeWithVertices(graph, 2, 1, 4); Graphs.addEdgeWithVertices(graph, 2, 3, 8); Graphs.addEdgeWithVertices(graph, 2, 3, 7); Graphs.addEdgeWithVertices(graph, 3, 2, 6); Graphs.addEdgeWithVertices(graph, 3, 2, 5); Graphs.addEdgeWithVertices(graph, 3, 4, 9); Graphs.addEdgeWithVertices(graph, 3, 4, 10); Graphs.addEdgeWithVertices(graph, 4, 3, 11); Graphs.addEdgeWithVertices(graph, 4, 3, 12); Graphs.addEdgeWithVertices(graph, 4, 5, 16); Graphs.addEdgeWithVertices(graph, 4, 5, 15); Graphs.addEdgeWithVertices(graph, 5, 4, 14); Graphs.addEdgeWithVertices(graph, 5, 4, 13); Graphs.addEdgeWithVertices(graph, 5, 6, 17); Graphs.addEdgeWithVertices(graph, 5, 6, 18); Graphs.addEdgeWithVertices(graph, 6, 5, 19); Graphs.addEdgeWithVertices(graph, 6, 5, 20); Graphs.addEdgeWithVertices(graph, 6, 1, 24); Graphs.addEdgeWithVertices(graph, 6, 1, 23); Graphs.addEdgeWithVertices(graph, 1, 6, 22); Graphs.addEdgeWithVertices(graph, 1, 6, 21); return graph; } } BellmanFordShortestPathTest.java000066400000000000000000000314771402514743400356110ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2006-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * . * * @author John V. Sichi */ public class BellmanFordShortestPathTest extends ShortestPathTestCase { // ~ Methods ---------------------------------------------------------------- @Test public void testUndirected() { SingleSourcePaths tree; Graph g = create(); tree = new BellmanFordShortestPath<>(g).getPaths(V3); // find best path assertEquals( Arrays.asList(new DefaultWeightedEdge[] { e13, e12, e24, e45 }), tree.getPath(V5).getEdgeList()); assertEquals(3.0, tree.getPath(V1).getWeight(), 1e-9); assertEquals(5.0, tree.getPath(V2).getWeight(), 1e-9); assertEquals(0.0, tree.getPath(V3).getWeight(), 1e-9); assertEquals(10.0, tree.getPath(V4).getWeight(), 1e-9); assertEquals(15.0, tree.getPath(V5).getWeight(), 1e-9); } @Override protected List findPathBetween( Graph g, String src, String dest) { return new BellmanFordShortestPath<>(g).getPaths(src).getPath(dest).getEdgeList(); } @Test public void testWithNegativeEdges() { Graph g = createWithBias(true); List path; path = findPathBetween(g, V1, V4); assertEquals(Arrays.asList(e13, e34), path); path = findPathBetween(g, V1, V5); assertEquals(Arrays.asList(e15), path); } @Test public void testNoPath() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("a"); g.addVertex("b"); BellmanFordShortestPath alg = new BellmanFordShortestPath<>(g); SingleSourcePaths paths = alg.getPaths("a"); assertEquals(paths.getWeight("b"), Double.POSITIVE_INFINITY, 0); assertNull(paths.getPath("b")); } @Test public void testWikipediaExampleBellmanFord() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("w"); g.addVertex("y"); g.addVertex("x"); g.addVertex("z"); g.addVertex("s"); g.setEdgeWeight(g.addEdge("w", "z"), 2); g.setEdgeWeight(g.addEdge("y", "w"), 4); g.setEdgeWeight(g.addEdge("x", "w"), 6); g.setEdgeWeight(g.addEdge("x", "y"), 3); g.setEdgeWeight(g.addEdge("z", "x"), -7); g.setEdgeWeight(g.addEdge("y", "z"), 5); g.setEdgeWeight(g.addEdge("z", "y"), -3); g.setEdgeWeight(g.addEdge("s", "w"), 0.0); g.setEdgeWeight(g.addEdge("s", "y"), 0.0); g.setEdgeWeight(g.addEdge("s", "x"), 0.0); g.setEdgeWeight(g.addEdge("s", "z"), 0.0); BellmanFordShortestPath alg = new BellmanFordShortestPath<>(g); SingleSourcePaths paths = alg.getPaths("s"); assertEquals(0d, paths.getPath("s").getWeight(), 1e-9); assertEquals(-1d, paths.getPath("w").getWeight(), 1e-9); assertEquals(-4d, paths.getPath("y").getWeight(), 1e-9); assertEquals(-7d, paths.getPath("x").getWeight(), 1e-9); assertEquals(0d, paths.getPath("z").getWeight(), 1e-9); } @Test public void testNegativeCycleDetection() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("w"); g.addVertex("y"); g.addVertex("x"); g.addVertex("z"); g.addVertex("s"); g.setEdgeWeight(g.addEdge("w", "z"), 2); g.setEdgeWeight(g.addEdge("y", "w"), 4); g.setEdgeWeight(g.addEdge("x", "w"), 6); g.setEdgeWeight(g.addEdge("x", "y"), 3); g.setEdgeWeight(g.addEdge("z", "x"), -7); g.setEdgeWeight(g.addEdge("y", "z"), 3); g.setEdgeWeight(g.addEdge("z", "y"), -3); g.setEdgeWeight(g.addEdge("s", "w"), 0.0); g.setEdgeWeight(g.addEdge("s", "y"), 0.0); g.setEdgeWeight(g.addEdge("s", "x"), 0.0); g.setEdgeWeight(g.addEdge("s", "z"), 0.0); try { new BellmanFordShortestPath<>(g).getPaths("s"); fail("Negative-weight cycle not detected"); } catch (RuntimeException e) { assertEquals("Graph contains a negative-weight cycle", e.getMessage()); } } @Test public void testNegativeCycleDetectionActualCycle() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("w"); g.addVertex("y"); g.addVertex("x"); g.addVertex("z"); g.addVertex("s"); g.setEdgeWeight(g.addEdge("w", "z"), 2); g.setEdgeWeight(g.addEdge("y", "w"), 4); g.setEdgeWeight(g.addEdge("x", "w"), 6); g.setEdgeWeight(g.addEdge("x", "y"), 3); g.setEdgeWeight(g.addEdge("z", "x"), -7); g.setEdgeWeight(g.addEdge("y", "z"), 3); g.setEdgeWeight(g.addEdge("z", "y"), -3); g.setEdgeWeight(g.addEdge("s", "w"), 0.0); g.setEdgeWeight(g.addEdge("s", "y"), 0.0); g.setEdgeWeight(g.addEdge("s", "x"), 0.0); g.setEdgeWeight(g.addEdge("s", "z"), 0.0); BellmanFordShortestPath alg = new BellmanFordShortestPath<>(g); try { alg.getPaths("s"); fail("Negative-weight cycle not detected"); } catch (NegativeCycleDetectedException e) { assertEquals("Graph contains a negative-weight cycle", e.getMessage()); @SuppressWarnings("unchecked") GraphPath cycle = (GraphPath) e.getCycle(); assertEquals("x", cycle.getStartVertex()); assertEquals("x", cycle.getEndVertex()); assertEquals(-1.0d, cycle.getWeight(), 1e-9); assertEquals(3, cycle.getLength()); } } @Test public void testNegativeEdgeUndirectedGraph() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("w"); g.addVertex("y"); g.addVertex("x"); g.setEdgeWeight(g.addEdge("w", "y"), 1); g.setEdgeWeight(g.addEdge("y", "x"), 1); g.setEdgeWeight(g.addEdge("y", "x"), -1); try { new BellmanFordShortestPath<>(g).getPaths("w"); fail("Negative-weight cycle not detected"); } catch (RuntimeException e) { assertEquals("Graph contains a negative-weight cycle", e.getMessage()); } } @Test public void testNegativeEdgeUndirectedGraphActualCycle() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("w"); g.addVertex("y"); g.addVertex("x"); g.setEdgeWeight(g.addEdge("w", "y"), 1); g.setEdgeWeight(g.addEdge("y", "x"), 1); g.setEdgeWeight(g.addEdge("y", "x"), -1); BellmanFordShortestPath alg = new BellmanFordShortestPath<>(g); try { alg.getPaths("w"); fail("Negative-weight cycle not detected"); } catch (NegativeCycleDetectedException e) { assertEquals("Graph contains a negative-weight cycle", e.getMessage()); @SuppressWarnings("unchecked") GraphPath cycle = (GraphPath) e.getCycle(); assertEquals("x", cycle.getStartVertex()); assertEquals("x", cycle.getEndVertex()); assertEquals(-2.0d, cycle.getWeight(), 1e-9); assertEquals(2, cycle.getLength()); } } @Test public void testDoNotDetectNonReachableNegativeCycle() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addVertex("6"); g.addVertex("7"); g.setEdgeWeight(g.addEdge("1", "2"), 1); g.setEdgeWeight(g.addEdge("2", "3"), 1); g.setEdgeWeight(g.addEdge("3", "4"), 1); g.setEdgeWeight(g.addEdge("5", "4"), 1); g.setEdgeWeight(g.addEdge("5", "6"), -1); g.setEdgeWeight(g.addEdge("6", "7"), -1); g.setEdgeWeight(g.addEdge("7", "5"), -1); BellmanFordShortestPath alg = new BellmanFordShortestPath<>(g); alg.getPaths("1"); } @Test public void testNegativeCycle() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addVertex("6"); g.addVertex("7"); g.addVertex("8"); g.addVertex("9"); g.addVertex("x"); g.setEdgeWeight(g.addEdge("1", "2"), 1); g.setEdgeWeight(g.addEdge("2", "3"), 1); g.setEdgeWeight(g.addEdge("3", "4"), 1); g.setEdgeWeight(g.addEdge("4", "5"), 1); g.setEdgeWeight(g.addEdge("5", "6"), 1); g.setEdgeWeight(g.addEdge("6", "7"), 1); g.setEdgeWeight(g.addEdge("7", "8"), 1); g.setEdgeWeight(g.addEdge("8", "9"), 1); g.setEdgeWeight(g.addEdge("7", "x"), -3); g.setEdgeWeight(g.addEdge("x", "4"), -3); BellmanFordShortestPath alg = new BellmanFordShortestPath<>(g); try { alg.getPaths("1"); fail("Negative-weight cycle not detected"); } catch (NegativeCycleDetectedException e) { assertEquals("Graph contains a negative-weight cycle", e.getMessage()); @SuppressWarnings("unchecked") GraphPath cycle = (GraphPath) e.getCycle(); assertEquals("6", cycle.getStartVertex()); assertEquals("6", cycle.getEndVertex()); assertEquals(-3.0d, cycle.getWeight(), 1e-9); assertEquals(5, cycle.getLength()); } } @Test public void testNegativeCycleWithMaxHops() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.setEdgeWeight(g.addEdge("1", "2"), 1); g.setEdgeWeight(g.addEdge("2", "3"), 1); g.setEdgeWeight(g.addEdge("3", "4"), 1); g.setEdgeWeight(g.addEdge("4", "1"), -5); int maxHops = 3; BellmanFordShortestPath alg = new BellmanFordShortestPath<>(g, 1e-16, maxHops); GraphPath path1 = alg.getPaths("1").getPath("3"); assertEquals(2.0d, path1.getWeight(), 1e-9); BellmanFordShortestPath alg1 = new BellmanFordShortestPath<>(g, 1e-16, maxHops + 1); try { alg1.getPaths("1"); fail("Negative-weight cycle not detected"); } catch (NegativeCycleDetectedException e) { assertEquals("Graph contains a negative-weight cycle", e.getMessage()); @SuppressWarnings("unchecked") GraphPath cycle = (GraphPath) e.getCycle(); assertEquals("1", cycle.getStartVertex()); assertEquals("1", cycle.getEndVertex()); assertEquals(-2.0d, cycle.getWeight(), 1e-9); assertEquals(4, cycle.getLength()); } } } BhandariKDisjointShortestPathsTest.java000066400000000000000000000070651402514743400371320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2018-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * * Tests for the {@link BhandariKDisjointShortestPaths} class. * * @author Assaf Mizrachi */ public class BhandariKDisjointShortestPathsTest extends KDisjointShortestPathsTestCase { /** * Tests two joint paths from 1 to 4, negative edges exist in path. * * Edges expected in path 1 --------------- {@literal 1 --> 2}, w=-1 {@literal 2 --> 6}, w=-3 * {@literal 6 --> 4}, w= 3 * * Edges expected in path 2 --------------- {@literal 1 --> 5}, w=-2 {@literal 5 --> 3}, w= 2 * {@literal 3 --> 4}, w=-1 * * Edges expected in no path --------------- {@literal 2 --> 3}, w=-1 * */ @Test public void testTwoDisjointPathsNegative() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); graph.addVertex(6); DefaultWeightedEdge e12 = graph.addEdge(1, 2); // this edge should not be used DefaultWeightedEdge e23 = graph.addEdge(2, 3); DefaultWeightedEdge e34 = graph.addEdge(3, 4); DefaultWeightedEdge e15 = graph.addEdge(1, 5); DefaultWeightedEdge e53 = graph.addEdge(5, 3); DefaultWeightedEdge e26 = graph.addEdge(2, 6); DefaultWeightedEdge e64 = graph.addEdge(6, 4); graph.setEdgeWeight(e12, -20); graph.setEdgeWeight(e23, -1); graph.setEdgeWeight(e34, -10); graph.setEdgeWeight(e15, -2); graph.setEdgeWeight(e53, 2); graph.setEdgeWeight(e26, -3); graph.setEdgeWeight(e64, 3); BhandariKDisjointShortestPaths alg = new BhandariKDisjointShortestPaths<>(graph); List> pathList = alg.getPaths(1, 4, 5); assertEquals(2, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 2, 6, 4), -20); assertEquals(expectedP1, pathList.get(0)); assertEquals(3, pathList.get(0).getLength()); assertEquals(-20.0, pathList.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 5, 3, 4), -10); assertEquals(expectedP2, pathList.get(1)); assertEquals(3, pathList.get(1).getLength()); assertEquals(-10.0, pathList.get(1).getWeight(), 0.0); } @Override protected KShortestPathAlgorithm getKShortestPathAlgorithm(Graph graph) { return new BhandariKDisjointShortestPaths<>(graph); } } BidirectionalAStarShortestPathTest.java000066400000000000000000000205661402514743400371240ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Test class for {@link BidirectionalAStarShortestPath} class. * * @author Semen Chudakov */ public class BidirectionalAStarShortestPathTest extends BaseHeuristicSearchTest { private static final String S = "S"; private static final String T = "T"; private static final String Y = "Y"; private static final String X = "X"; private static final String Z = "Z"; @Test public void testEmptyGraph() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.addVertex(S); new BidirectionalAStarShortestPath<>(graph, (sourceVertex, targetVertex) -> 0).getPaths(S); } @Test public void testSimpleGraph() { Graph graph = getSimpleGraph(); AStarAdmissibleHeuristic heuristic = getSimpleGraphHeuristic(); assertEquals( Arrays.asList(S, Y, Z), new BidirectionalAStarShortestPath<>(graph, heuristic).getPath(S, Z).getVertexList()); } private Graph getSimpleGraph() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph, Arrays.asList(S, T, Y, X, Z)); Graphs.addEdge(graph, S, T, 10); Graphs.addEdge(graph, S, Y, 5); Graphs.addEdge(graph, T, Y, 2); Graphs.addEdge(graph, T, X, 1); Graphs.addEdge(graph, Y, T, 3); Graphs.addEdge(graph, Y, Z, 2); Graphs.addEdge(graph, Y, X, 9); Graphs.addEdge(graph, X, Z, 4); Graphs.addEdge(graph, Z, X, 6); Graphs.addEdge(graph, Z, S, 7); return graph; } private AStarAdmissibleHeuristic getSimpleGraphHeuristic() { return (sourceVertex, targetVertex) -> { if (sourceVertex.equals(S) && targetVertex.equals(Z)) { return 7; } else if (sourceVertex.equals(Y) && targetVertex.equals(Z)) { return 2; } else if (sourceVertex.equals(T) && targetVertex.equals(Z)) { return 4; } else if (sourceVertex.equals(X) && targetVertex.equals(Z)) { return 4; } else if (sourceVertex.equals(T) && targetVertex.equals(S)) { return 8; } else if (sourceVertex.equals(Y) && targetVertex.equals(S)) { return 5; } else if (sourceVertex.equals(X) && targetVertex.equals(S)) { return 11; } else if (sourceVertex.equals(Z) && targetVertex.equals(S)) { return 7; } else { return 0; } }; } @Test public void testLabyrinth1() { this.readLabyrinth(labyrinth1); BidirectionalAStarShortestPath shortestPath1 = new BidirectionalAStarShortestPath<>(graph, new ManhattanDistance()); GraphPath path = shortestPath1.getPath(sourceNode, targetNode); assertNotNull(path); assertEquals(47, (int) path.getWeight()); assertEquals(47, path.getEdgeList().size()); assertEquals(48, path.getLength() + 1); BidirectionalAStarShortestPath shortestPath2 = new BidirectionalAStarShortestPath<>(graph, new EuclideanDistance()); GraphPath path2 = shortestPath2.getPath(sourceNode, targetNode); assertNotNull(path2); assertEquals(47, (int) path2.getWeight()); assertEquals(47, path2.getEdgeList().size()); } @Test public void testLabyrinth2() { this.readLabyrinth(labyrinth2); BidirectionalAStarShortestPath aStarShortestPath = new BidirectionalAStarShortestPath<>(graph, new ManhattanDistance()); GraphPath path = aStarShortestPath.getPath(sourceNode, targetNode); assertNull(path); } @Test public void testMultiGraph() { Graph multigraph = getMultigraph(); BidirectionalAStarShortestPath aStarShortestPath = new BidirectionalAStarShortestPath<>(multigraph, new ManhattanDistance()); GraphPath path = aStarShortestPath.getPath(n1, n3); assertNotNull(path); assertEquals((int) path.getWeight(), 6); assertEquals(path.getEdgeList().size(), 2); } @Test public void testInconsistentHeuristic() { Graph g = getInconsistentHeuristicTestGraph(); AStarAdmissibleHeuristic h = getInconsistentHeuristic(); BidirectionalAStarShortestPath shortestPath = new BidirectionalAStarShortestPath<>(g, h); // shortest path from 3 to 2 is 3->0->1->2 with weight 0.9641320715228003 assertEquals(0.9641320715228003, shortestPath.getPath(3, 2).getWeight(), 1e-9); } @Test public void testRandomGraphs() { int n = 1000; double p = 0.40; for (int i = 0; i < 10; i++) { Graph graph = getGnpRandomGraph(n, p); Integer[] vertices = graph.vertexSet().toArray(new Integer[0]); Set landmarks = new HashSet<>(); int numOfLandmarks = 5; while (landmarks.size() < numOfLandmarks) { int position = (int) (Math.random() * graph.vertexSet().size()); landmarks.add(vertices[position]); } AStarAdmissibleHeuristic heuristic = new ALTAdmissibleHeuristic<>(graph, landmarks); for (int j = 0; j < 10; j++) { int source = (int) (Math.random() * n); int target = (int) (Math.random() * n); testCorrectness(graph, source, target, heuristic); } } } private void testCorrectness( Graph graph, int source, int target, AStarAdmissibleHeuristic heuristic) { DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath<>(graph); BidirectionalAStarShortestPath bidirectionalAStarShortestPath = new BidirectionalAStarShortestPath<>(graph, heuristic); GraphPath path1 = dijkstraShortestPath.getPath(source, target); GraphPath path2 = bidirectionalAStarShortestPath.getPath(source, target); if (path1 == null) { assertNull(path2); } else { assertEquals(path1.getWeight(), path2.getWeight(), 1e-9); } } private Graph getGnpRandomGraph(int n, double p) { GraphGenerator generator = new GnpRandomGraphGenerator<>(n, p); DefaultUndirectedWeightedGraph result = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); result.setVertexSupplier(SupplierUtil.createIntegerSupplier()); generator.generateGraph(result); for (DefaultWeightedEdge e : result.edgeSet()) { result.setEdgeWeight(e, (int) (Math.random() * 10)); } return result; } } BidirectionalDijkstraShortestPathTest.java000066400000000000000000000362741402514743400376700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * @author Dimitrios Michail */ public class BidirectionalDijkstraShortestPathTest { @Test public void testGraphDirected() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.setEdgeWeight(g.addEdge("1", "2"), 3.0); g.setEdgeWeight(g.addEdge("3", "1"), 3.0); g.setEdgeWeight(g.addEdge("2", "4"), 3.0); g.setEdgeWeight(g.addEdge("3", "5"), 5.0); g.setEdgeWeight(g.addEdge("5", "4"), 5.0); GraphPath p = new BidirectionalDijkstraShortestPath<>(g).getPath("3", "4"); assertEquals("3", p.getStartVertex()); assertEquals("4", p.getEndVertex()); assertEquals(3, p.getLength()); assertEquals(9.0, p.getWeight(), 0); assertEquals("3", p.getVertexList().get(0)); assertEquals("1", p.getVertexList().get(1)); assertEquals("2", p.getVertexList().get(2)); assertEquals("4", p.getVertexList().get(3)); assertEquals(g.getEdge("3", "1"), p.getEdgeList().get(0)); assertEquals(g.getEdge("1", "2"), p.getEdgeList().get(1)); assertEquals(g.getEdge("2", "4"), p.getEdgeList().get(2)); } @Test public void testGraphDirectedRadius() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.setEdgeWeight(g.addEdge("1", "2"), 3.0); g.setEdgeWeight(g.addEdge("3", "1"), 3.0); g.setEdgeWeight(g.addEdge("2", "4"), 3.0); g.setEdgeWeight(g.addEdge("3", "5"), 5.0); g.setEdgeWeight(g.addEdge("5", "4"), 5.0); GraphPath p = new BidirectionalDijkstraShortestPath<>(g, 9.5).getPath("3", "4"); assertEquals("3", p.getStartVertex()); assertEquals("4", p.getEndVertex()); assertEquals(3, p.getLength()); assertEquals(9.0, p.getWeight(), 0); assertEquals("3", p.getVertexList().get(0)); assertEquals("1", p.getVertexList().get(1)); assertEquals("2", p.getVertexList().get(2)); assertEquals("4", p.getVertexList().get(3)); assertEquals(g.getEdge("3", "1"), p.getEdgeList().get(0)); assertEquals(g.getEdge("1", "2"), p.getEdgeList().get(1)); assertEquals(g.getEdge("2", "4"), p.getEdgeList().get(2)); } @Test public void testGraphUndirected() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.setEdgeWeight(g.addEdge("1", "2"), 3.0); g.setEdgeWeight(g.addEdge("3", "1"), 3.0); g.setEdgeWeight(g.addEdge("2", "4"), 3.0); g.setEdgeWeight(g.addEdge("3", "5"), 5.0); g.setEdgeWeight(g.addEdge("5", "4"), 5.0); GraphPath p = new BidirectionalDijkstraShortestPath<>(g).getPath("3", "4"); assertEquals("3", p.getStartVertex()); assertEquals("4", p.getEndVertex()); assertEquals(3, p.getLength()); assertEquals(9.0, p.getWeight(), 0); assertEquals("3", p.getVertexList().get(0)); assertEquals("1", p.getVertexList().get(1)); assertEquals("2", p.getVertexList().get(2)); assertEquals("4", p.getVertexList().get(3)); assertEquals(g.getEdge("3", "1"), p.getEdgeList().get(0)); assertEquals(g.getEdge("1", "2"), p.getEdgeList().get(1)); assertEquals(g.getEdge("2", "4"), p.getEdgeList().get(2)); } @Test public void testSourceTargetEqualUndirected() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.setEdgeWeight(g.addEdge("1", "2"), 3.0); g.setEdgeWeight(g.addEdge("3", "1"), 3.0); g.setEdgeWeight(g.addEdge("2", "4"), 3.0); g.setEdgeWeight(g.addEdge("3", "5"), 5.0); g.setEdgeWeight(g.addEdge("5", "4"), 5.0); GraphPath p = new BidirectionalDijkstraShortestPath<>(g).getPath("3", "3"); assertEquals("3", p.getStartVertex()); assertEquals("3", p.getEndVertex()); assertEquals(0, p.getLength()); assertEquals(0.0, p.getWeight(), 0); assertEquals("3", p.getVertexList().get(0)); assertTrue(p.getEdgeList().isEmpty()); } @Test public void testGraphDirectedNoPath() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.setEdgeWeight(g.addEdge("1", "2"), 3.0); g.setEdgeWeight(g.addEdge("3", "1"), 3.0); g.setEdgeWeight(g.addEdge("4", "2"), 3.0); g.setEdgeWeight(g.addEdge("3", "5"), 5.0); g.setEdgeWeight(g.addEdge("4", "5"), 5.0); assertNull(new BidirectionalDijkstraShortestPath<>(g).getPath("3", "4")); } @Test public void testSingleEdgePath() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addEdge("1", "2"); g.addEdge("1", "3"); g.addEdge("3", "1"); GraphPath p = new BidirectionalDijkstraShortestPath<>(g).getPath("1", "2"); assertEquals(p.getLength(), 1); assertEquals("1", p.getStartVertex()); assertEquals("2", p.getEndVertex()); assertEquals("1", p.getVertexList().get(0)); assertEquals("2", p.getVertexList().get(1)); assertEquals(g.getEdge("1", "2"), p.getEdgeList().get(0)); } @Test public void testSimple1() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("3"); g.addVertex("4"); g.addEdge("4", "3"); g.addEdge("3", "1"); g.addEdge("4", "1"); GraphPath p = new BidirectionalDijkstraShortestPath<>(g).getPath("4", "1"); assertEquals(1, p.getLength()); assertEquals(1.0, p.getWeight(), 0); assertEquals("4", p.getStartVertex()); assertEquals("1", p.getEndVertex()); assertEquals("4", p.getVertexList().get(0)); assertEquals("1", p.getVertexList().get(1)); assertEquals(g.getEdge("4", "1"), p.getEdgeList().get(0)); } @Test public void testGraphAllPairsDirected() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); for (int i = 0; i < 10; i++) { g.addVertex(i); } for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (i != j) { g.setEdgeWeight(g.addEdge(i, j), 1.0); } } } g.addVertex(10); g.setEdgeWeight(g.addEdge(0, 10), 100.0); for (int i = 11; i < 21; i++) { g.addVertex(i); } for (int i = 11; i < 21; i++) { for (int j = 11; j < 21; j++) { if (i != j) { g.setEdgeWeight(g.addEdge(i, j), 1.0); } } } g.setEdgeWeight(g.addEdge(10, 11), 100.0); for (int i = 0; i < 10; i++) { for (int j = 11; j < 21; j++) { GraphPath p = new BidirectionalDijkstraShortestPath<>(g).getPath(i, j); if (i == 0 && j == 11) { assertEquals(200.0, p.getWeight(), 0); } else if (i == 0) { assertEquals(201.0, p.getWeight(), 0); } else if (j == 11) { assertEquals(201.0, p.getWeight(), 0); } else { assertEquals(202.0, p.getWeight(), 0); } } } } @Test public void testRandomGraphsDirected() { GraphGenerator gen = new GnmRandomGraphGenerator<>(20, 100, 1); DirectedPseudograph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); for (String v : g.vertexSet()) { for (String u : g.vertexSet()) { GraphPath p1 = new DijkstraShortestPath<>(g).getPath(v, u); GraphPath p2 = new BidirectionalDijkstraShortestPath<>(g).getPath(v, u); if (p1 == null) { assertNull(p2); } else if (p2 == null) { assertNull(p1); } else { assertEquals(p1.getLength(), p2.getLength()); assertEquals(p1.getWeight(), p2.getWeight(), 0.0001); assertEquals(p2.getWeight(), computePathWeight(g, p2), 0.0001); assertEquals(p1.getStartVertex(), p2.getStartVertex()); assertEquals(p1.getEndVertex(), p2.getEndVertex()); } } } } @Test public void testRandomGraphsWeightedUndirected() { GraphGenerator gen = new GnmRandomGraphGenerator<>(20, 100, 1); DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); gen.generateGraph(g); Random weightedGenerator = new Random(7); for (DefaultWeightedEdge e : g.edgeSet()) { g.setEdgeWeight(e, weightedGenerator.nextDouble()); } for (String v : g.vertexSet()) { for (String u : g.vertexSet()) { GraphPath p1 = new DijkstraShortestPath<>(g).getPath(v, u); GraphPath p2 = new BidirectionalDijkstraShortestPath<>(g).getPath(v, u); if (p1 == null) { assertNull(p2); } else if (p2 == null) { assertNull(p1); } else { assertEquals(p1.getLength(), p2.getLength()); assertEquals(p1.getWeight(), p2.getWeight(), 0.0001); assertEquals(p2.getWeight(), computePathWeight(g, p2), 0.0001); assertEquals(p1.getStartVertex(), p2.getStartVertex()); assertEquals(p1.getEndVertex(), p2.getEndVertex()); } } } } @Test public void testRandomGraphsDirectedWithRadius() { GraphGenerator gen = new GnmRandomGraphGenerator<>(20, 100, 1); DirectedPseudograph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); double radius = 2.5; for (String v : g.vertexSet()) { for (String u : g.vertexSet()) { GraphPath p1 = new DijkstraShortestPath<>(g, radius).getPath(v, u); GraphPath p2 = new BidirectionalDijkstraShortestPath<>(g, radius).getPath(v, u); if (p1 == null || p2 == null) { assertNull(p1); assertNull(p2); } else { assertEquals(p1.getLength(), p2.getLength()); assertEquals(p1.getWeight(), p2.getWeight(), 0.0001); assertEquals(p2.getWeight(), computePathWeight(g, p2), 0.0001); assertEquals(p1.getStartVertex(), p2.getStartVertex()); assertEquals(p1.getEndVertex(), p2.getEndVertex()); } } } } @Test public void testWrongParameters() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("1"); g.addVertex("2"); g.addEdge("1", "2"); try { new BidirectionalDijkstraShortestPath<>(g, -2.0).getPath("1", "2"); fail("No!"); } catch (IllegalArgumentException e) { } try { new BidirectionalDijkstraShortestPath<>(g, 2.0).getPath("3", "2"); fail("No!"); } catch (IllegalArgumentException e) { } try { new BidirectionalDijkstraShortestPath<>(g, 2.0).getPath("2", "3"); fail("No!"); } catch (IllegalArgumentException e) { } try { new BidirectionalDijkstraShortestPath<>(null).getPath("1", "1"); fail("No!"); } catch (NullPointerException e) { } try { new BidirectionalDijkstraShortestPath<>(g).getPath(null, "1"); fail("No!"); } catch (IllegalArgumentException e) { } try { new BidirectionalDijkstraShortestPath<>(g).getPath("1", null); fail("No!"); } catch (IllegalArgumentException e) { } } private double computePathWeight(Graph g, GraphPath path) { if (path.getEdgeList().isEmpty()) { if (path.getStartVertex().equals(path.getEndVertex())) { return 0d; } else { return Double.POSITIVE_INFINITY; } } double total = 0d; for (E e : path.getEdgeList()) { total += g.getEdgeWeight(e); } return total; } } CHManyToManyShortestPathsTest.java000066400000000000000000000134031402514743400360430ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jgrapht.util.ConcurrencyUtil; import org.junit.*; import java.util.*; import java.util.concurrent.ThreadPoolExecutor; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionHierarchy; import static org.junit.Assert.assertEquals; /** * Test for {@link CHManyToManyShortestPaths}. * * @author Semen Chudakov */ public class CHManyToManyShortestPathsTest extends BaseManyToManyShortestPathsTest { /** * Executor which is supplied to the {@link CHManyToManyShortestPaths} in this test case. */ private static ThreadPoolExecutor executor; @BeforeClass public static void createExecutor() { executor = ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); } @AfterClass public static void shutdownExecutor() throws InterruptedException { ConcurrencyUtil.shutdownExecutionService(executor); } @Test public void testEmptyGraph() { super.testEmptyGraph(); } @Test(expected = NullPointerException.class) public void testSourcesIsNull() { super.testSourcesIsNull(); } @Test(expected = NullPointerException.class) public void testTargetsIsNull() { super.testTargetsIsNull(); } @Test public void testNoPath() { super.testNoPath(); } @Test public void testDifferentSourcesAndTargetsSimpleGraph() { super.testDifferentSourcesAndTargetsSimpleGraph(); } @Test public void testDifferentSourcesAndTargetsMultigraph() { super.testDifferentSourcesAndTargetsMultigraph(); } @Test public void testSourcesEqualTargetsSimpleGraph() { super.testSourcesEqualTargetsSimpleGraph(); } @Test public void testSourcesEqualTargetsMultigraph() { super.testSourcesEqualTargetsMultigraph(); } @Test public void testMoreSourcesThanTargets1() { Graph graph = getSimpleGraph(); ContractionHierarchy hierarchy = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths shortestPaths = new CHManyToManyShortestPaths<>(hierarchy) .getManyToManyPaths(Set.of(1, 3, 7, 9), Set.of(5)); assertEquals(2.0, shortestPaths.getWeight(1, 5), 1e-9); assertEquals(Arrays.asList(1, 4, 5), shortestPaths.getPath(1, 5).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(3, 5), 1e-9); assertEquals(Arrays.asList(3, 6, 5), shortestPaths.getPath(3, 5).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(7, 5), 1e-9); assertEquals(Arrays.asList(7, 4, 5), shortestPaths.getPath(7, 5).getVertexList()); assertEquals(2.0, shortestPaths.getWeight(9, 5), 1e-9); assertEquals(Arrays.asList(9, 6, 5), shortestPaths.getPath(9, 5).getVertexList()); } @Test public void testMoreSourcesThanTargets2() { Graph graph = getMultigraph(); ContractionHierarchy hierarchy = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths shortestPaths = new CHManyToManyShortestPaths<>(hierarchy) .getManyToManyPaths(Set.of(2, 3, 4, 5, 6), Set.of(1)); assertEquals(3.0, shortestPaths.getWeight(2, 1), 1e-9); assertEquals(Arrays.asList(2, 1), shortestPaths.getPath(2, 1).getVertexList()); assertEquals(8.0, shortestPaths.getWeight(3, 1), 1e-9); assertEquals(Arrays.asList(3, 2, 1), shortestPaths.getPath(3, 1).getVertexList()); assertEquals(19.0, shortestPaths.getWeight(4, 1), 1e-9); assertEquals(Arrays.asList(4, 3, 2, 1), shortestPaths.getPath(4, 1).getVertexList()); assertEquals(32.0, shortestPaths.getWeight(5, 1), 1e-9); assertEquals(Arrays.asList(5, 4, 3, 2, 1), shortestPaths.getPath(5, 1).getVertexList()); assertEquals(23.0, shortestPaths.getWeight(6, 1), 1e-9); assertEquals(Arrays.asList(6, 1), shortestPaths.getPath(6, 1).getVertexList()); } @Test public void testOnRandomGraphs() { super.testOnRandomGraphs(40, 5, new int[][] { { 10, 15 }, { 10, 10 }, { 15, 10 } }, 10); } @Override protected ManyToManyShortestPathsAlgorithm getAlgorithm( Graph graph) { ContractionHierarchy hierarchy = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); return new CHManyToManyShortestPaths<>(hierarchy); } } ContractionHierarchyBidirectionalDijkstraTest.java000066400000000000000000000234501402514743400413520ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.concurrent.ThreadPoolExecutor; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionHierarchy; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Tests for the {@link ContractionHierarchyBidirectionalDijkstra}. */ public class ContractionHierarchyBidirectionalDijkstraTest { /** * Seed for random numbers generator used in tests. */ private static final long SEED = 19L; /** * Executor which is supplied to the {@link ContractionHierarchyBidirectionalDijkstra} algorithm * in this test case. */ private static ThreadPoolExecutor executor; @BeforeClass public static void createExecutor() { executor = ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); } @AfterClass public static void shutdownExecutor() throws InterruptedException { ConcurrencyUtil.shutdownExecutionService(executor); } /** * This test asserts that not exception is thrown when an algorithm object is initialized with * an empty graph. */ @Test public void testEmptyGraph() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); ContractionHierarchyBidirectionalDijkstra dijkstra = new ContractionHierarchyBidirectionalDijkstra<>(graph, executor); } @Test(expected = IllegalArgumentException.class) public void testSourceNotPresent() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(2); ContractionHierarchyBidirectionalDijkstra dijkstra = new ContractionHierarchyBidirectionalDijkstra<>(graph, executor); dijkstra.getPath(1, 2); } @Test(expected = IllegalArgumentException.class) public void testTargetNotPresent() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); ContractionHierarchyBidirectionalDijkstra dijkstra = new ContractionHierarchyBidirectionalDijkstra<>(graph, executor); dijkstra.getPath(1, 2); } @Test public void testNoPath() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); ContractionHierarchyBidirectionalDijkstra dijkstra = new ContractionHierarchyBidirectionalDijkstra<>(graph, executor); GraphPath path = dijkstra.getPath(1, 2); assertNull(path); } @Test public void testSimpleGraph() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 3); Graphs.addEdgeWithVertices(graph, 1, 4, 1); Graphs.addEdgeWithVertices(graph, 2, 3, 3); Graphs.addEdgeWithVertices(graph, 2, 5, 1); Graphs.addEdgeWithVertices(graph, 3, 6, 1); Graphs.addEdgeWithVertices(graph, 4, 5, 1); Graphs.addEdgeWithVertices(graph, 4, 7, 1); Graphs.addEdgeWithVertices(graph, 5, 6, 1); Graphs.addEdgeWithVertices(graph, 5, 8, 1); Graphs.addEdgeWithVertices(graph, 6, 9, 1); Graphs.addEdgeWithVertices(graph, 7, 8, 3); Graphs.addEdgeWithVertices(graph, 8, 9, 3); ContractionHierarchyBidirectionalDijkstra dijkstra = new ContractionHierarchyBidirectionalDijkstra<>(graph, executor); assertEquals(Collections.singletonList(1), dijkstra.getPath(1, 1).getVertexList()); assertEquals(Arrays.asList(1, 2), dijkstra.getPath(1, 2).getVertexList()); assertEquals(Arrays.asList(1, 4, 5, 6, 3), dijkstra.getPath(1, 3).getVertexList()); assertEquals(Arrays.asList(1, 4, 5, 6, 9), dijkstra.getPath(1, 9).getVertexList()); assertEquals(Arrays.asList(7, 4, 1), dijkstra.getPath(7, 1).getVertexList()); assertEquals(Arrays.asList(8, 5, 2), dijkstra.getPath(8, 2).getVertexList()); } @Test public void testRingGraph() { int size = 100; Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); fillRingGraph(graph, size); test(graph, 0); } @Test public void testOnRandomGraphs() { int numOfVertices = 100; int vertexDegree = 5; int numOfIterations = 20; int source = 0; Random random = new Random(SEED); for (int i = 0; i < numOfIterations; i++) { test(generateRandomGraph(numOfVertices, vertexDegree * numOfVertices, random), source); } } /** * Creates a connected graph with {@code size} vertices in which every vertex is connected to * only $2$ other vertices. * * @param graph graph * @param size needed size */ private void fillRingGraph(Graph graph, int size) { Random random = new Random(SEED); for (int i = 0; i < size; ++i) { graph.addVertex(i); } for (int i = 0; i < size; ++i) { graph.addEdge(i, (i + 1) % size); graph.setEdgeWeight(graph.getEdge(i, (i + 1) % size), random.nextDouble()); } } /** * Test correctness of {@link ContractionHierarchyBidirectionalDijkstra} on {@code graph} * starting at {@code source}. * * @param graph graph * @param source vertex in {@code graph} */ private void test(Graph graph, Integer source) { ShortestPathAlgorithm.SingleSourcePaths dijkstraShortestPaths = new DijkstraShortestPath<>(graph).getPaths(source); ContractionHierarchy data = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); ShortestPathAlgorithm.SingleSourcePaths contractionDijkstra = new ContractionHierarchyBidirectionalDijkstra<>(data).getPaths(source); assertEqualPaths(dijkstraShortestPaths, contractionDijkstra, graph.vertexSet()); } /** * Generates an instance or random graph with {@code numOfVertices} vertices and * {@code numOfEdges} edges. * * @param numOfVertices number of vertices * @param numOfEdges number of edges * @return generated graph */ private Graph generateRandomGraph( int numOfVertices, int numOfEdges, Random random) { DirectedWeightedPseudograph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); GraphGenerator generator = new GnmRandomGraphGenerator<>(numOfVertices, numOfEdges - numOfVertices + 1, SEED); generator.generateGraph(graph); makeConnected(graph); addEdgeWeights(graph, random); return graph; } /** * Makes {@code graph} connected. * * @param graph graph */ private void makeConnected(Graph graph) { Object[] vertices = graph.vertexSet().toArray(); for (int i = 0; i < vertices.length - 1; ++i) { graph.addEdge((Integer) vertices[i], (Integer) vertices[i + 1]); graph.addEdge((Integer) vertices[i + 1], (Integer) vertices[i]); } } /** * Sets edge weights to edges in {@code graph}. * * @param graph graph * @param random random numbers generator */ private void addEdgeWeights(Graph graph, Random random) { for (DefaultWeightedEdge edge : graph.edgeSet()) { graph.setEdgeWeight(edge, random.nextDouble()); } } /** * Checks computed single source shortest paths tree for equality, * * @param expected expected paths * @param actual actual paths * @param vertexSet vertices */ private void assertEqualPaths( ShortestPathAlgorithm.SingleSourcePaths expected, ShortestPathAlgorithm.SingleSourcePaths actual, Set vertexSet) { for (Integer sink : vertexSet) { GraphPath expectedPath = expected.getPath(sink); GraphPath actualPath = actual.getPath(sink); assertEquals(expectedPath, actualPath); } } } ContractionHierarchyPrecomputationTest.java000066400000000000000000000553701402514743400401250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.concurrent.ThreadPoolExecutor; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.*; import static org.junit.Assert.*; /** * Tests for the {@link ContractionHierarchyPrecomputation}. */ public class ContractionHierarchyPrecomputationTest { /** * Seed for random numbers generator used in tests. */ private static final long SEED = 19L; /** * Executor which is supplied to the {@link ContractionHierarchyPrecomputation} algorithm in * this test case. */ private static ThreadPoolExecutor executor; @BeforeClass public static void createExecutor() { executor = ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); } @AfterClass public static void shutdownExecutor() throws InterruptedException { ConcurrencyUtil.shutdownExecutionService(executor); } @Test public void testEmptyGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); ContractionHierarchyPrecomputation contractor = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor); ContractionHierarchy hierarchy = contractor.computeContractionHierarchy(); assertNotNull(hierarchy); Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> contractionMapping = hierarchy.getContractionMapping(); assertNotNull(contractionGraph); assertNotNull(contractionMapping); assertTrue(contractionGraph.vertexSet().isEmpty()); assertTrue(contractionGraph.edgeSet().isEmpty()); assertTrue(contractionMapping.keySet().isEmpty()); } @Test public void testDirectedGraph1() { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 1); Graphs.addEdgeWithVertices(graph, 2, 3, 1); ContractionHierarchyPrecomputation contractor = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor); ContractionHierarchy hierarchy = contractor.computeContractionHierarchy(); assertNotNull(hierarchy); Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> contractionMapping = hierarchy.getContractionMapping(); assertTrue(contractionGraph.getType().isDirected()); assertTrue(contractionGraph.getType().isSimple()); assertEquals(3, contractionGraph.vertexSet().size()); assertEquals(2, contractionGraph.edgeSet().size()); assertTrue( contractionGraph.containsEdge(contractionMapping.get(1), contractionMapping.get(2))); assertTrue( contractionGraph.containsEdge(contractionMapping.get(2), contractionMapping.get(3))); } @Test public void testDirectedGraph2() { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 1); Graphs.addEdgeWithVertices(graph, 2, 1, 1); Graphs.addEdgeWithVertices(graph, 2, 3, 1); Graphs.addEdgeWithVertices(graph, 3, 2, 1); Graphs.addEdgeWithVertices(graph, 3, 1, 1); Graphs.addEdgeWithVertices(graph, 1, 3, 1); ContractionHierarchyPrecomputation contractor = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor); ContractionHierarchy hierarchy = contractor.computeContractionHierarchy(); assertNotNull(hierarchy); Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); assertTrue(contractionGraph.getType().isDirected()); assertTrue(contractionGraph.getType().isSimple()); assertEquals(3, contractionGraph.vertexSet().size()); assertEquals(6, contractionGraph.edgeSet().size()); } @Test public void testDirectedGraph3() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 3, 1); Graphs.addEdgeWithVertices(graph, 2, 3, 1); Graphs.addEdgeWithVertices(graph, 3, 4, 1); Graphs.addEdgeWithVertices(graph, 3, 5, 1); ContractionHierarchyPrecomputation contractor = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor); ContractionHierarchy hierarchy = contractor.computeContractionHierarchy(); assertNotNull(hierarchy); Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> contractionMapping = hierarchy.getContractionMapping(); assertTrue(contractionGraph.getType().isDirected()); assertTrue(contractionGraph.getType().isSimple()); assertEquals(5, contractionGraph.vertexSet().size()); assertEquals(4, contractionGraph.edgeSet().size()); List> vertexPairs = Arrays.asList(Pair.of(1, 3), Pair.of(2, 3), Pair.of(3, 4), Pair.of(3, 5)); for (Pair pair : vertexPairs) { assertTrue( contractionGraph .containsEdge( contractionMapping.get(pair.getFirst()), contractionMapping.get(pair.getSecond()))); } } @Test public void testUndirectedGraph1() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 1); Graphs.addEdgeWithVertices(graph, 2, 3, 1); ContractionHierarchyPrecomputation contractor = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor); ContractionHierarchy hierarchy = contractor.computeContractionHierarchy(); assertNotNull(hierarchy); Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> contractionMapping = hierarchy.getContractionMapping(); assertTrue(contractionGraph.getType().isDirected()); assertTrue(contractionGraph.getType().isSimple()); assertEquals(3, contractionGraph.vertexSet().size()); assertEquals(4, contractionGraph.edgeSet().size()); List> vertexPairs = Arrays.asList(Pair.of(1, 2), Pair.of(2, 1), Pair.of(2, 3), Pair.of(3, 2)); for (Pair pair : vertexPairs) { assertTrue( contractionGraph .containsEdge( contractionMapping.get(pair.getFirst()), contractionMapping.get(pair.getSecond()))); } } @Test public void testUndirectedGraph2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 1); Graphs.addEdgeWithVertices(graph, 2, 3, 1); Graphs.addEdgeWithVertices(graph, 3, 1, 1); ContractionHierarchyPrecomputation contractor = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor); ContractionHierarchy hierarchy = contractor.computeContractionHierarchy(); assertNotNull(hierarchy); Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); assertEquals(3, graph.vertexSet().size()); assertTrue(contractionGraph.getType().isDirected()); assertTrue(contractionGraph.getType().isSimple()); assertEquals(3, contractionGraph.vertexSet().size()); assertEquals(6, contractionGraph.edgeSet().size()); } @Test public void testUndirectedGraph3() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 3, 1); Graphs.addEdgeWithVertices(graph, 2, 3, 1); Graphs.addEdgeWithVertices(graph, 3, 4, 1); Graphs.addEdgeWithVertices(graph, 3, 5, 1); ContractionHierarchyPrecomputation contractor = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor); ContractionHierarchy hierarchy = contractor.computeContractionHierarchy(); assertNotNull(hierarchy); Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> contractionMapping = hierarchy.getContractionMapping(); assertTrue(contractionGraph.getType().isDirected()); assertTrue(contractionGraph.getType().isSimple()); assertEquals(5, contractionGraph.vertexSet().size()); assertEquals(8, contractionGraph.edgeSet().size()); List> vertexPairs = Arrays .asList( Pair.of(1, 3), Pair.of(3, 1), Pair.of(2, 3), Pair.of(3, 2), Pair.of(3, 4), Pair.of(4, 3), Pair.of(3, 5), Pair.of(5, 3)); for (Pair pair : vertexPairs) { assertTrue( contractionGraph .containsEdge( contractionMapping.get(pair.getFirst()), contractionMapping.get(pair.getSecond()))); } } @Test public void testUndirectedGraph4() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 3); Graphs.addEdgeWithVertices(graph, 1, 4, 1); Graphs.addEdgeWithVertices(graph, 2, 3, 3); Graphs.addEdgeWithVertices(graph, 2, 5, 1); Graphs.addEdgeWithVertices(graph, 3, 6, 1); Graphs.addEdgeWithVertices(graph, 4, 5, 1); Graphs.addEdgeWithVertices(graph, 4, 7, 1); Graphs.addEdgeWithVertices(graph, 5, 6, 1); Graphs.addEdgeWithVertices(graph, 5, 8, 1); Graphs.addEdgeWithVertices(graph, 6, 9, 1); Graphs.addEdgeWithVertices(graph, 7, 8, 3); Graphs.addEdgeWithVertices(graph, 8, 9, 3); ContractionHierarchyPrecomputation contractor = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor); ContractionHierarchy hierarchy = contractor.computeContractionHierarchy(); assertNotNull(hierarchy); Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> contractionMapping = hierarchy.getContractionMapping(); assertTrue(contractionGraph.getType().isDirected()); assertTrue(contractionGraph.getType().isSimple()); assertEquals(9, contractionGraph.vertexSet().size()); assertEquals(24, contractionGraph.edgeSet().size()); List> vertexPairs = Arrays .asList( Pair.of(1, 2), Pair.of(2, 1), Pair.of(1, 4), Pair.of(4, 1), Pair.of(2, 3), Pair.of(3, 2), Pair.of(2, 5), Pair.of(5, 2), Pair.of(3, 6), Pair.of(6, 3), Pair.of(4, 5), Pair.of(5, 4), Pair.of(4, 7), Pair.of(7, 4), Pair.of(5, 6), Pair.of(6, 5), Pair.of(5, 8), Pair.of(8, 5), Pair.of(6, 9), Pair.of(9, 6), Pair.of(7, 8), Pair.of(8, 7), Pair.of(8, 9), Pair.of(9, 8)); for (Pair pair : vertexPairs) { assertTrue( contractionGraph .containsEdge( contractionMapping.get(pair.getFirst()), contractionMapping.get(pair.getSecond()))); } } @Test public void testPseudograph() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 1); Graphs.addEdgeWithVertices(graph, 1, 2, 2); Graphs.addEdgeWithVertices(graph, 1, 2, 3); Graphs.addEdgeWithVertices(graph, 2, 1, 1); Graphs.addEdgeWithVertices(graph, 2, 1, 2); Graphs.addEdgeWithVertices(graph, 2, 2, 1); Graphs.addEdgeWithVertices(graph, 2, 2, 2); ContractionHierarchyPrecomputation contractor = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor); ContractionHierarchy hierarchy = contractor.computeContractionHierarchy(); assertNotNull(hierarchy); Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> contractionMapping = hierarchy.getContractionMapping(); assertTrue(contractionGraph.getType().isDirected()); assertTrue(contractionGraph.getType().isSimple()); assertEquals(2, contractionGraph.vertexSet().size()); assertEquals(2, contractionGraph.edgeSet().size()); assertTrue( contractionGraph.containsEdge(contractionMapping.get(1), contractionMapping.get(2))); assertTrue( contractionGraph.containsEdge(contractionMapping.get(2), contractionMapping.get(1))); assertEquals( 1, contractionGraph .getEdgeWeight( contractionGraph.getEdge(contractionMapping.get(1), contractionMapping.get(2))), 1e-9); assertEquals( 1, contractionGraph .getEdgeWeight( contractionGraph.getEdge(contractionMapping.get(2), contractionMapping.get(1))), 1e-9); } @Test public void testOnRandomGraphs() { int numOfGraphs = 20; int numOfVertices = 30; double probability = 0.2; for (int i = 0; i < numOfGraphs; ++i) { DirectedWeightedPseudograph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); generateRandomGraph(graph, numOfVertices, probability); ContractionHierarchy hierarchy = new ContractionHierarchyPrecomputation<>(graph, executor) .computeContractionHierarchy(); assertCorrectMapping(graph, hierarchy); assertNoEdgesRemoved(graph, hierarchy); assertCorrectEdgeWeights(graph, hierarchy); assertCorrectContractionEdges(graph, hierarchy); } } /** * Asserts that {@code mapping} includes all vertices in {@code graph} as keys, all vertices in * {@code contractionGraph} as values and the values in {@code mapping} are unique. * * @param graph graph * @param hierarchy contraction hierarchy */ private void assertCorrectMapping( Graph graph, ContractionHierarchy hierarchy) { Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> mapping = hierarchy.getContractionMapping(); assertEquals(graph.vertexSet(), mapping.keySet()); Set> uniqueValues = new HashSet<>(mapping.values()); assertEquals(graph.vertexSet().size(), uniqueValues.size()); assertEquals(contractionGraph.vertexSet(), uniqueValues); } /** * Asserts that for every edge in {@code graph} between $s$ and $t$ there exists an edge in * {@code contractionGraph} between contracted $s$ and $t$. * * @param graph graph * @param hierarchy contraction hierarchy */ private void assertNoEdgesRemoved( Graph graph, ContractionHierarchy hierarchy) { Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> mapping = hierarchy.getContractionMapping(); for (DefaultWeightedEdge edge : graph.edgeSet()) { Integer source = graph.getEdgeSource(edge); Integer target = graph.getEdgeTarget(edge); assertTrue(contractionGraph.containsEdge(mapping.get(source), mapping.get(target))); } } /** * Asserts that every edge in {@code graph} between $s$ and $t$ has greater or equal weight than * an edge in {@code contractedGraph} between the contracted $s$ and $t$. * * @param graph graph * @param hierarchy contraction hierarchy */ private void assertCorrectEdgeWeights( Graph graph, ContractionHierarchy hierarchy) { Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> mapping = hierarchy.getContractionMapping(); for (DefaultWeightedEdge edge : graph.edgeSet()) { Integer source = graph.getEdgeSource(edge); Integer target = graph.getEdgeTarget(edge); ContractionVertex contractedSource = mapping.get(source); ContractionVertex contractedTarget = mapping.get(target); double oldWeight = graph.getEdgeWeight(edge); double newWeight = contractionGraph .getEdgeWeight(contractionGraph.getEdge(contractedSource, contractedTarget)); assertTrue(oldWeight >= newWeight); } } /** * Asserts for every edge $e1$ in {@code contractionGraph} which is not a shortcuts and contains * an edge $e2$ of the original {@code graph} that weight og $e1$ is equal to the weight of * $e2$. Secondly for vertices in the {@code graph} which correspond to source $s$ and target * $t$ of $e1$ asserts that there is an edge $e2$ between them and the weight of $e2$ is minimum * among all edges between $s$ and $t$. * * @param graph graph * @param hierarchy contraction hierarchy */ private void assertCorrectContractionEdges( Graph graph, ContractionHierarchy hierarchy) { Graph, ContractionEdge> contractionGraph = hierarchy.getContractionGraph(); Map> mapping = hierarchy.getContractionMapping(); Map, Integer> inverseMapping = new HashMap<>(); for (Map.Entry> entry : mapping.entrySet()) { inverseMapping.put(entry.getValue(), entry.getKey()); } for (ContractionEdge contractionEdge : contractionGraph.edgeSet()) { if (contractionEdge.edge != null) { // it is not a shortcut edge double edgeWeight = graph.getEdgeWeight(contractionEdge.edge); assertEquals(edgeWeight, contractionGraph.getEdgeWeight(contractionEdge), 1e-9); ContractionVertex contractedSource = contractionGraph.getEdgeSource(contractionEdge); ContractionVertex contractedTarget = contractionGraph.getEdgeTarget(contractionEdge); Integer source = inverseMapping.get(contractedSource); Integer target = inverseMapping.get(contractedTarget); boolean containsEdge = false; for (DefaultWeightedEdge edge : graph.getAllEdges(source, target)) { assertTrue(graph.getEdgeWeight(edge) >= edgeWeight); if (edge.equals(contractionEdge.edge)) { containsEdge = true; } } assertTrue(containsEdge); } } } /** * Generates random graph from the $G(n, p)$ model. * * @param graph graph instance for the generator * @param n the number of nodes * @param p the edge probability */ private void generateRandomGraph(Graph graph, int n, double p) { Random random = new Random(SEED); GraphGenerator generator = new GnpRandomGraphGenerator<>(n, p, SEED); generator.generateGraph(graph); graph.edgeSet().forEach(e -> graph.setEdgeWeight(e, random.nextDouble())); } } DefaultManyToManyShortestPathsTest.java000066400000000000000000000044311402514743400371360ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; /** * Tests for {@link DefaultManyToManyShortestPaths}. * * @author Semen Chudakov */ public class DefaultManyToManyShortestPathsTest extends BaseManyToManyShortestPathsTest { @Test public void testEmptyGraph() { super.testEmptyGraph(); } @Test(expected = NullPointerException.class) public void testSourcesIsNull() { super.testSourcesIsNull(); } @Test(expected = NullPointerException.class) public void testTargetsIsNull() { super.testTargetsIsNull(); } @Test public void testNoPath() { super.testNoPath(); } @Test public void testDifferentSourcesAndTargetsSimpleGraph() { super.testDifferentSourcesAndTargetsSimpleGraph(); } @Test public void testDifferentSourcesAndTargetsMultigraph() { super.testDifferentSourcesAndTargetsMultigraph(); } @Test public void testSourcesEqualTargetsSimpleGraph() { super.testSourcesEqualTargetsSimpleGraph(); } @Test public void testSourcesEqualTargetsMultigraph() { super.testSourcesEqualTargetsMultigraph(); } @Test public void testOnRandomGraphs() { super.testOnRandomGraphs(30, 5, new int[][] { { 5, 10 }, { 5, 5 }, { 10, 5 } }, 10); } @Override protected ManyToManyShortestPathsAlgorithm getAlgorithm( Graph graph) { return new DefaultManyToManyShortestPaths<>(graph); } } DeltaSteppingShortestPathTest.java000066400000000000000000000253171402514743400361630ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2018-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.Triple; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.rules.*; import java.util.*; import java.util.concurrent.ThreadPoolExecutor; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Test case for {@link DeltaSteppingShortestPath}. * * @author Semen Chudakov */ public class DeltaSteppingShortestPathTest { /** * Seed value which is used to generate random graphs by * {@code generateRandomGraph(Graph, int, double)} method. */ private static final long SEED = 17l; /** * Executor which is supplied to {@link DeltaSteppingShortestPath} in this test case. */ private static ThreadPoolExecutor executor; @BeforeClass public static void createExecutor() { executor = ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); } @AfterClass public static void shutdownExecutor() throws InterruptedException { ConcurrencyUtil.shutdownExecutionService(executor); } private static final String S = "S"; private static final String T = "T"; private static final String Y = "Y"; private static final String X = "X"; private static final String Z = "Z"; @Rule public final ExpectedException exception = ExpectedException.none(); @Test public void testEmptyGraph() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.addVertex(S); new DeltaSteppingShortestPath<>(graph, executor).getPaths(S); } @Test public void testNegativeWeightEdge() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph, Arrays.asList(S, T)); Graphs.addEdge(graph, S, T, -10.0); exception.expect(IllegalArgumentException.class); new DeltaSteppingShortestPath<>(graph, executor).getPaths(S); } @Test public void testLineGraph() { int maxNumberOfVertices = 10; for (int numberOfVertices = 2; numberOfVertices < maxNumberOfVertices; ++numberOfVertices) { Triple, List, List> testInput = generateLineGraphTestInput(numberOfVertices); Graph graph = testInput.getFirst(); List vertices = testInput.getSecond(); List edges = testInput.getThird(); GraphPath shortestPath = new DeltaSteppingShortestPath<>(graph, executor).getPath(0, numberOfVertices - 1); assertEquals(numberOfVertices - 1, shortestPath.getWeight(), 1e-9); assertEquals(vertices, shortestPath.getVertexList()); assertEquals(edges, shortestPath.getEdgeList()); } } @Test public void testGetPath() { Graph graph = generateSimpleGraph(); assertEquals( Arrays.asList(S), new DeltaSteppingShortestPath<>(graph, executor).getPath(S, S).getVertexList()); assertEquals( Arrays.asList(S, Y, T), new DeltaSteppingShortestPath<>(graph, executor).getPath(S, T).getVertexList()); assertEquals( Arrays.asList(S, Y, T, X), new DeltaSteppingShortestPath<>(graph, executor).getPath(S, X).getVertexList()); assertEquals( Arrays.asList(S, Y), new DeltaSteppingShortestPath<>(graph, executor).getPath(S, Y).getVertexList()); assertEquals( Arrays.asList(S, Y, Z), new DeltaSteppingShortestPath<>(graph, executor).getPath(S, Z).getVertexList()); } @Test public void testGetPaths1() { Graph graph = generateSimpleGraph(); ShortestPathAlgorithm.SingleSourcePaths paths1 = new DeltaSteppingShortestPath<>(graph, 0.999, executor).getPaths(S); assertEquals(0d, paths1.getWeight(S), 1e-9); assertEquals(8d, paths1.getWeight(T), 1e-9); assertEquals(5d, paths1.getWeight(Y), 1e-9); assertEquals(9d, paths1.getWeight(X), 1e-9); assertEquals(7d, paths1.getWeight(Z), 1e-9); ShortestPathAlgorithm.SingleSourcePaths paths2 = new DeltaSteppingShortestPath<>(graph, 5.0, executor).getPaths(S); assertEquals(0d, paths2.getWeight(S), 1e-9); assertEquals(8d, paths2.getWeight(T), 1e-9); assertEquals(5d, paths2.getWeight(Y), 1e-9); assertEquals(9d, paths2.getWeight(X), 1e-9); assertEquals(7d, paths2.getWeight(Z), 1e-9); ShortestPathAlgorithm.SingleSourcePaths path3 = new DeltaSteppingShortestPath<>(graph, 11.0, executor).getPaths(S); assertEquals(0d, path3.getWeight(S), 1e-9); assertEquals(8d, path3.getWeight(T), 1e-9); assertEquals(5d, path3.getWeight(Y), 1e-9); assertEquals(9d, path3.getWeight(X), 1e-9); assertEquals(7d, path3.getWeight(Z), 1e-9); ShortestPathAlgorithm.SingleSourcePaths path4 = new DeltaSteppingShortestPath<>(graph, executor).getPaths(S); assertEquals(0d, path4.getWeight(S), 1e-9); assertEquals(8d, path4.getWeight(T), 1e-9); assertEquals(5d, path4.getWeight(Y), 1e-9); assertEquals(9d, path4.getWeight(X), 1e-9); assertEquals(7d, path4.getWeight(Z), 1e-9); } @Test public void testGetPaths2() { int numOfVertices = 100; int vertexDegree = 50; int numOfIterations = 30; int source = 0; Random random = new Random(SEED); for (int i = 0; i < numOfIterations; i++) { Graph graph = generateRandomGraph(numOfVertices, vertexDegree * numOfVertices, random); test(graph, source); } } private void test(Graph graph, Integer source) { ShortestPathAlgorithm.SingleSourcePaths dijkstraShortestPaths = new DijkstraShortestPath<>(graph).getPaths(source); ShortestPathAlgorithm.SingleSourcePaths deltaSteppingShortestPaths = new DeltaSteppingShortestPath<>(graph, executor).getPaths(source); assertEqualPaths(dijkstraShortestPaths, deltaSteppingShortestPaths, graph.vertexSet()); } private Graph generateSimpleGraph() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph, Arrays.asList(S, T, Y, X, Z)); Graphs.addEdge(graph, S, T, 10); Graphs.addEdge(graph, S, Y, 5); Graphs.addEdge(graph, T, Y, 2); Graphs.addEdge(graph, T, X, 1); Graphs.addEdge(graph, Y, T, 3); Graphs.addEdge(graph, Y, Z, 2); Graphs.addEdge(graph, Y, X, 9); Graphs.addEdge(graph, X, Z, 4); Graphs.addEdge(graph, Z, X, 6); Graphs.addEdge(graph, Z, S, 7); return graph; } private Triple, List, List> generateLineGraphTestInput(int numberOfVertices) { Graph result = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); List vertices = new ArrayList<>(numberOfVertices); List edges = new ArrayList<>(numberOfVertices - 1); for (int i = 0; i < numberOfVertices - 1; ++i) { DefaultWeightedEdge edge = Graphs.addEdgeWithVertices(result, i, i + 1); vertices.add(i); edges.add(edge); } vertices.add(numberOfVertices - 1); return Triple.of(result, vertices, edges); } private Graph generateRandomGraph( int numOfVertices, int numOfEdges, Random random) { DirectedWeightedPseudograph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); GraphGenerator generator = new GnmRandomGraphGenerator<>( numOfVertices, numOfEdges - numOfVertices + 1, random, true, true); generator.generateGraph(graph); makeConnected(graph); addEdgeWeights(graph, random); return graph; } private void makeConnected(Graph graph) { Object[] vertices = graph.vertexSet().toArray(); for (int i = 0; i < vertices.length - 1; i++) { graph.addEdge((Integer) vertices[i], (Integer) vertices[i + 1]); } } private void addEdgeWeights(Graph graph, Random random) { for (DefaultWeightedEdge edge : graph.edgeSet()) { graph.setEdgeWeight(edge, random.nextDouble()); } } private void assertEqualPaths( ShortestPathAlgorithm.SingleSourcePaths expected, ShortestPathAlgorithm.SingleSourcePaths actual, Set vertexSet) { for (Integer sink : vertexSet) { GraphPath path1 = expected.getPath(sink); GraphPath path2 = actual.getPath(sink); if (path1 == null) { assertNull(path2); } else { assertEquals( expected.getPath(sink).getWeight(), actual.getPath(sink).getWeight(), 1e-9); } } } } DijkstraClosestFirstIteratorTest.java000066400000000000000000000062321402514743400366740ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; /** * @author Dimitrios Michail */ public class DijkstraClosestFirstIteratorTest { @Test public void testUndirected() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList("1", "2", "3", "4", "5")); g.setEdgeWeight(g.addEdge("1", "2"), 2.0); g.setEdgeWeight(g.addEdge("1", "3"), 3.0); g.setEdgeWeight(g.addEdge("1", "5"), 100.0); g.setEdgeWeight(g.addEdge("2", "4"), 5.0); g.setEdgeWeight(g.addEdge("3", "4"), 20.0); g.setEdgeWeight(g.addEdge("4", "5"), 5.0); DijkstraClosestFirstIterator it = new DijkstraClosestFirstIterator<>(g, "3"); assertEquals("3", it.next()); assertEquals("1", it.next()); assertEquals("2", it.next()); assertEquals("4", it.next()); assertEquals("5", it.next()); assertFalse(it.hasNext()); DijkstraClosestFirstIterator it1 = new DijkstraClosestFirstIterator<>(g, "1"); assertEquals("1", it1.next()); assertEquals("2", it1.next()); assertEquals("3", it1.next()); assertEquals("4", it1.next()); assertEquals("5", it1.next()); assertFalse(it1.hasNext()); DijkstraClosestFirstIterator it2 = new DijkstraClosestFirstIterator<>(g, "1", 11.0); assertEquals("1", it2.next()); assertEquals("2", it2.next()); assertEquals("3", it2.next()); assertEquals("4", it2.next()); assertFalse(it2.hasNext()); DijkstraClosestFirstIterator it3 = new DijkstraClosestFirstIterator<>(g, "3", 12.0); assertEquals("3", it3.next()); assertEquals("1", it3.next()); assertEquals("2", it3.next()); assertEquals("4", it3.next()); assertFalse(it3.hasNext()); SingleSourcePaths paths3 = it3.getPaths(); assertEquals(10.0, paths3.getPath("4").getWeight(), 1e-9); assertEquals(5.0, paths3.getPath("2").getWeight(), 1e-9); assertEquals(3.0, paths3.getPath("1").getWeight(), 1e-9); } } DijkstraManyToManyShortestPathsTest.java000066400000000000000000000044431402514743400373300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; /** * Tests for {@link DijkstraManyToManyShortestPaths}. * * @author Semen Chudakov */ public class DijkstraManyToManyShortestPathsTest extends BaseManyToManyShortestPathsTest { @Test public void testEmptyGraph() { super.testEmptyGraph(); } @Test(expected = NullPointerException.class) public void testSourcesIsNull() { super.testSourcesIsNull(); } @Test(expected = NullPointerException.class) public void testTargetsIsNull() { super.testTargetsIsNull(); } @Test public void testNoPath() { super.testNoPath(); } @Test public void testDifferentSourcesAndTargetsSimpleGraph() { super.testDifferentSourcesAndTargetsSimpleGraph(); } @Test public void testDifferentSourcesAndTargetsMultigraph() { super.testDifferentSourcesAndTargetsMultigraph(); } @Test public void testSourcesEqualTargetsSimpleGraph() { super.testSourcesEqualTargetsSimpleGraph(); } @Test public void testSourcesEqualTargetsMultigraph() { super.testSourcesEqualTargetsMultigraph(); } @Test public void testOnRandomGraphs() { super.testOnRandomGraphs(100, 20, new int[][] { { 50, 30 }, { 40, 40 }, { 30, 50 } }, 50); } @Override protected ManyToManyShortestPathsAlgorithm getAlgorithm( Graph graph) { return new DijkstraManyToManyShortestPaths<>(graph); } } DijkstraShortestPathTest.java000066400000000000000000000135421402514743400351700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * . * * @author John V. Sichi */ public class DijkstraShortestPathTest extends ShortestPathTestCase { // ~ Methods ---------------------------------------------------------------- /** * . */ @Test public void testConstructor() { GraphPath path; Graph g = create(); path = new DijkstraShortestPath<>(g, Double.POSITIVE_INFINITY).getPath(V3, V4); assertEquals(Arrays.asList(e13, e12, e24), path.getEdgeList()); assertEquals(10.0, path.getWeight(), 0); path = new DijkstraShortestPath<>(g, 7.0).getPath(V3, V4); assertNull(path); } @Override protected List findPathBetween( Graph g, String src, String dest) { return new DijkstraShortestPath<>(g).getPath(src, dest).getEdgeList(); } @Test public void testShortestPathTree() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(V1, V2, V3, V4, V5)); DefaultWeightedEdge we12 = g.addEdge(V1, V2); DefaultWeightedEdge we24 = g.addEdge(V2, V4); DefaultWeightedEdge we13 = g.addEdge(V1, V3); DefaultWeightedEdge we32 = g.addEdge(V3, V2); DefaultWeightedEdge we34 = g.addEdge(V3, V4); g.setEdgeWeight(we12, 3.0); g.setEdgeWeight(we24, 1.0); g.setEdgeWeight(we13, 1.0); g.setEdgeWeight(we32, 1.0); g.setEdgeWeight(we34, 3.0); SingleSourcePaths pathsTree = new DijkstraShortestPath<>(g).getPaths(V1); assertEquals(g, pathsTree.getGraph()); assertEquals(V1, pathsTree.getSourceVertex()); assertEquals(0d, pathsTree.getWeight(V1), 1e-9); assertEquals(2d, pathsTree.getWeight(V2), 1e-9); assertEquals(1d, pathsTree.getWeight(V3), 1e-9); assertEquals(3d, pathsTree.getWeight(V4), 1e-9); assertEquals(Double.POSITIVE_INFINITY, pathsTree.getWeight(V5), 1e-9); GraphPath p11 = pathsTree.getPath(V1); assertEquals(V1, p11.getStartVertex()); assertEquals(V1, p11.getEndVertex()); assertEquals(0d, p11.getWeight(), 1e-9); assertTrue(p11.getEdgeList().isEmpty()); GraphPath p12 = pathsTree.getPath(V2); assertEquals(V1, p12.getStartVertex()); assertEquals(V2, p12.getEndVertex()); assertEquals(2d, p12.getWeight(), 1e-9); assertEquals(Arrays.asList(we13, we32), p12.getEdgeList()); GraphPath p13 = pathsTree.getPath(V3); assertEquals(V1, p13.getStartVertex()); assertEquals(V3, p13.getEndVertex()); assertEquals(1d, p13.getWeight(), 1e-9); assertEquals(Collections.singletonList(we13), p13.getEdgeList()); GraphPath p14 = pathsTree.getPath(V4); assertEquals(V1, p14.getStartVertex()); assertEquals(V4, p14.getEndVertex()); assertEquals(3d, p14.getWeight(), 1e-9); assertEquals(Arrays.asList(we13, we32, we24), p14.getEdgeList()); GraphPath p15 = pathsTree.getPath(V5); assertNull(p15); } @Test public void testGetPathWeight() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(V1, V2, V3, V4, V5)); DefaultWeightedEdge we12 = g.addEdge(V1, V2); DefaultWeightedEdge we24 = g.addEdge(V2, V4); DefaultWeightedEdge we13 = g.addEdge(V1, V3); DefaultWeightedEdge we32 = g.addEdge(V3, V2); DefaultWeightedEdge we34 = g.addEdge(V3, V4); g.setEdgeWeight(we12, 3.0); g.setEdgeWeight(we24, 1.0); g.setEdgeWeight(we13, 1.0); g.setEdgeWeight(we32, 1.0); g.setEdgeWeight(we34, 3.0); assertEquals(0d, new DijkstraShortestPath<>(g).getPathWeight(V1, V1), 0); assertEquals(2d, new DijkstraShortestPath<>(g).getPathWeight(V1, V2), 0); assertEquals(1d, new DijkstraShortestPath<>(g).getPathWeight(V1, V3), 0); assertEquals(3d, new DijkstraShortestPath<>(g).getPathWeight(V1, V4), 0); assertEquals( Double.POSITIVE_INFINITY, new DijkstraShortestPath<>(g).getPathWeight(V1, V5), 0); } @Test public void testNonNegativeWeights() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(V1, V2)); DefaultWeightedEdge we12 = g.addEdge(V1, V2); g.setEdgeWeight(we12, -100.0); try { new DijkstraShortestPath<>(g).getPath(V1, V2); fail("No!"); } catch (IllegalArgumentException e) { } } } EppsteinKShortestPathTest.java000066400000000000000000000111221402514743400353070ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Tests for {@link EppsteinKShortestPath} class. * * @author Semen Chudakov */ public class EppsteinKShortestPathTest { final int[][] simpleGraph1 = { { 1, 2, 2 }, { 2, 3, 20 }, { 3, 4, 14 }, { 1, 5, 13 }, { 2, 6, 27 }, { 3, 7, 14 }, { 4, 8, 15 }, { 5, 6, 9 }, { 6, 7, 10 }, { 7, 8, 25 }, { 5, 9, 15 }, { 6, 10, 20 }, { 7, 11, 12 }, { 8, 12, 7 }, { 9, 10, 18 }, { 10, 11, 8 }, { 11, 12, 11 } }; final int[][] cyclicGraph3 = { { 1, 2, 1 }, { 2, 3, 1 }, { 3, 4, 1 }, { 3, 4, 1 }, { 4, 3, 1 }, { 4, 5, 1 }, { 5, 4, 1 } }; @Test(expected = IllegalArgumentException.class) public void testNegativeK() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2); new EppsteinKShortestPath<>(graph).getPaths(1, 2, -1); } /** * If k equals $0$ and there is no paths in the graph between source and target, no exception * should be thrown and an empty list should be returned. */ @Test public void testKEqualsZero() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); List> paths = new EppsteinKShortestPath<>(graph).getPaths(1, 2, 0); assertEquals(0, paths.size()); } @Test(expected = IllegalArgumentException.class) public void testNoSourceGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(2); new EppsteinKShortestPath<>(graph).getPaths(1, 2, 1); } @Test(expected = IllegalArgumentException.class) public void testNoSinkGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); new EppsteinKShortestPath<>(graph).getPaths(1, 2, 1); } @Test public void testCyclicGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, cyclicGraph3); List> paths = new EppsteinKShortestPath<>(graph).getPaths(1, 3, 6); List weights = Arrays.asList(2.0, 4.0, 6.0, 6.0, 8.0, 8.0); assertSameWeights(paths, weights); } /** * If the specified k is greater than the total number of paths between source and target, a * list of all existing paths should be returned and no exception should be thrown. */ @Test public void testLessThanKPaths() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, simpleGraph1); List> paths = new EppsteinKShortestPath<>(graph).getPaths(1, 12, 12); List weights = Arrays.asList(55.0, 58.0, 59.0, 61.0, 62.0, 64.0, 65.0, 68.0, 68.0, 71.0); assertSameWeights(paths, weights); } private void assertSameWeights( List> paths, List weights) { assertEquals(weights.size(), paths.size()); for (int i = 0; i < paths.size(); i++) { assertEquals(weights.get(i), paths.get(i).getWeight(), 1e-9); } } private void readGraph(Graph graph, int[][] representation) { for (int[] ints : representation) { Graphs.addEdgeWithVertices(graph, ints[0], ints[1], ints[2]); } } } EppsteinShortestPathIteratorTest.java000066400000000000000000000361431402514743400367200ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; /** * Tests for {@link EppsteinShortestPathIterator}. * * @author Semen Chudakov */ public class EppsteinShortestPathIteratorTest { /** * Seed value which is used to generate random graphs by * {@code getRandomGraph(Graph, int, double)} method. */ private static final long SEED = 13L; /** * Number of path to iterate over for each random graph in the * {@code testOnRandomGraph(Graph, Integer, Integer)} method. */ private static final int NUMBER_OF_PATH_TO_ITERATE = 10; private final int[][] simpleGraph1 = { { 1, 2, 2 }, { 2, 3, 20 }, { 3, 4, 14 }, { 1, 5, 13 }, { 2, 6, 27 }, { 3, 7, 14 }, { 4, 8, 15 }, { 5, 6, 9 }, { 6, 7, 10 }, { 7, 8, 25 }, { 5, 9, 15 }, { 6, 10, 20 }, { 7, 11, 12 }, { 8, 12, 7 }, { 9, 10, 18 }, { 10, 11, 8 }, { 11, 12, 11 } }; private final int[][] simpleGraph2 = { { 1, 2, 5 }, { 1, 3, 6 }, { 2, 3, 7 }, { 2, 4, 8 }, { 3, 4, 9 } }; private final int[][] simpleGraph3 = { { 0, 1, 6 }, { 2, 0, 9 }, { 4, 0, 4 }, { 0, 5, 6 }, { 0, 6, 5 }, { 2, 1, 1 }, { 1, 4, 9 }, { 4, 1, 2 }, { 1, 5, 7 }, { 1, 6, 5 }, { 2, 4, 1 }, { 2, 5, 0 }, { 3, 4, 4 }, { 4, 3, 4 }, { 4, 5, 6 }, { 5, 4, 8 }, { 4, 6, 3 }, { 6, 5, 0 } }; private final int[][] cyclicGraph1 = { { 1, 2, 1 }, { 2, 1, 1 } }; private final int[][] cyclicGraph2 = { { 1, 2, 1 }, { 2, 3, 1 }, { 3, 4, 1 }, { 4, 1, 1 }, { 1, 5, 2 }, { 5, 6, 2 }, { 6, 7, 2 }, { 7, 1, 2 }, { 3, 6, 2 }, { 6, 3, 2 } }; private final int[][] cyclicGraph3 = { { 1, 2, 1 }, { 2, 3, 1 }, { 3, 4, 1 }, { 3, 4, 1 }, { 4, 3, 1 }, { 4, 5, 1 }, { 5, 4, 1 } }; private final int[][] restHeapGraph = { { 1, 2, 2 }, { 1, 3, 3 }, { 1, 4, 4 }, { 1, 5, 5 }, { 1, 6, 6 }, { 1, 7, 7 }, { 1, 8, 8 }, { 1, 9, 9 }, { 2, 10, 1 }, { 3, 10, 1 }, { 4, 10, 1 }, { 5, 10, 1 }, { 6, 10, 1 }, { 7, 10, 1 }, { 8, 10, 1 }, { 9, 10, 1 } }; private final int[][] notShortestPathEdgesGraph = { { 1, 2, 1 }, { 1, 3, 3 }, { 1, 4, 4 }, { 1, 5, 5 }, { 1, 6, 6 }, { 1, 7, 7 }, { 1, 8, 8 }, { 1, 9, 9 } }; @Test(expected = IllegalArgumentException.class) public void testNoSourceGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(2); new EppsteinShortestPathIterator<>(graph, 1, 2); } @Test(expected = IllegalArgumentException.class) public void testNoSinkGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); new EppsteinShortestPathIterator<>(graph, 1, 2); } @Test public void testNoPathInGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, 1, 2); assertFalse(it.hasNext()); } @Test(expected = NoSuchElementException.class) public void testNoPathLeft() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, 1, 2); assertFalse(it.hasNext()); it.next(); } @Test public void testSourceEqualsTarget() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); Integer source = 1; Integer target = 1; EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 0.0, false); } @Test public void testNoSidetracksInGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge a = Graphs.addEdgeWithVertices(graph, 1, 2, 1.0); DefaultWeightedEdge b = Graphs.addEdgeWithVertices(graph, 2, 3, 1.0); EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, 1, 3); assertTrue(it.hasNext()); GraphPath path = it.next(); assertEquals(2.0, path.getWeight(), 1e-9); assertEquals(Arrays.asList(a, b), path.getEdgeList()); assertFalse(it.hasNext()); } @Test public void testSimpleGraph1() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, simpleGraph1); Integer source = 1; Integer target = 12; EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 55.0, true); verifyNextPath(it, 58.0, true); verifyNextPath(it, 59.0, true); verifyNextPath(it, 61.0, true); verifyNextPath(it, 62.0, true); verifyNextPath(it, 64.0, true); verifyNextPath(it, 65.0, true); verifyNextPath(it, 68.0, true); verifyNextPath(it, 68.0, true); verifyNextPath(it, 71.0, false); } @Test public void testSimpleGraph2() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, simpleGraph2); Integer source = 1; Integer target = 4; EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 13.0, true); verifyNextPath(it, 15.0, true); verifyNextPath(it, 21.0, false); } @Test public void testSimpleGraph3() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, simpleGraph3); Integer source = 5; Integer target = 4; EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 8.0, true); verifyNextPath(it, 16.0, true); verifyNextPath(it, 19.0, true); verifyNextPath(it, 19.0, true); verifyNextPath(it, 22.0, true); verifyNextPath(it, 23.0, true); verifyNextPath(it, 24.0, true); verifyNextPath(it, 25.0, true); verifyNextPath(it, 25.0, true); verifyNextPath(it, 26.0, true); } @Test public void testCyclicGraph1() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Integer source = 1; Integer target = 2; readGraph(graph, cyclicGraph1); EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 1.0, true); verifyNextPath(it, 3.0, true); verifyNextPath(it, 5.0, true); verifyNextPath(it, 7.0, true); verifyNextPath(it, 9.0, true); // and so on } @Test public void testCyclicGraph2() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, cyclicGraph2); Integer source = 1; Integer target = 6; EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); for (int i = 0; i < 2; i++) { verifyNextPath(it, 4.0, true); } for (int i = 0; i < 4; i++) { verifyNextPath(it, 8.0, true); } for (int i = 0; i < 12; i++) { verifyNextPath(it, 12.0, true); } // and so on } @Test public void testCyclicGraph3() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, cyclicGraph3); Integer source = 1; Integer target = 3; EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 2.0, true); verifyNextPath(it, 4.0, true); verifyNextPath(it, 6.0, true); verifyNextPath(it, 6.0, true); verifyNextPath(it, 8.0, true); verifyNextPath(it, 8.0, true); // and so on } @Test public void testRestHeapGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, restHeapGraph); Integer source = 1; Integer target = 10; EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 3.0, true); verifyNextPath(it, 4.0, true); verifyNextPath(it, 5.0, true); verifyNextPath(it, 6.0, true); verifyNextPath(it, 7.0, true); verifyNextPath(it, 8.0, true); verifyNextPath(it, 9.0, true); verifyNextPath(it, 10.0, false); } @Test public void testNotShortestPathEdgesGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, notShortestPathEdgesGraph); Integer source = 1; Integer target = 2; EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 1.0, false); } @Test public void testOnRandomGraphs() { int n = 100; double p = 0.5; for (int i = 0; i < 1000; i++) { SimpleDirectedWeightedGraph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); getRandomGraph(graph, n, p); Integer source = (int) (Math.random() * n); Integer target = (int) (Math.random() * n); testOnRandomGraph(graph, source, target); } } /** * If the overall number of paths between {@code source} and {@code target} is denoted by $n$ * and the value of {@code #NUMBER_OF_PATH_TO_ITERATE} is denoted by $m$ then the method * iterates over $p = min\{n, m\}$ such paths and verifies that they are built correctly. * Additionally method checks that are returned in the increasing order by weight. * * @param graph graph the iterator is being tested on * @param source source vertex * @param target target vertex */ private void testOnRandomGraph( Graph graph, Integer source, Integer target) { EppsteinShortestPathIterator it = new EppsteinShortestPathIterator<>(graph, source, target); GraphPath path; double weight = 0.0; Set> paths = new HashSet<>(); int i = 0; for (; i < NUMBER_OF_PATH_TO_ITERATE && it.hasNext(); i++) { path = it.next(); paths.add(path); verifyPath(path); assertTrue(weight <= path.getWeight()); weight = path.getWeight(); } assertEquals(i, paths.size()); } /** * Performs assertions to check correctness of the next path which the {@code it} is expected to * return. * * @param it shortest paths iterator * @param expectedWeight expected weight of the next path * @param hasNext expected return value of the {@link YenShortestPathIterator#hasNext()} method */ private void verifyNextPath( EppsteinShortestPathIterator it, double expectedWeight, boolean hasNext) { GraphPath path = it.next(); assertEquals(expectedWeight, path.getWeight(), 1e-9); verifyPath(path); assertEquals(it.hasNext(), hasNext); } /** * Creates a graph walk of the give {@code graph} and verifies that the path is correct by * callig {@link GraphWalk#verify()}. * * @param path path to verify */ private void verifyPath(GraphPath path) { GraphWalk walk = new GraphWalk<>( path.getGraph(), path.getStartVertex(), path.getEndVertex(), path.getVertexList(), path.getEdgeList(), path.getWeight()); walk.verify(); } /** * Generates random graph from the $G(n, p)$ model. * * @param graph graph instance for the generator * @param n the number of nodes * @param p the edge probability */ private void getRandomGraph(Graph graph, int n, double p) { GraphGenerator generator = new GnpRandomGraphGenerator<>(n, p, SEED); generator.generateGraph(graph); graph.edgeSet().forEach(e -> graph.setEdgeWeight(e, (int) (Math.random() * 10))); } private void readGraph(Graph graph, int[][] representation) { for (int[] ints : representation) { Graphs.addEdgeWithVertices(graph, ints[0], ints[1], ints[2]); } } } FloydWarshallPseudographsTest.java000066400000000000000000000275421402514743400362110ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.*; /** * Test {@link FloydWarshallShortestPaths} on pseudo graphs. * * @author Dimitrios Michail */ public class FloydWarshallPseudographsTest { @Test @Category(SlowTests.class) public void testRandomGraphs() { final int tests = 20; final int n = 50; final double p = 0.85; Random rng = new Random(); List>> graphs = new ArrayList<>(); graphs .add( () -> new DirectedWeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER)); graphs .add( () -> new WeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER)); for (Supplier> gSupplier : graphs) { GraphGenerator gen = new GnpRandomGraphGenerator<>(n, p, rng, true); for (int i = 0; i < tests; i++) { Graph g = gSupplier.get(); gen.generateGraph(g); // assign random weights for (DefaultWeightedEdge e : g.edgeSet()) { g.setEdgeWeight(e, rng.nextDouble()); } // for each vertex add more edges Integer[] allVertices = g.vertexSet().toArray(new Integer[0]); for (Integer v : g.vertexSet()) { for (int j = 0; j < n * p; j++) { Integer u = allVertices[rng.nextInt(n)]; DefaultWeightedEdge e = new DefaultWeightedEdge(); g.addEdge(v, u, e); g.setEdgeWeight(e, rng.nextDouble()); } } // run one Floyd-Warshall FloydWarshallShortestPaths fw = new FloydWarshallShortestPaths<>(g); for (Integer v : g.vertexSet()) { // run Dijkstra SingleSourcePaths dTree = new DijkstraShortestPath<>(g).getPaths(v); SingleSourcePaths fwTree = fw.getPaths(v); for (Integer u : g.vertexSet()) { // compare with Dijkstra assertEquals(dTree.getWeight(u), fw.getPath(v, u).getWeight(), 1e-9); // Test getPath method w.r.t getPathsTree assertEquals( fwTree.getPath(u).getEdgeList(), fw.getPath(v, u).getEdgeList()); } } } } } @Test public void test1() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); DefaultWeightedEdge e12_1 = g.addEdge(1, 2); g.setEdgeWeight(e12_1, -5.0); DefaultWeightedEdge e12_2 = g.addEdge(1, 2); g.setEdgeWeight(e12_2, -2.0); DefaultWeightedEdge e12_3 = g.addEdge(1, 2); g.setEdgeWeight(e12_3, 1.0); DefaultWeightedEdge e23_1 = g.addEdge(2, 3); g.setEdgeWeight(e23_1, 0d); DefaultWeightedEdge e23_2 = g.addEdge(2, 3); g.setEdgeWeight(e23_2, -2.0); DefaultWeightedEdge e23_3 = g.addEdge(2, 3); g.setEdgeWeight(e23_3, -5.0); DefaultWeightedEdge e34_1 = g.addEdge(3, 4); g.setEdgeWeight(e34_1, -100.0); DefaultWeightedEdge e34_2 = g.addEdge(3, 4); g.setEdgeWeight(e34_2, 100.0); DefaultWeightedEdge e34_3 = g.addEdge(3, 4); g.setEdgeWeight(e34_3, 1.0); FloydWarshallShortestPaths fw = new FloydWarshallShortestPaths<>(g); SingleSourcePaths t1 = fw.getPaths(1); assertEquals(0d, t1.getWeight(1), 1e-9); assertTrue(t1.getPath(1).getEdgeList().isEmpty()); assertEquals( Collections.singletonList(g.getEdgeSource(e12_1)), t1.getPath(1).getVertexList()); assertEquals(-5d, t1.getWeight(2), 1e-9); assertEquals(Collections.singletonList(e12_1), t1.getPath(2).getEdgeList()); assertEquals(-10d, t1.getWeight(3), 1e-9); assertEquals(Arrays.asList(e12_1, e23_3), t1.getPath(3).getEdgeList()); assertEquals(-110d, t1.getWeight(4), 1e-9); assertEquals(Arrays.asList(e12_1, e23_3, e34_1), t1.getPath(4).getEdgeList()); SingleSourcePaths t2 = fw.getPaths(2); assertEquals(Double.POSITIVE_INFINITY, t2.getWeight(1), 1e-9); assertNull(t2.getPath(1)); assertEquals(0d, t2.getWeight(2), 1e-9); assertTrue(t2.getPath(2).getEdgeList().isEmpty()); assertEquals( Collections.singletonList(g.getEdgeSource(e23_1)), t2.getPath(2).getVertexList()); assertEquals(-5d, t2.getWeight(3), 1e-9); assertEquals(Collections.singletonList(e23_3), t2.getPath(3).getEdgeList()); assertEquals(-105d, t2.getWeight(4), 1e-9); assertEquals(Arrays.asList(e23_3, e34_1), t2.getPath(4).getEdgeList()); SingleSourcePaths t3 = fw.getPaths(3); assertEquals(Double.POSITIVE_INFINITY, t3.getWeight(1), 1e-9); assertNull(t3.getPath(1)); assertEquals(Double.POSITIVE_INFINITY, t3.getWeight(2), 1e-9); assertNull(t3.getPath(2)); assertEquals(0d, t3.getWeight(3), 1e-9); assertTrue(t3.getPath(3).getEdgeList().isEmpty()); assertEquals(-100d, t3.getWeight(4), 1e-9); assertEquals(Collections.singletonList(e34_1), t3.getPath(4).getEdgeList()); SingleSourcePaths t4 = fw.getPaths(4); assertNull(t4.getPath(1)); assertNull(t4.getPath(2)); assertNull(t4.getPath(3)); assertEquals(0d, t4.getWeight(4), 1e-9); assertTrue(t4.getPath(4).getEdgeList().isEmpty()); // test shortest path count assertEquals(6, fw.getShortestPathsCount()); // test first hop assertNull(fw.getFirstHop(1, 1)); assertEquals(2, fw.getFirstHop(1, 2).intValue()); assertEquals(2, fw.getFirstHop(1, 3).intValue()); assertEquals(2, fw.getFirstHop(1, 4).intValue()); assertNull(fw.getFirstHop(2, 1)); assertNull(fw.getFirstHop(2, 2)); assertEquals(3, fw.getFirstHop(2, 3).intValue()); assertEquals(3, fw.getFirstHop(2, 4).intValue()); assertNull(fw.getFirstHop(3, 1)); assertNull(fw.getFirstHop(3, 2)); assertNull(fw.getFirstHop(3, 3)); assertEquals(4, fw.getFirstHop(3, 4).intValue()); assertNull(fw.getFirstHop(4, 1)); assertNull(fw.getFirstHop(4, 2)); assertNull(fw.getFirstHop(4, 3)); assertNull(fw.getFirstHop(4, 4)); // test last hop assertNull(fw.getLastHop(1, 1)); assertEquals(1, fw.getLastHop(1, 2).intValue()); assertEquals(2, fw.getLastHop(1, 3).intValue()); assertEquals(3, fw.getLastHop(1, 4).intValue()); assertNull(fw.getLastHop(2, 1)); assertNull(fw.getLastHop(2, 2)); assertEquals(2, fw.getLastHop(2, 3).intValue()); assertEquals(3, fw.getLastHop(2, 4).intValue()); assertNull(fw.getLastHop(3, 1)); assertNull(fw.getLastHop(3, 2)); assertNull(fw.getLastHop(3, 3)); assertEquals(3, fw.getLastHop(3, 4).intValue()); assertNull(fw.getLastHop(4, 1)); assertNull(fw.getLastHop(4, 2)); assertNull(fw.getLastHop(4, 3)); assertNull(fw.getLastHop(4, 4)); } @Test public void testGetPathWeight() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); DefaultWeightedEdge e12_1 = g.addEdge(1, 2); g.setEdgeWeight(e12_1, -5.0); DefaultWeightedEdge e12_2 = g.addEdge(1, 2); g.setEdgeWeight(e12_2, -2.0); DefaultWeightedEdge e12_3 = g.addEdge(1, 2); g.setEdgeWeight(e12_3, 1.0); DefaultWeightedEdge e23_1 = g.addEdge(2, 3); g.setEdgeWeight(e23_1, 0d); DefaultWeightedEdge e23_2 = g.addEdge(2, 3); g.setEdgeWeight(e23_2, -2.0); DefaultWeightedEdge e23_3 = g.addEdge(2, 3); g.setEdgeWeight(e23_3, -5.0); DefaultWeightedEdge e34_1 = g.addEdge(3, 4); g.setEdgeWeight(e34_1, -100.0); DefaultWeightedEdge e34_2 = g.addEdge(3, 4); g.setEdgeWeight(e34_2, 100.0); DefaultWeightedEdge e34_3 = g.addEdge(3, 4); g.setEdgeWeight(e34_3, 1.0); FloydWarshallShortestPaths fw = new FloydWarshallShortestPaths<>(g); assertEquals(Double.POSITIVE_INFINITY, fw.getPathWeight(2, 1), 1e-9); assertEquals(0d, fw.getPathWeight(2, 2), 1e-9); assertEquals(-5d, fw.getPathWeight(2, 3), 1e-9); assertEquals(-105d, fw.getPathWeight(2, 4), 1e-9); } @Test public void testLoops() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2)); DefaultWeightedEdge e12_1 = g.addEdge(1, 2); g.setEdgeWeight(e12_1, 5.0); DefaultWeightedEdge e21_1 = g.addEdge(2, 1); g.setEdgeWeight(e21_1, 15.0); g.addEdge(1, 1); g.addEdge(1, 1); g.addEdge(1, 1); g.addEdge(2, 2); g.addEdge(2, 2); g.addEdge(2, 2); FloydWarshallShortestPaths fw = new FloydWarshallShortestPaths<>(g); GraphPath p1 = fw.getPath(1, 1); assertEquals(1, p1.getStartVertex().intValue()); assertEquals(1, p1.getEndVertex().intValue()); assertEquals(0, p1.getLength()); assertEquals(0d, p1.getWeight(), 1e-9); assertTrue(p1.getEdgeList().isEmpty()); assertEquals(1, p1.getVertexList().size()); assertEquals(1, p1.getVertexList().get(0).intValue()); GraphPath p2 = fw.getPath(2, 2); assertEquals(2, p2.getStartVertex().intValue()); assertEquals(2, p2.getEndVertex().intValue()); assertEquals(0, p2.getLength()); assertEquals(0d, p2.getWeight(), 1e-9); assertTrue(p2.getEdgeList().isEmpty()); assertEquals(1, p2.getVertexList().size()); assertEquals(2, p2.getVertexList().get(0).intValue()); assertEquals(5.0, fw.getPath(1, 2).getWeight(), 1e-9); assertEquals(15.0, fw.getPath(2, 1).getWeight(), 1e-9); } } FloydWarshallShortestPathsTest.java000066400000000000000000000152771402514743400363620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2009-2021, by Tom Larkworthy and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * @author Tom Larkworthy */ public class FloydWarshallShortestPathsTest { // ~ Methods ---------------------------------------------------------------- @Test public void testCompareWithDijkstra() { GraphGenerator gen = new GnmRandomGraphGenerator<>(10, 15); for (int i = 0; i < 10; i++) { // Generate directed graph SimpleDirectedGraph directed = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER, false); gen.generateGraph(directed); // setup our shortest path measurer FloydWarshallShortestPaths fw = new FloydWarshallShortestPaths<>(directed); for (Integer v1 : directed.vertexSet()) { for (Integer v2 : directed.vertexSet()) { GraphPath dPath = new DijkstraShortestPath<>(directed).getPath(v1, v2); if (dPath == null) { assertNull(fw.getPath(v1, v2)); } else { double fwSp = fw.getPathWeight(v1, v2); double dijSp = dPath.getWeight(); assertTrue( (Math.abs(dijSp - fwSp) < .01) || (Double.isInfinite(fwSp) && Double.isInfinite(dijSp))); GraphPath path = fw.getPath(v1, v2); if (!path.getEdgeList().isEmpty()) { this.verifyPath(directed, path, fw.getPathWeight(v1, v2)); } } } } // Generate Undirected graph SimpleGraph undirected = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER, false); gen.generateGraph(undirected); // setup our shortest path measurer fw = new FloydWarshallShortestPaths<>(undirected); for (Integer v1 : undirected.vertexSet()) { for (Integer v2 : undirected.vertexSet()) { GraphPath dPath = new DijkstraShortestPath<>(undirected).getPath(v1, v2); if (dPath == null) { assertNull(fw.getPath(v1, v2)); } else { double fwSp = fw.getPathWeight(v1, v2); double dijSp = dPath.getWeight(); assertTrue( (Math.abs(dijSp - fwSp) < .01) || (Double.isInfinite(fwSp) && Double.isInfinite(dijSp))); GraphPath path = fw.getPath(v1, v2); if (!path.getEdgeList().isEmpty()) { this.verifyPath(undirected, path, fw.getPathWeight(v1, v2)); List vertexPath = path.getVertexList(); assertEquals(fw.getFirstHop(v1, v2), vertexPath.get(1)); assertEquals( fw.getLastHop(v1, v2), vertexPath.get(vertexPath.size() - 2)); } } } } } } /** * Verify whether the path calculated by FloydWarshallShortestPaths is an actual valid path. */ private void verifyPath(Graph graph, GraphPath path, double pathCost) { assertEquals(pathCost, path.getWeight(), .00000001); double verifiedEdgeCost = 0; List vertexList = new ArrayList<>(); vertexList.add(path.getStartVertex()); V v = path.getStartVertex(); for (E e : path.getEdgeList()) { assertNotNull(e); verifiedEdgeCost += graph.getEdgeWeight(e); try { v = Graphs.getOppositeVertex(graph, e, v); } catch (IllegalArgumentException ex) { fail( "Invalid path encountered: the sequence of edges does not present a valid path through the graph"); } } assertEquals(pathCost, verifiedEdgeCost, .00000001); assertEquals(path.getStartVertex(), path.getVertexList().get(0)); assertEquals(path.getEndVertex(), path.getVertexList().get(path.getLength())); } @Test public void testWeightedEdges() { SimpleDirectedWeightedGraph weighted = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); weighted.addVertex("a"); weighted.addVertex("b"); DefaultWeightedEdge edge = weighted.addEdge("a", "b"); weighted.setEdgeWeight(edge, 5.0); FloydWarshallShortestPaths fw = new FloydWarshallShortestPaths<>(weighted); double sD = fw.getPathWeight("a", "b"); assertEquals(5.0, sD, 0.1); GraphPath path = fw.getPath("a", "b"); assertNotNull(path); assertEquals(Collections.singletonList(edge), path.getEdgeList()); assertEquals("a", path.getStartVertex()); assertEquals("b", path.getEndVertex()); assertEquals(5.0, path.getWeight(), 0); assertEquals(weighted, path.getGraph()); List vertexPath = path.getVertexList(); assertEquals(fw.getFirstHop("a", "b"), vertexPath.get(1)); assertEquals(fw.getLastHop("a", "b"), vertexPath.get(vertexPath.size() - 2)); assertNull(fw.getPath("b", "a")); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/GraphMeasurerTest.java000066400000000000000000000224021402514743400336630ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for GraphMeasurer * * @author Joris Kinable * @author Alexandru Valeanu */ public class GraphMeasurerTest { private final double EPSILON = 0.000000001; private Graph getGraph1() { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); IntStream.range(0, 7).forEach(i -> g.addVertex()); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(1, 5); g.addEdge(3, 4); g.addEdge(5, 6); return g; } private Graph getGraph2() { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); IntStream.range(0, 7).forEach(i -> g.addVertex()); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(1, 4); g.addEdge(1, 5); g.addEdge(5, 6); return g; } private Graph getGraph3() { Graph g = new SimpleWeightedGraph<>(DefaultEdge.class); Random random = new Random(12345678); final int N = 100; final int M = 100000; for (int i = 0; i < N; i++) { g.addVertex(i); } for (int i = 0; i < N - 1; i++) { g.addEdge(i, i + 1); g.setEdgeWeight(g.getEdge(i, i + 1), 50 + random.nextInt(50)); } for (int i = N - 1; i < M; i++) { int u = random.nextInt(N); int v = random.nextInt(N); if (u != v) { g.addEdge(u, v); g.setEdgeWeight(g.getEdge(u, v), 100 + random.nextInt(200)); } } return g; } @Test public void testVertexEccentricityG1() { Graph g1 = getGraph1(); List> spAlgs = Arrays.asList(new FloydWarshallShortestPaths<>(g1), new JohnsonShortestPaths<>(g1)); for (ShortestPathAlgorithm spAlg : spAlgs) { GraphMeasurer gdm = new GraphMeasurer<>(g1, spAlg); Map vertexEccentricity = gdm.getVertexEccentricityMap(); assertEquals(3.0, vertexEccentricity.get(0), EPSILON); assertEquals(2.0, vertexEccentricity.get(1), EPSILON); assertEquals(3.0, vertexEccentricity.get(2), EPSILON); assertEquals(3.0, vertexEccentricity.get(3), EPSILON); assertEquals(4.0, vertexEccentricity.get(4), EPSILON); assertEquals(3.0, vertexEccentricity.get(5), EPSILON); assertEquals(4.0, vertexEccentricity.get(6), EPSILON); } } @Test public void testVertexEccentricityG2() { Graph g2 = getGraph2(); List> spAlgs = Arrays.asList(new FloydWarshallShortestPaths<>(g2), new JohnsonShortestPaths<>(g2)); for (ShortestPathAlgorithm spAlg : spAlgs) { GraphMeasurer gdm = new GraphMeasurer<>(g2, spAlg); Map vertexEccentricity = gdm.getVertexEccentricityMap(); assertEquals(3.0, vertexEccentricity.get(0), EPSILON); assertEquals(2.0, vertexEccentricity.get(1), EPSILON); assertEquals(3.0, vertexEccentricity.get(2), EPSILON); assertEquals(3.0, vertexEccentricity.get(3), EPSILON); assertEquals(3.0, vertexEccentricity.get(4), EPSILON); assertEquals(2.0, vertexEccentricity.get(5), EPSILON); assertEquals(3.0, vertexEccentricity.get(6), EPSILON); } } @Test public void testDiameterEmptyGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); GraphMeasurer gdm = new GraphMeasurer<>(g); double diameter = gdm.getDiameter(); assertEquals(0.0, diameter, EPSILON); } @Test public void testDiameterDisconnectedGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1)); GraphMeasurer gdm = new GraphMeasurer<>(g); double diameter = gdm.getDiameter(); assertTrue(Double.isInfinite(diameter)); } @Test public void testDiameterDirectedGraph1() { Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(g, 0, 1, 10); GraphMeasurer gdm = new GraphMeasurer<>(g); double diameter = gdm.getDiameter(); assertTrue(Double.isInfinite(diameter)); } @Test public void testDiameterDirectedGraph2() { Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(g, 0, 1, 10); Graphs.addEdgeWithVertices(g, 1, 0, 12); GraphMeasurer gdm = new GraphMeasurer<>(g); double diameter = gdm.getDiameter(); assertEquals(12.0, diameter, EPSILON); } @Test public void testRadiusEmptyGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); GraphMeasurer gdm = new GraphMeasurer<>(g); double radius = gdm.getRadius(); assertEquals(0.0, radius, EPSILON); } @Test public void testRadiusDisconnectedGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1)); GraphMeasurer gdm = new GraphMeasurer<>(g); double radius = gdm.getRadius(); assertTrue(Double.isInfinite(radius)); } @Test public void testGraphCenterG1() { Graph g1 = getGraph1(); GraphMeasurer gdm = new GraphMeasurer<>(g1); Set graphCenter1 = gdm.getGraphCenter(); assertEquals(Set.of(1), graphCenter1); } @Test public void testGraphCenterG2() { Graph g2 = getGraph2(); GraphMeasurer gdm = new GraphMeasurer<>(g2); Set graphCenter2 = gdm.getGraphCenter(); assertEquals(Set.of(1, 5), graphCenter2); } @Test public void testGraphPeripheryG1() { Graph g1 = getGraph1(); GraphMeasurer gdm = new GraphMeasurer<>(g1); Set graphPeriphery1 = gdm.getGraphPeriphery(); assertEquals(Set.of(4, 6), graphPeriphery1); } @Test public void testGraphPeripheryG2() { Graph g2 = getGraph2(); GraphMeasurer gdm = new GraphMeasurer<>(g2); Set graphPeriphery2 = gdm.getGraphPeriphery(); assertEquals(Set.of(0, 2, 3, 4, 6), graphPeriphery2); } @Test public void testGraphPseudoPeripheryG1() { Graph g1 = getGraph1(); GraphMeasurer gdm = new GraphMeasurer<>(g1); Set graphPseudoPeriphery1 = gdm.getGraphPseudoPeriphery(); assertEquals(Set.of(4, 6), graphPseudoPeriphery1); } @Test public void testGraphPseudoPeripheryG2() { Graph g2 = getGraph2(); GraphMeasurer gdm = new GraphMeasurer<>(g2); Set graphPseudoPeriphery2 = gdm.getGraphPseudoPeriphery(); assertEquals(Set.of(0, 2, 3, 4, 6), graphPseudoPeriphery2); } @Test public void testGraphPseudoPeripheryG3() { Graph g3 = getGraph3(); GraphMeasurer gdm = new GraphMeasurer<>(g3); Set graphPseudoPeriphery3 = gdm.getGraphPseudoPeriphery(); assertEquals( Set .of( 6, 7, 13, 17, 19, 20, 21, 24, 32, 36, 37, 39, 41, 42, 46, 48, 51, 53, 60, 61, 63, 64, 66, 67, 69, 70, 71, 83, 89, 90, 95, 98), graphPseudoPeriphery3); } } IntVertexDijkstraShortestPathTest.java000066400000000000000000000270561402514743400370460ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for {@link IntVertexDijkstraShortestPath}. * * @author Dimitrios Michail */ public class IntVertexDijkstraShortestPathTest { @Test public void testUndirected() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3, 4)); g.setEdgeWeight(g.addEdge(0, 1), 2.0); g.setEdgeWeight(g.addEdge(0, 2), 3.0); g.setEdgeWeight(g.addEdge(0, 4), 100.0); g.setEdgeWeight(g.addEdge(1, 3), 5.0); g.setEdgeWeight(g.addEdge(2, 3), 20.0); g.setEdgeWeight(g.addEdge(3, 4), 5.0); IntVertexDijkstraShortestPath algo = new IntVertexDijkstraShortestPath<>(g); SingleSourcePaths source0 = algo.getPaths(0); assertEquals(source0.getWeight(0), 0d, 1e-9); assertEquals(source0.getWeight(1), 2d, 1e-9); assertEquals(source0.getWeight(2), 3d, 1e-9); assertEquals(source0.getWeight(3), 7d, 1e-9); assertEquals(source0.getWeight(4), 12d, 1e-9); SingleSourcePaths source1 = algo.getPaths(1); assertEquals(source1.getWeight(0), 2d, 1e-9); assertEquals(source1.getWeight(1), 0d, 1e-9); assertEquals(source1.getWeight(2), 5d, 1e-9); assertEquals(source1.getWeight(3), 5d, 1e-9); assertEquals(source1.getWeight(4), 10d, 1e-9); SingleSourcePaths source2 = algo.getPaths(2); assertEquals(source2.getWeight(0), 3d, 1e-9); assertEquals(source2.getWeight(1), 5d, 1e-9); assertEquals(source2.getWeight(2), 0d, 1e-9); assertEquals(source2.getWeight(3), 10d, 1e-9); assertEquals(source2.getWeight(4), 15d, 1e-9); SingleSourcePaths source3 = algo.getPaths(3); assertEquals(source3.getWeight(0), 7d, 1e-9); assertEquals(source3.getWeight(1), 5d, 1e-9); assertEquals(source3.getWeight(2), 10d, 1e-9); assertEquals(source3.getWeight(3), 0d, 1e-9); assertEquals(source3.getWeight(4), 5d, 1e-9); SingleSourcePaths source4 = algo.getPaths(4); assertEquals(source4.getWeight(0), 12d, 1e-9); assertEquals(source4.getWeight(1), 10d, 1e-9); assertEquals(source4.getWeight(2), 15d, 1e-9); assertEquals(source4.getWeight(3), 5d, 1e-9); assertEquals(source4.getWeight(4), 0d, 1e-9); } @Test public void testUndirectedWithIdMap() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(100, 1, 2, 3, 4)); g.setEdgeWeight(g.addEdge(100, 1), 2.0); g.setEdgeWeight(g.addEdge(100, 2), 3.0); g.setEdgeWeight(g.addEdge(100, 4), 100.0); g.setEdgeWeight(g.addEdge(1, 3), 5.0); g.setEdgeWeight(g.addEdge(2, 3), 20.0); g.setEdgeWeight(g.addEdge(3, 4), 5.0); IntVertexDijkstraShortestPath algo = new IntVertexDijkstraShortestPath<>(g); SingleSourcePaths source100 = algo.getPaths(100); assertEquals(source100.getWeight(100), 0d, 1e-9); assertEquals(source100.getWeight(1), 2d, 1e-9); assertEquals(source100.getWeight(2), 3d, 1e-9); assertEquals(source100.getWeight(3), 7d, 1e-9); assertEquals(source100.getWeight(4), 12d, 1e-9); SingleSourcePaths source1 = algo.getPaths(1); assertEquals(source1.getWeight(100), 2d, 1e-9); assertEquals(source1.getWeight(1), 0d, 1e-9); assertEquals(source1.getWeight(2), 5d, 1e-9); assertEquals(source1.getWeight(3), 5d, 1e-9); assertEquals(source1.getWeight(4), 10d, 1e-9); SingleSourcePaths source2 = algo.getPaths(2); assertEquals(source2.getWeight(100), 3d, 1e-9); assertEquals(source2.getWeight(1), 5d, 1e-9); assertEquals(source2.getWeight(2), 0d, 1e-9); assertEquals(source2.getWeight(3), 10d, 1e-9); assertEquals(source2.getWeight(4), 15d, 1e-9); SingleSourcePaths source3 = algo.getPaths(3); assertEquals(source3.getWeight(100), 7d, 1e-9); assertEquals(source3.getWeight(1), 5d, 1e-9); assertEquals(source3.getWeight(2), 10d, 1e-9); assertEquals(source3.getWeight(3), 0d, 1e-9); assertEquals(source3.getWeight(4), 5d, 1e-9); SingleSourcePaths source4 = algo.getPaths(4); assertEquals(source4.getWeight(100), 12d, 1e-9); assertEquals(source4.getWeight(1), 10d, 1e-9); assertEquals(source4.getWeight(2), 15d, 1e-9); assertEquals(source4.getWeight(3), 5d, 1e-9); assertEquals(source4.getWeight(4), 0d, 1e-9); } @Test public void testDirected() { testDirected(0); } @Test public void testDirectedWithIdMap() { testDirected(10000); } private void testDirected(int offset) { Graph g = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); Graphs .addAllVertices( g, Arrays .asList( offset + 0, offset + 1, offset + 2, offset + 3, offset + 4, offset + 5)); DefaultWeightedEdge e01 = g.addEdge(offset + 0, offset + 1); g.setEdgeWeight(e01, 10.0); DefaultWeightedEdge e02 = g.addEdge(offset + 0, offset + 2); g.setEdgeWeight(e02, 20.0); DefaultWeightedEdge e24 = g.addEdge(offset + 2, offset + 4); g.setEdgeWeight(e24, 33.0); DefaultWeightedEdge e23 = g.addEdge(offset + 2, offset + 3); g.setEdgeWeight(e23, 20.0); DefaultWeightedEdge e14 = g.addEdge(offset + 1, offset + 4); g.setEdgeWeight(e14, 10.0); DefaultWeightedEdge e13 = g.addEdge(offset + 1, offset + 3); g.setEdgeWeight(e13, 50.0); DefaultWeightedEdge e34 = g.addEdge(offset + 3, offset + 4); g.setEdgeWeight(e34, 20.0); DefaultWeightedEdge e45 = g.addEdge(offset + 4, offset + 5); g.setEdgeWeight(e45, 1.0); DefaultWeightedEdge e35 = g.addEdge(offset + 3, offset + 5); g.setEdgeWeight(e35, 2.0); IntVertexDijkstraShortestPath algo = new IntVertexDijkstraShortestPath<>(g); SingleSourcePaths source0 = algo.getPaths(offset + 0); assertEquals(source0.getWeight(offset + 0), 0d, 1e-9); assertEquals(Arrays.asList(), source0.getPath(offset + 0).getEdgeList()); assertEquals(source0.getWeight(offset + 1), 10d, 1e-9); assertEquals(Arrays.asList(e01), source0.getPath(offset + 1).getEdgeList()); assertEquals(source0.getWeight(offset + 2), 20d, 1e-9); assertEquals(Arrays.asList(e02), source0.getPath(offset + 2).getEdgeList()); assertEquals(source0.getWeight(offset + 3), 40d, 1e-9); assertEquals(Arrays.asList(e02, e23), source0.getPath(offset + 3).getEdgeList()); assertEquals(source0.getWeight(offset + 4), 20d, 1e-9); assertEquals(Arrays.asList(e01, e14), source0.getPath(offset + 4).getEdgeList()); assertEquals(source0.getWeight(offset + 5), 21d, 1e-9); assertEquals(Arrays.asList(e01, e14, e45), source0.getPath(offset + 5).getEdgeList()); SingleSourcePaths source1 = algo.getPaths(offset + 1); assertTrue(Double.isInfinite(source1.getWeight(offset + 0))); assertEquals(source1.getWeight(offset + 1), 0d, 1e-9); assertTrue(Double.isInfinite(source1.getWeight(offset + 2))); assertEquals(source1.getWeight(offset + 3), 50d, 1e-9); assertEquals(source1.getWeight(offset + 4), 10d, 1e-9); assertEquals(source1.getWeight(offset + 5), 11d, 1e-9); SingleSourcePaths source2 = algo.getPaths(offset + 2); assertTrue(Double.isInfinite(source2.getWeight(offset + 0))); assertTrue(Double.isInfinite(source2.getWeight(offset + 1))); assertEquals(source2.getWeight(offset + 2), 0d, 1e-9); assertEquals(source2.getWeight(offset + 3), 20d, 1e-9); assertEquals(source2.getWeight(offset + 4), 33d, 1e-9); assertEquals(source2.getWeight(offset + 5), 22d, 1e-9); SingleSourcePaths source3 = algo.getPaths(offset + 3); assertTrue(Double.isInfinite(source3.getWeight(offset + 0))); assertTrue(Double.isInfinite(source3.getWeight(offset + 1))); assertTrue(Double.isInfinite(source3.getWeight(offset + 2))); assertEquals(source3.getWeight(offset + 3), 0d, 1e-9); assertEquals(source3.getWeight(offset + 4), 20d, 1e-9); assertEquals(source3.getWeight(offset + 5), 2d, 1e-9); SingleSourcePaths source4 = algo.getPaths(offset + 4); assertTrue(Double.isInfinite(source4.getWeight(offset + 0))); assertTrue(Double.isInfinite(source4.getWeight(offset + 1))); assertTrue(Double.isInfinite(source4.getWeight(offset + 2))); assertTrue(Double.isInfinite(source4.getWeight(offset + 3))); assertEquals(source4.getWeight(offset + 4), 0d, 1e-9); assertEquals(source4.getWeight(offset + 5), 1d, 1e-9); SingleSourcePaths source5 = algo.getPaths(offset + 5); assertTrue(Double.isInfinite(source5.getWeight(offset + 0))); assertTrue(Double.isInfinite(source5.getWeight(offset + 1))); assertTrue(Double.isInfinite(source5.getWeight(offset + 2))); assertTrue(Double.isInfinite(source5.getWeight(offset + 3))); assertTrue(Double.isInfinite(source5.getWeight(offset + 4))); assertEquals(source5.getWeight(offset + 5), 0d, 1e-9); } @Test public void testNonNegativeWeights() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2)); DefaultWeightedEdge we12 = g.addEdge(1, 2); g.setEdgeWeight(we12, -100.0); try { new IntVertexDijkstraShortestPath<>(g).getPath(1, 2); fail("No!"); } catch (IllegalArgumentException e) { } } } JohnsonShortestPathsTest.java000066400000000000000000000131471402514743400352170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Dimitrios Michail */ public class JohnsonShortestPathsTest { @Test public void testIssue408() { Graph graph = GraphTypeBuilder .directed().edgeClass(DefaultEdge.class) .vertexSupplier(SupplierUtil.createIntegerSupplier()).allowingMultipleEdges(false) .allowingSelfLoops(false).buildGraph(); for (int i = 0; i < 7; i++) { graph.addVertex(); } graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 0); graph.addEdge(4, 5); graph.addEdge(5, 6); graph.addEdge(6, 4); JohnsonShortestPaths sp = new JohnsonShortestPaths<>(graph); assertEquals(2.0, sp.getPathWeight(0, 2), 0.0); assertEquals(1.0, sp.getPathWeight(4, 5), 0.0); assertTrue(Double.isInfinite(sp.getPathWeight(3, 4))); } @Test public void testWikipediaExample() { Graph g = GraphTypeBuilder .directed().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeClass(DefaultWeightedEdge.class).weighted(true).allowingMultipleEdges(true) .allowingSelfLoops(true).buildGraph(); g.addVertex("w"); g.addVertex("y"); g.addVertex("x"); g.addVertex("z"); g.setEdgeWeight(g.addEdge("w", "z"), 2); g.setEdgeWeight(g.addEdge("y", "w"), 4); g.setEdgeWeight(g.addEdge("x", "w"), 6); g.setEdgeWeight(g.addEdge("x", "y"), 3); g.setEdgeWeight(g.addEdge("z", "x"), -7); g.setEdgeWeight(g.addEdge("y", "z"), 5); g.setEdgeWeight(g.addEdge("z", "y"), -3); JohnsonShortestPaths alg = new JohnsonShortestPaths<>(g); assertEquals(-1d, alg.getPathWeight("z", "w"), 1e-9); assertEquals(-4d, alg.getPathWeight("z", "y"), 1e-9); assertEquals(0, alg.getPathWeight("z", "z"), 1e-9); assertEquals(-7, alg.getPathWeight("z", "x"), 1e-9); } @Test public void testRandomGraphsCompareWithFloydWarshall() { final int tests = 20; final int n = 50; final double p = 0.55; Random rng = new Random(); List>> graphs = new ArrayList<>(); graphs .add( () -> GraphTypeBuilder .directed().vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeClass(DefaultWeightedEdge.class).weighted(true).allowingMultipleEdges(true) .allowingSelfLoops(true).buildGraph()); for (Supplier> gSupplier : graphs) { GraphGenerator gen = new GnpRandomGraphGenerator<>(n, p, rng, false); for (int i = 0; i < tests; i++) { Graph g = gSupplier.get(); gen.generateGraph(g); // assign weights for (DefaultWeightedEdge e : g.edgeSet()) { Integer u = g.getEdgeSource(e); Integer v = g.getEdgeTarget(e); double rWeight; if (u >= v) { rWeight = (n + 1) + 2 * (n + 1) * rng.nextDouble(); } else { rWeight = rng.nextDouble(); if (rng.nextDouble() < 0.5) { rWeight *= -1; } } g.setEdgeWeight(e, rWeight); } try { // run Johnson algorithm JohnsonShortestPaths fw = new JohnsonShortestPaths<>(g); // run Floyd-Warshall algorithm FloydWarshallShortestPaths fw1 = new FloydWarshallShortestPaths<>(g); for (Integer v : g.vertexSet()) { for (Integer u : g.vertexSet()) { // compare with Dijkstra assertEquals( fw1.getPath(v, u).getWeight(), fw.getPath(v, u).getWeight(), 1e-9); } } } catch (RuntimeException e) { // negative weight cycle, skip test assertEquals("Graph contains a negative-weight cycle", e.getMessage()); } } } } } KDisjointShortestPathsTestCase.java000066400000000000000000001146501402514743400362740ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2018-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.*; /** * * Tests for the {@link BaseKDisjointShortestPathsAlgorithm} class. * * @author Assaf Mizrachi */ public abstract class KDisjointShortestPathsTestCase { /** * Tests single path * * Edges expected in path --------------- {@literal 1 --> 2} */ @Test public void testSinglePath() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); DefaultWeightedEdge edge = graph.addEdge(1, 2); graph.setEdgeWeight(edge, 8); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 2, 5); assertEquals(1, pathList.size()); assertEquals(1, pathList.get(0).getLength()); assertTrue(pathList.get(0).getEdgeList().contains(edge)); assertEquals(Integer.valueOf(2), pathList.get(0).getEndVertex()); assertEquals(Integer.valueOf(1), pathList.get(0).getStartVertex()); assertEquals(2, pathList.get(0).getVertexList().size()); assertTrue(pathList.get(0).getVertexList().contains(1)); assertTrue(pathList.get(0).getVertexList().contains(2)); assertEquals(8.0, pathList.get(0).getWeight(), 0.0); } /** * Tests two disjoint paths traversing common vertex. * * Expected path 1 --------------- {@literal 1 --> 2 --> 3 --> 4 --> 5} * * Expected path 2 --------------- {@literal 1 --> 7 --> 3 --> 6 --> 5} * */ @Test public void testTwoDisjointPathsJointNode() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); graph.addVertex(6); graph.addVertex(7); graph.addEdge(1, 2); graph.addEdge(1, 7); graph.addEdge(2, 3); graph.addEdge(7, 3); graph.addEdge(3, 4); graph.addEdge(3, 6); graph.addEdge(4, 5); graph.addEdge(6, 5); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 5, 2); assertEquals(2, pathList.size()); assertEquals(4, pathList.get(0).getLength()); assertEquals(4.0, pathList.get(0).getWeight(), 0.0); assertEquals(4, pathList.get(1).getLength()); assertEquals(4.0, pathList.get(1).getWeight(), 0.0); // We have four potential paths all must pass through the joint node #3 GraphPath potentialP1_1 = new GraphWalk<>(graph, Arrays.asList(1, 2, 3, 4, 5), 4); GraphPath potentialP1_2 = new GraphWalk<>(graph, Arrays.asList(1, 2, 3, 6, 5), 4); GraphPath potentialP1_3 = new GraphWalk<>(graph, Arrays.asList(1, 7, 3, 4, 5), 4); GraphPath potentialP1_4 = new GraphWalk<>(graph, Arrays.asList(1, 7, 3, 6, 5), 4); if (pathList.get(0).equals(potentialP1_1)) { assertEquals(potentialP1_4, pathList.get(1)); } else if (pathList.get(0).equals(potentialP1_2)) { assertEquals(potentialP1_3, pathList.get(1)); } else if (pathList.get(0).equals(potentialP1_3)) { assertEquals(potentialP1_2, pathList.get(1)); } else if (pathList.get(0).equals(potentialP1_4)) { assertEquals(potentialP1_1, pathList.get(1)); } else { fail("Unexpected path"); } } /** * Tests two disjoint paths from 1 to 3 * * Edges expected in path 1 --------------- {@literal 1 --> 3} * * Edges expected in path 2 --------------- {@literal 1 --> 2} {@literal 2 --> 3} * */ @Test public void testTwoDisjointPaths() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(1, 3); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 3, 5); assertEquals(2, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 3), 1); assertEquals(expectedP1, pathList.get(0)); assertEquals(1, pathList.get(0).getLength()); assertEquals(1.0, pathList.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 2, 3), 2); assertEquals(expectedP2, pathList.get(1)); assertEquals(2, pathList.get(1).getLength()); assertEquals(2.0, pathList.get(1).getWeight(), 0.0); } @Test public void testDisconnectedGraph() { Graph graph = createDisconnectedGraph(); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 3, 5); assertEquals(2, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 2, 3), 3); assertEquals(expectedP1, pathList.get(0)); assertEquals(2, pathList.get(0).getLength()); assertEquals(3.0, pathList.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 3), 4); assertEquals(expectedP2, pathList.get(1)); assertEquals(1, pathList.get(1).getLength()); assertEquals(4.0, pathList.get(1).getWeight(), 0.0); } private Graph createDisconnectedGraph() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); graph.addVertex(6); DefaultWeightedEdge e; e = graph.addEdge(1, 2); graph.setEdgeWeight(e, 2.0); e = graph.addEdge(2, 1); graph.setEdgeWeight(e, 2.0); e = graph.addEdge(2, 3); graph.setEdgeWeight(e, 1.0); e = graph.addEdge(3, 2); graph.setEdgeWeight(e, 1.0); e = graph.addEdge(3, 1); graph.setEdgeWeight(e, 4.0); e = graph.addEdge(1, 3); graph.setEdgeWeight(e, 4.0); e = graph.addEdge(4, 5); graph.setEdgeWeight(e, 7.0); e = graph.addEdge(5, 6); graph.setEdgeWeight(e, 8.0); e = graph.addEdge(6, 4); graph.setEdgeWeight(e, 9.0); return graph; } /** * Tests two joint paths from 1 to 4, merge paths is not required. * * Edges expected in path 1 --------------- {@literal 1 --> 2}, w=1 {@literal 2 --> 6}, w=1 * {@literal 6 --> 4}, w=1 * * Edges expected in path 2 --------------- {@literal 1 --> 5}, w=2 {@literal 5 --> 3}, w=2 * {@literal 3 --> 4}, w=2 * * Edges expected in no path --------------- {@literal 2 --> 3}, w=3 * */ @Test public void testTwoDisjointPathsNoNeedToMerge() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); graph.addVertex(6); DefaultWeightedEdge e12 = graph.addEdge(1, 2); // this edge should not be used DefaultWeightedEdge e23 = graph.addEdge(2, 3); DefaultWeightedEdge e34 = graph.addEdge(3, 4); DefaultWeightedEdge e15 = graph.addEdge(1, 5); DefaultWeightedEdge e53 = graph.addEdge(5, 3); DefaultWeightedEdge e26 = graph.addEdge(2, 6); DefaultWeightedEdge e64 = graph.addEdge(6, 4); graph.setEdgeWeight(e12, 1); graph.setEdgeWeight(e23, 3); graph.setEdgeWeight(e34, 2); graph.setEdgeWeight(e15, 2); graph.setEdgeWeight(e53, 2); graph.setEdgeWeight(e26, 1); graph.setEdgeWeight(e64, 1); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 4, 5); assertEquals(2, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 2, 6, 4), 3); assertEquals(expectedP1, pathList.get(0)); assertEquals(3, pathList.get(0).getLength()); assertEquals(3.0, pathList.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 5, 3, 4), 6); assertEquals(expectedP2, pathList.get(1)); assertEquals(3, pathList.get(1).getLength()); assertEquals(6.0, pathList.get(1).getWeight(), 0.0); } /** * Tests two joint paths from 1 to 4, merge paths is required. * * Edges expected in path 1 --------------- {@literal 1 --> 2}, w=1 {@literal 2 --> 6}, w=2 * {@literal 6 --> 4}, w=2 * * Edges expected in path 2 --------------- {@literal 1 --> 5}, w=1 {@literal 5 --> 3}, w=3 * {@literal 3 --> 4}, w=3 * * Edges expected in no path --------------- {@literal 2 --> 3}, w=1 * */ @Test public void testTwoDisjointPathsNeedToMerge() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); graph.addVertex(6); DefaultWeightedEdge e12 = graph.addEdge(1, 2); // this edge should not be used DefaultWeightedEdge e23 = graph.addEdge(2, 3); DefaultWeightedEdge e34 = graph.addEdge(3, 4); DefaultWeightedEdge e15 = graph.addEdge(1, 5); DefaultWeightedEdge e53 = graph.addEdge(5, 3); DefaultWeightedEdge e26 = graph.addEdge(2, 6); DefaultWeightedEdge e64 = graph.addEdge(6, 4); graph.setEdgeWeight(e12, 1); graph.setEdgeWeight(e23, 1); graph.setEdgeWeight(e34, 1); graph.setEdgeWeight(e15, 3); graph.setEdgeWeight(e53, 3); graph.setEdgeWeight(e26, 2); graph.setEdgeWeight(e64, 2); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 4, 5); assertEquals(2, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 2, 6, 4), 5); assertEquals(expectedP1, pathList.get(0)); assertEquals(3, pathList.get(0).getLength()); assertEquals(5.0, pathList.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 5, 3, 4), 7); assertEquals(expectedP2, pathList.get(1)); assertEquals(3, pathList.get(1).getLength()); assertEquals(7.0, pathList.get(1).getWeight(), 0.0); } /** * Tests two joint paths from 1 to 4, reversed edges already exist in graph so not added when * preparing for next phase. * * Edges expected in path 1 --------------- {@literal 1 --> 2}, w=1 {@literal 2 --> 6}, w=2 * {@literal 6 --> 4}, w=2 * * Edges expected in path 2 --------------- {@literal 1 --> 5}, w=1 {@literal 5 --> 3}, w=3 * {@literal 3 --> 4}, w=3 * * Edges expected in no path --------------- {@literal 2 --> 3}, w=1 * */ @Test public void testTwoDisjointPathsWithReversedEdgesExist() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); graph.addVertex(6); DefaultWeightedEdge e12 = graph.addEdge(1, 2); // this edge should not be used DefaultWeightedEdge e23 = graph.addEdge(2, 3); DefaultWeightedEdge e34 = graph.addEdge(3, 4); DefaultWeightedEdge e15 = graph.addEdge(1, 5); DefaultWeightedEdge e53 = graph.addEdge(5, 3); DefaultWeightedEdge e26 = graph.addEdge(2, 6); DefaultWeightedEdge e64 = graph.addEdge(6, 4); DefaultWeightedEdge e21 = graph.addEdge(2, 1); // this edge should not be used DefaultWeightedEdge e32 = graph.addEdge(3, 2); DefaultWeightedEdge e43 = graph.addEdge(4, 3); DefaultWeightedEdge e51 = graph.addEdge(5, 1); DefaultWeightedEdge e35 = graph.addEdge(3, 5); DefaultWeightedEdge e62 = graph.addEdge(6, 2); DefaultWeightedEdge e46 = graph.addEdge(4, 6); graph.setEdgeWeight(e12, 1); graph.setEdgeWeight(e23, 1); graph.setEdgeWeight(e34, 1); graph.setEdgeWeight(e15, 3); graph.setEdgeWeight(e53, 3); graph.setEdgeWeight(e26, 2); graph.setEdgeWeight(e64, 2); graph.setEdgeWeight(e21, 1); graph.setEdgeWeight(e32, 1); graph.setEdgeWeight(e43, 1); graph.setEdgeWeight(e51, 3); graph.setEdgeWeight(e35, 3); graph.setEdgeWeight(e62, 2); graph.setEdgeWeight(e46, 2); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 4, 5); assertEquals(2, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 2, 6, 4), 5); assertEquals(expectedP1, pathList.get(0)); assertEquals(3, pathList.get(0).getLength()); assertEquals(5.0, pathList.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 5, 3, 4), 7); assertEquals(expectedP2, pathList.get(1)); assertEquals(3, pathList.get(1).getLength()); assertEquals(7.0, pathList.get(1).getWeight(), 0.0); } /** * Tests three joint paths from 1 to 5. *

    * Edges expected in path 1 --------------- {@literal 1 --> 4}, w=4 {@literal 4 --> 5}, w=1 *

    * Edges expected in path 2 --------------- {@literal 1 --> 2}, w=1 {@literal 2 --> 5}, w=6 *

    * Edges expected in path 3 --------------- {@literal 1 --> 3}, w=4 {@literal 3 --> 5}, w=5 *

    * Edges expected in no path --------------- {@literal 2 --> 3}, w=1 {@literal 3 --> 4}, w=1 */ @Test public void testThreeDisjointPaths() { Graph graph = createThreeDisjointPathsGraph(); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 5, 5); assertEquals(3, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 4, 5), 5); assertEquals(expectedP1, pathList.get(0)); assertEquals(2, pathList.get(0).getLength()); assertEquals(5.0, pathList.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 2, 5), 7); assertEquals(expectedP2, pathList.get(1)); assertEquals(2, pathList.get(1).getLength()); assertEquals(7.0, pathList.get(1).getWeight(), 0.0); GraphPath expectedP3 = new GraphWalk<>(graph, Arrays.asList(1, 3, 5), 9); assertEquals(expectedP3, pathList.get(2)); assertEquals(2, pathList.get(2).getLength()); assertEquals(9.0, pathList.get(2).getWeight(), 0.0); } @Test public void testThreeDisjointPathsReverseEdgeExist() { Graph graph = createThreeDisjointPathsGraphBidirectional(); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 5, 5); assertEquals(3, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 4, 5), 5); assertEquals(expectedP1, pathList.get(0)); assertEquals(2, pathList.get(0).getLength()); assertEquals(5.0, pathList.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 2, 5), 7); assertEquals(expectedP2, pathList.get(1)); assertEquals(2, pathList.get(1).getLength()); assertEquals(7.0, pathList.get(1).getWeight(), 0.0); GraphPath expectedP3 = new GraphWalk<>(graph, Arrays.asList(1, 3, 5), 9); assertEquals(expectedP3, pathList.get(2)); assertEquals(2, pathList.get(2).getLength()); assertEquals(9.0, pathList.get(2).getWeight(), 0.0); } @Test public void testMaximumKPathsAreReturned() { Graph graph = createThreeDisjointPathsGraph(); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 5, 1); assertEquals(1, pathList.size()); pathList = alg.getPaths(1, 5, 2); assertEquals(2, pathList.size()); pathList = alg.getPaths(1, 5, 3); assertEquals(3, pathList.size()); } /** * Tests that sequential calls return the same result. */ @Test public void testSequentialCallsSanity() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); DefaultWeightedEdge edge = graph.addEdge(1, 2); graph.setEdgeWeight(edge, 8); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList_1 = alg.getPaths(1, 2, 5); List> pathList_2 = alg.getPaths(1, 2, 5); assertEquals(pathList_1, pathList_2); } private Graph createThreeDisjointPathsGraph() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); DefaultWeightedEdge e12 = graph.addEdge(1, 2); DefaultWeightedEdge e25 = graph.addEdge(2, 5); DefaultWeightedEdge e13 = graph.addEdge(1, 3); DefaultWeightedEdge e35 = graph.addEdge(3, 5); DefaultWeightedEdge e14 = graph.addEdge(1, 4); DefaultWeightedEdge e45 = graph.addEdge(4, 5); DefaultWeightedEdge e23 = graph.addEdge(2, 3); DefaultWeightedEdge e34 = graph.addEdge(3, 4); graph.setEdgeWeight(e12, 1); graph.setEdgeWeight(e25, 6); graph.setEdgeWeight(e13, 4); graph.setEdgeWeight(e35, 5); graph.setEdgeWeight(e14, 4); graph.setEdgeWeight(e45, 1); graph.setEdgeWeight(e23, 1); graph.setEdgeWeight(e34, 1); return graph; } private Graph createThreeDisjointPathsGraphBidirectional() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); DefaultWeightedEdge e12 = graph.addEdge(1, 2); DefaultWeightedEdge e21 = graph.addEdge(2, 1); DefaultWeightedEdge e25 = graph.addEdge(2, 5); DefaultWeightedEdge e52 = graph.addEdge(5, 2); DefaultWeightedEdge e13 = graph.addEdge(1, 3); DefaultWeightedEdge e31 = graph.addEdge(3, 1); DefaultWeightedEdge e35 = graph.addEdge(3, 5); DefaultWeightedEdge e53 = graph.addEdge(5, 3); DefaultWeightedEdge e14 = graph.addEdge(1, 4); DefaultWeightedEdge e41 = graph.addEdge(4, 1); DefaultWeightedEdge e45 = graph.addEdge(4, 5); DefaultWeightedEdge e54 = graph.addEdge(5, 4); DefaultWeightedEdge e23 = graph.addEdge(2, 3); DefaultWeightedEdge e32 = graph.addEdge(3, 2); DefaultWeightedEdge e34 = graph.addEdge(3, 4); DefaultWeightedEdge e43 = graph.addEdge(4, 3); graph.setEdgeWeight(e12, 1); graph.setEdgeWeight(e21, 1); graph.setEdgeWeight(e25, 6); graph.setEdgeWeight(e52, 6); graph.setEdgeWeight(e13, 4); graph.setEdgeWeight(e31, 4); graph.setEdgeWeight(e35, 5); graph.setEdgeWeight(e53, 5); graph.setEdgeWeight(e14, 4); graph.setEdgeWeight(e41, 4); graph.setEdgeWeight(e45, 1); graph.setEdgeWeight(e54, 1); graph.setEdgeWeight(e23, 1); graph.setEdgeWeight(e32, 1); graph.setEdgeWeight(e34, 1); graph.setEdgeWeight(e43, 1); return graph; } private Graph createUnweightedGraph() { DefaultDirectedGraph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addVertex(5); graph.addVertex(6); graph.addVertex(7); graph.addVertex(8); graph.addEdge(1, 2); graph.addEdge(2, 5); graph.addEdge(1, 3); graph.addEdge(3, 6); graph.addEdge(6, 5); graph.addEdge(1, 4); graph.addEdge(4, 7); graph.addEdge(7, 8); graph.addEdge(8, 5); graph.addEdge(2, 3); graph.addEdge(3, 4); return graph; } @Test public void testThreeDisjointPathsGraphIsNotChanged() { checkGraphIsNotChanged(createThreeDisjointPathsGraph()); } @Test public void testDisconnectedGraphIsNotChanged() { checkGraphIsNotChanged(createDisconnectedGraph()); } public void checkGraphIsNotChanged(Graph source) { Graph destination = new DefaultDirectedWeightedGraph<>( source.getVertexSupplier(), source.getEdgeSupplier()); Graphs.addGraph(destination, source); Map originalWeightMap = new HashMap<>(); for (DefaultWeightedEdge e : source.edgeSet()) { originalWeightMap.put(e, source.getEdgeWeight(e)); } getKShortestPathAlgorithm(source).getPaths(1, 5, 5); assertEquals(destination, source); Map weightMap = new HashMap<>(); for (DefaultWeightedEdge e : source.edgeSet()) { weightMap.put(e, source.getEdgeWeight(e)); } assertEquals(originalWeightMap, weightMap); } @Test public void testUnweightedGraphIsNotChanged() { Graph source = createUnweightedGraph(); Graph destination = new DefaultDirectedGraph<>(source.getVertexSupplier(), source.getEdgeSupplier(), false); Graphs.addGraph(destination, source); Map originalWeightMap = new HashMap<>(); for (DefaultEdge e : source.edgeSet()) { originalWeightMap.put(e, source.getEdgeWeight(e)); } getKShortestPathAlgorithm(source).getPaths(1, 5, 5); assertEquals(destination, source); Map weightMap = new HashMap<>(); for (DefaultEdge e : source.edgeSet()) { weightMap.put(e, source.getEdgeWeight(e)); } assertEquals(originalWeightMap, weightMap); } @Test public void testUnweightedGraph() { Graph graph = createUnweightedGraph(); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 5, 5); assertEquals(3, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 2, 5), 2); assertEquals(expectedP1, pathList.get(0)); assertEquals(2, pathList.get(0).getLength()); assertEquals(2.0, pathList.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 3, 6, 5), 3); assertEquals(expectedP2, pathList.get(1)); assertEquals(3, pathList.get(1).getLength()); assertEquals(3.0, pathList.get(1).getWeight(), 0.0); GraphPath expectedP3 = new GraphWalk<>(graph, Arrays.asList(1, 4, 7, 8, 5), 4); assertEquals(expectedP3, pathList.get(2)); assertEquals(4, pathList.get(2).getLength()); assertEquals(4.0, pathList.get(2).getWeight(), 0.0); } @Test public void testWikipediaGraph() { Graph graph = buildWikipediaGraph(); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths("A", "F", 3); assertEquals(2, pathList.size()); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList("A", "C", "D", "F"), 5); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList("A", "B", "E", "F"), 5); if (pathList.get(0).equals(expectedP1)) { assertEquals(expectedP2, pathList.get(1)); } else if (pathList.get(0).equals(expectedP2)) { assertEquals(expectedP1, pathList.get(1)); } else { fail("Unexpected result"); } } private Graph buildWikipediaGraph() { DefaultDirectedWeightedGraph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addVertex("D"); graph.addVertex("E"); graph.addVertex("F"); DefaultWeightedEdge e; e = graph.addEdge("A", "B"); graph.setEdgeWeight(e, 1.0); e = graph.addEdge("B", "A"); graph.setEdgeWeight(e, 1.0); e = graph.addEdge("A", "C"); graph.setEdgeWeight(e, 2.0); e = graph.addEdge("C", "A"); graph.setEdgeWeight(e, 2.0); e = graph.addEdge("B", "D"); graph.setEdgeWeight(e, 1.0); e = graph.addEdge("D", "B"); graph.setEdgeWeight(e, 1.0); e = graph.addEdge("B", "E"); graph.setEdgeWeight(e, 2.0); e = graph.addEdge("E", "B"); graph.setEdgeWeight(e, 2.0); e = graph.addEdge("D", "C"); graph.setEdgeWeight(e, 2.0); e = graph.addEdge("C", "D"); graph.setEdgeWeight(e, 2.0); e = graph.addEdge("D", "F"); graph.setEdgeWeight(e, 1.0); e = graph.addEdge("F", "D"); graph.setEdgeWeight(e, 1.0); e = graph.addEdge("E", "F"); graph.setEdgeWeight(e, 2.0); e = graph.addEdge("F", "E"); graph.setEdgeWeight(e, 2.0); return graph; } /** * Only one disjoint path should exist on the line */ @Test public void testLinear() { Graph graph = new DefaultDirectedWeightedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultWeightedEdgeSupplier()); GraphGenerator graphGenerator = new LinearGraphGenerator<>(20); graphGenerator.generateGraph(graph); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 20, 2); assertEquals(1, pathList.size()); assertEquals(19, pathList.get(0).getLength()); assertEquals(19.0, pathList.get(0).getWeight(), 0.0); for (int i = 1; i < 21; i++) { assertTrue(pathList.get(0).getVertexList().contains(i)); } } /** * Exactly one disjoint path should exist on the ring */ @Test public void testRing() { Graph graph = new DefaultDirectedWeightedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultWeightedEdgeSupplier()); GraphGenerator graphGenerator = new RingGraphGenerator<>(20); graphGenerator.generateGraph(graph); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); List> pathList = alg.getPaths(1, 10, 2); assertEquals(1, pathList.size()); assertEquals(9, pathList.get(0).getLength()); assertEquals(9.0, pathList.get(0).getWeight(), 0.0); for (int i = 1; i < 10; i++) { assertTrue(pathList.get(0).getVertexList().contains(i)); } } /** * Exactly one disjoint path should exist in a clique */ @Test public void testClique() { Graph graph = new DefaultDirectedWeightedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultWeightedEdgeSupplier()); GraphGenerator graphGenerator = new CompleteGraphGenerator<>(20); graphGenerator.generateGraph(graph); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); for (int i = 2; i < 20; i++) { List> pathList = alg.getPaths(1, i, 2); assertEquals(2, pathList.size()); } } /** * Exactly one disjoint path should exist is a star graph. */ @Test public void testStar() { Graph graph = new DefaultDirectedWeightedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultWeightedEdgeSupplier()); GraphGenerator graphGenerator = new StarGraphGenerator<>(20); graphGenerator.generateGraph(graph); KShortestPathAlgorithm alg = getKShortestPathAlgorithm(graph); for (int i = 2; i < 20; i++) { List> pathList = alg.getPaths(i, 1, 2); assertEquals(1, pathList.size()); } } /** * A complex test case with the goal of finding the three shortest paths from vertex 1 to vertex 2 through the following weighted directed graph. Vertices are numbers in boxes, i.e., 1, 3, 4, 5, 6, 7, 8, 2. * Weights are numbers close to an edge. Each edge has its origin to the left, and destination to the right * The source is node 1. Sink is node 2. * The weight of each edge is the unboxed number close to the edge. * * @formatter:off * * +-+ 2 +-+ * /|3|-----------------|6|- * /-- +-+ \-- /- +-+ \- * /-- \- /--5 \-- * /--- \-/-- 1 \- * /-- 1 /- \-- \- * /-- /-- 1 \- \-- * +-+ /-- +-+ /- \ +-+ \- +-+ * |1|---------------------|4|-----------------|7|----------------|2| * +-+ \--- 1 +-+ 6 /- +-+ 1 /-- +-+ * \--- /-- /-- * \-- --/ /- * 1 \--- /-- 3 /-- 1 * \--- +-+ /-- +-+ /-- * \|5|-----------------|8|- * +-+ 6 +-+ * * @formatter:on * * The expected result is the three paths through vertices: * p1 = 1, 3, 7, 2 * p2 = 1, 4, 6, 2 * p3 = 1, 5, 8, 2 * */ @Test public void testThreeDisjointPathsWithMultiHitsOnEdge() { GraphBuilder> builder = SimpleDirectedWeightedGraph.createBuilder(DefaultWeightedEdge.class); builder.addEdge(1, 3, 1); builder.addEdge(1, 4, 1); builder.addEdge(1, 5, 1); builder.addEdge(3, 6, 2); builder.addEdge(3, 7, 1); builder.addEdge(4, 7, 6); builder.addEdge(4, 6, 5); builder.addEdge(5, 7, 3); builder.addEdge(5, 8, 6); builder.addEdge(6, 2, 1); builder.addEdge(7, 2, 1); builder.addEdge(8, 2, 1); SimpleDirectedWeightedGraph graph = builder.build(); KShortestPathAlgorithm ksp = getKShortestPathAlgorithm(graph); List> paths = ksp.getPaths(1, 2, 3); int[] p1 = { 1, 3, 7, 2 }; int[] p2 = { 1, 4, 6, 2 }; int[] p3 = { 1, 5, 8, 2 }; List expectedPaths = Arrays.asList(p1, p2, p3); List resultPaths = paths .stream().map(GraphPath::getVertexList) .map(vlist -> vlist.stream().mapToInt(i -> i).toArray()).collect(Collectors.toList()); Assert.assertEquals(expectedPaths.size(), resultPaths.size()); for (int[] expectedPath : expectedPaths) { boolean isIncluded = resultPaths.stream().anyMatch(result -> Arrays.equals(result, expectedPath)); Assert.assertTrue(isIncluded); } } @Test public void testFirstPathEdgesFirst() { GraphBuilder> builder = SimpleDirectedWeightedGraph.createBuilder(DefaultWeightedEdge.class); builder.addEdge(1, 2, 1); builder.addEdge(2, 1, 1); builder.addEdge(2, 3, 1); builder.addEdge(3, 2, 1); builder.addEdge(1, 4, 1); builder.addEdge(4, 1, 1); builder.addEdge(1, 5, 1); builder.addEdge(5, 1, 1); builder.addEdge(2, 5, 1); builder.addEdge(5, 2, 1); builder.addEdge(2, 6, 1); builder.addEdge(6, 2, 1); builder.addEdge(3, 6, 1); builder.addEdge(6, 3, 1); builder.addEdge(3, 7, 1); builder.addEdge(7, 3, 1); builder.addEdge(4, 5, 1); builder.addEdge(5, 4, 1); builder.addEdge(6, 7, 1); builder.addEdge(7, 6, 1); builder.addEdge(5, 8, 1); builder.addEdge(8, 5, 1); builder.addEdge(6, 9, 1); builder.addEdge(9, 6, 1); builder.addEdge(8, 9, 1); builder.addEdge(9, 8, 1); SimpleDirectedWeightedGraph graph = builder.build(); KShortestPathAlgorithm ksp = getKShortestPathAlgorithm(graph); List> paths = ksp.getPaths(1, 3, 3); GraphPath expectedP1 = new GraphWalk<>(graph, Arrays.asList(1, 2, 3), 2); assertEquals(expectedP1, paths.get(0)); assertEquals(2, paths.get(0).getLength()); assertEquals(2.0, paths.get(0).getWeight(), 0.0); GraphPath expectedP2 = new GraphWalk<>(graph, Arrays.asList(1, 5, 2, 6, 3), 4); assertEquals(expectedP2, paths.get(1)); assertEquals(4, paths.get(1).getLength()); assertEquals(4.0, paths.get(1).getWeight(), 0.0); GraphPath expectedP3 = new GraphWalk<>(graph, Arrays.asList(1, 4, 5, 8, 9, 6, 7, 3), 7); assertEquals(expectedP3, paths.get(2)); assertEquals(7, paths.get(2).getLength()); assertEquals(7.0, paths.get(2).getWeight(), 0.0); } protected abstract KShortestPathAlgorithm getKShortestPathAlgorithm(Graph graph); } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/KSPExample.png000066400000000000000000000271621402514743400321020ustar00rootroot00000000000000PNG  IHDRU Jb pHYsKJk{.$IDATxPםqP޸41vv Led#H.PeE3ժEC赱Ш 0E+8:S"2LDNoCD~{<# {>{Ԕ4`74gBs!4gBs!4gBs!4gBs!4gBs!4gBs!4gO )UpWÁ͙σl6p<h&МhМhМhМh8{?33_~cŋ=<rrrr^dOOՕpu?__p&۷o~v}o|F~͛7o޼yʕ{/~񋚚|^vؗzgy7߬<|uuuu?o޾m?qjjj!+%A/S.i>5؇3927ow޽qiΜ9sȑh5؇Ç׿V\rɒ7nܰZz뭷?U`_%{8u ɞ3:X9p4gZ .mddddd$>>>>>_uEWGsf6fszzzzzZps9vڵk׶o߾}vյFs㣺 @Ch4 @C~C/p>8q5Xpٔ"k׮]vmYYYYY>o DZ篪JHHHHHyƚk֬YfMcccccZ`lٲe˖Jp8 #޽{ݥK.]Tu]\ؖ˗/_\WQ r)A@MMMMM`-[l2bXrsssssUW ؜W^yW^immmmmU] ,TrrrrrM cM166666{Ν;wxyyyyy kpDDDDDjZDvL\rʕ +'ʰ͙HJJJJJbrdeeeee=x+`O5&wD᝝D1|LM$`$qgjjjjj B96lذa&㑈d2LrX(ǚbrrrrrR͞M;[d/X9ה+{V0h͛7o|ܹsΩ/'''''G"N@\"nr&GsNiK)N"N@\hL^MJ&8 8=rLlݺu֭-#####Ã5D`````7nܸ!g 19?ִM ˭yzzzzzʝ/^xQ8pL0 uqzr;7޽{]YQS]Qkڒ)N98-sѕ39D#.....M@88-sL$%%%%%?kgj-#INn~|&Ni.upss3iV^zjMCv|apssXSH)nr&{fi/*****jZV9Mu]bpM) `ݻw޶m۶mTW:/kZkkkkkZٳgd284L>>>>>>CCCCCCR]}~ivP/H+F ''Yf͚5kUj۷o߾}ѣGU]ZtK_-8CCCCCC8`nbX,989 dSڲRƧvG#`/5mAwq 0&Vξ&ؒj%&7VNNNNN'8_lwpΝ;wMseX'`_=&Llٲe˖qEsn֪"+++++KǏ?~\uEqj7,bM[q)􍕳 G3?????_$lV7QdS^rP-p]l/McXӖ))@ Vfpf&XaaaaaaJJJJJ '0?4gs@ 3Ȑ8qv{!0Wkڒ8;;;;;;8cl$܌$G333333SuEМYRRRRR&L9=[l_1??????M3sXӖLq߸qƍDX93i7`6l8.NZGs6O0{DbBn bM[wq V6ܬQ]hř"@hd>۷o~'x'x_|+W\re~ @;f0ӧOV]-4g vڵkvwwwwwrH||||||>Hg~駟~iӦM6M߷1eN:uTAAAAALt Ќ),ܹYVVVVV6%3/o}[O>yG}G}=3t9`T%%%%%%+W\rg}gPmv gΜ9slqˠf2dɒ%Kܿ3 9fsrD(.@%{8LnȫʏUU&\?s̾rL NqP`ϙ,777777?O>˾}$hw~~~~~Lqʊ5hq~7)\򞉉sHۃjϞ={YjМB&7gbŊ+V8qĉcǎ;v,*****j~ TQQQQQ!Swp: F3͙׮]vLb.^xŁnݺu>sI3ٖ>>>>?+՚L&tÇj', &/Ěz422222~_zիNĉ`Lg7flNOOOOO_)μ<"N͙.Iب0k׮]vm۷o_g)Nyb.I+ᦗ|;'gggggg''3]psիWnhhhhhP]`&bqbf4g:&ϟ?ܹsΝb&Ȳ*惿?0~(%⌊ZLGs ݭl6F q=zQI?dZA #9ݳgϞ={m"Nآ9@bSuEP edsݻwf ۷o>OOOOOO"NМ D9@/_|r"NМ!"8IA34G"N333333Xu-0IiTW{93 ֯_קuֽ|J;_FEF dG9rH_____`4g!ׯwt|{ކ ?fի7mW]5`L9kmmm>pӧUWXlٲe.N8 `.~ ?лW^yW^kUWc ݓ8%P] tL,A,,,,,,LIIIII!/3нP9;~pppppPuEP######C.N9;uԩSnݺu-9X!88888877777F5]DzDs'w/Jvƍ7nLLLLLLШ&ۻ8333333UW9CyT5!T/4g`Xڝ;wܹA dS/h%x{{{{{6j~}ܪ+ќ˙ި-]tҥQ3"N}tiMUW113(eӧONNNNNN˓˂TŘ?ie=(((((Hu]2V4aJ󚚚BBBBBB>? F'lvEM0L&IV8p11̇j˩x+W9ì$$$$$$&bpڽ{ݻ'ҨWNNNNN_F9/ />kjjjjjo߾}v'_ T׈Ǔ3*****jZVP]X9<׫خuuuuuuٮ1Lq޽{ݩũ4g3Y ޵k׮]8' pMӧ>}g}VӨiwzzzzzz={ٳ+M,)))))IIIIII{T@ 9CMo߿~iؖcZ^lkdƍ7n|^{5iTW@׿L}j'Z4gyKuE¶Q>4jZc{P-ZLkn$֔.Z$ ١C:d;sΝ;wJKFW$oFGGGGG˵r*/\pV]-v@!'ZBs;@ U]풶LM>iԜO"NyAG Gjkkkkkr(Ar~_W IIIIIID!T+':+gp id ҫӣSOqZ,"w04gpfy"c;9f5DZ8tbM8۷o߾-@mmmmmmfl6 MLLLLL=zѣgΜ9s֭[nJi_lSUeLIBBBBBBdMx `dO>O>aE;l#N8 N lڴiӦM``/N&{$?cًDrpReeeeeꊌX HCjժUVEFFFFFYG`LҐx jȺ꺌3( j4555555P]c& dEml#N٦Bb_4gPF͍MziDsE8ĚY9ۻw޽{qI.#G9r>g83hZudEMQ63ۻ8SRRRRRTWd4gF%ErG1[n %'./////W]ќACӨ%IÇJ+/)UW^EEEEEšC:$HhΠ9… .\]h$Ϟg}gصk׮]\9S0Ź4gШcǎ;vL1: v)O?OG------MǏ?~\uEô&t@EU_& Yiiiii+)/eMQ,$Q]>r()))))덳UW_vEMVJWأ&jH \ќAdy\v3Eu]0s=sz+ H)oq&tWO8Xy͛7oڴiӦM{>iq+ 3о}g?;)Sr)_yyyyyԣ9aɠ@}}}}}lݺuKޯaSN:uJ6a+R %'YV]8SZ8{A)  RSSSSS9is`~7j/^x4jZأ&TYYYYY@a޽{C7 zӧ>ebX,! 5tuuuuuiԾX9 p iTWM&`QVLZC"N3f+FM~Iy޽{J6:::::JXZ"NbMUVZJ"NSu]E24P\\\\\,Iio__|89K߿~9Z&TFmݻw)Ҝuwww'1fA &`6Es"%P]^ќ_( r@l5&gZJ+ BUUUUUpFMԧQ 3 2:.7 dgggggs̏4j%%%%%%2)6j+"3ȍǎ;vB t/7Lo233333Uר-4gWA-[lٲAFM xNQc[nݺur4lrOL>>>>_1:nݺu0(xlWf322222М:311111!M}frcuuuuuАl>GE}۷flV]BMGs@4]_ٲfh(#YooooooSSSSS`nq233333_x^xaϞ={Q]̖#9!,bS]qwМVZjժ9tCu]\ќѱ d2TcL#bbbbbb6lذaÆW`pw2(@ r+9 Nn]hyyyyyyrS hÒI%U%qx2h8Vwww~gf٬6}ɠe hМhМhМhМh7xGK/R\J8lN=As`V?d^zŽ{]whWCĚIZ1i~W]>Lu-`Ě4q{Yh;= LMMM@þ/~hXXLn <]r~'Ӛ +# 0+wp9 `: C !4g9W] `44g9W]`44g90f9=====]u-1ќڵk׮]۾}U`||||||TWМhOMMMq/`DVEx./<* !3 93 93 93 93 93 93 93 93 9АgŽ7<IENDB`ListSingleSourcePathsTest.java000066400000000000000000000044121402514743400352760ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * @author Dimitrios Michail */ public class ListSingleSourcePathsTest { @Test public void test() { int n = 50; DirectedPseudograph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER, false); GraphGenerator gen = new GnpRandomGraphGenerator<>(n, 0.7); gen.generateGraph(g); List> p = new ArrayList<>(); Map> map = new HashMap<>(); for (int i = 1; i < n; i++) { GraphPath path = new DijkstraShortestPath<>(g).getPath(0, i); p.add(path); map.put(i, path); } ListSingleSourcePathsImpl paths = new ListSingleSourcePathsImpl<>(g, 0, map); assertEquals(0, paths.getSourceVertex().intValue()); assertEquals(0d, paths.getWeight(0), 1e-9); for (int i = 1; i < n; i++) { assertEquals(p.get(i - 1).getWeight(), paths.getWeight(i), 1e-9); assertEquals(p.get(i - 1).getEdgeList(), paths.getPath(i).getEdgeList()); } assertEquals(Double.POSITIVE_INFINITY, paths.getWeight(n), 1e-9); } } MartinShortestPathTest.java000066400000000000000000000074241402514743400346510ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.MultiObjectiveShortestPathAlgorithm.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.assertEquals; /** * Test {@link MartinShortestPath}. * * @author Dimitrios Michail */ public class MartinShortestPathTest { @Test public void testGraphDirected() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); IntStream.range(1, 6).forEach(g::addVertex); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e13 = g.addEdge(1, 3); DefaultEdge e14 = g.addEdge(1, 4); DefaultEdge e24 = g.addEdge(2, 4); DefaultEdge e25 = g.addEdge(2, 5); DefaultEdge e34 = g.addEdge(3, 4); DefaultEdge e35 = g.addEdge(3, 5); DefaultEdge e45 = g.addEdge(4, 5); DefaultEdgeFunction f = new DefaultEdgeFunction<>(new double[] { 0.0, 0.0 }); f.set(e12, new double[] { 1.0, 5.0 }); f.set(e13, new double[] { 4.0, 2.0 }); f.set(e14, new double[] { 4.0, 4.0 }); f.set(e24, new double[] { 1.0, 2.0 }); f.set(e25, new double[] { 2.0, 5.0 }); f.set(e34, new double[] { 2.0, 3.0 }); f.set(e35, new double[] { 6.0, 1.0 }); f.set(e45, new double[] { 3.0, 3.0 }); MultiObjectiveSingleSourcePaths paths1 = new MartinShortestPath<>(g, f).getPaths(1); List> paths11 = paths1.getPaths(1); assertEquals(1, paths11.size()); List> paths12 = paths1.getPaths(2); assertEquals(1, paths12.size()); List> paths13 = paths1.getPaths(3); assertEquals(1, paths13.size()); List> paths14 = paths1.getPaths(4); assertEquals(2, paths14.size()); List> paths15 = paths1.getPaths(5); assertEquals(3, paths15.size()); } @Test public void testNoPaths() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); DefaultEdgeFunction f = new DefaultEdgeFunction<>(new double[] { 0.0, 0.0 }); MultiObjectiveSingleSourcePaths paths1 = new MartinShortestPath<>(g, f).getPaths(1); List> paths11 = paths1.getPaths(1); assertEquals(1, paths11.size()); List> paths12 = paths1.getPaths(2); assertEquals(0, paths12.size()); MultiObjectiveSingleSourcePaths paths2 = new MartinShortestPath<>(g, f).getPaths(2); List> paths21 = paths2.getPaths(1); assertEquals(0, paths21.size()); List> paths22 = paths2.getPaths(2); assertEquals(1, paths22.size()); } } ShortestPathTestCase.java000066400000000000000000000065651402514743400342770ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * . * * @author John V. Sichi */ public abstract class ShortestPathTestCase { // ~ Static fields/initializers --------------------------------------------- static final String V1 = "v1"; static final String V2 = "v2"; static final String V3 = "v3"; static final String V4 = "v4"; static final String V5 = "v5"; // ~ Instance fields -------------------------------------------------------- DefaultWeightedEdge e12; DefaultWeightedEdge e13; DefaultWeightedEdge e15; DefaultWeightedEdge e24; DefaultWeightedEdge e34; DefaultWeightedEdge e45; // ~ Methods ---------------------------------------------------------------- /** * . */ @Test public void testPathBetween() { List path; Graph g = create(); path = findPathBetween(g, V1, V2); assertEquals(Arrays.asList(new DefaultWeightedEdge[] { e12 }), path); path = findPathBetween(g, V1, V4); assertEquals(Arrays.asList(new DefaultWeightedEdge[] { e12, e24 }), path); path = findPathBetween(g, V1, V5); assertEquals(Arrays.asList(new DefaultWeightedEdge[] { e12, e24, e45 }), path); path = findPathBetween(g, V3, V4); assertEquals(Arrays.asList(new DefaultWeightedEdge[] { e13, e12, e24 }), path); } protected abstract List findPathBetween( Graph g, String src, String dest); protected Graph create() { return createWithBias(false); } protected Graph createWithBias(boolean negate) { Graph g; double bias = 1; if (negate) { // negative-weight edges are being tested, so only a directed graph // makes sense g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); bias = -1; } else { // by default, use an undirected graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); } g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); e12 = Graphs.addEdge(g, V1, V2, bias * 2); e13 = Graphs.addEdge(g, V1, V3, bias * 3); e24 = Graphs.addEdge(g, V2, V4, bias * 5); e34 = Graphs.addEdge(g, V3, V4, bias * 20); e45 = Graphs.addEdge(g, V4, V5, bias * 5); e15 = Graphs.addEdge(g, V1, V5, bias * 100); return g; } } SuurballeKDisjointShortestPathsTest.java000066400000000000000000000021511402514743400373470ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2018-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; /** * * Tests for the {@link SuurballeKDisjointShortestPaths} class. * * @author Assaf Mizrachi */ public class SuurballeKDisjointShortestPathsTest extends KDisjointShortestPathsTestCase { @Override protected KShortestPathAlgorithm getKShortestPathAlgorithm(Graph graph) { return new SuurballeKDisjointShortestPaths<>(graph); } } TransitNodeRoutingPrecomputationTest.java000066400000000000000000000412141402514743400375750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.Graph; import org.jgrapht.GraphPath; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm; import org.jgrapht.generate.GnmRandomGraphGenerator; import org.jgrapht.generate.GraphGenerator; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.DirectedWeightedPseudograph; import org.jgrapht.graph.GraphWalk; import org.jgrapht.util.ConcurrencyUtil; import org.jgrapht.util.SupplierUtil; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ThreadPoolExecutor; import java.util.stream.Collectors; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionHierarchy; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionVertex; import static org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.AccessVertex; import static org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.AccessVertices; import static org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.TransitNodeRouting; import static org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.VoronoiDiagram; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * Tests for the {@link TransitNodeRoutingPrecomputation}. * * @author Semen Chudakov */ public class TransitNodeRoutingPrecomputationTest { /** * Seed for random numbers generator used in tests. */ private static final long SEED = 19L; /** * Executor which is supplied to {@link ContractionHierarchyPrecomputation} and * {@link TransitNodeRoutingPrecomputation} in this test case. */ private static ThreadPoolExecutor executor; @BeforeClass public static void createExecutor() { executor = ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); } @AfterClass public static void shutdownExecutor() throws InterruptedException { ConcurrencyUtil.shutdownExecutionService(executor); } /** * Tests the algorithm on an empty graph to ensure no exception is thrown. */ @Test public void testEmptyGraph() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); ContractionHierarchy contractionHierarchy = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); TransitNodeRoutingPrecomputation routing = new TransitNodeRoutingPrecomputation<>(contractionHierarchy, 0, executor); routing.computeTransitNodeRouting(); } @Test public void testOneVertex() { // initialisation Integer vertex = 1; Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.addVertex(vertex); ContractionHierarchy contractionHierarchy = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); // computation TransitNodeRoutingPrecomputation precomputation = new TransitNodeRoutingPrecomputation<>(contractionHierarchy, 1, executor); TransitNodeRouting routing = precomputation.computeTransitNodeRouting(); Map> contractionMapping = contractionHierarchy.getContractionMapping(); ContractionVertex contractionVertex = contractionMapping.get(vertex); // transit vertices assertTrue(routing.getTransitVertices().contains(contractionVertex)); // access vertices AccessVertices accessVertices = routing.getAccessVertices(); List> forwardAccessVertices = accessVertices.getForwardAccessVertices(contractionVertex); List> backwardAccessVertices = accessVertices.getBackwardAccessVertices(contractionVertex); assertEquals(forwardAccessVertices.size(), 1); assertEquals(forwardAccessVertices.get(0).getVertex(), vertex); GraphPath expectedPath1 = new GraphWalk<>( graph, vertex, vertex, Collections.singletonList(vertex), Collections.emptyList(), 0.0); assertEquals(expectedPath1, forwardAccessVertices.get(0).getPath()); assertEquals(backwardAccessVertices.size(), 1); assertEquals(backwardAccessVertices.get(0).getVertex(), vertex); GraphPath expectedPath2 = new GraphWalk<>( graph, vertex, vertex, Collections.singletonList(vertex), Collections.emptyList(), 0.0); assertEquals(expectedPath2, forwardAccessVertices.get(0).getPath()); // locality filter assertFalse(routing.getLocalityFilter().isLocal(vertex, vertex)); } @Test public void testThreeVertices() { Integer v1 = 1; Integer v2 = 2; Integer v3 = 3; Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.addVertex(v1); graph.addVertex(v2); graph.addVertex(v3); DefaultWeightedEdge edge1 = Graphs.addEdgeWithVertices(graph, v1, v2, 1.0); DefaultWeightedEdge edge2 = Graphs.addEdgeWithVertices(graph, v2, v1, 1.0); DefaultWeightedEdge edge3 = Graphs.addEdgeWithVertices(graph, v2, v3, 2.0); // to ensure // Voronoi // diagram // correctness DefaultWeightedEdge edge4 = Graphs.addEdgeWithVertices(graph, v3, v2, 2.0); ContractionHierarchy contractionHierarchy = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); // computation TransitNodeRoutingPrecomputation precomputation = new TransitNodeRoutingPrecomputation<>(contractionHierarchy, 1, executor); TransitNodeRouting routing = precomputation.computeTransitNodeRouting(); Map> contractionMapping = routing.getContractionHierarchy().getContractionMapping(); ContractionVertex cv1 = contractionMapping.get(v1); ContractionVertex cv2 = contractionMapping.get(v2); ContractionVertex cv3 = contractionMapping.get(v3); // transit vertices assertTrue(routing.getTransitVertices().contains(cv2)); // access vertices AccessVertices accessVertices = routing.getAccessVertices(); List> cv1ForwardAccessVertices = accessVertices.getForwardAccessVertices(cv1); List> cv1BackwardAccessVertices = accessVertices.getBackwardAccessVertices(cv1); assertEquals(cv1ForwardAccessVertices.size(), 1); assertEquals(cv1ForwardAccessVertices.get(0).getVertex(), v2); GraphPath expectedPath1 = new GraphWalk<>( graph, v1, v2, Arrays.asList(v1, v2), Collections.singletonList(edge1), 1.0); assertEquals(expectedPath1, cv1ForwardAccessVertices.get(0).getPath()); assertEquals(cv1BackwardAccessVertices.size(), 1); assertEquals(cv1BackwardAccessVertices.get(0).getVertex(), v2); GraphPath expectedPath2 = new GraphWalk<>( graph, v2, v1, Arrays.asList(v2, v1), Collections.singletonList(edge2), 1.0); assertEquals(expectedPath2, cv1BackwardAccessVertices.get(0).getPath()); List> cv2ForwardAccessVertices = accessVertices.getForwardAccessVertices(cv2); List> cv2BackwardAccessVertices = accessVertices.getBackwardAccessVertices(cv2); assertEquals(cv2ForwardAccessVertices.size(), 1); assertEquals(cv2BackwardAccessVertices.size(), 1); List> cv3ForwardAccessVertices = accessVertices.getForwardAccessVertices(cv3); List> cv3BackwardAccessVertices = accessVertices.getBackwardAccessVertices(cv3); assertEquals(cv3ForwardAccessVertices.size(), 1); assertEquals(cv3ForwardAccessVertices.get(0).getVertex(), v2); GraphPath expectedPath3 = new GraphWalk<>( graph, v3, v2, Arrays.asList(v3, v2), Collections.singletonList(edge4), 2.0); assertEquals(expectedPath3, cv3ForwardAccessVertices.get(0).getPath()); assertEquals(cv3BackwardAccessVertices.size(), 1); assertEquals(cv3ForwardAccessVertices.get(0).getVertex(), v2); GraphPath expectedPath4 = new GraphWalk<>( graph, v2, v3, Arrays.asList(v2, v3), Collections.singletonList(edge3), 2.0); assertEquals(expectedPath4, cv3BackwardAccessVertices.get(0).getPath()); // locality filter assertTrue(routing.getLocalityFilter().isLocal(v1, v1)); assertFalse(routing.getLocalityFilter().isLocal(v1, v2)); assertTrue(routing.getLocalityFilter().isLocal(v1, v3)); assertFalse(routing.getLocalityFilter().isLocal(v2, v2)); assertFalse(routing.getLocalityFilter().isLocal(v2, v3)); assertTrue(routing.getLocalityFilter().isLocal(v3, v3)); } @Test public void testOnRandomGraphs() { int numOfVertices = 30; int vertexDegree = 5; int numOfIterations = 50; Random random = new Random(SEED); for (int i = 0; i < numOfIterations; ++i) { Graph graph = generateRandomGraph(numOfVertices, vertexDegree * numOfVertices, random); TransitNodeRoutingPrecomputation routing = new TransitNodeRoutingPrecomputation<>(graph, executor); assertCorrectTNR(graph, routing.computeTransitNodeRouting()); } } /** * Checks given {@code routing} for correctness wrt the provided {@code graph}. Firstly, checks * that the number of transit vertices is equal to $\sqrt{|V|}$, here $V$ is the set of * vertices. Secondly, checks that the transit vertices are selected from the top of the * contraction hierarchy. Thirdly, checks that the set of sources and targets of many-to-many * shortest paths are equal to the set of transit vertices. Fourthly, checks that cell ids in * the Voronoi diagram are in range $[-1, |V| - 1]$. Finally, checks that paths for access * vertices are computed correctly. * * @param graph graph * @param routing transit node routing */ private void assertCorrectTNR( Graph graph, TransitNodeRouting routing) { int numberOfVertices = graph.vertexSet().size(); // check transit vertices assertEquals((int) Math.sqrt(numberOfVertices), routing.getTransitVertices().size()); for (ContractionVertex vertex : routing.getTransitVertices()) { assertTrue( vertex.contractionLevel >= numberOfVertices - routing.getTransitVertices().size()); } // many-to-many shortest paths Set transitVerticesSet = routing .getTransitVertices().stream().map(v -> v.vertex) .collect(Collectors.toCollection(HashSet::new)); assertEquals(transitVerticesSet, routing.getTransitVerticesPaths().getSources()); assertEquals(transitVerticesSet, routing.getTransitVerticesPaths().getTargets()); // check Voronoi diagram VoronoiDiagram voronoiDiagram = routing.getVoronoiDiagram(); for (ContractionVertex vertex : routing .getContractionHierarchy().getContractionGraph().vertexSet()) { int voronoiCellId = voronoiDiagram.getVoronoiCellId(vertex); assertTrue(voronoiCellId >= -1 && voronoiCellId < numberOfVertices); } // check access vertices AccessVertices accessVertices = routing.getAccessVertices(); ShortestPathAlgorithm sp = new BidirectionalDijkstraShortestPath<>(graph); for (ContractionVertex vertex : routing .getContractionHierarchy().getContractionGraph().vertexSet()) { List> av = accessVertices.getForwardAccessVertices(vertex); for (AccessVertex accessVertex : av) { assertEquals( sp.getPath(vertex.vertex, accessVertex.getVertex()), accessVertex.getPath()); } } for (ContractionVertex vertex : routing .getContractionHierarchy().getContractionGraph().vertexSet()) { List> av = accessVertices.getBackwardAccessVertices(vertex); for (AccessVertex accessVertex : av) { assertEquals( sp.getPath(accessVertex.getVertex(), vertex.vertex), accessVertex.getPath()); } } } /** * Generates a graph instance from the $G(n,M)$ random graphs model with {@code numOfVertices} * vertices and {@code numOfEdges} edges. * * @param numOfVertices number of vertices in a graph * @param numOfEdges number of edges in a graph * @param random random generator * @return random graph */ private Graph generateRandomGraph( int numOfVertices, int numOfEdges, Random random) { DirectedWeightedPseudograph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); GraphGenerator generator = new GnmRandomGraphGenerator<>(numOfVertices, numOfEdges - numOfVertices + 1, SEED); generator.generateGraph(graph); makeConnected(graph); addEdgeWeights(graph, random); return graph; } /** * Makes {@code graph} connected. * * @param graph a graph */ private void makeConnected(Graph graph) { Object[] vertices = graph.vertexSet().toArray(); for (int i = 0; i < vertices.length - 1; ++i) { graph.addEdge((Integer) vertices[i], (Integer) vertices[i + 1]); graph.addEdge((Integer) vertices[i + 1], (Integer) vertices[i]); } } /** * Sets weight for every edge in the {@code graph}. * * @param graph a graph * @param random random generator instance */ private void addEdgeWeights(Graph graph, Random random) { for (DefaultWeightedEdge edge : graph.edgeSet()) { graph.setEdgeWeight(edge, random.nextDouble()); } } } TransitNodeRoutingShortestPathTest.java000066400000000000000000000253401402514743400372160ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.Graph; import org.jgrapht.GraphPath; import org.jgrapht.Graphs; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm; import org.jgrapht.generate.GnmRandomGraphGenerator; import org.jgrapht.generate.GraphGenerator; import org.jgrapht.graph.DefaultWeightedEdge; import org.jgrapht.graph.DirectedWeightedPseudograph; import org.jgrapht.graph.GraphWalk; import org.jgrapht.util.ConcurrencyUtil; import org.jgrapht.util.SupplierUtil; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import java.util.Arrays; import java.util.Collections; import java.util.Random; import java.util.Set; import java.util.concurrent.ThreadPoolExecutor; import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionHierarchy; import static org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation.TransitNodeRouting; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Test for the {@link TransitNodeRoutingShortestPath}. * * @author Semen Chudakov */ public class TransitNodeRoutingShortestPathTest { /** * Seed for random numbers generator used in tests. */ private static final long SEED = 19L; /** * Executor which is supplied to {@link TransitNodeRoutingShortestPath}, * {@link TransitNodeRoutingPrecomputation} and {@link ContractionHierarchyPrecomputation} in * this test case. */ private static ThreadPoolExecutor executor; @BeforeClass public static void createExecutor() { executor = ConcurrencyUtil.createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); } @AfterClass public static void shutdownExecutor() throws InterruptedException { ConcurrencyUtil.shutdownExecutionService(executor); } @Test public void testOneVertex() { Integer vertex = 1; Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.addVertex(vertex); TransitNodeRoutingShortestPath shortestPath = new TransitNodeRoutingShortestPath<>(graph, executor); GraphPath path = shortestPath.getPath(vertex, vertex); GraphWalk expectedPath = new GraphWalk<>( graph, vertex, vertex, Collections.singletonList(vertex), Collections.emptyList(), 0.0); assertEquals(expectedPath, path); } @Test public void testTwoVertices() { Integer v1 = 1; Integer v2 = 2; Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); DefaultWeightedEdge edge1 = Graphs.addEdgeWithVertices(graph, v1, v2, 1.0); DefaultWeightedEdge edge2 = Graphs.addEdgeWithVertices(graph, v1, v2, 2.0); DefaultWeightedEdge edge3 = Graphs.addEdgeWithVertices(graph, v2, v1, 1.0); ContractionHierarchy contractionHierarchy = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); TransitNodeRouting routing = new TransitNodeRoutingPrecomputation<>(contractionHierarchy, 1, executor) .computeTransitNodeRouting(); TransitNodeRoutingShortestPath shortestPath = new TransitNodeRoutingShortestPath<>(routing); GraphPath expectedPath1 = new GraphWalk<>( graph, v1, v2, Arrays.asList(v1, v2), Collections.singletonList(edge1), 1.0); assertEquals(expectedPath1, shortestPath.getPath(v1, v2)); GraphPath expectedPath2 = new GraphWalk<>( graph, v2, v1, Arrays.asList(v2, v1), Collections.singletonList(edge3), 1.0); assertEquals(expectedPath2, shortestPath.getPath(v2, v1)); } @Test public void testThreeVertices() { Integer v1 = 1; Integer v2 = 2; Integer v3 = 3; Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); DefaultWeightedEdge edge1 = Graphs.addEdgeWithVertices(graph, v1, v2, 1.0); DefaultWeightedEdge edge2 = Graphs.addEdgeWithVertices(graph, v2, v3, 2.0); DefaultWeightedEdge edge3 = Graphs.addEdgeWithVertices(graph, v3, v2, 1.0); ContractionHierarchy contractionHierarchy = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); TransitNodeRouting routing = new TransitNodeRoutingPrecomputation<>(contractionHierarchy, 1, executor) .computeTransitNodeRouting(); TransitNodeRoutingShortestPath shortestPath = new TransitNodeRoutingShortestPath<>(routing); GraphPath expectedPath1 = new GraphWalk<>( graph, v1, v2, Arrays.asList(v1, v2), Collections.singletonList(edge1), 1.0); assertEquals(expectedPath1, shortestPath.getPath(v1, v2)); assertNull(shortestPath.getPath(v2, v1)); GraphPath expectedPath2 = new GraphWalk<>( graph, v2, v3, Arrays.asList(v2, v3), Collections.singletonList(edge2), 2.0); assertEquals(expectedPath2, shortestPath.getPath(v2, v3)); GraphPath expectedPath3 = new GraphWalk<>( graph, v3, v2, Arrays.asList(v3, v2), Collections.singletonList(edge3), 1.0); assertEquals(expectedPath3, shortestPath.getPath(v3, v2)); GraphPath expectedPath4 = new GraphWalk<>( graph, v1, v3, Arrays.asList(v1, v2, v3), Arrays.asList(edge1, edge2), 3.0); assertEquals(expectedPath4, shortestPath.getPath(v1, v3)); assertNull(shortestPath.getPath(v3, v1)); } @Test public void testOnRandomGraphs() { int numOfVertices = 30; int vertexDegree = 5; int numOfIterations = 20; int source = 0; Random random = new Random(SEED); for (int i = 0; i < numOfIterations; ++i) { testOnGraph( generateRandomGraph(numOfVertices, vertexDegree * numOfVertices, random), source); } } /** * Test correctness of {@link TransitNodeRoutingShortestPath} on {@code graph} starting at * {@code source}. * * @param graph graph * @param source vertex in {@code graph} */ private void testOnGraph(Graph graph, Integer source) { ShortestPathAlgorithm.SingleSourcePaths dijkstraShortestPaths = new DijkstraShortestPath<>(graph).getPaths(source); ContractionHierarchy contractionHierarchy = new ContractionHierarchyPrecomputation<>(graph, () -> new Random(SEED), executor) .computeContractionHierarchy(); TransitNodeRouting routing = new TransitNodeRoutingPrecomputation<>(contractionHierarchy, executor) .computeTransitNodeRouting(); TransitNodeRoutingShortestPath transitNodeRoutingShortestPath = new TransitNodeRoutingShortestPath<>(routing); ShortestPathAlgorithm.SingleSourcePaths tnrShortestPaths = transitNodeRoutingShortestPath.getPaths(source); assertEqualPaths(dijkstraShortestPaths, tnrShortestPaths, graph.vertexSet()); } /** * Generates an instance of random graph with {@code numOfVertices} vertices and * {@code numOfEdges} edges. * * @param numOfVertices number of vertices * @param numOfEdges number of edges * @return generated graph */ private Graph generateRandomGraph( int numOfVertices, int numOfEdges, Random random) { DirectedWeightedPseudograph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); GraphGenerator generator = new GnmRandomGraphGenerator<>(numOfVertices, numOfEdges - numOfVertices + 1, SEED); generator.generateGraph(graph); makeConnected(graph); addEdgeWeights(graph, random); return graph; } /** * Makes {@code graph} connected. * * @param graph graph */ private void makeConnected(Graph graph) { Object[] vertices = graph.vertexSet().toArray(); for (int i = 0; i < vertices.length - 1; ++i) { graph.addEdge((Integer) vertices[i], (Integer) vertices[i + 1]); graph.addEdge((Integer) vertices[i + 1], (Integer) vertices[i]); } } /** * Sets edge weights to edges in {@code graph}. * * @param graph graph * @param random random numbers generator */ private void addEdgeWeights(Graph graph, Random random) { for (DefaultWeightedEdge edge : graph.edgeSet()) { graph.setEdgeWeight(edge, random.nextDouble()); } } /** * Checks computed single source shortest paths tree for equality. * * @param expected expected paths * @param actual actual paths * @param vertexSet vertices */ private void assertEqualPaths( ShortestPathAlgorithm.SingleSourcePaths expected, ShortestPathAlgorithm.SingleSourcePaths actual, Set vertexSet) { for (Integer sink : vertexSet) { assertEquals(expected.getPath(sink), actual.getPath(sink)); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/TreeMeasurerTest.java000066400000000000000000000043001402514743400335160ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Tests for {@link TreeMeasurer} * * @author Alexandru Valeanu */ public class TreeMeasurerTest { @Test public void testNoCenters() { Graph tree = new SimpleGraph<>(DefaultEdge.class); TreeMeasurer treeMeasurer = new TreeMeasurer<>(tree); assertEquals(new HashSet<>(), treeMeasurer.getGraphCenter()); } @Test public void testTwoCenters() { Graph tree = new SimpleGraph<>(DefaultEdge.class); tree.addVertex(1); tree.addVertex(2); tree.addVertex(3); tree.addVertex(4); tree.addEdge(1, 2); tree.addEdge(2, 3); tree.addEdge(3, 4); TreeMeasurer treeMeasurer = new TreeMeasurer<>(tree); assertEquals(Set.of(2, 3), treeMeasurer.getGraphCenter()); } @Test public void testOneCenter() { Graph tree = new SimpleGraph<>(DefaultEdge.class); tree.addVertex(1); tree.addVertex(2); tree.addVertex(3); tree.addVertex(4); tree.addVertex(5); tree.addEdge(1, 2); tree.addEdge(2, 3); tree.addEdge(3, 4); tree.addEdge(4, 5); TreeMeasurer treeMeasurer = new TreeMeasurer<>(tree); assertEquals(Set.of(3), treeMeasurer.getGraphCenter()); } } TreeSingleSourcePathsTest.java000066400000000000000000000057251402514743400352720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Dimitrios Michail */ public class TreeSingleSourcePathsTest { @Test public void test() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); DefaultWeightedEdge e12_1 = g.addEdge(1, 2); g.setEdgeWeight(e12_1, -5.0); DefaultWeightedEdge e12_2 = g.addEdge(1, 2); g.setEdgeWeight(e12_2, -2.0); DefaultWeightedEdge e12_3 = g.addEdge(1, 2); g.setEdgeWeight(e12_3, 1.0); DefaultWeightedEdge e23_1 = g.addEdge(2, 3); g.setEdgeWeight(e23_1, 0d); DefaultWeightedEdge e23_2 = g.addEdge(2, 3); g.setEdgeWeight(e23_2, -2.0); DefaultWeightedEdge e23_3 = g.addEdge(2, 3); g.setEdgeWeight(e23_3, -5.0); DefaultWeightedEdge e34_1 = g.addEdge(3, 4); g.setEdgeWeight(e34_1, -100.0); DefaultWeightedEdge e34_2 = g.addEdge(3, 4); g.setEdgeWeight(e34_2, 100.0); DefaultWeightedEdge e34_3 = g.addEdge(3, 4); g.setEdgeWeight(e34_3, 1.0); Map> map = new HashMap<>(); map.put(2, Pair.of(-5d, e12_1)); map.put(3, Pair.of(-10d, e23_3)); map.put(4, Pair.of(-110d, e34_1)); TreeSingleSourcePathsImpl t1 = new TreeSingleSourcePathsImpl<>(g, 1, map); assertEquals(1, t1.getSourceVertex().intValue()); assertEquals(0d, t1.getWeight(1), 1e-9); assertTrue(t1.getPath(1).getEdgeList().isEmpty()); assertEquals(Arrays.asList(g.getEdgeSource(e12_1)), t1.getPath(1).getVertexList()); assertEquals(-5d, t1.getWeight(2), 1e-9); assertEquals(Arrays.asList(e12_1), t1.getPath(2).getEdgeList()); assertEquals(-10d, t1.getWeight(3), 1e-9); assertEquals(Arrays.asList(e12_1, e23_3), t1.getPath(3).getEdgeList()); assertEquals(-110d, t1.getWeight(4), 1e-9); assertEquals(Arrays.asList(e12_1, e23_3, e34_1), t1.getPath(4).getEdgeList()); } } YenKShortestPathTest.java000066400000000000000000000241021402514743400342550ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Tests for the {@link YenKShortestPath}. */ public class YenKShortestPathTest extends BaseKShortestPathTest { /** * Seed value which is used to generate random graphs by * {@code getRandomGraph(Graph, int, double)} method. */ private static final long SEED = 13l; @Test(expected = IllegalArgumentException.class) public void testNegativeK() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2); new YenKShortestPath<>(graph).getPaths(1, 2, -1); } /** * If k equals to $0$ and there is no paths in the graph between source and target, no exception * should be thrown and an empty list should be returned. */ @Test public void testKEqualsZero() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); List> paths = new YenKShortestPath<>(graph).getPaths(1, 2, 0); assertEquals(0, paths.size()); } @Test(expected = IllegalArgumentException.class) public void testNoSourceGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(2); new YenKShortestPath<>(graph).getPaths(1, 2, 1); } @Test(expected = IllegalArgumentException.class) public void testNoSinkGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); new YenKShortestPath<>(graph).getPaths(1, 2, 1); } @Test public void testCyclicGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, cyclicGraph3); List> paths = new YenKShortestPath<>(graph).getPaths(1, 3, 1); List weights = Collections.singletonList(2.0); assertSameWeights(paths, weights); } /** * If the specified k is greater than the total amount of paths between source and target, a * list of all existing paths should be returned and no exception should be thrown. */ @Test public void testLessThanKPaths() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, simpleGraph1); List> paths = new YenKShortestPath<>(graph).getPaths(1, 12, 12); List weights = Arrays.asList(55.0, 58.0, 59.0, 61.0, 62.0, 64.0, 65.0, 68.0, 68.0, 71.0); assertSameWeights(paths, weights); } @Test public void testOnRandomGraphs() { Random random = new Random(SEED); int n = 25; double p = 0.1; int numberOfRandomEdges = 5; for (int i = 0; i < 50; i++) { DirectedWeightedPseudograph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); getRandomGraph(graph, n, p, random); Integer source = (int) (random.nextDouble() * n); Integer target = (int) (random.nextDouble() * n); Set randomEdges = getRandomEdges(graph, numberOfRandomEdges); PathValidator pathValidator = (path, edge) -> !randomEdges.contains(edge); testOnRandomGraph(graph, source, target, pathValidator); } } /** * Computes all simple shortest paths between {@code source} and {@code target} without * {@code pathValidator}. Then computes all shortest paths between {@code source} and * {@code target} with {@code pathValidator}. Finally, checks that only valid paths are returned * using {@code isValidPath}. * * * @param graph graph the iterator is being tested on * @param source source vertex * @param target target vertex * @param pathValidator validator for returned paths paths */ private void testOnRandomGraph( Graph graph, Integer source, Integer target, PathValidator pathValidator) { int maximumNumberOfPaths = Integer.MAX_VALUE; // iterate all paths List> paths = new YenKShortestPath<>(graph).getPaths(source, target, maximumNumberOfPaths); List> validatedPaths = new YenKShortestPath<>(graph, pathValidator) .getPaths(source, target, maximumNumberOfPaths); Set> uniquePaths = new HashSet<>(); int pathsIndex = 0; int validatedPathsIndex = 0; while (pathsIndex < paths.size() || validatedPathsIndex < validatedPaths.size()) { GraphPath path = paths.get(pathsIndex); if (!isValidPath(path, pathValidator)) { ++pathsIndex; } else { GraphPath validatedPath = validatedPaths.get(validatedPathsIndex); assertEquals(validatedPath, path); ((GraphWalk) validatedPath).verify(); uniquePaths.add(validatedPath); ++pathsIndex; ++validatedPathsIndex; } } assertEquals(validatedPaths.size(), uniquePaths.size()); } /** * Checks that {@code path} is valid w.r.t. the {@code pathValidator}. For the original path * $source = v_1, ... ,v_n = target$ for every pair of consecutive intermediate vertices $v_i, * v_{i+1}$ checks that the edge $(v_i, v_{i+1})$ can be added to the subpath $v1, ..., v_i$. * * @param path path to be checked for correctness * @param pathValidator validator * @return {@code true} iff {@code path} is correct w.r.t. {@code pathValidator} */ private boolean isValidPath( GraphPath path, PathValidator pathValidator) { Graph graph = path.getGraph(); List vertices = path.getVertexList(); List edges = path.getEdgeList(); int startVertex = vertices.get(0); double weight = 0.0; for (int i = 0; i < vertices.size() - 1; ++i) { int endVertex = vertices.get(i); DefaultWeightedEdge edge = edges.get(i); List verticesSublist = vertices.subList(0, i + 1); List edgesSublist = edges.subList(0, i); GraphPath subpath = new GraphWalk<>( graph, startVertex, endVertex, verticesSublist, edgesSublist, weight); if (!pathValidator.isValidPath(subpath, edge)) { return false; } weight += graph.getEdgeWeight(edge); } return true; } /** * Generates random graph from the $G(n, p)$ model. * * @param graph graph instance for the generator * @param n the number of nodes * @param p the edge probability */ private void getRandomGraph( Graph graph, int n, double p, Random random) { GnpRandomGraphGenerator generator = new GnpRandomGraphGenerator<>(n, p, random, false); generator.generateGraph(graph); graph.edgeSet().forEach(e -> graph.setEdgeWeight(e, random.nextDouble())); } /** * Computes a set of random vertices of {@code graph}. The size of the set is * {@code numberOfEdges}. * * @param graph a graph * @param numberOfEdges number of random vertices * @return set of random vertices */ private Set getRandomEdges( Graph graph, int numberOfEdges) { Set result = CollectionUtil.newHashSetWithExpectedSize(numberOfEdges); Object[] edges = graph.edgeSet().toArray(); Random random = new Random(SEED); while (result.size() != numberOfEdges) { result.add((DefaultWeightedEdge) edges[random.nextInt(edges.length)]); } return result; } /** * Checks that {@code paths} has weights identical to {@code weights} in the same order. * * @param paths graph paths * @param weights expected weights */ private void assertSameWeights( List> paths, List weights) { assertEquals(weights.size(), paths.size()); for (int i = 0; i < paths.size(); i++) { assertEquals(weights.get(i), paths.get(i).getWeight(), 1e-9); } } } YenShortestPathIteratorTest.java000066400000000000000000000362111402514743400356600ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/shortestpath/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static junit.framework.TestCase.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; /** * Tests for the {@link YenShortestPathIterator}. */ public class YenShortestPathIteratorTest extends BaseKShortestPathTest { @Test(expected = IllegalArgumentException.class) public void testNoSourceGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(2); new YenShortestPathIterator<>(graph, 1, 2); } @Test(expected = IllegalArgumentException.class) public void testNoSinkGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); new YenShortestPathIterator<>(graph, 1, 2); } @Test public void testNoPathInGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); YenShortestPathIterator it = new YenShortestPathIterator<>(graph, 1, 2); assertFalse(it.hasNext()); } @Test(expected = NoSuchElementException.class) public void testNoPathLeft() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); graph.addVertex(2); YenShortestPathIterator it = new YenShortestPathIterator<>(graph, 1, 2); assertFalse(it.hasNext()); it.next(); } @Test public void testSourceEqualsTarget() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(1); Integer source = 1; Integer target = 1; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 0.0, false); } @Test public void testOnlyShortestPathGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); DefaultWeightedEdge a = Graphs.addEdgeWithVertices(graph, 1, 2, 1.0); DefaultWeightedEdge b = Graphs.addEdgeWithVertices(graph, 2, 3, 1.0); YenShortestPathIterator it = new YenShortestPathIterator<>(graph, 1, 3); assertTrue(it.hasNext()); GraphPath path = it.next(); assertEquals(2.0, path.getWeight(), 1e-9); assertEquals(Arrays.asList(a, b), path.getEdgeList()); assertFalse(it.hasNext()); } @Test public void testSimpleGraph1() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, simpleGraph1); Integer source = 1; Integer target = 12; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 55.0, true); verifyNextPath(it, 58.0, true); verifyNextPath(it, 59.0, true); verifyNextPath(it, 61.0, true); verifyNextPath(it, 62.0, true); verifyNextPath(it, 64.0, true); verifyNextPath(it, 65.0, true); verifyNextPath(it, 68.0, true); verifyNextPath(it, 68.0, true); verifyNextPath(it, 71.0, false); } @Test public void testSimpleGraph2() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, simpleGraph2); Integer source = 1; Integer target = 4; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 13.0, true); verifyNextPath(it, 15.0, true); verifyNextPath(it, 21.0, false); } @Test public void testSimpleGraph3() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, simpleGraph3); Integer source = 1; Integer target = 4; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 9.0, true); verifyNextPath(it, 13.0, true); verifyNextPath(it, 15.0, false); } @Test public void testSimpleGraph4() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, simpleGraph4); Integer source = 1; Integer target = 3; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 13.0, true); verifyNextPath(it, 15.0, true); verifyNextPath(it, 21.0, false); } @Test public void testCyclicGraph1() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Integer source = 1; Integer target = 2; readGraph(graph, cyclicGraph1); YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 1.0, false); } @Test public void testCyclicGraph2() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, cyclicGraph2); Integer source = 1; Integer target = 6; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 4.0, true); verifyNextPath(it, 4.0, false); } @Test public void testCyclicGraph3() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, cyclicGraph3); Integer source = 1; Integer target = 3; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 2.0, false); } @Test public void testPseudoGraph1() { Graph graph = new WeightedPseudograph<>(DefaultWeightedEdge.class); readGraph(graph, pseudograph1); Integer source = 1; Integer target = 5; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 2.0, true); verifyNextPath(it, 4.0, true); verifyNextPath(it, 4.0, true); verifyNextPath(it, 4.0, true); verifyNextPath(it, 5.0, true); verifyNextPath(it, 6.0, true); verifyNextPath(it, 7.0, true); verifyNextPath(it, 9.0, true); verifyNextPath(it, 10.0, true); verifyNextPath(it, 11.0, false); } @Test public void testPseudoGraph2() { Graph graph = new WeightedPseudograph<>(DefaultWeightedEdge.class); readGraph(graph, pseudograph2); Integer source = 2; Integer target = 3; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 6.0, true); verifyNextPath(it, 7.0, false); source = 1; target = 3; it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 8.0, true); verifyNextPath(it, 9.0, true); verifyNextPath(it, 9.0, true); verifyNextPath(it, 10.0, true); verifyNextPath(it, 10.0, true); verifyNextPath(it, 11.0, false); source = 1; target = 4; it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 17.0, true); verifyNextPath(it, 18.0, true); verifyNextPath(it, 18.0, true); verifyNextPath(it, 18.0, true); verifyNextPath(it, 19.0, true); verifyNextPath(it, 19.0, true); verifyNextPath(it, 19.0, true); verifyNextPath(it, 19.0, true); verifyNextPath(it, 20.0, true); verifyNextPath(it, 20.0, true); verifyNextPath(it, 20.0, true); verifyNextPath(it, 21.0, false); } @Test public void testPseudoGraph3() { Graph graph = new Multigraph<>(DefaultEdge.class); graph.addVertex("19"); graph.addVertex("1e"); graph.addVertex("1c"); graph.addVertex("1b"); graph.addVertex("1d"); graph.addVertex("1f"); graph.addVertex("16"); graph.addVertex("17"); graph.addVertex("12"); graph.addVertex("14"); graph.addVertex("18"); graph.addVertex("15"); graph.addVertex("21"); DefaultEdge e1 = graph.addEdge("19", "1e"); graph.addEdge("19", "1c"); graph.addEdge("19", "1b"); graph.addEdge("19", "1d"); DefaultEdge e5 = graph.addEdge("19", "1f"); DefaultEdge e6 = graph.addEdge("19", "16"); graph.addEdge("12", "17"); graph.addEdge("12", "14"); graph.addEdge("12", "15"); DefaultEdge e10 = graph.addEdge("12", "16"); DefaultEdge e11 = graph.addEdge("12", "16"); DefaultEdge e12 = graph.addEdge("12", "18"); DefaultEdge e13 = graph.addEdge("12", "21"); DefaultEdge e14 = graph.addEdge("21", "1f"); KShortestPathAlgorithm yen = new YenKShortestPath<>(graph); // should contain exactly 3 elements each List> yenPaths = yen.getPaths("1e", "18", 7); List expectedEdgeList1 = Arrays.asList(e1, e6, e10, e12); List expectedEdgeList2 = Arrays.asList(e1, e6, e11, e12); List expectedEdgeList3 = Arrays.asList(e1, e5, e14, e13, e12); boolean option1 = yenPaths.get(0).getEdgeList().equals(expectedEdgeList1) && yenPaths.get(1).getEdgeList().equals(expectedEdgeList2); boolean option2 = yenPaths.get(0).getEdgeList().equals(expectedEdgeList2) && yenPaths.get(1).getEdgeList().equals(expectedEdgeList1); assertEquals(3, yenPaths.size()); assertTrue(option1 ^ option2); assertEquals(expectedEdgeList3, yenPaths.get(2).getEdgeList()); } @Test public void testNotShortestPathEdgesGraph() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); readGraph(graph, notShortestPathEdgesGraph); Integer source = 1; Integer target = 2; YenShortestPathIterator it = new YenShortestPathIterator<>(graph, source, target); assertTrue(it.hasNext()); verifyNextPath(it, 1.0, false); } @Test public void testForbidAll() { Graph graph = new WeightedPseudograph<>(DefaultWeightedEdge.class); readGraph(graph, pseudograph1); Integer source = 1; Integer target = 5; YenShortestPathIterator iterator = new YenShortestPathIterator<>(graph, source, target, (partialPath, edge) -> false); assertFalse(iterator.hasNext()); } @Test public void testNonTrivialPathValidator() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); readGraph(graph, pseudograph3); Integer source = 1; Integer target = 3; PathValidator validator = (partialPath, edge) -> { if (graph.getEdgeSource(edge).equals(1) && graph.getEdgeTarget(edge).equals(2) && graph.getEdgeWeight(edge) == 2.0) { return false; } if (graph.getEdgeSource(edge).equals(2) && graph.getEdgeTarget(edge).equals(3) && graph.getEdgeWeight(edge) == 4.0) { return false; } return true; }; YenShortestPathIterator iterator = new YenShortestPathIterator<>(graph, source, target, validator); verifyNextPath(iterator, 6.0, true); verifyNextPath(iterator, 8.0, false); } /** * Performs assertions to check correctness of the next path which the {@code it} is expected to * return. * * @param it shortest paths iterator * @param expectedWeight expected weight of the next path * @param hasNext expected return value of the {@link YenShortestPathIterator#hasNext()} method */ private void verifyNextPath( YenShortestPathIterator it, double expectedWeight, boolean hasNext) { GraphPath path = it.next(); assertEquals(expectedWeight, path.getWeight(), 1e-9); ((GraphWalk) path).verify(); assertLooplessPath(path); assertEquals(it.hasNext(), hasNext); } /** * Asserts that {@code path} is loopless. More formally checks that the {@code path} has no * duplicate vertices. */ private void assertLooplessPath(GraphPath path) { Set uniqueVertices = new HashSet<>(path.getVertexList()); assertEquals(path.getVertexList().size(), uniqueVertices.size()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/similarity/000077500000000000000000000000001402514743400270515ustar00rootroot00000000000000ZhangShashaTreeEditDistanceTest.java000066400000000000000000000242321402514743400360000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/similarity/* * (C) Copyright 2020-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.similarity; import org.jgrapht.Graph; import org.jgrapht.Graphs; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.SimpleGraph; import org.junit.Test; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.jgrapht.alg.similarity.ZhangShashaTreeEditDistance.EditOperation; import static org.jgrapht.alg.similarity.ZhangShashaTreeEditDistance.OperationType; import static org.junit.Assert.assertEquals; /** * Tests for {@link ZhangShashaTreeEditDistance}. */ public class ZhangShashaTreeEditDistanceTest { // test graph instances int[][] articleTree1 = { { 1, 2 }, { 1, 3 }, { 2, 4 }, { 2, 5 }, { 5, 6 } }; int[][] articleTree2 = { { 1, 5 }, { 1, 3 }, { 5, 2 }, { 2, 4 }, { 2, 6 } }; int[][] tree1 = { { 1, 2 }, { 1, 4 }, { 2, 5 }, { 3, 6 }, { 4, 10 }, { 5, 6 }, { 5, 7 }, { 5, 9 }, { 7, 8 }, { 10, 4 } }; int[][] tree2 = { { 0, 1 }, { 0, 3 }, { 1, 4 }, { 1, 0 }, { 2, 5 }, { 3, 0 }, { 3, 9 }, { 4, 5 }, { 4, 6 }, { 4, 8 }, { 4, 1 }, { 5, 2 }, { 5, 4 }, { 6, 7 }, { 6, 4 }, { 7, 6 }, { 8, 4 }, { 9, 3 } }; int[][] tree3 = { { 0, 3 }, { 0, 6 }, { 0, 4 }, { 0, 9 }, { 1, 8 }, { 1, 2 }, { 2, 5 }, { 2, 1 }, { 2, 4 }, { 3, 0 }, { 4, 7 }, { 4, 2 }, { 4, 0 }, { 5, 2 }, { 6, 0 }, { 7, 4 }, { 8, 1 }, { 9, 0 } }; @Test public void testTED_treeWithOneVertex_to_treeWithOneVertex() { Set> expectedEditOperations = Collections.singleton(new EditOperation<>(OperationType.CHANGE, 1, 1)); testOnTrees( getGraphWithOneVertex(), 1, getGraphWithOneVertex(), 1, 0.0, expectedEditOperations); } @Test public void testTED_treeWithOneVertex_to_articleTree2() { Set> expectedEditOperations = new HashSet<>( Arrays .asList( new EditOperation<>(OperationType.CHANGE, 1, 1), new EditOperation<>(OperationType.INSERT, 2, null), new EditOperation<>(OperationType.INSERT, 3, null), new EditOperation<>(OperationType.INSERT, 4, null), new EditOperation<>(OperationType.INSERT, 5, null), new EditOperation<>(OperationType.INSERT, 6, null))); testOnTrees( getGraphWithOneVertex(), 1, readGraph(articleTree2), 1, 5.0, expectedEditOperations); } @Test public void testTED_articleTree1_to_treeWithOneVertex() { Set> expectedEditOperations = new HashSet<>( Arrays .asList( new EditOperation<>(OperationType.CHANGE, 1, 1), new EditOperation<>(OperationType.REMOVE, 2, null), new EditOperation<>(OperationType.REMOVE, 3, null), new EditOperation<>(OperationType.REMOVE, 4, null), new EditOperation<>(OperationType.REMOVE, 5, null), new EditOperation<>(OperationType.REMOVE, 6, null))); testOnTrees( readGraph(articleTree1), 1, getGraphWithOneVertex(), 1, 5.0, expectedEditOperations); } @Test public void testTED_articleTree1_to_articleTree2() { Set> expectedEditOperations = new HashSet<>( Arrays .asList( new EditOperation<>(OperationType.REMOVE, 5, null), new EditOperation<>(OperationType.INSERT, 5, null), new EditOperation<>(OperationType.CHANGE, 1, 1), new EditOperation<>(OperationType.CHANGE, 2, 2), new EditOperation<>(OperationType.CHANGE, 3, 3), new EditOperation<>(OperationType.CHANGE, 4, 4), new EditOperation<>(OperationType.CHANGE, 6, 6))); testOnTrees( readGraph(articleTree1), 1, readGraph(articleTree2), 1, 2.0, expectedEditOperations); } @Test public void testTED_tree1_to_articleTree2() { Set> expectedEditOperations = new HashSet<>( Arrays .asList( new EditOperation<>(OperationType.CHANGE, 1, 1), new EditOperation<>(OperationType.REMOVE, 4, null), new EditOperation<>(OperationType.CHANGE, 10, 3), new EditOperation<>(OperationType.REMOVE, 2, null), new EditOperation<>(OperationType.CHANGE, 5, 5), new EditOperation<>(OperationType.REMOVE, 9, null), new EditOperation<>(OperationType.REMOVE, 7, null), new EditOperation<>(OperationType.REMOVE, 8, null), new EditOperation<>(OperationType.INSERT, 2, null), new EditOperation<>(OperationType.CHANGE, 6, 6), new EditOperation<>(OperationType.REMOVE, 3, null), new EditOperation<>(OperationType.INSERT, 4, null))); testOnTrees(readGraph(tree1), 1, readGraph(articleTree2), 1, 9.0, expectedEditOperations); } @Test public void testTED_articleTree1_to_tree1() { Set> expectedEditOperations = new HashSet<>( Arrays .asList( new EditOperation<>(OperationType.CHANGE, 1, 1), new EditOperation<>(OperationType.INSERT, 4, null), new EditOperation<>(OperationType.CHANGE, 3, 10), new EditOperation<>(OperationType.CHANGE, 2, 2), new EditOperation<>(OperationType.CHANGE, 5, 5), new EditOperation<>(OperationType.INSERT, 9, null), new EditOperation<>(OperationType.INSERT, 7, null), new EditOperation<>(OperationType.INSERT, 8, null), new EditOperation<>(OperationType.CHANGE, 6, 6), new EditOperation<>(OperationType.INSERT, 3, null), new EditOperation<>(OperationType.REMOVE, 4, null))); testOnTrees(readGraph(articleTree1), 1, readGraph(tree1), 1, 7.0, expectedEditOperations); } @Test public void testTED_tree2_to_tree3() { Set> expectedEditOperations = new HashSet<>( Arrays .asList( new EditOperation<>(OperationType.CHANGE, 0, 0), new EditOperation<>(OperationType.REMOVE, 3, null), new EditOperation<>(OperationType.CHANGE, 9, 9), new EditOperation<>(OperationType.REMOVE, 1, null), new EditOperation<>(OperationType.CHANGE, 4, 4), new EditOperation<>(OperationType.REMOVE, 8, null), new EditOperation<>(OperationType.REMOVE, 6, null), new EditOperation<>(OperationType.CHANGE, 7, 7), new EditOperation<>(OperationType.REMOVE, 5, null), new EditOperation<>(OperationType.CHANGE, 2, 2), new EditOperation<>(OperationType.INSERT, 5, null), new EditOperation<>(OperationType.INSERT, 1, null), new EditOperation<>(OperationType.INSERT, 8, null), new EditOperation<>(OperationType.INSERT, 6, null), new EditOperation<>(OperationType.INSERT, 3, null))); testOnTrees(readGraph(tree2), 0, readGraph(tree3), 0, 10.0, expectedEditOperations); } /** * Tests the {@link ZhangShashaTreeEditDistance} algorithm on {@code tree1} and {@code tree2} * instances. This method expects to get edit distance value equal to {@code expectedDistance} * and list of edit operations equal to {@code expectedEditOperations}. * * @param tree1 first tree * @param root1 root vertex of {@code tree1} * @param tree2 second tree * @param root2 root vertex of {@code tree2} * @param expectedDistance expected value of edit distance * @param expectedEditOperations expected list of edit operations */ private static void testOnTrees( Graph tree1, int root1, Graph tree2, int root2, double expectedDistance, Set> expectedEditOperations) { ZhangShashaTreeEditDistance treeEditDistance = new ZhangShashaTreeEditDistance<>(tree1, root1, tree2, root2); double distance = treeEditDistance.getDistance(); List> actualEditOperations = treeEditDistance.getEditOperationLists(); assertEquals(expectedDistance, distance, 1e-9); assertEquals(expectedEditOperations, new HashSet<>(actualEditOperations)); } /** * Reads graph supplied in form of {@code representation}. * * @param representation list of graph edges * @return created instance of a graph */ protected static Graph readGraph(int[][] representation) { Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] ints : representation) { Graphs.addEdgeWithVertices(graph, ints[0], ints[1]); } return graph; } /** * Returns a graph instance with one vertex. * * @return graph instance which has one vertex */ private static Graph getGraphWithOneVertex() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex(1); return graph; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/000077500000000000000000000000001402514743400265005ustar00rootroot00000000000000AhujaOrlinSharmaCapacitatedMinimumSpanningTreeTest.java000066400000000000000000001050431402514743400413140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/* * (C) Copyright 2018-2021, by Christoph Grüne and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; public class AhujaOrlinSharmaCapacitatedMinimumSpanningTreeTest { /** * A simple cyclic exchange */ @Test public void testInstance1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); for (int i = 0; i < 7; ++i) { graph.addVertex(i); } graph.setEdgeWeight(graph.addEdge(0, 1), 1); graph.setEdgeWeight(graph.addEdge(0, 2), 1); graph.setEdgeWeight(graph.addEdge(0, 3), 1); graph.setEdgeWeight(graph.addEdge(1, 4), 2); graph.setEdgeWeight(graph.addEdge(1, 5), 1); graph.setEdgeWeight(graph.addEdge(2, 4), 1); graph.setEdgeWeight(graph.addEdge(2, 5), 2); graph.setEdgeWeight(graph.addEdge(3, 6), 1); Map weights = new HashMap<>(); weights.put(1, 1.0); weights.put(2, 1.0); weights.put(3, 1.0); weights.put(4, 1.0); weights.put(5, 1.0); weights.put(6, 1.0); Map labels = new HashMap<>(); labels.put(1, 0); labels.put(2, 1); labels.put(3, 2); labels.put(4, 0); labels.put(5, 1); labels.put(6, 2); Map, Double>> partition = new HashMap<>(); partition.put(0, Pair.of(new HashSet<>(Arrays.asList(1, 4)), 2.0)); partition.put(1, Pair.of(new HashSet<>(Arrays.asList(2, 5)), 2.0)); partition.put(2, Pair.of(new HashSet<>(Arrays.asList(3, 6)), 2.0)); Set edges = new HashSet<>(); edges.add(graph.getEdge(0, 1)); edges.add(graph.getEdge(0, 2)); edges.add(graph.getEdge(0, 3)); edges.add(graph.getEdge(1, 4)); edges.add(graph.getEdge(2, 5)); edges.add(graph.getEdge(3, 6)); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree initialSolution = new CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTreeImpl<>( labels, partition, edges, 8); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( initialSolution, graph, 0, 2.0, weights, 2, false, true, true, false, 0, 0) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue(cmst.isCapacitatedSpanningTree(graph, 0, 2.0, weights)); assertEquals(6.0, cmst.getWeight(), 0.0000001); assertEquals(Pair.of(Set.of(1, 5), 2.0), cmst.getPartition().get(cmst.getLabels().get(1))); assertEquals(Pair.of(Set.of(2, 4), 2.0), cmst.getPartition().get(cmst.getLabels().get(2))); assertEquals(Pair.of(Set.of(3, 6), 2.0), cmst.getPartition().get(cmst.getLabels().get(3))); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(5), 0); assertEquals(cmst.getLabels().get(2), cmst.getLabels().get(4), 0); assertEquals(cmst.getLabels().get(3), cmst.getLabels().get(6), 0); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(2)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(3)); for (DefaultWeightedEdge e : cmst.getEdges()) { assertTrue( e == graph.getEdge(0, 1) || e == graph.getEdge(0, 2) || e == graph.getEdge(0, 3) || e == graph.getEdge(1, 5) || e == graph.getEdge(2, 4) || e == graph.getEdge(3, 6)); } } /** * In this example, the initial solution should not be changed */ @Test public void testInstance2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); for (int i = 0; i < 6; ++i) { graph.addVertex(i); } for (int i = 0; i < 6; ++i) { for (int j = i + 1; j < 6; ++j) { graph.addEdge(i, j); } } graph.setEdgeWeight(graph.getEdge(0, 1), 7); graph.setEdgeWeight(graph.getEdge(0, 2), 5); graph.setEdgeWeight(graph.getEdge(0, 3), 1); graph.setEdgeWeight(graph.getEdge(0, 4), 2); graph.setEdgeWeight(graph.getEdge(0, 5), 8); graph.setEdgeWeight(graph.getEdge(1, 2), 8); graph.setEdgeWeight(graph.getEdge(1, 3), 5); graph.setEdgeWeight(graph.getEdge(1, 4), 2); graph.setEdgeWeight(graph.getEdge(1, 5), 2); graph.setEdgeWeight(graph.getEdge(2, 3), 2); graph.setEdgeWeight(graph.getEdge(2, 4), 5); graph.setEdgeWeight(graph.getEdge(2, 5), 6); graph.setEdgeWeight(graph.getEdge(3, 4), 9); graph.setEdgeWeight(graph.getEdge(3, 5), 5); graph.setEdgeWeight(graph.getEdge(4, 5), 1); Map weights = new HashMap<>(); weights.put(1, 2.0); weights.put(2, 1.0); weights.put(3, 2.0); weights.put(4, 3.0); weights.put(5, 2.0); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph, 0, 4, weights, 7, false, 1, true, true, false, 0, 0) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue(cmst.isCapacitatedSpanningTree(graph, 0, 4.0, weights)); assertEquals(14.0, cmst.getWeight(), 0.0000001); assertEquals(cmst.getPartition().get(cmst.getLabels().get(1)), Pair.of(Set.of(1, 5), 4.0)); assertEquals(cmst.getPartition().get(cmst.getLabels().get(2)), Pair.of(Set.of(2, 3), 3.0)); assertEquals(cmst.getPartition().get(cmst.getLabels().get(4)), Pair.of(Set.of(4), 3.0)); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(5), 0); assertEquals(cmst.getLabels().get(2), cmst.getLabels().get(3), 0); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(4)); for (DefaultWeightedEdge e : cmst.getEdges()) { assertTrue( e == graph.getEdge(0, 1) || e == graph.getEdge(0, 3) || e == graph.getEdge(0, 4) || e == graph.getEdge(1, 5) || e == graph.getEdge(3, 2)); } } /** * A double cyclic exchange */ @Test public void testInstance3() { Graph graph = new DefaultUndirectedWeightedGraph( DefaultWeightedEdge.class); for (int i = 0; i < 6; ++i) { graph.addVertex(i); } graph.setEdgeWeight(graph.addEdge(0, 1), 1); graph.setEdgeWeight(graph.addEdge(0, 2), 1); graph.setEdgeWeight(graph.addEdge(0, 3), 1); graph.setEdgeWeight(graph.addEdge(0, 4), 1); graph.setEdgeWeight(graph.addEdge(1, 5), 1); graph.setEdgeWeight(graph.addEdge(2, 5), 3); graph.setEdgeWeight(graph.addEdge(3, 5), 2); graph.setEdgeWeight(graph.addEdge(4, 5), 0); Map weights = new HashMap<>(); weights.put(1, 1.0); weights.put(2, 1.0); weights.put(3, 1.0); weights.put(4, 2.0); weights.put(5, 1.0); Map labels = new HashMap<>(); labels.put(1, 0); labels.put(2, 1); labels.put(3, 2); labels.put(4, 3); labels.put(5, 1); Map, Double>> partition = new HashMap<>(); partition.put(0, Pair.of(new HashSet<>(Arrays.asList(1)), 1.0)); partition.put(1, Pair.of(new HashSet<>(Arrays.asList(2, 5)), 2.0)); partition.put(2, Pair.of(new HashSet<>(Arrays.asList(3)), 1.0)); partition.put(3, Pair.of(new HashSet<>(Arrays.asList(4)), 2.0)); Set edges = new HashSet<>(); edges.add(graph.getEdge(0, 1)); edges.add(graph.getEdge(0, 2)); edges.add(graph.getEdge(0, 3)); edges.add(graph.getEdge(0, 4)); edges.add(graph.getEdge(2, 5)); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree initialSolution = new CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTreeImpl<>( labels, partition, edges, 7); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( initialSolution, graph, 0, 2.0, weights, 2, false, true, true, false, 0, 0) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue(cmst.isCapacitatedSpanningTree(graph, 0, 2.0, weights)); assertEquals(5.0, cmst.getWeight(), 0.0000001); assertEquals(Pair.of(Set.of(1, 5), 2.0), cmst.getPartition().get(cmst.getLabels().get(1))); assertEquals(Pair.of(Set.of(2), 1.0), cmst.getPartition().get(cmst.getLabels().get(2))); assertEquals(Pair.of(Set.of(3), 1.0), cmst.getPartition().get(cmst.getLabels().get(3))); assertEquals(Pair.of(Set.of(4), 2.0), cmst.getPartition().get(cmst.getLabels().get(4))); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(5), 0); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(2)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(4)); for (DefaultWeightedEdge e : cmst.getEdges()) { assertTrue( e == graph.getEdge(0, 1) || e == graph.getEdge(0, 2) || e == graph.getEdge(0, 3) || e == graph.getEdge(0, 4) || e == graph.getEdge(1, 5)); } } /** * A simple path exchange */ @Test public void testInstance4() { Graph graph = new DefaultUndirectedWeightedGraph( DefaultWeightedEdge.class); for (int i = 0; i < 8; ++i) { graph.addVertex(i); } graph.setEdgeWeight(graph.addEdge(0, 1), 1); graph.setEdgeWeight(graph.addEdge(0, 2), 1); graph.setEdgeWeight(graph.addEdge(0, 3), 1); graph.setEdgeWeight(graph.addEdge(1, 4), 1); graph.setEdgeWeight(graph.addEdge(2, 5), 1); graph.setEdgeWeight(graph.addEdge(3, 6), 1); graph.setEdgeWeight(graph.addEdge(4, 7), 1); graph.setEdgeWeight(graph.addEdge(5, 7), 3); graph.setEdgeWeight(graph.addEdge(6, 7), 2); Map weights = new HashMap<>(); for (int i = 1; i < 8; ++i) { weights.put(i, 1.0); } Map labels = new HashMap<>(); labels.put(1, 0); labels.put(2, 1); labels.put(3, 2); labels.put(4, 0); labels.put(5, 1); labels.put(6, 2); labels.put(7, 1); Map, Double>> partition = new HashMap<>(); partition.put(0, Pair.of(new HashSet<>(Arrays.asList(1, 4)), 2.0)); partition.put(1, Pair.of(new HashSet<>(Arrays.asList(2, 5, 7)), 3.0)); partition.put(2, Pair.of(new HashSet<>(Arrays.asList(3, 6)), 2.0)); Set edges = new HashSet<>(); edges.add(graph.getEdge(0, 1)); edges.add(graph.getEdge(0, 2)); edges.add(graph.getEdge(0, 3)); edges.add(graph.getEdge(1, 4)); edges.add(graph.getEdge(2, 5)); edges.add(graph.getEdge(3, 6)); edges.add(graph.getEdge(5, 7)); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree initialSolution = new CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTreeImpl<>( labels, partition, edges, 7); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( initialSolution, graph, 0, 3.0, weights, 3, false, true, true, false, 0, 0) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue(cmst.isCapacitatedSpanningTree(graph, 0, 3.0, weights)); assertEquals(7.0, cmst.getWeight(), 0.0000001); assertEquals( Pair.of(Set.of(1, 4, 7), 3.0), cmst.getPartition().get(cmst.getLabels().get(1))); assertEquals(Pair.of(Set.of(2, 5), 2.0), cmst.getPartition().get(cmst.getLabels().get(2))); assertEquals(Pair.of(Set.of(3, 6), 2.0), cmst.getPartition().get(cmst.getLabels().get(3))); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(4), 0); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(7), 0); assertEquals(cmst.getLabels().get(2), cmst.getLabels().get(5), 0); assertEquals(cmst.getLabels().get(3), cmst.getLabels().get(6), 0); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(2)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(5)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(6)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(6)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(7)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(7)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(5)); for (DefaultWeightedEdge e : cmst.getEdges()) { assertTrue( e == graph.getEdge(0, 1) || e == graph.getEdge(0, 2) || e == graph.getEdge(0, 3) || e == graph.getEdge(1, 4) || e == graph.getEdge(2, 5) || e == graph.getEdge(3, 6) || e == graph.getEdge(4, 7)); } } /** * A simple subtree exchange */ @Test public void testInstance5() { Graph graph = new DefaultUndirectedWeightedGraph( DefaultWeightedEdge.class); for (int i = 0; i < 9; ++i) { graph.addVertex(i); } graph.setEdgeWeight(graph.addEdge(0, 1), 1); graph.setEdgeWeight(graph.addEdge(0, 2), 1); graph.setEdgeWeight(graph.addEdge(0, 3), 1); graph.setEdgeWeight(graph.addEdge(1, 4), 1); graph.setEdgeWeight(graph.addEdge(2, 5), 1); graph.setEdgeWeight(graph.addEdge(3, 6), 1); graph.setEdgeWeight(graph.addEdge(4, 7), 1); graph.setEdgeWeight(graph.addEdge(5, 7), 3); graph.setEdgeWeight(graph.addEdge(6, 7), 2); graph.setEdgeWeight(graph.addEdge(7, 8), 1); Map weights = new HashMap<>(); for (int i = 1; i < 9; ++i) { weights.put(i, 1.0); } Map labels = new HashMap<>(); labels.put(1, 0); labels.put(2, 1); labels.put(3, 2); labels.put(4, 0); labels.put(5, 1); labels.put(6, 2); labels.put(7, 1); labels.put(8, 1); Map, Double>> partition = new HashMap<>(); partition.put(0, Pair.of(new HashSet<>(Arrays.asList(1, 4)), 2.0)); partition.put(1, Pair.of(new HashSet<>(Arrays.asList(2, 5, 7, 8)), 4.0)); partition.put(2, Pair.of(new HashSet<>(Arrays.asList(3, 6)), 2.0)); Set edges = new HashSet<>(); edges.add(graph.getEdge(0, 1)); edges.add(graph.getEdge(0, 2)); edges.add(graph.getEdge(0, 3)); edges.add(graph.getEdge(1, 4)); edges.add(graph.getEdge(2, 5)); edges.add(graph.getEdge(3, 6)); edges.add(graph.getEdge(5, 7)); edges.add(graph.getEdge(7, 8)); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree initialSolution = new CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTreeImpl<>( labels, partition, edges, 8); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( initialSolution, graph, 0, 4.0, weights, 3, false, true, true, false, 0, 0) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue(cmst.isCapacitatedSpanningTree(graph, 0, 4.0, weights)); assertEquals(8.0, cmst.getWeight(), 0.0000001); assertEquals( Pair.of(Set.of(1, 4, 7, 8), 4.0), cmst.getPartition().get(cmst.getLabels().get(1))); assertEquals(Pair.of(Set.of(2, 5), 2.0), cmst.getPartition().get(cmst.getLabels().get(2))); assertEquals(Pair.of(Set.of(3, 6), 2.0), cmst.getPartition().get(cmst.getLabels().get(3))); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(4), 0); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(7), 0); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(8), 0); assertEquals(cmst.getLabels().get(2), cmst.getLabels().get(5), 0); assertEquals(cmst.getLabels().get(3), cmst.getLabels().get(6), 0); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(2)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(5)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(6)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(6)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(7)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(8)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(7)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(5)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(8)); for (DefaultWeightedEdge e : cmst.getEdges()) { assertTrue( e == graph.getEdge(0, 1) || e == graph.getEdge(0, 2) || e == graph.getEdge(0, 3) || e == graph.getEdge(1, 4) || e == graph.getEdge(2, 5) || e == graph.getEdge(3, 6) || e == graph.getEdge(4, 7) || e == graph.getEdge(7, 8)); } } /** * A more complicate example */ @Test public void testInstance6() { Graph graph = new DefaultUndirectedWeightedGraph( DefaultWeightedEdge.class); for (int i = 0; i < 6; ++i) { graph.addVertex(i); } for (int i = 0; i < 6; ++i) { for (int j = i + 1; j < 6; ++j) { graph.addEdge(i, j); } } graph.setEdgeWeight(graph.getEdge(0, 1), 7); graph.setEdgeWeight(graph.getEdge(0, 2), 5); graph.setEdgeWeight(graph.getEdge(0, 3), 1); graph.setEdgeWeight(graph.getEdge(0, 4), 2); graph.setEdgeWeight(graph.getEdge(0, 5), 8); graph.setEdgeWeight(graph.getEdge(1, 2), 8); graph.setEdgeWeight(graph.getEdge(1, 3), 5); graph.setEdgeWeight(graph.getEdge(1, 4), 2); graph.setEdgeWeight(graph.getEdge(1, 5), 2); graph.setEdgeWeight(graph.getEdge(2, 3), 2); graph.setEdgeWeight(graph.getEdge(2, 4), 5); graph.setEdgeWeight(graph.getEdge(2, 5), 6); graph.setEdgeWeight(graph.getEdge(3, 4), 9); graph.setEdgeWeight(graph.getEdge(3, 5), 5); graph.setEdgeWeight(graph.getEdge(4, 5), 1); Map weights = new HashMap<>(); weights.put(1, 2.0); weights.put(2, 1.0); weights.put(3, 2.0); weights.put(4, 3.0); weights.put(5, 2.0); Map labels = new HashMap<>(); labels.put(1, 0); labels.put(2, 1); labels.put(3, 2); labels.put(4, 3); labels.put(5, 4); Map, Double>> partition = new HashMap<>(); partition.put(0, Pair.of(new HashSet<>(Arrays.asList(1)), 2.0)); partition.put(1, Pair.of(new HashSet<>(Arrays.asList(2)), 1.0)); partition.put(2, Pair.of(new HashSet<>(Arrays.asList(3)), 2.0)); partition.put(3, Pair.of(new HashSet<>(Arrays.asList(4)), 3.0)); partition.put(4, Pair.of(new HashSet<>(Arrays.asList(5)), 2.0)); Set edges = new HashSet<>(); edges.add(graph.getEdge(0, 1)); edges.add(graph.getEdge(0, 2)); edges.add(graph.getEdge(0, 3)); edges.add(graph.getEdge(0, 4)); edges.add(graph.getEdge(0, 5)); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree initialSolution = new CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTreeImpl<>( labels, partition, edges, 8); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( initialSolution, graph, 0, 4, weights, 7, false, true, true, false, 0, 0) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue(cmst.isCapacitatedSpanningTree(graph, 0, 4.0, weights)); assertEquals(14.0, cmst.getWeight(), 0.0000001); assertEquals(cmst.getPartition().get(cmst.getLabels().get(1)), Pair.of(Set.of(1, 5), 4.0)); assertEquals(cmst.getPartition().get(cmst.getLabels().get(2)), Pair.of(Set.of(2, 3), 3.0)); assertEquals(cmst.getPartition().get(cmst.getLabels().get(4)), Pair.of(Set.of(4), 3.0)); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(5), 0); assertEquals(cmst.getLabels().get(2), cmst.getLabels().get(3), 0); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(4)); for (DefaultWeightedEdge e : cmst.getEdges()) { assertTrue( e == graph.getEdge(0, 1) || e == graph.getEdge(0, 3) || e == graph.getEdge(0, 4) || e == graph.getEdge(1, 5) || e == graph.getEdge(3, 2)); } } /** * A complicated example, on which local search with vertex exchanges and first improvement is * executed. */ @Test public void testInstanceVertex() { Pair, Map> graph = generateComplicatedTestExample(40); double capacity = 30.0; CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, false, 1, true, false, false, 0, 0); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = capacitatedSpanningTreeAlgorithm.getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue( cmst.isCapacitatedSpanningTree(graph.getFirst(), 0, capacity, graph.getSecond())); } /** * A complicated example, on which tabu search with vertex exchanges and first improvement is * executed. */ @Test public void testInstanceVertexTabu() { Pair, Map> graph = generateComplicatedTestExample(40); double capacity = 30.0; CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, false, 1, true, false, true, 10, 15); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = capacitatedSpanningTreeAlgorithm.getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue( cmst.isCapacitatedSpanningTree(graph.getFirst(), 0, capacity, graph.getSecond())); } /** * A complicated example, on which local search with subtree exchanges and first improvement is * executed. */ @Test public void testInstanceSubtree() { Pair, Map> graph = generateComplicatedTestExample(40); double capacity = 30.0; CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, false, 1, false, true, false, 0, 0); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = capacitatedSpanningTreeAlgorithm.getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue( cmst.isCapacitatedSpanningTree(graph.getFirst(), 0, capacity, graph.getSecond())); } /** * A complicated example, on which tabu search with subtree exchanges and first improvement is * executed. */ @Test public void testInstanceSubtreeTabu() { Pair, Map> graph = generateComplicatedTestExample(40); double capacity = 30.0; CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, false, 1, false, true, true, 10, 15); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = capacitatedSpanningTreeAlgorithm.getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue( cmst.isCapacitatedSpanningTree(graph.getFirst(), 0, capacity, graph.getSecond())); } /** * A complicated example, on which local search with vertex and subtree exchanges and first * improvement is executed. */ @Test public void testInstanceVertexAndSubtree() { Pair, Map> graph = generateComplicatedTestExample(40); double capacity = 30.0; CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, false, 1, true, true, false, 0, 0); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = capacitatedSpanningTreeAlgorithm.getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue( cmst.isCapacitatedSpanningTree(graph.getFirst(), 0, capacity, graph.getSecond())); } /** * A complicated example, on which tabu search with vertex and subtree exchanges and first * improvement is executed. */ @Test public void testInstanceVertexAndSubtreeTabu() { Pair, Map> graph = generateComplicatedTestExample(40); double capacity = 30.0; CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, false, 1, true, true, true, 10, 50); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = capacitatedSpanningTreeAlgorithm.getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue( cmst.isCapacitatedSpanningTree(graph.getFirst(), 0, capacity, graph.getSecond())); } /** * A complicated example, on which local search with vertex exchanges and best improvement is * executed. */ @Test public void testInstanceVertexBestImprovement() { Pair, Map> graph = generateComplicatedTestExample(40); double capacity = 30.0; CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, true, 1, true, false, false, 0, 0); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = capacitatedSpanningTreeAlgorithm.getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue( cmst.isCapacitatedSpanningTree(graph.getFirst(), 0, capacity, graph.getSecond())); } /** * A complicated example, on which tabu search with vertex exchanges and best improvement is * executed. */ @Test public void testInstanceVertexTabuBestImprovement() { Pair, Map> graph = generateComplicatedTestExample(40); double capacity = 30.0; CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, true, 1, true, false, true, 10, 15); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = capacitatedSpanningTreeAlgorithm.getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue( cmst.isCapacitatedSpanningTree(graph.getFirst(), 0, capacity, graph.getSecond())); } /** * An unconected graph */ @Test public void testUnconnectedGraph() { Pair, Map> graph = generateComplicatedTestExample(20); graph.getFirst().addVertex(50); graph.getSecond().put(50, 1.0); double capacity = 30.0; boolean testOK = false; try { CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, true, 1, true, false, true, 10, 15); } catch (IllegalArgumentException e) { testOK = true; } assertTrue(testOK); } /** * Graph violating the capacity constraint */ @Test public void testViolatedCapacityConstraint() { Pair, Map> graph = generateComplicatedTestExample(10); double capacity = -1.0; boolean testOK = false; try { CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new AhujaOrlinSharmaCapacitatedMinimumSpanningTree<>( graph.getFirst(), 0, capacity, graph.getSecond(), 7, true, 1, true, false, true, 10, 15); } catch (IllegalArgumentException e) { testOK = true; } assertTrue(testOK); } /** * Generates a pseudo random graph with 75 vertices * * @return a pseudo random graph */ private Pair, Map> generateComplicatedTestExample(int numberOfVertices) { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); Map demands = new HashMap<>(); for (int i = 0; i < numberOfVertices; ++i) { graph.addVertex(i); demands.put(i, (double) ((i * i * i) % 5) + 1); } for (int i = 0; i < numberOfVertices; ++i) { for (int j = i + 1; j < numberOfVertices; ++j) { if ((i + j) % 5 == 1) { graph.setEdgeWeight(graph.addEdge(i, j), (7 * i + 5 * j) % 183); } else if ((i + j) % 5 == 2) { graph.setEdgeWeight(graph.addEdge(i, j), (i * j) % 253); } else if ((i + j) % 5 == 3) { graph.setEdgeWeight(graph.addEdge(i, j), (11 * i + 17 * j) % 193); } } } return Pair.of(graph, demands); } } BoruvkaMinimumSpanningTreeTest.java000066400000000000000000000020361402514743400354100ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; public class BoruvkaMinimumSpanningTreeTest extends MinimumSpanningTreeTest { @Override SpanningTreeAlgorithm createSolver( Graph network) { return new BoruvkaMinimumSpanningTree<>(network); } } EsauWilliamsCapacitatedMinimumSpanningTreeTest.java000066400000000000000000000355671402514743400405400ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/* * (C) Copyright 2018-2021, by Christoph Grüne and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; public class EsauWilliamsCapacitatedMinimumSpanningTreeTest { /** * This example is presented here: http://www.pitt.edu/~dtipper/2110/CMST_example.pdf */ @Test public void testInstance1() { Graph graph = new DefaultUndirectedWeightedGraph( DefaultWeightedEdge.class); for (int i = 0; i < 7; ++i) { graph.addVertex(i); } for (int i = 0; i < 7; ++i) { for (int j = i + 1; j < 7; ++j) { graph.addEdge(i, j); } } graph.setEdgeWeight(graph.getEdge(0, 1), 5); graph.setEdgeWeight(graph.getEdge(0, 2), 6); graph.setEdgeWeight(graph.getEdge(0, 3), 9); graph.setEdgeWeight(graph.getEdge(0, 4), 10); graph.setEdgeWeight(graph.getEdge(0, 5), 11); graph.setEdgeWeight(graph.getEdge(0, 6), 15); graph.setEdgeWeight(graph.getEdge(1, 2), 9); graph.setEdgeWeight(graph.getEdge(1, 3), 6); graph.setEdgeWeight(graph.getEdge(1, 4), 6); graph.setEdgeWeight(graph.getEdge(1, 5), 8); graph.setEdgeWeight(graph.getEdge(1, 6), 17); graph.setEdgeWeight(graph.getEdge(2, 3), 7); graph.setEdgeWeight(graph.getEdge(2, 4), 9); graph.setEdgeWeight(graph.getEdge(2, 5), 8); graph.setEdgeWeight(graph.getEdge(2, 6), 12); graph.setEdgeWeight(graph.getEdge(3, 4), 10); graph.setEdgeWeight(graph.getEdge(3, 5), 5); graph.setEdgeWeight(graph.getEdge(3, 6), 11); graph.setEdgeWeight(graph.getEdge(4, 5), 14); graph.setEdgeWeight(graph.getEdge(4, 6), 9); graph.setEdgeWeight(graph.getEdge(5, 6), 8); Map weights = new HashMap<>(); weights.put(1, 1.0); weights.put(2, 1.0); weights.put(3, 2.0); weights.put(4, 1.0); weights.put(5, 1.0); weights.put(6, 1.0); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new EsauWilliamsCapacitatedMinimumSpanningTree<>(graph, 0, 3, weights, 1) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertEquals(42.0, cmst.getWeight(), 0.0000001); assertEquals(cmst.getPartition().get(cmst.getLabels().get(1)), Pair.of(Set.of(1, 4), 2.0)); assertEquals( cmst.getPartition().get(cmst.getLabels().get(2)), Pair.of(Set.of(2, 5, 6), 3.0)); assertEquals(cmst.getPartition().get(cmst.getLabels().get(3)), Pair.of(Set.of(3), 2.0)); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(4), 0); assertEquals(cmst.getLabels().get(2), cmst.getLabels().get(5), 0); assertEquals(cmst.getLabels().get(2), cmst.getLabels().get(6), 0); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(2)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(1)); assertNotEquals(cmst.getLabels().get(2), cmst.getLabels().get(3)); for (DefaultWeightedEdge e : cmst.getEdges()) { assertTrue( e == graph.getEdge(0, 1) || e == graph.getEdge(0, 2) || e == graph.getEdge(0, 3) || e == graph.getEdge(1, 4) || e == graph.getEdge(2, 5) || e == graph.getEdge(5, 6)); } } /** * Instance calculated by hand */ @Test public void testInstance2() { Graph graph = new DefaultUndirectedWeightedGraph( DefaultWeightedEdge.class); for (int i = 0; i < 6; ++i) { graph.addVertex(i); } for (int i = 0; i < 6; ++i) { for (int j = i + 1; j < 6; ++j) { graph.addEdge(i, j); } } graph.setEdgeWeight(graph.getEdge(0, 1), 7); graph.setEdgeWeight(graph.getEdge(0, 2), 5); graph.setEdgeWeight(graph.getEdge(0, 3), 1); graph.setEdgeWeight(graph.getEdge(0, 4), 2); graph.setEdgeWeight(graph.getEdge(0, 5), 8); graph.setEdgeWeight(graph.getEdge(1, 2), 8); graph.setEdgeWeight(graph.getEdge(1, 3), 5); graph.setEdgeWeight(graph.getEdge(1, 4), 2); graph.setEdgeWeight(graph.getEdge(1, 5), 2); graph.setEdgeWeight(graph.getEdge(2, 3), 2); graph.setEdgeWeight(graph.getEdge(2, 4), 5); graph.setEdgeWeight(graph.getEdge(2, 5), 6); graph.setEdgeWeight(graph.getEdge(3, 4), 9); graph.setEdgeWeight(graph.getEdge(3, 5), 5); graph.setEdgeWeight(graph.getEdge(4, 5), 1); Map weights = new HashMap<>(); weights.put(1, 2.0); weights.put(2, 1.0); weights.put(3, 2.0); weights.put(4, 3.0); weights.put(5, 2.0); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new EsauWilliamsCapacitatedMinimumSpanningTree<>(graph, 0, 4, weights, 1) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertEquals(14.0, cmst.getWeight(), 0.0000001); assertEquals(cmst.getPartition().get(cmst.getLabels().get(1)), Pair.of(Set.of(1, 5), 4.0)); assertEquals(cmst.getPartition().get(cmst.getLabels().get(2)), Pair.of(Set.of(2, 3), 3.0)); assertEquals(cmst.getPartition().get(cmst.getLabels().get(4)), Pair.of(Set.of(4), 3.0)); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(5), 0); assertEquals(cmst.getLabels().get(2), cmst.getLabels().get(3), 0); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(4)); for (DefaultWeightedEdge e : cmst.getEdges()) { assertTrue( e == graph.getEdge(0, 1) || e == graph.getEdge(0, 3) || e == graph.getEdge(0, 4) || e == graph.getEdge(1, 5) || e == graph.getEdge(3, 2)); } } /** * Instance as in testInstance2() but without edge (0,5). That is, an incomplete graph is * tested. */ @Test public void testInstance3() { Graph graph = new DefaultUndirectedWeightedGraph( DefaultWeightedEdge.class); for (int i = 0; i < 6; ++i) { graph.addVertex(i); } for (int i = 0; i < 6; ++i) { for (int j = i + 1; j < 6; ++j) { if (i != 0 || j != 5) { graph.addEdge(i, j); } } } graph.setEdgeWeight(graph.getEdge(0, 1), 7); graph.setEdgeWeight(graph.getEdge(0, 2), 5); graph.setEdgeWeight(graph.getEdge(0, 3), 1); graph.setEdgeWeight(graph.getEdge(0, 4), 2); graph.setEdgeWeight(graph.getEdge(1, 2), 8); graph.setEdgeWeight(graph.getEdge(1, 3), 5); graph.setEdgeWeight(graph.getEdge(1, 4), 2); graph.setEdgeWeight(graph.getEdge(1, 5), 2); graph.setEdgeWeight(graph.getEdge(2, 3), 2); graph.setEdgeWeight(graph.getEdge(2, 4), 5); graph.setEdgeWeight(graph.getEdge(2, 5), 6); graph.setEdgeWeight(graph.getEdge(3, 4), 9); graph.setEdgeWeight(graph.getEdge(3, 5), 5); graph.setEdgeWeight(graph.getEdge(4, 5), 1); Map weights = new HashMap<>(); weights.put(1, 2.0); weights.put(2, 1.0); weights.put(3, 2.0); weights.put(4, 3.0); weights.put(5, 2.0); CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new EsauWilliamsCapacitatedMinimumSpanningTree<>(graph, 0, 4, weights, 1) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertEquals(14.0, cmst.getWeight(), 0.0000001); assertEquals(cmst.getPartition().get(cmst.getLabels().get(1)), Pair.of(Set.of(1, 5), 4.0)); assertEquals(cmst.getPartition().get(cmst.getLabels().get(2)), Pair.of(Set.of(2, 3), 3.0)); assertEquals(cmst.getPartition().get(cmst.getLabels().get(4)), Pair.of(Set.of(4), 3.0)); assertEquals(cmst.getLabels().get(1), cmst.getLabels().get(5), 0); assertEquals(cmst.getLabels().get(2), cmst.getLabels().get(3), 0); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(3)); assertNotEquals(cmst.getLabels().get(1), cmst.getLabels().get(4)); assertNotEquals(cmst.getLabels().get(3), cmst.getLabels().get(4)); for (DefaultWeightedEdge e : cmst.getEdges()) { assertTrue( e == graph.getEdge(0, 1) || e == graph.getEdge(0, 3) || e == graph.getEdge(0, 4) || e == graph.getEdge(1, 5) || e == graph.getEdge(3, 2)); } } @Test public void testInstanceWithRandomness() { Graph graph = new DefaultUndirectedWeightedGraph( DefaultWeightedEdge.class); for (int i = 0; i < 7; ++i) { graph.addVertex(i); } for (int i = 0; i < 7; ++i) { for (int j = i + 1; j < 7; ++j) { graph.addEdge(i, j); } } graph.setEdgeWeight(graph.getEdge(0, 1), 5); graph.setEdgeWeight(graph.getEdge(0, 2), 6); graph.setEdgeWeight(graph.getEdge(0, 3), 9); graph.setEdgeWeight(graph.getEdge(0, 4), 10); graph.setEdgeWeight(graph.getEdge(0, 5), 11); graph.setEdgeWeight(graph.getEdge(0, 6), 15); graph.setEdgeWeight(graph.getEdge(1, 2), 9); graph.setEdgeWeight(graph.getEdge(1, 3), 6); graph.setEdgeWeight(graph.getEdge(1, 4), 6); graph.setEdgeWeight(graph.getEdge(1, 5), 8); graph.setEdgeWeight(graph.getEdge(1, 6), 17); graph.setEdgeWeight(graph.getEdge(2, 3), 7); graph.setEdgeWeight(graph.getEdge(2, 4), 9); graph.setEdgeWeight(graph.getEdge(2, 5), 8); graph.setEdgeWeight(graph.getEdge(2, 6), 12); graph.setEdgeWeight(graph.getEdge(3, 4), 10); graph.setEdgeWeight(graph.getEdge(3, 5), 5); graph.setEdgeWeight(graph.getEdge(3, 6), 11); graph.setEdgeWeight(graph.getEdge(4, 5), 14); graph.setEdgeWeight(graph.getEdge(4, 6), 9); graph.setEdgeWeight(graph.getEdge(5, 6), 8); Map weights = new HashMap<>(); weights.put(1, 1.0); weights.put(2, 1.0); weights.put(3, 2.0); weights.put(4, 1.0); weights.put(5, 1.0); weights.put(6, 1.0); for (int i = 0; i < 30; ++i) { CapacitatedSpanningTreeAlgorithm.CapacitatedSpanningTree cmst = new EsauWilliamsCapacitatedMinimumSpanningTree<>(graph, 0, 3, weights, 20) .getCapacitatedSpanningTree(); assertNotNull(cmst); assertTrue(cmst.isCapacitatedSpanningTree(graph, 0, 3, weights)); } } /** * An unconected graph */ @Test public void testUnconnectedGraph() { Graph graph = new DefaultUndirectedGraph<>(DefaultWeightedEdge.class); Map demands = new HashMap<>(); graph.addVertex(0); demands.put(0, 1.0); graph.addVertex(1); demands.put(1, 1.0); double capacity = 30.0; boolean testOK = false; try { CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new EsauWilliamsCapacitatedMinimumSpanningTree<>( graph, 0, capacity, demands, 1); } catch (IllegalArgumentException e) { testOK = true; } assertTrue(testOK); } /** * Graph violating the capacity constraint */ @Test public void testViolatedCapacityConstraint() { Graph graph = new DefaultUndirectedGraph<>(DefaultWeightedEdge.class); Map demands = new HashMap<>(); graph.addVertex(0); demands.put(0, 1.0); graph.addVertex(1); demands.put(1, 1.0); graph.addEdge(0, 1); double capacity = -1.0; boolean testOK = false; try { CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new EsauWilliamsCapacitatedMinimumSpanningTree<>( graph, 0, capacity, demands, 1); } catch (IllegalArgumentException e) { testOK = true; } assertTrue(testOK); } /** * Graph violating the capacity constraint */ @Test public void testInvalidGraph() { Graph graph = new DefaultUndirectedGraph<>(DefaultWeightedEdge.class); Map demands = new HashMap<>(); graph.addVertex(0); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); demands.put(0, 1.0); demands.put(1, 1.0); demands.put(2, 1.0); demands.put(3, 1.0); demands.put(4, 1.0); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 4); double capacity = 2.0; boolean testOK = false; try { CapacitatedSpanningTreeAlgorithm capacitatedSpanningTreeAlgorithm = new EsauWilliamsCapacitatedMinimumSpanningTree<>( graph, 0, capacity, demands, 1); capacitatedSpanningTreeAlgorithm.getCapacitatedSpanningTree(); } catch (IllegalArgumentException e) { testOK = true; } assertTrue(testOK); } } GreedyMultiplicativeSpannerTest.java000066400000000000000000000267611402514743400356220ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * @author Dimitrios Michail */ public class GreedyMultiplicativeSpannerTest { // ~ Static fields/initializers // --------------------------------------------- private static final String V0 = "v0"; private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String V4 = "v4"; private static final String V5 = "v5"; private static final String V6 = "v6"; private static final String V7 = "v7"; private static final String V8 = "v8"; private static final String V9 = "v9"; private static final String V10 = "v10"; private static final String V11 = "v11"; private static final String V12 = "v12"; private static final String V13 = "v13"; private static final String V14 = "v14"; private static final String V15 = "v15"; // ~ Methods // ---------------------------------------------------------------- public void createGraph1(Graph g) { g.addVertex(V0); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); g.addVertex(V6); g.addVertex(V7); g.addEdge(V0, V1); g.addEdge(V1, V2); g.addEdge(V0, V5); g.addEdge(V1, V5); g.addEdge(V1, V4); g.addEdge(V2, V4); g.addEdge(V2, V3); g.addEdge(V5, V4); g.addEdge(V4, V3); g.addEdge(V5, V6); g.addEdge(V4, V6); g.addEdge(V3, V6); g.addEdge(V3, V7); g.addEdge(V6, V7); } public void createGraph2(Graph g) { g.addVertex(V0); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); g.addVertex(V6); g.addVertex(V7); g.addVertex(V8); g.addVertex(V9); g.addVertex(V10); g.addVertex(V11); g.addVertex(V12); g.addVertex(V13); g.addVertex(V14); g.addVertex(V15); g.addEdge(V0, V1); g.addEdge(V0, V3); g.addEdge(V0, V2); g.addEdge(V1, V3); g.addEdge(V1, V2); g.addEdge(V3, V2); g.addEdge(V3, V4); g.addEdge(V2, V5); g.addEdge(V4, V5); g.addEdge(V4, V6); g.addEdge(V5, V7); g.addEdge(V6, V7); g.addEdge(V8, V9); g.addEdge(V8, V10); g.addEdge(V8, V11); g.addEdge(V9, V10); g.addEdge(V9, V11); g.addEdge(V10, V11); g.addEdge(V10, V12); g.addEdge(V11, V13); g.addEdge(V12, V14); g.addEdge(V13, V15); } // ~ Methods // ---------------------------------------------------------------- public void createGraph3(Graph g) { g.addVertex(V0); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); g.addVertex(V6); g.addVertex(V7); g.setEdgeWeight(g.addEdge(V0, V1), 3.0); g.setEdgeWeight(g.addEdge(V1, V2), 15.0); g.setEdgeWeight(g.addEdge(V0, V5), 5.0); g.setEdgeWeight(g.addEdge(V1, V5), 20.0); g.setEdgeWeight(g.addEdge(V1, V4), 100.0); g.setEdgeWeight(g.addEdge(V2, V4), 5.0); g.setEdgeWeight(g.addEdge(V2, V3), 10.0); g.setEdgeWeight(g.addEdge(V5, V4), 1.0); g.setEdgeWeight(g.addEdge(V4, V3), 100.0); g.setEdgeWeight(g.addEdge(V5, V6), 10.0); g.setEdgeWeight(g.addEdge(V4, V6), 20.0); g.setEdgeWeight(g.addEdge(V3, V6), 100.0); g.setEdgeWeight(g.addEdge(V3, V7), 1000.0); g.setEdgeWeight(g.addEdge(V6, V7), 5.0); } private void runTest(Graph g, int k, Set correct) { Set result = new GreedyMultiplicativeSpanner<>(g, k).getSpanner(); assertEquals(correct.size(), result.size()); for (E e : correct) { assertTrue(result.contains(e)); } } @Test public void testGraph1() { Graph g = new Pseudograph<>(DefaultEdge.class); createGraph1(g); // test 3-spanner using k = 2 Set spanner3 = new HashSet<>(); spanner3.add(g.getEdge(V0, V1)); spanner3.add(g.getEdge(V1, V2)); spanner3.add(g.getEdge(V0, V5)); spanner3.add(g.getEdge(V1, V4)); spanner3.add(g.getEdge(V2, V3)); spanner3.add(g.getEdge(V5, V6)); spanner3.add(g.getEdge(V4, V6)); spanner3.add(g.getEdge(V3, V6)); spanner3.add(g.getEdge(V3, V7)); runTest(g, 2, spanner3); // test 5-spanner using k = 3 Set spanner5 = new HashSet<>(); spanner5.add(g.getEdge(V0, V1)); spanner5.add(g.getEdge(V1, V2)); spanner5.add(g.getEdge(V0, V5)); spanner5.add(g.getEdge(V1, V4)); spanner5.add(g.getEdge(V2, V3)); spanner5.add(g.getEdge(V5, V6)); spanner5.add(g.getEdge(V3, V7)); spanner5.add(g.getEdge(V6, V7)); runTest(g, 3, spanner5); // test 7-spanner using k = 4 Set spanner7 = new HashSet<>(); spanner7.add(g.getEdge(V0, V1)); spanner7.add(g.getEdge(V1, V2)); spanner7.add(g.getEdge(V0, V5)); spanner7.add(g.getEdge(V1, V4)); spanner7.add(g.getEdge(V2, V3)); spanner7.add(g.getEdge(V5, V6)); spanner7.add(g.getEdge(V3, V7)); runTest(g, 4, spanner7); // test minimum spanning tree using large k runTest(g, 100, spanner7); } @Test public void testGraph1WithLoops() { Graph g = new Pseudograph<>(DefaultEdge.class); createGraph1(g); g.addEdge(V0, V0); g.addEdge(V1, V1); g.addEdge(V2, V2); // test 3-spanner using k = 2 Set spanner3 = new HashSet<>(); spanner3.add(g.getEdge(V0, V1)); spanner3.add(g.getEdge(V1, V2)); spanner3.add(g.getEdge(V0, V5)); spanner3.add(g.getEdge(V1, V4)); spanner3.add(g.getEdge(V2, V3)); spanner3.add(g.getEdge(V5, V6)); spanner3.add(g.getEdge(V4, V6)); spanner3.add(g.getEdge(V3, V6)); spanner3.add(g.getEdge(V3, V7)); runTest(g, 2, spanner3); } @Test public void testGraph1WithMultipleEdges() { Graph g = new Pseudograph<>(DefaultEdge.class); createGraph1(g); g.addEdge(V0, V1); g.addEdge(V1, V2); g.addEdge(V0, V5); // test 3-spanner using k = 2 Set spanner3 = new HashSet<>(); spanner3.add(g.getEdge(V0, V1)); spanner3.add(g.getEdge(V1, V2)); spanner3.add(g.getEdge(V0, V5)); spanner3.add(g.getEdge(V1, V4)); spanner3.add(g.getEdge(V2, V3)); spanner3.add(g.getEdge(V5, V6)); spanner3.add(g.getEdge(V4, V6)); spanner3.add(g.getEdge(V3, V6)); spanner3.add(g.getEdge(V3, V7)); runTest(g, 2, spanner3); } @Test public void testGraph2() { Graph g = new Pseudograph<>(DefaultEdge.class); createGraph2(g); // test 3-spanner using k = 2 Set spanner3 = new HashSet<>(); spanner3.add(g.getEdge(V0, V1)); spanner3.add(g.getEdge(V0, V3)); spanner3.add(g.getEdge(V0, V2)); spanner3.add(g.getEdge(V3, V4)); spanner3.add(g.getEdge(V2, V5)); spanner3.add(g.getEdge(V4, V5)); spanner3.add(g.getEdge(V4, V6)); spanner3.add(g.getEdge(V5, V7)); spanner3.add(g.getEdge(V8, V9)); spanner3.add(g.getEdge(V8, V10)); spanner3.add(g.getEdge(V8, V11)); spanner3.add(g.getEdge(V10, V12)); spanner3.add(g.getEdge(V11, V13)); spanner3.add(g.getEdge(V12, V14)); spanner3.add(g.getEdge(V13, V15)); runTest(g, 2, spanner3); // test 5-spanner using k = 3 Set spanner5 = new HashSet<>(); spanner5.add(g.getEdge(V0, V1)); spanner5.add(g.getEdge(V0, V3)); spanner5.add(g.getEdge(V0, V2)); spanner5.add(g.getEdge(V3, V4)); spanner5.add(g.getEdge(V2, V5)); spanner5.add(g.getEdge(V4, V6)); spanner5.add(g.getEdge(V5, V7)); spanner5.add(g.getEdge(V6, V7)); spanner5.add(g.getEdge(V8, V9)); spanner5.add(g.getEdge(V8, V10)); spanner5.add(g.getEdge(V8, V11)); spanner5.add(g.getEdge(V10, V12)); spanner5.add(g.getEdge(V11, V13)); spanner5.add(g.getEdge(V12, V14)); spanner5.add(g.getEdge(V13, V15)); runTest(g, 3, spanner5); // test 7-spanner using k = 4 Set spanner7 = new HashSet<>(); spanner7.add(g.getEdge(V0, V1)); spanner7.add(g.getEdge(V0, V3)); spanner7.add(g.getEdge(V0, V2)); spanner7.add(g.getEdge(V3, V4)); spanner7.add(g.getEdge(V2, V5)); spanner7.add(g.getEdge(V4, V6)); spanner7.add(g.getEdge(V5, V7)); spanner7.add(g.getEdge(V8, V9)); spanner7.add(g.getEdge(V8, V10)); spanner7.add(g.getEdge(V8, V11)); spanner7.add(g.getEdge(V10, V12)); spanner7.add(g.getEdge(V11, V13)); spanner7.add(g.getEdge(V12, V14)); spanner7.add(g.getEdge(V13, V15)); runTest(g, 4, spanner7); // test minimum spanning tree using large k runTest(g, 100, spanner7); } @Test public void testGraph3() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); createGraph3(g); // test 3-spanner using k = 2 Set spanner3 = new HashSet<>(); spanner3.add(g.getEdge(V5, V4)); spanner3.add(g.getEdge(V0, V1)); spanner3.add(g.getEdge(V0, V5)); spanner3.add(g.getEdge(V2, V4)); spanner3.add(g.getEdge(V2, V3)); spanner3.add(g.getEdge(V5, V6)); spanner3.add(g.getEdge(V6, V7)); runTest(g, 2, spanner3); // compute minimum-spanning-tree runTest(g, Integer.MAX_VALUE, spanner3); } @Test public void testNegativeWeightsGraph() { WeightedPseudograph g = new WeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex(V0); g.addVertex(V1); g.addVertex(V2); g.setEdgeWeight(g.addEdge(V0, V1), 1.0); g.setEdgeWeight(g.addEdge(V1, V2), -1.0); g.setEdgeWeight(g.addEdge(V2, V0), 1.0); try { new GreedyMultiplicativeSpanner<>(g, 2).getSpanner(); fail("Negative edge weights not permitted."); } catch (IllegalArgumentException e) { } } } KruskalMinimumSpanningTreeTest.java000066400000000000000000000020361402514743400354130ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; public class KruskalMinimumSpanningTreeTest extends MinimumSpanningTreeTest { @Override SpanningTreeAlgorithm createSolver( Graph network) { return new KruskalMinimumSpanningTree<>(network); } } MinimumSpanningTreeTest.java000066400000000000000000000131231402514743400340550ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/* * (C) Copyright 2010-2021, by Tom Conerly and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.interfaces.SpanningTreeAlgorithm.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public abstract class MinimumSpanningTreeTest { abstract SpanningTreeAlgorithm createSolver( Graph network); // ~ Static fields/initializers --------------------------------------------- public static final Integer A = "A".codePointAt(0); public static final Integer B = "B".codePointAt(0); public static final Integer C = "C".codePointAt(0); public static final Integer D = "D".codePointAt(0); public static final Integer E = "E".codePointAt(0); public static final Integer F = "F".codePointAt(0); public static final Integer G = "G".codePointAt(0); public static final Integer H = "H".codePointAt(0); // ~ Instance fields -------------------------------------------------------- public static DefaultWeightedEdge AB; public static DefaultWeightedEdge AC; public static DefaultWeightedEdge BD; public static DefaultWeightedEdge DE; public static DefaultWeightedEdge EG; public static DefaultWeightedEdge GH; public static DefaultWeightedEdge FH; // ~ Methods ---------------------------------------------------------------- @Test public void testSimpleDisconnectedWeightedGraph() { testMinimumSpanningTreeBuilding( createSolver(createSimpleDisconnectedWeightedGraph()).getSpanningTree(), Arrays.asList(AB, AC, BD, EG, GH, FH), 60.0); } @Test public void testSimpleConnectedWeightedGraph() { testMinimumSpanningTreeBuilding( createSolver(createSimpleConnectedWeightedGraph()).getSpanningTree(), Arrays.asList(AB, AC, BD, DE), 15.0); } @Test public void testRandomInstances() { final Random rng = new Random(33); final double edgeProbability = 0.5; final int numberVertices = 200; final int repeat = 100; GraphGenerator gg = new GnpRandomGraphGenerator<>(numberVertices, edgeProbability, rng, false); for (int i = 0; i < repeat; i++) { WeightedPseudograph g = new WeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); gg.generateGraph(g); for (DefaultWeightedEdge e : g.edgeSet()) { g.setEdgeWeight(e, rng.nextDouble()); } SpanningTreeAlgorithm alg1 = createSolver(g); SpanningTreeAlgorithm alg2; if (alg1 instanceof KruskalMinimumSpanningTree) alg2 = new PrimMinimumSpanningTree<>(g); else alg2 = new KruskalMinimumSpanningTree<>(g); assertEquals( alg1.getSpanningTree().getWeight(), alg2.getSpanningTree().getWeight(), 1e-9); } } public static void testMinimumSpanningTreeBuilding( final SpanningTree mst, final Collection edgeSet, final double weight) { assertEquals(weight, mst.getWeight(), 0); assertTrue(mst.getEdges().containsAll(edgeSet)); } public static Graph createSimpleDisconnectedWeightedGraph() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); /* * * A -- B E -- F | | | | C -- D G -- H * */ g.addVertex(A); g.addVertex(B); g.addVertex(C); g.addVertex(D); AB = Graphs.addEdge(g, A, B, 5); AC = Graphs.addEdge(g, A, C, 10); BD = Graphs.addEdge(g, B, D, 15); Graphs.addEdge(g, C, D, 20); g.addVertex(E); g.addVertex(F); g.addVertex(G); g.addVertex(H); Graphs.addEdge(g, E, F, 20); EG = Graphs.addEdge(g, E, G, 15); GH = Graphs.addEdge(g, G, H, 10); FH = Graphs.addEdge(g, F, H, 5); return g; } public static Graph createSimpleConnectedWeightedGraph() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); double bias = 1; g.addVertex(A); g.addVertex(B); g.addVertex(C); g.addVertex(D); g.addVertex(E); AB = Graphs.addEdge(g, A, B, bias * 2); AC = Graphs.addEdge(g, A, C, bias * 3); BD = Graphs.addEdge(g, B, D, bias * 5); Graphs.addEdge(g, C, D, bias * 20); DE = Graphs.addEdge(g, D, E, bias * 5); Graphs.addEdge(g, A, E, bias * 100); return g; } } PrimMinimumSpanningTreeTest.java000066400000000000000000000020301402514743400347000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/spanning/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; public class PrimMinimumSpanningTreeTest extends MinimumSpanningTreeTest { @Override SpanningTreeAlgorithm createSolver( Graph network) { return new PrimMinimumSpanningTree<>(network); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/tour/000077500000000000000000000000001402514743400256545ustar00rootroot00000000000000ChristofidesThreeHalvesApproxMetricTSPTest.java000066400000000000000000000151421402514743400370110ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/tour/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit tests for the {@link ChristofidesThreeHalvesApproxMetricTSP} * * @author Timofey Chudakov */ public class ChristofidesThreeHalvesApproxMetricTSPTest { /** * Directed graph */ @Test(expected = IllegalArgumentException.class) public void testGetTour0() { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2, 5); ChristofidesThreeHalvesApproxMetricTSP approxMetricTSP = new ChristofidesThreeHalvesApproxMetricTSP<>(); approxMetricTSP.getTour(graph); } /** * Empty graph */ @Test(expected = IllegalArgumentException.class) public void testGetTour1() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); ChristofidesThreeHalvesApproxMetricTSP approxMetricTSP = new ChristofidesThreeHalvesApproxMetricTSP<>(); approxMetricTSP.getTour(graph); } /** * Not complete */ @Test(expected = IllegalArgumentException.class) public void testGetTour2() { Graph graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.addVertex(0); graph.addVertex(1); ChristofidesThreeHalvesApproxMetricTSP approxMetricTSP = new ChristofidesThreeHalvesApproxMetricTSP<>(); approxMetricTSP.getTour(graph); } /** * There is only one tour */ @Test public void testGetTour3() { int[][] edges = { { 1, 2, 5 } }; Graph graph = TestUtil.createUndirected(edges); ChristofidesThreeHalvesApproxMetricTSP approxMetricTSP = new ChristofidesThreeHalvesApproxMetricTSP<>(); GraphPath tour = approxMetricTSP.getTour(graph); assertHamiltonian(graph, tour); assertEquals(10, tour.getWeight(), 1e-9); } /** * There is only one tour */ @Test public void testGetTour4() { int[][] edges = { { 1, 2, 5 }, { 1, 3, 5 }, { 2, 3, 9 }, }; Graph graph = TestUtil.createUndirected(edges); ChristofidesThreeHalvesApproxMetricTSP approxMetricTSP = new ChristofidesThreeHalvesApproxMetricTSP<>(); GraphPath tour = approxMetricTSP.getTour(graph); assertHamiltonian(graph, tour); assertEquals(19, tour.getWeight(), 1e-9); } @Test public void testGetTour5() { int[][] edges = new int[][] { { 1, 0, 2 }, { 2, 0, 5 }, { 2, 1, 6 }, { 3, 0, 2 }, { 3, 1, 4 }, { 3, 2, 5 } }; testOnInstance(edges, 15); } @Test public void testGetTour6() { int[][] edges = new int[][] { { 1, 0, 8 }, { 2, 0, 4 }, { 2, 1, 4 }, { 3, 0, 5 }, { 3, 1, 8 }, { 3, 2, 6 }, { 4, 0, 7 }, { 4, 1, 7 }, { 4, 2, 5 }, { 4, 3, 6 } }; testOnInstance(edges, 26); } @Test public void testGetTour7() { int[][] edges = new int[][] { { 1, 0, 3 }, { 2, 0, 6 }, { 2, 1, 7 }, { 3, 0, 6 }, { 3, 1, 7 }, { 3, 2, 7 }, { 4, 0, 5 }, { 4, 1, 6 }, { 4, 2, 9 }, { 4, 3, 9 }, { 5, 0, 3 }, { 5, 1, 2 }, { 5, 2, 10 }, { 5, 3, 10 }, { 5, 4, 9 } }; testOnInstance(edges, 33); } @Test public void testGetTour8() { int[][] edges = new int[][] { { 1, 0, 6 }, { 2, 0, 2 }, { 2, 1, 9 }, { 3, 0, 7 }, { 3, 1, 1 }, { 3, 2, 8 }, { 4, 0, 2 }, { 4, 1, 7 }, { 4, 2, 3 }, { 4, 3, 8 }, { 5, 0, 5 }, { 5, 1, 5 }, { 5, 2, 6 }, { 5, 3, 6 }, { 5, 4, 3 }, { 6, 0, 4 }, { 6, 1, 5 }, { 6, 2, 5 }, { 6, 3, 6 }, { 6, 4, 2 }, { 6, 5, 5 } }; testOnInstance(edges, 24); } @Test public void testGetTour9() { int[][] edges = new int[][] { { 1, 0, 1 }, { 2, 0, 3 }, { 2, 1, 2 }, { 3, 0, 5 }, { 3, 1, 6 }, { 3, 2, 8 }, { 4, 0, 4 }, { 4, 1, 5 }, { 4, 2, 7 }, { 4, 3, 4 }, { 5, 0, 6 }, { 5, 1, 7 }, { 5, 2, 9 }, { 5, 3, 6 }, { 5, 4, 8 }, { 6, 0, 6 }, { 6, 1, 7 }, { 6, 2, 9 }, { 6, 3, 6 }, { 6, 4, 8 }, { 6, 5, 9 }, { 7, 0, 4 }, { 7, 1, 5 }, { 7, 2, 7 }, { 7, 3, 4 }, { 7, 4, 6 }, { 7, 5, 7 }, { 7, 6, 6 } }; testOnInstance(edges, 39); } @Test public void testGetTour10() { int[][] edges = new int[][] { { 1, 0, 5 }, { 2, 0, 4 }, { 2, 1, 5 }, { 3, 0, 3 }, { 3, 1, 7 }, { 3, 2, 6 }, { 4, 0, 5 }, { 4, 1, 7 }, { 4, 2, 6 }, { 4, 3, 5 }, { 5, 0, 5 }, { 5, 1, 8 }, { 5, 2, 7 }, { 5, 3, 6 }, { 5, 4, 8 }, { 6, 0, 5 }, { 6, 1, 8 }, { 6, 2, 7 }, { 6, 3, 6 }, { 6, 4, 8 }, { 6, 5, 7 }, { 7, 0, 5 }, { 7, 1, 7 }, { 7, 2, 6 }, { 7, 3, 5 }, { 7, 4, 7 }, { 7, 5, 6 }, { 7, 6, 8 }, { 8, 0, 5 }, { 8, 1, 6 }, { 8, 2, 5 }, { 8, 3, 4 }, { 8, 4, 6 }, { 8, 5, 5 }, { 8, 6, 8 }, { 8, 7, 7 }, { 9, 0, 5 }, { 9, 1, 5 }, { 9, 2, 4 }, { 9, 3, 3 }, { 9, 4, 5 }, { 9, 5, 4 }, { 9, 6, 8 }, { 9, 7, 7 }, { 9, 8, 6 } }; testOnInstance(edges, 52); } private void testOnInstance(int[][] edges, double optWeight) { Graph graph = TestUtil.createUndirected(edges); ChristofidesThreeHalvesApproxMetricTSP approxMetricTSP = new ChristofidesThreeHalvesApproxMetricTSP<>(); GraphPath path = approxMetricTSP.getTour(graph); assertHamiltonian(graph, path); assertTrue(path.getWeight() <= 1.5 * optWeight); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/tour/GeometricTSPTest.java000066400000000000000000000102141402514743400316620ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Peter Harman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.apache.commons.math3.geometry.euclidean.twod.*; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.junit.*; import org.junit.experimental.categories.*; import org.junit.runner.*; import org.junit.runners.*; import java.util.*; import java.util.PrimitiveIterator.*; import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; /** * Tests of Travelling Salesman Problem algorithms based on a random set of 2D points, with graphs * of increasing size * * @author Peter Harman */ @Category(SlowTests.class) @RunWith(Parameterized.class) public class GeometricTSPTest { private static final OfDouble RNG = new Random().doubles(0.0, 100.0).iterator(); private final Graph graph; public GeometricTSPTest(Graph graph, Integer size) { this.graph = graph; } @Parameterized.Parameters(name = "{1} Points") public static Object[][] graphs() { List graphs = new ArrayList<>(); for (int i = 0; i < 4; i++) { int size = (int) Math.pow(10, i); graphs.add(new Object[] { generate(size), size }); } return graphs.toArray(new Object[0][]); } static Graph generate(int n) { Vector2D[] points = new Vector2D[n]; for (int i = 0; i < n; i++) { points[i] = new Vector2D(RNG.next(), RNG.next()); } return generate(points); } static Graph generate(Vector2D[] points) { GraphBuilder> builder = GraphTypeBuilder .undirected().vertexClass(Vector2D.class).edgeClass(DefaultWeightedEdge.class) .weighted(true).buildGraphBuilder(); for (Vector2D point : points) { builder.addVertex(point); } for (int i = 0; i < points.length; i++) { for (int j = i + 1; j < points.length; j++) { builder.addEdge(points[i], points[j], points[i].distance(points[j])); } } return builder.build(); } void testWith( String description, HamiltonianCycleAlgorithm algorithm) { GraphPath tour = algorithm.getTour(graph); assertHamiltonian(graph, tour); } @Test public void testGreedy() { testWith("Greedy", new GreedyHeuristicTSP<>()); } @Test public void testNearestInsertionHeuristic() { testWith( "Nearest insertion starting from shortest edge", new NearestInsertionHeuristicTSP<>()); } @Test public void testNearestNeighbourHeuristic() { testWith("Nearest neighbour", new NearestNeighborHeuristicTSP<>()); } @Test public void testRandom() { testWith("Random", new RandomTourTSP<>()); } @Test public void testTwoOptNearestNeighbour() { testWith( "Two-opt of nearest neighbour", new TwoOptHeuristicTSP<>(new NearestNeighborHeuristicTSP<>())); } @Test public void testTwoOpt1() { testWith("Two-opt, 1 attempt from random", new TwoOptHeuristicTSP<>(1)); } @Test public void testChristofides() { testWith("Christofides", new ChristofidesThreeHalvesApproxMetricTSP<>()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/tour/HeldKarpTSPTest.java000066400000000000000000000264451402514743400314530ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; import static org.junit.Assert.*; /** * Tests for {@link HeldKarpTSP} * * @author Alexandru Valeanu * */ @Category(SlowTests.class) public class HeldKarpTSPTest { static Graph directedGraph() { // Solution exists; cost 26 Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("0"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.setEdgeWeight(g.addEdge("0", "1"), 9d); g.setEdgeWeight(g.addEdge("0", "3"), 8d); g.setEdgeWeight(g.addEdge("1", "0"), 7d); g.setEdgeWeight(g.addEdge("1", "2"), 1d); g.setEdgeWeight(g.addEdge("1", "4"), 3d); g.setEdgeWeight(g.addEdge("2", "0"), 5d); g.setEdgeWeight(g.addEdge("2", "4"), 4d); g.setEdgeWeight(g.addEdge("3", "2"), 6d); g.setEdgeWeight(g.addEdge("4", "3"), 7d); g.setEdgeWeight(g.addEdge("4", "1"), 1d); return g; } static Graph directedGraph2() { // Solution exists; cost 2166782 Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("0"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.setEdgeWeight(g.addEdge("1", "3"), 578985d); g.setEdgeWeight(g.addEdge("1", "2"), 316670d); g.setEdgeWeight(g.addEdge("2", "3"), 121118d); g.setEdgeWeight(g.addEdge("3", "2"), 585978d); g.setEdgeWeight(g.addEdge("0", "1"), 220022d); g.setEdgeWeight(g.addEdge("2", "1"), 62190d); g.setEdgeWeight(g.addEdge("0", "3"), 599952d); g.setEdgeWeight(g.addEdge("3", "1"), 540561d); g.setEdgeWeight(g.addEdge("0", "2"), 960850d); g.setEdgeWeight(g.addEdge("2", "0"), 781797d); return g; } static Graph noSolutionDirectedGraph() { Graph g = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("0"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.setEdgeWeight(g.addEdge("2", "1"), 200526d); g.setEdgeWeight(g.addEdge("1", "3"), 427820d); g.setEdgeWeight(g.addEdge("3", "1"), 375699d); g.setEdgeWeight(g.addEdge("3", "2"), 541104d); g.setEdgeWeight(g.addEdge("0", "2"), 311063d); return g; } static Graph noSolutionUndirectedGraph() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("0"); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.setEdgeWeight(g.addEdge("0", "1"), 1d); g.setEdgeWeight(g.addEdge("0", "2"), 1d); g.setEdgeWeight(g.addEdge("0", "3"), 1d); return g; } static Graph undirectedGraph() { // Solution exists; cost 80 Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.setEdgeWeight(g.addEdge("1", "2"), 10d); g.setEdgeWeight(g.addEdge("1", "3"), 15d); g.setEdgeWeight(g.addEdge("1", "4"), 20d); g.setEdgeWeight(g.addEdge("2", "3"), 35d); g.setEdgeWeight(g.addEdge("2", "4"), 25d); g.setEdgeWeight(g.addEdge("3", "4"), 30d); return g; } static Graph symmetric4CitiesGraph() { // Solution exists; cost 97 Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.setEdgeWeight(g.addEdge("A", "B"), 20d); g.setEdgeWeight(g.addEdge("A", "C"), 42d); g.setEdgeWeight(g.addEdge("A", "D"), 35d); g.setEdgeWeight(g.addEdge("B", "C"), 30d); g.setEdgeWeight(g.addEdge("B", "D"), 34d); g.setEdgeWeight(g.addEdge("C", "D"), 12d); return g; } static Graph oneVertexGraph() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("A"); return g; } @Test public void testDirectedGraph() { Graph g = directedGraph(); GraphPath tour = new HeldKarpTSP().getTour(g); assertNotNull(tour); assertHamiltonian(g, tour); assertEquals(tour.getWeight(), 26d, 1e-9); } @Test public void testDirectedGraph2() { Graph g = directedGraph2(); GraphPath tour = new HeldKarpTSP().getTour(g); assertNotNull(tour); assertHamiltonian(g, tour); assertEquals(tour.getWeight(), 2166782d, 1e-9); } @Test public void testUndirectedGraph() { Graph g = undirectedGraph(); GraphPath tour = new HeldKarpTSP().getTour(g); assertNotNull(tour); assertHamiltonian(g, tour); assertEquals(tour.getWeight(), 80d, 1e-9); } @Test public void testUndirectedGraph2() { Graph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); int[][] weights = new int[5][]; weights[0] = new int[] { 0, 8, 7, 5, 6 }; weights[1] = new int[] { 8, 0, 3, 1, 7 }; weights[2] = new int[] { 7, 3, 0, 8, 6 }; weights[3] = new int[] { 5, 1, 8, 0, 1 }; weights[4] = new int[] { 6, 7, 6, 1, 0 }; for (int i = 0; i < 5; i++) { g.addVertex(i); } for (int i = 0; i < 5; i++) { for (int j = i + 1; j < 5; j++) { g.addEdge(i, j); g.setEdgeWeight(g.getEdge(i, j), weights[i][j]); } } GraphPath tour = new HeldKarpTSP().getTour(g); assertNotNull(tour); assertHamiltonian(g, tour); assertEquals(tour.getWeight(), 18d, 1e-9); } @Test public void testDirectedWeightedPseudograph() { Graph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); int[][] weights = new int[5][]; weights[0] = new int[] { 0, 9, 3, 3, 7 }; weights[1] = new int[] { 9, 0, 10, 7, 5 }; weights[2] = new int[] { 3, 10, 0, 1, 1 }; weights[3] = new int[] { 3, 7, 1, 0, 10 }; weights[4] = new int[] { 7, 5, 1, 10, 0 }; for (int i = 0; i < 5; i++) { g.addVertex(i); } for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { g.addEdge(i, j); g.setEdgeWeight(g.getEdge(i, j), weights[i][j]); } } GraphPath tour = new HeldKarpTSP().getTour(g); assertNotNull(tour); assertHamiltonian(g, tour); assertEquals(19d, tour.getWeight(), 1e-9); } @Test public void testWikiExampleSymmetric4Cities() { Graph g = symmetric4CitiesGraph(); GraphPath tour = new HeldKarpTSP().getTour(g); assertNotNull(tour); assertHamiltonian(g, tour); assertEquals(tour.getWeight(), 97d, 1e-9); } @Test public void testNoSolutionDirectedGraph() { Graph g = noSolutionDirectedGraph(); GraphPath tour = new HeldKarpTSP().getTour(g); assertNull(tour); } @Test public void testNoSolutionUndirectedGraph() { Graph g = noSolutionUndirectedGraph(); GraphPath tour = new HeldKarpTSP().getTour(g); assertNull(tour); } @Test(expected = IllegalArgumentException.class) public void testInvalidInstanceEmpty() { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); new HeldKarpTSP().getTour(g); } @Test public void testOneVertexGraph() { Graph g = oneVertexGraph(); GraphPath tour = new HeldKarpTSP().getTour(g); assertHamiltonian(g, tour); } @Test public void testRandomGraphs() { /* * Generate 500 'random' directed multigraphs that contain a tour */ final int NUM_TESTS = 500; Random random = new Random(123); for (int test = 0; test < NUM_TESTS; test++) { Graph g = new DirectedMultigraph<>(DefaultWeightedEdge.class); // Generate n - number of nodes; 2 <= n <= 20 final int n = 2 + random.nextInt(19); for (int i = 0; i < n; i++) { g.addVertex(Integer.toString(i)); } // Make sure that there is at least one path for (int i = 0; i < n - 1; i++) { g.addEdge(Integer.toString(i), Integer.toString(i + 1)); } if (n > 1) g.addEdge(Integer.toString(n - 1), Integer.toString(0)); // Add some extra edges final int m = n * (1 + random.nextInt(6)); for (int i = 0; i < m; i++) { int u = random.nextInt(n); int v = random.nextInt(n); if (u != v) g.addEdge(Integer.toString(u), Integer.toString(v)); } GraphPath tour = new HeldKarpTSP().getTour(g); assertNotNull(tour); assertHamiltonian(g, tour); } } } NearestNeighborHeuristicTSPTest.java000066400000000000000000000170041402514743400346300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/tour/* * (C) Copyright 2020-2021, by Hannes Wellmann and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.apache.commons.math3.geometry.euclidean.twod.*; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.junit.*; import java.io.*; import java.net.*; import java.util.*; import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; /** * Tests for {@link NearestNeighborHeuristicTSP}. *

    * The test data used in this test are designed so that for one vertex, the weight of each touching * edges is distinct to the other touching edges of that vertex. This has the intended consequence * that for a given first vertex the expected tour computed with the * {@code NearestNeighborHeuristic} is unambiguous and the result must never change. *

    * * @author Hannes Wellmann * */ public class NearestNeighborHeuristicTSPTest { private static List locations; private static Graph graph; private static List> expectedTours; @BeforeClass public static void setUpBeforeClass() throws Exception { List loc = new ArrayList<>(); loc.add(new Vector2D(235, 170)); loc.add(new Vector2D(326, 212)); loc.add(new Vector2D(215, 430)); loc.add(new Vector2D(511, 693)); loc.add(new Vector2D(806, 463)); loc.add(new Vector2D(504, 62)); loc.add(new Vector2D(434, 742)); loc.add(new Vector2D(487, 614)); loc.add(new Vector2D(719, 147)); loc.add(new Vector2D(182, 449)); locations = Collections.unmodifiableList(loc); // build complete graph Graph g = new SimpleWeightedGraph<>(loc.iterator()::next, DefaultWeightedEdge::new); new CompleteGraphGenerator(loc.size()).generateGraph(g); // compute edge weights for (DefaultWeightedEdge edge : g.edgeSet()) { Vector2D source = g.getEdgeSource(edge); Vector2D target = g.getEdgeTarget(edge); double weight = source.distance(target); g.setEdgeWeight(edge, weight); } graph = new AsUnmodifiableGraph<>(g); // build expected tours: // For each of the above specified locations the distances to each other location are // different. Therefore for a given start-vertex the resulting tour computed according to // the NearestNeighbour heuristic is unambiguous. List> tours = new ArrayList<>(); tours.add(buildTourPath(new int[] { 0, 1, 5, 8, 4, 7, 3, 6, 2, 9 }, graph, loc)); tours.add(buildTourPath(new int[] { 1, 0, 2, 9, 7, 3, 6, 4, 8, 5 }, graph, loc)); tours.add(buildTourPath(new int[] { 2, 9, 1, 0, 5, 8, 4, 7, 3, 6 }, graph, loc)); tours.add(buildTourPath(new int[] { 3, 7, 6, 2, 9, 1, 0, 5, 8, 4 }, graph, loc)); tours.add(buildTourPath(new int[] { 4, 8, 5, 1, 0, 2, 9, 7, 3, 6 }, graph, loc)); tours.add(buildTourPath(new int[] { 5, 8, 4, 7, 3, 6, 2, 9, 1, 0 }, graph, loc)); tours.add(buildTourPath(new int[] { 6, 3, 7, 2, 9, 1, 0, 5, 8, 4 }, graph, loc)); tours.add(buildTourPath(new int[] { 7, 3, 6, 2, 9, 1, 0, 5, 8, 4 }, graph, loc)); tours.add(buildTourPath(new int[] { 8, 5, 1, 0, 2, 9, 7, 3, 6, 4 }, graph, loc)); tours.add(buildTourPath(new int[] { 9, 2, 1, 0, 5, 8, 4, 7, 3, 6 }, graph, loc)); expectedTours = Collections.unmodifiableList(tours); } @Test public void testConstructorWithRandomNumberGenerator() throws URISyntaxException, IOException { int randomSeed = 0; int tours = graph.vertexSet().size(); // the following order is used in within the heuristic List orderedVertices = new ArrayList<>(graph.vertexSet()); Random testRnd = new Random(randomSeed); HamiltonianCycleAlgorithm alg = new NearestNeighborHeuristicTSP<>(new Random(randomSeed)); for (int i = 0; i < tours; i++) { Vector2D expectedStartVertex = orderedVertices.get(testRnd.nextInt(tours)); GraphPath tour = alg.getTour(graph); assertStartVertex(tour, expectedStartVertex); } } @Test public void testConstructorWithFirst() { Vector2D first = locations.get(2); HamiltonianCycleAlgorithm alg = new NearestNeighborHeuristicTSP<>(first); GraphPath tour = alg.getTour(graph); assertStartVertex(tour, first); } @Test public void testConstructorWithInitialVertices() { List initalVertices = new ArrayList<>(graph.vertexSet()); long seed = stringBytesAsLong("JGraphT"); // a fixed seed Collections.shuffle(initalVertices, new Random(seed)); HamiltonianCycleAlgorithm alg = new NearestNeighborHeuristicTSP<>(initalVertices); for (Vector2D expectedStartVertex : initalVertices) { GraphPath tour = alg.getTour(graph); assertStartVertex(tour, expectedStartVertex); } } @Test public void testGetTour() { for (int i = 0; i < locations.size(); i++) { Vector2D startVertex = locations.get(i); GraphPath expectedTour = expectedTours.get(i); GraphPath tour = new NearestNeighborHeuristicTSP(startVertex) .getTour(graph); assertThat(tour, is(equalTo(expectedTour))); } } // utilities private static void assertStartVertex(GraphPath tour, V expectedStartVertex) { assertThat(tour.getStartVertex(), is(sameInstance(expectedStartVertex))); } private static GraphPath buildTourPath( int[] tourVertexIndices, Graph graph, List vertexList) { List tour = Arrays.stream(tourVertexIndices).mapToObj(vertexList::get).collect(toList()); tour.add(tour.get(0)); // close path double weight = 0; for (int i = 1; i < tourVertexIndices.length; i++) { E edge = graph.getEdge(tour.get(i - 1), tour.get(i)); weight += graph.getEdgeWeight(edge); } return new GraphWalk<>(graph, tour, weight); } private static long stringBytesAsLong(String str) { int length = str.length(); // if longer than 8, bytes are lost long l = 0; for (int i = 0; i < length; i++) { l += ((long) str.charAt(length - 1 - i)) << (8 * i); } return l; } } PalmerHamiltonianCycleTest.java000066400000000000000000000155621402514743400336750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/tour/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class PalmerHamiltonianCycleTest { /** * Small graph of 4 nodes. */ @Test public void testSmallGraph() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex("A"); graph.addVertex("B"); graph.addVertex("C"); graph.addVertex("D"); graph.addEdge("A", "B"); graph.addEdge("A", "C"); graph.addEdge("B", "D"); graph.addEdge("C", "D"); GraphPath tour = new PalmerHamiltonianCycle().getTour(graph); assertNotNull(tour); assertHamiltonian(graph, tour); } /** * Test that contains a simple cycle of 10 nodes. The graph has a Hamiltonian cycle but it * doesn't meet Ore's condition. */ @Test(expected = IllegalArgumentException.class) public void testLineGraph() { Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int i = 0; i < 10; i++) { graph.addVertex(i); } for (int i = 0; i < 10; i++) { graph.addEdge(i, (i + 1) % 10); } GraphPath tour = new PalmerHamiltonianCycle().getTour(graph); assertNotNull(tour); assertHamiltonian(graph, tour); } private void testRandomGraphs(Random random) { final int NUM_TESTS = 500; for (int test = 0; test < NUM_TESTS; test++) { Graph graph = new SimpleGraph<>(DefaultEdge.class); final int n = 3 + random.nextInt(150); for (int i = 0; i < n; i++) { graph.addVertex(i); } List vertexList = new ArrayList<>(graph.vertexSet()); while (!GraphTests.hasOreProperty(graph)) { Collections.shuffle(vertexList, random); search: for (int i = 0; i < vertexList.size(); i++) { for (int j = i + 1; j < vertexList.size(); j++) { int u = vertexList.get(i); int v = vertexList.get(j); if (!graph.containsEdge(u, v) && graph.degreeOf(u) + graph.degreeOf(v) < n) { graph.addEdge(u, v); break search; } } } } GraphPath tour = new PalmerHamiltonianCycle().getTour(graph); assertNotNull(tour); assertHamiltonian(graph, tour); } } /** * Test with 500 randomly generated graphs. Method of generation: randomly add edges while the * graph doesn't have Ore's property */ @Test @Category(SlowTests.class) public void testRandomGraphs() { testRandomGraphs(new Random(0xC0FFEE)); } private void testRandomGraphs2(Random random) { final int NUM_TESTS = 500; for (int test = 0; test < NUM_TESTS; test++) { Graph graph = new SimpleGraph<>(DefaultEdge.class); final int n = 3 + random.nextInt(150); for (int i = 0; i < n; i++) { graph.addVertex(i); } List vertexList = new ArrayList<>(graph.vertexSet()); boolean changed; do { changed = false; Collections.shuffle(vertexList, random); search: for (int v : vertexList) { if (graph.degreeOf(v) < (n + 1) / 2) { for (int u : vertexList) { if (u != v && !graph.containsEdge(u, v)) { graph.addEdge(u, v); changed = true; break search; } } } } } while (changed); GraphPath tour = new PalmerHamiltonianCycle().getTour(graph); assertNotNull(tour); assertHamiltonian(graph, tour); } } /** * Test with 500 randomly generated graphs (fixed seed). Method of generation: make sure that * each node has (n+1)/2 neighbours */ @Test @Category(SlowTests.class) public void testRandomGraphs2FixedSeed() { testRandomGraphs2(new Random(0xBEEF)); } private static Graph bigGraph = new SimpleGraph<>(DefaultEdge.class); @BeforeClass public static void generateBigGraph() { Random random = new Random(0xC0FFEE); final int n = 1000; for (int i = 0; i < n; i++) { bigGraph.addVertex(i); } List vertexList = new ArrayList<>(bigGraph.vertexSet()); while (!GraphTests.hasOreProperty(bigGraph)) { Collections.shuffle(vertexList, random); search: for (int i = 0; i < vertexList.size(); i++) { for (int j = i + 1; j < vertexList.size(); j++) { int u = vertexList.get(i); int v = vertexList.get(j); if (!bigGraph.containsEdge(u, v) && bigGraph.degreeOf(u) + bigGraph.degreeOf(v) < n) { bigGraph.addEdge(u, v); break search; } } } } assertTrue(GraphTests.hasOreProperty(bigGraph)); } @Test @Category(SlowTests.class) public void testBigGraph() { GraphPath tour = new PalmerHamiltonianCycle().getTour(bigGraph); assertNotNull(tour); assertHamiltonian(bigGraph, tour); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/tour/TwoApproxMetricTSPTest.java000066400000000000000000000146451402514743400330670ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.alg.spanning.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * @author Dimitrios Michail */ public class TwoApproxMetricTSPTest { @Test public void testWikiExampleSymmetric4Cities() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.setEdgeWeight(g.addEdge("A", "B"), 20d); g.setEdgeWeight(g.addEdge("A", "C"), 42d); g.setEdgeWeight(g.addEdge("A", "D"), 35d); g.setEdgeWeight(g.addEdge("B", "C"), 30d); g.setEdgeWeight(g.addEdge("B", "D"), 34d); g.setEdgeWeight(g.addEdge("C", "D"), 12d); GraphPath tour = new TwoApproxMetricTSP().getTour(g); assertHamiltonian(g, tour); assertTrue( 2 * new KruskalMinimumSpanningTree<>(g).getSpanningTree().getWeight() >= tour .getWeight()); } @Test public void testComplete() { final int maxSize = 50; for (int i = 1; i < maxSize; i++) { SimpleGraph g = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator generator = new CompleteGraphGenerator<>(i); generator.generateGraph(g); GraphPath tour = new TwoApproxMetricTSP().getTour(g); assertHamiltonian(g, tour); double mstWeight = new KruskalMinimumSpanningTree<>(g).getSpanningTree().getWeight(); double tourWeight = tour.getWeight(); assertTrue(2 * mstWeight >= tourWeight); } } @Test public void testStar() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addVertex("6"); g.setEdgeWeight(g.addEdge("1", "2"), 1d); g.setEdgeWeight(g.addEdge("1", "3"), 1d); g.setEdgeWeight(g.addEdge("1", "4"), 1d); g.setEdgeWeight(g.addEdge("1", "5"), 2d); g.setEdgeWeight(g.addEdge("1", "6"), 2d); g.setEdgeWeight(g.addEdge("2", "3"), 2d); g.setEdgeWeight(g.addEdge("2", "4"), 1d); g.setEdgeWeight(g.addEdge("2", "5"), 1d); g.setEdgeWeight(g.addEdge("2", "6"), 2d); g.setEdgeWeight(g.addEdge("3", "4"), 1d); g.setEdgeWeight(g.addEdge("3", "5"), 2d); g.setEdgeWeight(g.addEdge("3", "6"), 1d); g.setEdgeWeight(g.addEdge("4", "5"), 1d); g.setEdgeWeight(g.addEdge("4", "6"), 1d); g.setEdgeWeight(g.addEdge("5", "6"), 1d); GraphPath tour = new TwoApproxMetricTSP().getTour(g); assertHamiltonian(g, tour); double mstWeight = new KruskalMinimumSpanningTree<>(g).getSpanningTree().getWeight(); double tourWeight = tour.getWeight(); assertTrue(2 * mstWeight >= tourWeight); } @Test(expected = IllegalArgumentException.class) public void testInvalidInstanceDirected() { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); g.addVertex("A"); new TwoApproxMetricTSP().getTour(g); } @Test(expected = IllegalArgumentException.class) public void testInvalidInstanceNotComplete() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.setEdgeWeight(g.addEdge("A", "B"), 20d); g.setEdgeWeight(g.addEdge("A", "C"), 42d); new TwoApproxMetricTSP().getTour(g); } static void assertHamiltonian(Graph g, GraphPath path) { List tourVertices = path.getVertexList(); List tourEdges = path.getEdgeList(); // check tour length, beginning and end of the tour assertEquals(path.getStartVertex(), path.getEndVertex()); assertEquals(path.getStartVertex(), tourVertices.get(0)); if (g.vertexSet().size() == 1) { assertTrue(tourEdges.isEmpty()); assertEquals(path.getWeight(), 0.0, 1e-9); return; } assertEquals(g.vertexSet().size(), tourVertices.size() - 1); assertEquals(g.vertexSet().size(), tourEdges.size()); // check tour with edges double weight = 0d; V v = path.getStartVertex(); Set visited = new HashSet<>(); for (E e : tourEdges) { v = Graphs.getOppositeVertex(g, e, v); assertTrue(visited.add(v)); weight += g.getEdgeWeight(e); } assertEquals(path.getWeight(), weight, 1e-9); assertEquals(visited.size(), g.vertexSet().size()); // check tour with vertices visited.clear(); Iterator vIt = tourVertices.iterator(); V start = vIt.next(); visited.add(start); while (vIt.hasNext()) { v = vIt.next(); if (!vIt.hasNext()) { assertTrue(v.equals(start)); } else { assertTrue(visited.add(v)); } } assertEquals(visited.size(), g.vertexSet().size()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/tour/TwoOptHeuristicTSPTest.java000066400000000000000000000111541402514743400330640ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.tour; import org.jgrapht.*; import org.jgrapht.alg.spanning.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.junit.experimental.categories.*; import static org.jgrapht.alg.tour.TwoApproxMetricTSPTest.assertHamiltonian; import static org.junit.Assert.assertTrue; /** * Tests for {@link TwoOptHeuristicTSP}. * * @author Dimitrios Michail */ @Category(SlowTests.class) public class TwoOptHeuristicTSPTest { @Test public void testWikiExampleSymmetric4Cities() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.addVertex("D"); g.setEdgeWeight(g.addEdge("A", "B"), 20d); g.setEdgeWeight(g.addEdge("A", "C"), 42d); g.setEdgeWeight(g.addEdge("A", "D"), 35d); g.setEdgeWeight(g.addEdge("B", "C"), 30d); g.setEdgeWeight(g.addEdge("B", "D"), 34d); g.setEdgeWeight(g.addEdge("C", "D"), 12d); GraphPath tour = new TwoOptHeuristicTSP().getTour(g); assertHamiltonian(g, tour); } @Test public void testComplete() { final int maxSize = 50; for (int i = 1; i < maxSize; i++) { SimpleGraph g = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator generator = new CompleteGraphGenerator<>(i); generator.generateGraph(g); GraphPath tour = new TwoOptHeuristicTSP().getTour(g); assertHamiltonian(g, tour); } } @Test public void testStar() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("1"); g.addVertex("2"); g.addVertex("3"); g.addVertex("4"); g.addVertex("5"); g.addVertex("6"); g.setEdgeWeight(g.addEdge("1", "2"), 1d); g.setEdgeWeight(g.addEdge("1", "3"), 1d); g.setEdgeWeight(g.addEdge("1", "4"), 1d); g.setEdgeWeight(g.addEdge("1", "5"), 2d); g.setEdgeWeight(g.addEdge("1", "6"), 2d); g.setEdgeWeight(g.addEdge("2", "3"), 2d); g.setEdgeWeight(g.addEdge("2", "4"), 1d); g.setEdgeWeight(g.addEdge("2", "5"), 1d); g.setEdgeWeight(g.addEdge("2", "6"), 2d); g.setEdgeWeight(g.addEdge("3", "4"), 1d); g.setEdgeWeight(g.addEdge("3", "5"), 2d); g.setEdgeWeight(g.addEdge("3", "6"), 1d); g.setEdgeWeight(g.addEdge("4", "5"), 1d); g.setEdgeWeight(g.addEdge("4", "6"), 1d); g.setEdgeWeight(g.addEdge("5", "6"), 1d); GraphPath tour = new TwoOptHeuristicTSP().getTour(g); assertHamiltonian(g, tour); double mstWeight = new KruskalMinimumSpanningTree<>(g).getSpanningTree().getWeight(); double tourWeight = tour.getWeight(); assertTrue(2 * mstWeight >= tourWeight); } @Test(expected = IllegalArgumentException.class) public void testInvalidInstanceDirected() { new TwoOptHeuristicTSP() .getTour(new SimpleDirectedGraph<>(DefaultEdge.class)); } @Test(expected = IllegalArgumentException.class) public void testInvalidInstanceNotComplete() { SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex("A"); g.addVertex("B"); g.addVertex("C"); g.setEdgeWeight(g.addEdge("A", "B"), 20d); g.setEdgeWeight(g.addEdge("A", "C"), 42d); new TwoOptHeuristicTSP().getTour(g); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/transform/000077500000000000000000000000001402514743400266765ustar00rootroot00000000000000LineGraphConverterTest.java000066400000000000000000000155211402514743400340670ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/transform/* * (C) Copyright 2018-2021, by Nikhil Sharma and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.transform; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for LineGraphConverter * * @author Nikhil Sharma * @author Joris Kinable */ public class LineGraphConverterTest { @Test public void testEmptyGraph() { // Line Graph of an empty graph should be empty Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); LineGraphConverter lgc = new LineGraphConverter<>(g); lgc.convertToLineGraph(new SimpleWeightedGraph<>(DefaultEdge.class)); assertTrue(GraphTests.isEmpty(g)); } @Test public void testStarGraph() { // Line Graph of a star graph is a complete graph Graph starGraph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator generator = new StarGraphGenerator<>(5); Map resultMap = new HashMap<>(); generator.generateGraph(starGraph, resultMap); LineGraphConverter lgc = new LineGraphConverter<>(starGraph); Graph target = new SimpleGraph<>(DefaultEdge.class); lgc.convertToLineGraph(target); assertTrue(GraphTests.isComplete(target)); } @Test public void testUndirectedGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e25 = g.addEdge(2, 5); DefaultEdge e54 = g.addEdge(5, 4); DefaultEdge e41 = g.addEdge(4, 1); DefaultEdge e43 = g.addEdge(4, 3); DefaultEdge e13 = g.addEdge(1, 3); LineGraphConverter lgc = new LineGraphConverter<>(g); Graph target = new SimpleGraph<>(DefaultEdge.class); lgc.convertToLineGraph(target); assertEquals(target.vertexSet(), g.edgeSet()); assertEquals(9, target.edgeSet().size()); assertTrue(target.containsEdge(e12, e25)); assertTrue(target.containsEdge(e25, e54)); assertTrue(target.containsEdge(e54, e43)); assertTrue(target.containsEdge(e43, e13)); assertTrue(target.containsEdge(e12, e13)); assertTrue(target.containsEdge(e41, e12)); assertTrue(target.containsEdge(e41, e54)); assertTrue(target.containsEdge(e41, e43)); assertTrue(target.containsEdge(e41, e13)); } @Test public void testDirectedGraph() { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e14 = g.addEdge(1, 4); DefaultEdge e23 = g.addEdge(2, 3); DefaultEdge e31 = g.addEdge(3, 1); DefaultEdge e32 = g.addEdge(3, 2); DefaultEdge e34 = g.addEdge(3, 4); DefaultEdge e43 = g.addEdge(4, 3); LineGraphConverter lgc = new LineGraphConverter<>(g); Graph target = new SimpleDirectedGraph<>(DefaultEdge.class); lgc.convertToLineGraph(target); assertEquals(target.vertexSet(), g.edgeSet()); assertEquals(12, target.edgeSet().size()); assertTrue(target.containsEdge(e12, e23)); assertTrue(target.containsEdge(e23, e32)); assertTrue(target.containsEdge(e23, e34)); assertTrue(target.containsEdge(e23, e31)); assertTrue(target.containsEdge(e32, e23)); assertTrue(target.containsEdge(e31, e12)); assertTrue(target.containsEdge(e31, e14)); assertTrue(target.containsEdge(e34, e43)); assertTrue(target.containsEdge(e43, e34)); assertTrue(target.containsEdge(e43, e32)); assertTrue(target.containsEdge(e43, e31)); assertTrue(target.containsEdge(e14, e43)); } @Test public void selfLoopTestUndirected() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e23 = g.addEdge(2, 3); DefaultEdge e31 = g.addEdge(3, 1); DefaultEdge e22 = g.addEdge(2, 2); LineGraphConverter lgc = new LineGraphConverter<>(g); Graph target = new SimpleGraph<>(DefaultEdge.class); lgc.convertToLineGraph(target); assertEquals(target.vertexSet(), g.edgeSet()); assertEquals(5, target.edgeSet().size()); assertTrue(target.containsEdge(e12, e23)); assertTrue(target.containsEdge(e12, e31)); assertTrue(target.containsEdge(e23, e31)); assertTrue(target.containsEdge(e12, e22)); assertTrue(target.containsEdge(e22, e23)); } @Test public void selfLoopTestDirected() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e23 = g.addEdge(2, 3); DefaultEdge e31 = g.addEdge(3, 1); DefaultEdge e22 = g.addEdge(2, 2); LineGraphConverter lgc = new LineGraphConverter<>(g); Graph target = new DirectedPseudograph<>(DefaultEdge.class); lgc.convertToLineGraph(target); assertEquals(target.vertexSet(), g.edgeSet()); assertEquals(6, target.edgeSet().size()); assertTrue(target.containsEdge(e12, e23)); assertTrue(target.containsEdge(e23, e31)); assertTrue(target.containsEdge(e31, e12)); assertTrue(target.containsEdge(e22, e22)); assertTrue(target.containsEdge(e12, e22)); assertTrue(target.containsEdge(e22, e23)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/util/000077500000000000000000000000001402514743400256405ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/util/AliasMethodSamplerTest.java000066400000000000000000000040651402514743400330660ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Test {@link AliasMethodSampler}. * * @author Dimitrios Michail */ public class AliasMethodSamplerTest { @Test public void test1() { final long seed = 5; double[] prob = { 0.1, 0.2, 0.3, 0.4 }; AliasMethodSampler am = new AliasMethodSampler(prob, new Random(seed)); int[] counts = new int[4]; for (int i = 0; i < 1000000; i++) { counts[am.next()]++; } assertEquals(100033, counts[0]); assertEquals(200069, counts[1]); assertEquals(299535, counts[2]); assertEquals(400363, counts[3]); } @Test public void test2() { final long seed = 17; double[] prob = { 0.05, 0.05, 0.05, 0.05, 0.8 }; AliasMethodSampler am = new AliasMethodSampler(prob, new Random(seed)); int[] counts = new int[5]; for (int i = 0; i < 1000000; i++) { counts[am.next()]++; } assertEquals(49949, counts[0]); assertEquals(49726, counts[1]); assertEquals(50441, counts[2]); assertEquals(49894, counts[3]); assertEquals(799990, counts[4]); } @Test(expected = IllegalArgumentException.class) public void testNonValid() { double[] prob = { 0.5, 0.6 }; new AliasMethodSampler(prob, new Random(15)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/util/FixedSizeIntegerQueueTest.java000066400000000000000000000025721402514743400335660ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import org.junit.*; import static org.junit.Assert.*; /** * Tests for FixedSizeIntegerQueue * * @author Joris Kinable */ public class FixedSizeIntegerQueueTest { @Test public void testQueue() { FixedSizeIntegerQueue queue = new FixedSizeIntegerQueue(10); assertTrue(queue.isEmpty()); assertEquals(0, queue.size()); queue.enqueue(1); assertFalse(queue.isEmpty()); assertEquals(1, queue.size()); int v = queue.poll(); assertEquals(1, v); assertTrue(queue.isEmpty()); assertEquals(0, queue.size()); queue.enqueue(2); assertFalse(queue.isEmpty()); queue.clear(); assertTrue(queue.isEmpty()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/util/NeighborCacheTest.java000066400000000000000000000141231402514743400320250ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import org.junit.rules.*; import java.util.*; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; /** * . * * @author Charles Fry */ public class NeighborCacheTest { // ~ Static fields/initializers --------------------------------------------- private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; @Rule public final ExpectedException exception = ExpectedException.none(); // ~ Methods ---------------------------------------------------------------- @Test public void testNeighborSet() { // We use Object instead of DefaultEdge for the edge type // in order to cover the case in // https://sourceforge.net/tracker/index.php?func=detail&aid=3486775&group_id=86459&atid=579687 ListenableGraph g = new DefaultListenableGraph<>(new SimpleGraph<>(Object.class)); NeighborCache cache = new NeighborCache<>(g); g.addGraphListener(cache); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); Set neighbors1 = cache.neighborsOf(V1); assertEquals(1, neighbors1.size()); assertEquals(true, neighbors1.contains(V2)); g.addVertex(V3); g.addEdge(V3, V1); Set neighbors3 = cache.neighborsOf(V3); assertEquals(2, neighbors1.size()); assertEquals(true, neighbors1.contains(V3)); assertEquals(1, neighbors3.size()); assertEquals(true, neighbors3.contains(V1)); g.removeEdge(V3, V1); assertEquals(1, neighbors1.size()); assertEquals(false, neighbors1.contains(V3)); assertEquals(0, neighbors3.size()); g.removeVertex(V2); assertEquals(0, neighbors1.size()); } @Test public void testDirectedNeighborSet() { ListenableGraph g = new DefaultListenableGraph<>(new DefaultDirectedGraph<>(Object.class)); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); NeighborCache index = new NeighborCache<>(g); g.addGraphListener(index); Set p = index.predecessorsOf(V1); Set s = index.successorsOf(V1); assertEquals(0, p.size()); assertEquals(1, s.size()); assertEquals(true, s.contains(V2)); g.addVertex(V3); g.addEdge(V3, V1); Set q = index.successorsOf(V3); assertEquals(1, p.size()); assertEquals(1, s.size()); assertEquals(true, p.contains(V3)); assertEquals(1, q.size()); assertEquals(true, q.contains(V1)); g.removeEdge(V3, V1); assertEquals(0, q.size()); assertEquals(0, p.size()); g.removeVertex(V2); assertEquals(0, s.size()); } @Test public void testVertexRemoval() { ListenableGraph graph = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); final String A = "A"; final String B = "B"; final String C = "C"; final String D = "D"; NeighborCache cache = new NeighborCache<>(graph); graph.addGraphListener(cache); graph.addVertex(A); graph.addVertex(B); graph.addVertex(C); graph.addVertex(D); graph.addEdge(D, A); graph.addEdge(D, B); graph.addEdge(D, C); Set neighborsOfD = cache.neighborsOf(D); Set neighborsOfC = cache.neighborsOf(C); Set neighborsOfB = cache.neighborsOf(B); Set neighborsOfA = cache.neighborsOf(A); assertThat(neighborsOfD, hasItems(A, B, C)); assertThat(neighborsOfA.size(), is(1)); assertThat(neighborsOfB.size(), is(1)); assertThat(neighborsOfC.size(), is(1)); graph.removeVertex(D); assertTrue(neighborsOfD.isEmpty()); assertThat(neighborsOfA.size(), is(0)); assertThat(neighborsOfB.size(), is(0)); assertThat(neighborsOfC.size(), is(0)); } @Test public void testNeighborListCreation() { ListenableGraph graph = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); final String A = "A"; final String B = "B"; final String C = "C"; final String D = "D"; NeighborCache cache = new NeighborCache<>(graph); graph.addGraphListener(cache); graph.addVertex(A); graph.addVertex(B); graph.addVertex(C); graph.addVertex(D); graph.addEdge(D, A); graph.addEdge(D, B); graph.addEdge(D, C); assertThat(cache.neighborListOf(B), hasItems(D)); assertThat(cache.neighborListOf(B).size(), is(1)); graph.addEdge(A, B); assertThat(cache.neighborListOf(B), hasItems(A, D)); assertThat(cache.neighborListOf(B).size(), is(2)); graph.removeEdge(D, B); assertThat(cache.neighborListOf(B), hasItems(A)); assertThat(cache.neighborListOf(B).size(), is(1)); graph.removeVertex(B); exception.expect(IllegalArgumentException.class); exception.expectMessage("no such vertex"); cache.neighborListOf(B); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/util/PairTest.java000066400000000000000000000175401402514743400302450ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import org.junit.*; import static org.junit.Assert.*; /** * Test {@link Pair} and {@link UnorderedPair} classes. * * @author Dimitrios Michail */ public class PairTest { private static final String ANOTHER = "another"; private static final String CUSTOM = "custom"; @Test public void testPair() { Pair p = Pair.of(CUSTOM, new Custom(1)); assertEquals(CUSTOM, p.getFirst()); assertNotEquals(ANOTHER, p.getFirst()); assertEquals(new Custom(1), p.getSecond()); assertNotEquals(new Custom(2), p.getSecond()); assertTrue(p.hasElement(CUSTOM)); assertFalse(p.hasElement(ANOTHER)); assertTrue(p.hasElement(new Custom(1))); assertFalse(p.hasElement(new Custom(2))); assertFalse(p.hasElement(null)); } @Test public void testUnorderedPair() { Pair p = UnorderedPair.of(CUSTOM, new Custom(1)); assertEquals(CUSTOM, p.getFirst()); assertNotEquals(ANOTHER, p.getFirst()); assertEquals(new Custom(1), p.getSecond()); assertNotEquals(new Custom(2), p.getSecond()); assertTrue(p.hasElement(CUSTOM)); assertFalse(p.hasElement(ANOTHER)); assertTrue(p.hasElement(new Custom(1))); assertFalse(p.hasElement(new Custom(2))); assertFalse(p.hasElement(null)); } @Test public void testPairWithNull() { Pair p = Pair.of(null, new Custom(1)); assertNull(p.getFirst()); assertEquals(new Custom(1), p.getSecond()); assertTrue(p.hasElement(null)); assertTrue(p.hasElement(new Custom(1))); } @Test public void testUnorderedPairWithNull() { Pair p = UnorderedPair.of(null, new Custom(1)); assertNull(p.getFirst()); assertEquals(new Custom(1), p.getSecond()); assertTrue(p.hasElement(null)); assertTrue(p.hasElement(new Custom(1))); } @Test public void testPairEquals() { Pair p1 = Pair.of(CUSTOM, new Custom(1)); Pair p2 = Pair.of(ANOTHER, new Custom(1)); Pair p3 = Pair.of(ANOTHER, new Custom(2)); Pair p4 = Pair.of(CUSTOM, new Custom(1)); Pair p5 = Pair.of(CUSTOM, new Custom(2)); Pair p6 = Pair.of(new Custom(1), CUSTOM); assertNotEquals(p1, p2); assertNotEquals(p1, p3); assertEquals(p1, p4); assertNotEquals(p1, p5); assertNotEquals(p1, p6); assertNotEquals(p2, p3); assertNotEquals(p2, p4); assertNotEquals(p2, p5); assertNotEquals(p2, p6); assertNotEquals(p3, p4); assertNotEquals(p3, p5); assertNotEquals(p3, p6); assertNotEquals(p4, p5); assertNotEquals(p4, p6); assertNotEquals(p5, p6); } @Test public void testUnorderedPairEquals() { Pair p1 = UnorderedPair.of(CUSTOM, new Custom(1)); Pair p2 = UnorderedPair.of(ANOTHER, new Custom(1)); Pair p3 = UnorderedPair.of(ANOTHER, new Custom(2)); Pair p4 = UnorderedPair.of(CUSTOM, new Custom(1)); Pair p5 = UnorderedPair.of(CUSTOM, new Custom(2)); Pair p6 = UnorderedPair.of(new Custom(1), CUSTOM); assertNotEquals(p1, p2); assertNotEquals(p1, p3); assertEquals(p1, p4); assertNotEquals(p1, p5); assertEquals(p1, p6); assertNotEquals(p2, p3); assertNotEquals(p2, p4); assertNotEquals(p2, p5); assertNotEquals(p2, p6); assertNotEquals(p3, p4); assertNotEquals(p3, p5); assertNotEquals(p3, p6); assertNotEquals(p4, p5); assertEquals(p4, p6); assertNotEquals(p5, p6); } @Test public void testPairEqualsWithNull() { Pair p1 = Pair.of(CUSTOM, null); Pair p2 = Pair.of(null, CUSTOM); Pair p3 = Pair.of(ANOTHER, null); Pair p4 = Pair.of(CUSTOM, null); Pair p5 = Pair.of(CUSTOM, new Custom(1)); assertNotEquals(p1, p2); assertNotEquals(p1, p3); assertEquals(p1, p4); assertNotEquals(p1, p5); assertNotEquals(p2, p3); assertNotEquals(p2, p4); assertNotEquals(p2, p5); assertNotEquals(p3, p4); assertNotEquals(p3, p5); assertNotEquals(p4, p5); } @Test public void testUnorderedPairEqualsWithNull() { Pair p1 = UnorderedPair.of(CUSTOM, null); Pair p2 = UnorderedPair.of(null, CUSTOM); Pair p3 = UnorderedPair.of(ANOTHER, null); Pair p4 = UnorderedPair.of(CUSTOM, null); Pair p5 = UnorderedPair.of(CUSTOM, new Custom(1)); assertEquals(p1, p2); assertNotEquals(p1, p3); assertEquals(p1, p4); assertNotEquals(p1, p5); assertNotEquals(p2, p3); assertEquals(p2, p4); assertNotEquals(p2, p5); assertNotEquals(p3, p4); assertNotEquals(p3, p5); assertNotEquals(p4, p5); } @Test public void testDifferentTypesEqualsWithNull() { Pair p1 = Pair.of(null, null); Pair p2 = Pair.of(null, null); Pair p3 = Pair.of(null, null); assertEquals(p1, p2); assertEquals(p2, p3); assertEquals(p3, p1); UnorderedPair p4 = UnorderedPair.of(null, null); UnorderedPair p5 = UnorderedPair.of(null, null); UnorderedPair p6 = UnorderedPair.of(null, null); assertEquals(p4, p5); assertEquals(p5, p6); assertEquals(p6, p4); } @Test public void testUnorderedSameHashCode() { UnorderedPair p1 = UnorderedPair.of(CUSTOM, new Custom(1)); UnorderedPair p2 = UnorderedPair.of(new Custom(1), CUSTOM); assertEquals(p1.hashCode(), p2.hashCode()); } class Custom { private int id; public Custom(int id) { this.id = id; } public int getId() { return id; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getOuterType().hashCode(); result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Custom other = (Custom) obj; if (!getOuterType().equals(other.getOuterType())) return false; if (id != other.id) return false; return true; } private PairTest getOuterType() { return PairTest.this; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/util/UnionFindTest.java000066400000000000000000000073761402514743400312510ustar00rootroot00000000000000/* * (C) Copyright 2010-2021, by Tom Conerly and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * . * * @author Tom Conerly */ public class UnionFindTest { // ~ Methods ---------------------------------------------------------------- /** * . */ @Test public void testUnionFind() { TreeSet set = new TreeSet(); String[] strs = { "aaa", "bbb", "ccc", "ddd", "eee" }; ArrayList> sets = new ArrayList>(); for (String str : strs) { set.add(str); sets.add(new ArrayList()); sets.get(sets.size() - 1).add(str); } UnionFind uf = new UnionFind(set); assertEquals(5, uf.size()); assertEquals(5, uf.numberOfSets()); testIdentical(strs, sets, uf); uf.union(strs[0], strs[1]); assertEquals(4, uf.numberOfSets()); union(sets, strs[0], strs[1]); testIdentical(strs, sets, uf); assertTrue(uf.inSameSet("aaa", "bbb")); assertFalse(uf.inSameSet("bbb", "ccc")); uf.union(strs[2], strs[3]); assertEquals(3, uf.numberOfSets()); union(sets, strs[2], strs[3]); testIdentical(strs, sets, uf); uf.union(strs[2], strs[4]); assertEquals(2, uf.numberOfSets()); union(sets, strs[2], strs[4]); testIdentical(strs, sets, uf); uf.union(strs[2], strs[4]); assertEquals(2, uf.numberOfSets()); union(sets, strs[2], strs[4]); testIdentical(strs, sets, uf); uf.union(strs[0], strs[4]); assertEquals(1, uf.numberOfSets()); union(sets, strs[0], strs[4]); testIdentical(strs, sets, uf); uf.addElement("fff"); assertEquals(2, uf.numberOfSets()); assertEquals(6, uf.size()); uf.reset(); assertEquals(6, uf.numberOfSets()); } private void union(ArrayList> sets, String a, String b) { ArrayList toAdd = new ArrayList(); for (int i = 0; i < sets.size(); i++) { if (sets.get(i).contains(a)) { toAdd.addAll(sets.get(i)); sets.remove(i); break; } } for (int i = 0; i < sets.size(); i++) { if (sets.get(i).contains(b)) { toAdd.addAll(sets.get(i)); sets.remove(i); break; } } sets.add(toAdd); } private boolean same(ArrayList> sets, String a, String b) { for (ArrayList set : sets) { if (set.contains(a) && set.contains(b)) { return true; } } return false; } private void testIdentical( String[] universe, ArrayList> sets, UnionFind uf) { for (String a : universe) { for (String b : universe) { boolean same1 = uf.find(a).equals(uf.find(b)); boolean same2 = same(sets, a, b); assertEquals(same1, same2); } } } } VertexDegreeComparatorTest.java000066400000000000000000000046171402514743400337150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/util/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.util; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertTrue; /** * Unit tests for VertexDegreeComparator * * @author Joris Kinable */ public class VertexDegreeComparatorTest { protected static final int TEST_REPEATS = 20; private GraphGenerator randomGraphGenerator; public VertexDegreeComparatorTest() { randomGraphGenerator = new GnmRandomGraphGenerator<>(100, 1000, 0); } @Test public void testVertexDegreeComparator() { for (int repeat = 0; repeat < TEST_REPEATS; repeat++) { Graph graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); randomGraphGenerator.generateGraph(graph); List vertices = new ArrayList<>(graph.vertexSet()); // Sort in ascending vertex degree Collections .sort( vertices, new VertexDegreeComparator<>(graph, VertexDegreeComparator.Order.ASCENDING)); for (int i = 0; i < vertices.size() - 1; i++) assertTrue(graph.degreeOf(vertices.get(i)) <= graph.degreeOf(vertices.get(i + 1))); // Sort in descending vertex degree Collections .sort( vertices, new VertexDegreeComparator<>(graph, VertexDegreeComparator.Order.DESCENDING)); for (int i = 0; i < vertices.size() - 1; i++) assertTrue(graph.degreeOf(vertices.get(i)) >= graph.degreeOf(vertices.get(i + 1))); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/000077500000000000000000000000001402514743400272375ustar00rootroot00000000000000BarYehudaEvenTwoApproxVCImplTest.java000066400000000000000000000023361402514743400363100ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; public class BarYehudaEvenTwoApproxVCImplTest extends WeightedVertexCoverTwoApproxTest { @Override public VertexCoverAlgorithm createSolver(Graph graph) { return new BarYehudaEvenTwoApproxVCImpl<>(graph); } @Override public VertexCoverAlgorithm createWeightedSolver( Graph graph, Map vertexWeightMap) { return new BarYehudaEvenTwoApproxVCImpl<>(graph, vertexWeightMap); } } ClarksonTwoApproxVCImplTest.java000066400000000000000000000023171402514743400354010ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; public class ClarksonTwoApproxVCImplTest extends WeightedVertexCoverTwoApproxTest { @Override public VertexCoverAlgorithm createSolver(Graph graph) { return new ClarksonTwoApproxVCImpl<>(graph); } @Override public VertexCoverAlgorithm createWeightedSolver( Graph graph, Map vertexWeightMap) { return new ClarksonTwoApproxVCImpl<>(graph, vertexWeightMap); } } EdgeBasedTwoApproxVCImplTest.java000066400000000000000000000017271402514743400354340ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; public class EdgeBasedTwoApproxVCImplTest extends VertexCoverTwoApproxTest { @Override public VertexCoverAlgorithm createSolver(Graph graph) { return new EdgeBasedTwoApproxVCImpl<>(graph); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/GreedyVCImplTest.java000066400000000000000000000053361402514743400332430ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Linda Buisman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.vertexcover.VertexCoverTestUtils.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class GreedyVCImplTest { public VertexCoverAlgorithm createSolver(Graph graph) { return new GreedyVCImpl<>(graph); } public VertexCoverAlgorithm createWeightedSolver( Graph graph, Map vertexWeightMap) { return new GreedyVCImpl<>(graph, vertexWeightMap); } // ------- Greedy algorithms ------ /** * Test greedy algorithm for the minimum vertex cover problem. */ @Test public void testFindGreedyCover() { for (int i = 0; i < TEST_REPEATS; i++) { Graph g = createRandomPseudoGraph(TEST_GRAPH_SIZE); VertexCoverAlgorithm mvc = createSolver(Graphs.undirectedGraph(g)); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertTrue(isCover(g, vertexCover)); assertEquals(vertexCover.getWeight(), 1.0 * vertexCover.size(), 0); } } /** * Test greedy algorithm for the minimum weighted vertex cover problem. */ @Test public void testFindGreedyWeightedCover() { for (int i = 0; i < TEST_REPEATS; i++) { Graph g = createRandomPseudoGraph(TEST_GRAPH_SIZE); Map vertexWeights = WeightedVertexCoverTest.getRandomVertexWeights(g); VertexCoverAlgorithm mvc = createWeightedSolver(Graphs.undirectedGraph(g), vertexWeights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertTrue(isCover(g, vertexCover)); assertEquals( vertexCover.getWeight(), vertexCover.stream().mapToDouble(vertexWeights::get).sum(), 0); } } } RecursiveExactVCImplTest.java000066400000000000000000000023021402514743400346670ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import java.util.*; public class RecursiveExactVCImplTest extends WeightedVertexCoverExactTest { @Override public VertexCoverAlgorithm createSolver(Graph graph) { return new RecursiveExactVCImpl<>(graph); } @Override public VertexCoverAlgorithm createWeightedSolver( Graph graph, Map vertexWeightMap) { return new RecursiveExactVCImpl<>(graph, vertexWeightMap); } } VertexCoverExactTest.java000066400000000000000000000400161402514743400341250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2003-2021, by Linda Buisman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.vertexcover.VertexCoverTestUtils.isCover; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests exact vertex cover algorithms. * * @author Linda Buisman */ public abstract class VertexCoverExactTest implements VertexCoverTest { // ------- Exact algorithms ------ /** * 4-cycle graph (optimal=2) */ @Test public void test4Cycle() { Graph g1 = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g1, Arrays.asList(0, 1, 2, 3)); g1.addEdge(0, 1); g1.addEdge(1, 2); g1.addEdge(2, 3); g1.addEdge(3, 0); VertexCoverAlgorithm mvc1 = createSolver(g1); VertexCoverAlgorithm.VertexCover vertexCover = mvc1.getVertexCover(); assertTrue(isCover(g1, vertexCover)); assertEquals(vertexCover.getWeight(), 2.0, 0); } /** * Wheel graph W_8 (Optimal=5) */ @Test public void testWheel() { Graph g1 = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g1, Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7)); g1.addEdge(1, 2); g1.addEdge(2, 3); g1.addEdge(3, 4); g1.addEdge(4, 5); g1.addEdge(5, 6); g1.addEdge(6, 7); g1.addEdge(7, 1); g1.addEdge(0, 1); g1.addEdge(0, 2); g1.addEdge(0, 3); g1.addEdge(0, 4); g1.addEdge(0, 5); g1.addEdge(0, 6); g1.addEdge(0, 7); VertexCoverAlgorithm mvc1 = createSolver(g1); VertexCoverAlgorithm.VertexCover vertexCover = mvc1.getVertexCover(); assertTrue(isCover(g1, vertexCover)); assertEquals(vertexCover.getWeight(), 5.0, 0); } /** * Cubic graph with 8 vertices (Optimal=7) */ @Test public void testCubic() { Graph g1 = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g1, Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)); g1.addEdge(0, 1); g1.addEdge(0, 9); g1.addEdge(0, 7); g1.addEdge(1, 2); g1.addEdge(1, 5); g1.addEdge(2, 3); g1.addEdge(2, 4); g1.addEdge(3, 4); g1.addEdge(3, 5); g1.addEdge(4, 11); g1.addEdge(5, 6); g1.addEdge(6, 7); g1.addEdge(6, 8); g1.addEdge(7, 8); g1.addEdge(8, 10); g1.addEdge(9, 10); g1.addEdge(9, 11); g1.addEdge(10, 11); VertexCoverAlgorithm mvc1 = createSolver(g1); VertexCoverAlgorithm.VertexCover vertexCover = mvc1.getVertexCover(); assertTrue(isCover(g1, vertexCover)); assertEquals(vertexCover.getWeight(), 7.0, 0); } /** * Graph with 6 vertices in the shape >-< (Optimal=2) */ @Test public void testWhisker() { Graph g1 = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g1, Arrays.asList(0, 1, 2, 3, 4, 5)); g1.addEdge(0, 2); g1.addEdge(1, 2); g1.addEdge(2, 3); g1.addEdge(3, 4); g1.addEdge(3, 5); VertexCoverAlgorithm mvc1 = createSolver(g1); VertexCoverAlgorithm.VertexCover vertexCover = mvc1.getVertexCover(); assertTrue(isCover(g1, vertexCover)); assertEquals(vertexCover.getWeight(), 2.0, 0); } /** * Random graphs */ @Test public void testExactMinimumCover1() { int[][] edges = { { 0, 5 }, { 0, 6 }, { 0, 8 }, { 0, 13 }, { 0, 18 }, { 0, 24 }, { 0, 26 }, { 0, 32 }, { 0, 40 }, { 1, 8 }, { 1, 20 }, { 1, 36 }, { 1, 47 }, { 1, 50 }, { 2, 18 }, { 2, 49 }, { 2, 56 }, { 3, 12 }, { 3, 20 }, { 3, 55 }, { 4, 16 }, { 4, 20 }, { 4, 25 }, { 4, 34 }, { 4, 36 }, { 5, 9 }, { 5, 22 }, { 5, 29 }, { 5, 32 }, { 5, 39 }, { 5, 40 }, { 5, 45 }, { 5, 54 }, { 6, 11 }, { 6, 34 }, { 7, 19 }, { 7, 26 }, { 7, 29 }, { 7, 35 }, { 8, 12 }, { 8, 31 }, { 8, 39 }, { 8, 59 }, { 9, 22 }, { 9, 42 }, { 9, 51 }, { 9, 54 }, { 9, 57 }, { 11, 15 }, { 11, 50 }, { 12, 15 }, { 12, 30 }, { 12, 31 }, { 12, 40 }, { 12, 45 }, { 12, 49 }, { 13, 14 }, { 13, 16 }, { 13, 30 }, { 13, 37 }, { 13, 48 }, { 14, 40 }, { 14, 49 }, { 14, 58 }, { 15, 22 }, { 15, 32 }, { 15, 57 }, { 16, 42 }, { 16, 49 }, { 16, 52 }, { 16, 56 }, { 16, 58 }, { 17, 19 }, { 17, 29 }, { 17, 32 }, { 17, 36 }, { 18, 25 }, { 18, 31 }, { 18, 39 }, { 19, 31 }, { 20, 21 }, { 20, 25 }, { 20, 44 }, { 21, 45 }, { 21, 59 }, { 22, 34 }, { 22, 52 }, { 22, 59 }, { 23, 24 }, { 23, 54 }, { 24, 57 }, { 25, 50 }, { 26, 27 }, { 26, 38 }, { 26, 45 }, { 26, 54 }, { 26, 55 }, { 27, 42 }, { 28, 55 }, { 29, 30 }, { 29, 45 }, { 32, 42 }, { 33, 44 }, { 33, 45 }, { 33, 50 }, { 33, 53 }, { 34, 36 }, { 34, 42 }, { 34, 46 }, { 35, 51 }, { 35, 59 }, { 36, 43 }, { 36, 46 }, { 36, 48 }, { 36, 53 }, { 37, 50 }, { 38, 40 }, { 38, 47 }, { 38, 58 }, { 40, 59 }, { 41, 57 }, { 43, 51 }, { 43, 54 }, { 44, 48 }, { 44, 58 }, { 46, 47 }, { 47, 55 }, { 48, 56 }, { 50, 53 }, { 51, 57 }, { 52, 58 }, { 55, 57 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 33.0, 0); } @Test public void testExactMinimumCover2() { int[][] edges = { { 0, 10 }, { 0, 20 }, { 0, 37 }, { 0, 58 }, { 1, 2 }, { 1, 10 }, { 1, 27 }, { 1, 56 }, { 2, 49 }, { 2, 53 }, { 3, 20 }, { 3, 53 }, { 4, 15 }, { 5, 6 }, { 5, 8 }, { 6, 11 }, { 6, 25 }, { 6, 56 }, { 7, 26 }, { 10, 25 }, { 10, 29 }, { 11, 17 }, { 13, 34 }, { 13, 45 }, { 13, 57 }, { 15, 27 }, { 16, 45 }, { 17, 39 }, { 18, 41 }, { 18, 48 }, { 20, 57 }, { 21, 49 }, { 21, 59 }, { 22, 35 }, { 22, 45 }, { 23, 32 }, { 24, 32 }, { 24, 34 }, { 25, 27 }, { 25, 46 }, { 25, 59 }, { 27, 37 }, { 28, 53 }, { 31, 45 }, { 33, 51 }, { 38, 39 }, { 39, 40 }, { 39, 44 }, { 44, 45 }, { 48, 54 }, { 48, 55 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 22.0, 0); } @Test public void testExactMinimumCover3() { int[][] edges = { { 1, 5 }, { 1, 37 }, { 2, 48 }, { 4, 48 }, { 7, 56 }, { 15, 18 }, { 20, 58 }, { 40, 50 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 6.0, 0); } @Test public void testExactMinimumCover4() { int[][] edges = { { 1, 55 }, { 4, 7 }, { 6, 13 }, { 11, 30 }, { 11, 40 }, { 16, 46 }, { 17, 24 }, { 24, 31 }, { 29, 32 }, { 40, 52 }, { 45, 49 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 9.0, 0); } @Test public void testExactMinimumCover5() { int[][] edges = { { 0, 47 }, { 0, 48 }, { 0, 58 }, { 1, 17 }, { 1, 25 }, { 1, 36 }, { 1, 55 }, { 2, 20 }, { 2, 46 }, { 3, 4 }, { 3, 17 }, { 4, 44 }, { 4, 54 }, { 5, 27 }, { 6, 13 }, { 6, 25 }, { 6, 31 }, { 6, 38 }, { 6, 48 }, { 6, 56 }, { 7, 10 }, { 7, 14 }, { 7, 31 }, { 7, 45 }, { 8, 13 }, { 8, 51 }, { 9, 23 }, { 10, 45 }, { 11, 22 }, { 11, 37 }, { 11, 41 }, { 12, 21 }, { 13, 54 }, { 14, 24 }, { 14, 52 }, { 15, 19 }, { 15, 56 }, { 17, 43 }, { 19, 24 }, { 19, 42 }, { 19, 53 }, { 20, 55 }, { 21, 41 }, { 21, 55 }, { 22, 59 }, { 23, 29 }, { 25, 43 }, { 25, 50 }, { 26, 31 }, { 27, 43 }, { 27, 54 }, { 28, 35 }, { 28, 41 }, { 30, 36 }, { 30, 42 }, { 30, 44 }, { 30, 51 }, { 30, 59 }, { 31, 41 }, { 32, 53 }, { 32, 55 }, { 33, 36 }, { 33, 56 }, { 35, 54 }, { 37, 44 }, { 38, 55 }, { 40, 41 }, { 41, 42 }, { 41, 43 }, { 41, 53 }, { 43, 45 }, { 44, 52 }, { 45, 46 }, { 45, 50 }, { 45, 53 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 26.0, 0); } @Test public void testExactMinimumCover6() { int[][] edges = { { 2, 21 }, { 2, 41 }, { 3, 47 }, { 4, 48 }, { 5, 36 }, { 6, 57 }, { 12, 46 }, { 13, 41 }, { 23, 26 }, { 25, 45 }, { 26, 28 }, { 26, 31 }, { 26, 52 }, { 29, 49 }, { 30, 55 }, { 33, 36 }, { 35, 55 }, { 38, 45 }, { 51, 59 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 12.0, 0); } @Test public void testExactMinimumCover7() { int[][] edges = { { 20, 51 }, { 21, 28 }, { 23, 55 }, { 23, 59 }, { 25, 59 }, { 33, 46 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 5.0, 0); } @Test public void testExactMinimumCover8() { int[][] edges = { { 0, 16 }, { 0, 52 }, { 0, 58 }, { 1, 8 }, { 1, 27 }, { 1, 38 }, { 1, 49 }, { 1, 56 }, { 1, 57 }, { 2, 3 }, { 2, 20 }, { 2, 23 }, { 2, 28 }, { 2, 38 }, { 3, 19 }, { 3, 20 }, { 3, 28 }, { 3, 37 }, { 3, 39 }, { 3, 59 }, { 4, 26 }, { 4, 31 }, { 4, 41 }, { 5, 9 }, { 5, 33 }, { 5, 42 }, { 6, 26 }, { 6, 37 }, { 6, 55 }, { 7, 27 }, { 7, 29 }, { 7, 59 }, { 8, 32 }, { 8, 41 }, { 8, 43 }, { 9, 28 }, { 9, 35 }, { 9, 42 }, { 10, 14 }, { 10, 17 }, { 10, 38 }, { 11, 33 }, { 11, 57 }, { 12, 27 }, { 12, 31 }, { 12, 34 }, { 12, 41 }, { 12, 50 }, { 12, 52 }, { 13, 16 }, { 13, 30 }, { 13, 36 }, { 13, 44 }, { 14, 28 }, { 14, 51 }, { 15, 26 }, { 15, 43 }, { 15, 50 }, { 15, 53 }, { 16, 19 }, { 16, 27 }, { 16, 48 }, { 16, 50 }, { 16, 52 }, { 17, 26 }, { 17, 55 }, { 18, 45 }, { 18, 49 }, { 18, 57 }, { 19, 22 }, { 19, 26 }, { 19, 53 }, { 20, 26 }, { 20, 58 }, { 21, 28 }, { 21, 40 }, { 21, 46 }, { 21, 57 }, { 22, 33 }, { 22, 52 }, { 22, 56 }, { 22, 58 }, { 23, 28 }, { 23, 56 }, { 24, 26 }, { 24, 27 }, { 24, 29 }, { 24, 31 }, { 24, 34 }, { 24, 43 }, { 24, 47 }, { 24, 49 }, { 24, 53 }, { 25, 27 }, { 25, 56 }, { 25, 59 }, { 26, 32 }, { 26, 47 }, { 26, 54 }, { 26, 59 }, { 27, 47 }, { 28, 57 }, { 29, 33 }, { 29, 37 }, { 30, 40 }, { 31, 33 }, { 31, 38 }, { 31, 41 }, { 31, 48 }, { 31, 49 }, { 31, 58 }, { 32, 33 }, { 32, 37 }, { 33, 41 }, { 34, 35 }, { 35, 40 }, { 37, 40 }, { 37, 51 }, { 37, 52 }, { 38, 50 }, { 38, 52 }, { 39, 45 }, { 39, 50 }, { 39, 52 }, { 40, 59 }, { 41, 49 }, { 42, 51 }, { 42, 54 }, { 43, 51 }, { 50, 52 }, { 54, 59 }, { 58, 59 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 33.0, 0); } @Test public void testExactMinimumCover9() { int[][] edges = { { 0, 16 }, { 0, 19 }, { 0, 32 }, { 1, 4 }, { 1, 16 }, { 1, 18 }, { 1, 26 }, { 2, 47 }, { 2, 55 }, { 3, 5 }, { 3, 9 }, { 3, 28 }, { 3, 31 }, { 4, 17 }, { 4, 53 }, { 5, 55 }, { 6, 48 }, { 7, 39 }, { 7, 53 }, { 8, 32 }, { 8, 37 }, { 8, 57 }, { 10, 18 }, { 10, 26 }, { 10, 29 }, { 10, 39 }, { 10, 49 }, { 10, 54 }, { 11, 13 }, { 11, 45 }, { 12, 18 }, { 12, 32 }, { 12, 34 }, { 12, 37 }, { 12, 53 }, { 13, 42 }, { 13, 43 }, { 14, 26 }, { 15, 38 }, { 16, 52 }, { 16, 54 }, { 18, 27 }, { 18, 39 }, { 18, 46 }, { 18, 59 }, { 19, 41 }, { 19, 45 }, { 20, 37 }, { 20, 56 }, { 21, 53 }, { 23, 47 }, { 23, 55 }, { 24, 25 }, { 24, 32 }, { 27, 48 }, { 27, 51 }, { 27, 52 }, { 28, 34 }, { 29, 36 }, { 30, 52 }, { 31, 48 }, { 31, 49 }, { 32, 50 }, { 35, 37 }, { 36, 37 }, { 37, 59 }, { 39, 52 }, { 40, 58 }, { 42, 45 }, { 42, 59 }, { 43, 48 }, { 43, 57 }, { 48, 52 }, { 49, 52 }, { 52, 57 }, { 54, 56 }, { 54, 59 }, { 57, 59 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 27.0, 0); } @Test public void testExactMinimumCover10() { int[][] edges = { { 1, 21 }, { 2, 6 }, { 2, 43 }, { 2, 56 }, { 4, 7 }, { 4, 43 }, { 6, 7 }, { 6, 58 }, { 7, 14 }, { 7, 23 }, { 7, 40 }, { 7, 57 }, { 9, 49 }, { 10, 39 }, { 18, 25 }, { 18, 26 }, { 18, 34 }, { 20, 40 }, { 22, 32 }, { 23, 32 }, { 23, 34 }, { 25, 39 }, { 26, 34 }, { 26, 41 }, { 27, 49 }, { 29, 42 }, { 29, 46 }, { 30, 55 }, { 33, 47 }, { 34, 38 }, { 35, 43 }, { 36, 39 }, { 39, 59 }, { 40, 57 }, { 46, 52 }, { 49, 51 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); VertexCoverAlgorithm mvc = createSolver(graph); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 16.0, 0); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/VertexCoverTest.java000066400000000000000000000016261402514743400332230ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Linda Buisman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; /** * Tests the vertex cover algorithms. * * @author Linda Buisman */ public interface VertexCoverTest { VertexCoverAlgorithm createSolver(Graph graph); } VertexCoverTestUtils.java000066400000000000000000000054141402514743400341640ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2003-2021, by Linda Buisman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import java.util.*; /** * Base class for vertex cover tests * * @author Linda Buisman */ public class VertexCoverTestUtils { public VertexCoverTestUtils() { } // ~ Static fields/initializers --------------------------------------------- public final static int TEST_GRAPH_SIZE = 200; public final static int TEST_REPEATS = 20; public final static Random RANDOM = new Random(0); // ------- Helper methods ------ /** * Checks if the specified vertex set covers every edge of the graph. Uses the definition of * Vertex Cover - removes every edge that is incident on a vertex in vertexSet. If no edges are * left, vertexSet is a vertex cover for the specified graph. * * @param vertexCover the vertex cover to be tested for covering the graph. * @param g the graph to be covered. * * @return returns true if the provided vertex cover is a valid cover in the given graph */ static boolean isCover( Graph g, VertexCoverAlgorithm.VertexCover vertexCover) { Set uncoveredEdges = new HashSet<>(g.edgeSet()); for (Integer v : vertexCover) uncoveredEdges.removeAll(g.edgesOf(v)); return uncoveredEdges.isEmpty(); } /** * Create a random PSEUDO graph of TEST_GRAPH_SIZE nodes. * * @return random pseudo graph with TEST_GRAPH_SIZE vertices and a random number of edges drawn * from the domain [1, TEST_GRAPH_SIZE/2] */ static Graph createRandomPseudoGraph(int vertices) { Pseudograph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphGenerator graphGenerator = new GnmRandomGraphGenerator<>(vertices, RANDOM.nextInt(vertices / 2) + 1); graphGenerator.generateGraph(g); return g; } } VertexCoverTwoApproxTest.java000066400000000000000000000051261402514743400350270ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2003-2021, by Linda Buisman and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import static org.jgrapht.alg.vertexcover.VertexCoverTestUtils.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests 2-approximation vertex cover algorithms. * * @author Linda Buisman */ public abstract class VertexCoverTwoApproxTest implements VertexCoverTest { // ------- Approximation algorithms ------ /** * Test 2-approximation algorithms for the minimum vertex cover problem. */ @Test public void testFind2ApproximationCover() { for (int i = 0; i < TEST_REPEATS; i++) { Graph g = createRandomPseudoGraph(TEST_GRAPH_SIZE); VertexCoverAlgorithm mvc = createSolver(Graphs.undirectedGraph(g)); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertTrue(isCover(g, vertexCover)); assertEquals(vertexCover.getWeight(), 1.0 * vertexCover.size(), 0); } } /** * Test whether the 2 approximations are indeed within 2 times the optimum value */ @Test public void testFind2ApproximationCover2() { for (int i = 0; i < TEST_REPEATS; i++) { Graph g = createRandomPseudoGraph(70); VertexCoverAlgorithm.VertexCover optimalCover = new RecursiveExactVCImpl<>(g).getVertexCover(); VertexCoverAlgorithm mvc = createSolver(Graphs.undirectedGraph(g)); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertTrue(isCover(g, vertexCover)); assertEquals(vertexCover.getWeight(), 1.0 * vertexCover.size(), 0); assertTrue(vertexCover.getWeight() <= optimalCover.getWeight() * 2); // Verify // 2-approximation } } } WeightedVertexCoverExactTest.java000066400000000000000000000474711402514743400356220ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Tests the weighted exact vertex cover algorithms. * * @author Joris Kinable */ public abstract class WeightedVertexCoverExactTest extends VertexCoverExactTest implements WeightedVertexCoverTest { // ------- Exact algorithms ------ @Test @Category(SlowTests.class) public void testExactMinimumCover1() { int[] weightArray = { 18, 16, 13, 14, 12, 0, 20, 11, 10, 10, 10, 6, 6, 12, 15, 6, 24, 2, 6, 6, 12, 7, 6, 11, 23, 3, 5, 23, 4, 24, 22, 17, 24, 7, 15, 14, 23, 12, 3, 18, 3, 20, 3, 5, 19, 25, 8, 13, 22, 0, 20, 7, 21, 9, 0, 6, 0, 18, 16, 1 }; int[][] edges = { { 1, 21 }, { 2, 4 }, { 2, 13 }, { 2, 44 }, { 2, 45 }, { 3, 24 }, { 3, 31 }, { 3, 35 }, { 3, 42 }, { 5, 14 }, { 5, 36 }, { 6, 9 }, { 6, 13 }, { 6, 25 }, { 6, 46 }, { 7, 47 }, { 7, 58 }, { 8, 12 }, { 8, 33 }, { 9, 21 }, { 9, 30 }, { 10, 59 }, { 12, 15 }, { 12, 43 }, { 12, 57 }, { 13, 32 }, { 13, 33 }, { 13, 59 }, { 14, 26 }, { 14, 48 }, { 16, 57 }, { 21, 31 }, { 22, 57 }, { 23, 44 }, { 23, 56 }, { 24, 49 }, { 25, 34 }, { 25, 46 }, { 26, 33 }, { 26, 40 }, { 26, 59 }, { 27, 59 }, { 28, 33 }, { 30, 51 }, { 36, 48 }, { 36, 54 }, { 37, 38 }, { 38, 43 }, { 40, 41 }, { 41, 58 }, { 44, 50 }, { 45, 49 }, { 47, 49 }, { 48, 56 }, { 49, 55 }, { 52, 54 }, { 54, 55 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 185.0, 0); } @Test @Category(SlowTests.class) public void testExactMinimumCover2() { int[] weightArray = { 13, 11, 3, 5, 16, 0, 16, 16, 14, 25, 15, 23, 4, 12, 23, 20, 19, 12, 15, 18, 25, 15, 9, 2, 20, 6, 21, 17, 16, 21, 20, 9, 0, 23, 7, 24, 17, 15, 19, 12, 4, 13, 1, 19, 7, 22, 20, 6, 13, 2, 5, 19, 4, 0, 11, 16, 13, 1, 15, 25 }; int[][] edges = { { 0, 20 }, { 0, 34 }, { 0, 46 }, { 0, 48 }, { 0, 58 }, { 1, 2 }, { 1, 5 }, { 1, 18 }, { 2, 7 }, { 2, 22 }, { 2, 41 }, { 2, 51 }, { 2, 55 }, { 2, 56 }, { 3, 7 }, { 3, 41 }, { 3, 48 }, { 3, 57 }, { 4, 36 }, { 4, 44 }, { 4, 54 }, { 5, 29 }, { 5, 30 }, { 5, 47 }, { 6, 55 }, { 6, 59 }, { 7, 19 }, { 7, 28 }, { 8, 18 }, { 8, 46 }, { 9, 36 }, { 10, 12 }, { 10, 13 }, { 10, 21 }, { 10, 39 }, { 11, 53 }, { 12, 20 }, { 12, 51 }, { 13, 25 }, { 13, 57 }, { 13, 58 }, { 14, 32 }, { 14, 34 }, { 14, 44 }, { 14, 55 }, { 15, 19 }, { 15, 30 }, { 16, 28 }, { 16, 55 }, { 17, 27 }, { 17, 29 }, { 17, 38 }, { 17, 41 }, { 19, 30 }, { 19, 51 }, { 19, 59 }, { 20, 55 }, { 21, 33 }, { 22, 25 }, { 22, 30 }, { 22, 32 }, { 22, 40 }, { 24, 43 }, { 25, 26 }, { 25, 32 }, { 26, 39 }, { 26, 59 }, { 27, 38 }, { 28, 35 }, { 28, 51 }, { 29, 31 }, { 29, 34 }, { 29, 53 }, { 31, 36 }, { 32, 34 }, { 32, 49 }, { 34, 38 }, { 35, 38 }, { 35, 40 }, { 35, 50 }, { 36, 38 }, { 36, 45 }, { 36, 49 }, { 36, 56 }, { 37, 58 }, { 38, 40 }, { 38, 59 }, { 39, 44 }, { 39, 45 }, { 39, 59 }, { 41, 44 }, { 42, 45 }, { 43, 46 }, { 43, 49 }, { 44, 46 }, { 46, 51 }, { 47, 56 }, { 48, 56 }, { 50, 57 }, { 54, 59 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 339.0, 0); } @Test @Category(SlowTests.class) public void testExactMinimumCover3() { int[] weightArray = { 20, 15, 16, 0, 20, 7, 1, 25, 0, 23, 6, 7, 8, 11, 3, 18, 25, 12, 20, 18, 24, 10, 9, 25, 0, 9, 22, 18, 23, 17, 23, 3, 12, 8, 9, 21, 2, 0, 20, 0, 14, 6, 13, 16, 17, 25, 5, 10, 20, 4, 16, 0, 5, 21, 9, 7, 12, 15, 5, 25 }; int[][] edges = { { 0, 7 }, { 0, 45 }, { 0, 54 }, { 2, 39 }, { 3, 10 }, { 3, 20 }, { 4, 20 }, { 4, 37 }, { 5, 29 }, { 6, 12 }, { 7, 17 }, { 7, 29 }, { 8, 29 }, { 8, 55 }, { 10, 25 }, { 11, 33 }, { 12, 51 }, { 12, 58 }, { 13, 50 }, { 15, 30 }, { 16, 17 }, { 16, 24 }, { 16, 32 }, { 17, 55 }, { 18, 31 }, { 18, 45 }, { 18, 49 }, { 19, 41 }, { 20, 48 }, { 21, 27 }, { 21, 56 }, { 23, 30 }, { 25, 28 }, { 26, 45 }, { 30, 40 }, { 30, 45 }, { 30, 52 }, { 31, 43 }, { 31, 50 }, { 32, 48 }, { 33, 55 }, { 36, 42 }, { 36, 47 }, { 37, 39 }, { 38, 42 }, { 38, 49 }, { 41, 44 }, { 49, 58 }, { 51, 55 }, { 51, 58 }, { 53, 57 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 220.0, 0); } @Test @Category(SlowTests.class) public void testExactMinimumCover4() { int[] weightArray = { 0, 20, 10, 0, 0, 15, 18, 20, 12, 18, 1, 13, 1, 25, 14, 6, 10, 16, 18, 10, 12, 24, 22, 23, 3, 13, 9, 21, 5, 17, 22, 20, 13, 12, 22, 4, 5, 18, 0, 14, 25, 6, 1, 18, 22, 15, 4, 6, 13, 10, 2, 21, 24, 16, 6, 6, 23, 9, 9, 2 }; int[][] edges = { { 0, 19 }, { 1, 6 }, { 1, 16 }, { 2, 47 }, { 2, 58 }, { 3, 49 }, { 3, 53 }, { 4, 57 }, { 5, 19 }, { 5, 28 }, { 6, 16 }, { 6, 26 }, { 6, 35 }, { 7, 10 }, { 7, 17 }, { 7, 25 }, { 7, 51 }, { 7, 59 }, { 8, 51 }, { 10, 27 }, { 10, 57 }, { 11, 20 }, { 11, 23 }, { 12, 43 }, { 12, 50 }, { 13, 55 }, { 14, 28 }, { 14, 31 }, { 14, 48 }, { 15, 21 }, { 15, 29 }, { 15, 57 }, { 17, 44 }, { 18, 20 }, { 19, 45 }, { 20, 22 }, { 20, 26 }, { 20, 27 }, { 21, 27 }, { 21, 28 }, { 21, 52 }, { 22, 23 }, { 22, 27 }, { 22, 48 }, { 23, 33 }, { 27, 41 }, { 28, 51 }, { 30, 42 }, { 30, 52 }, { 30, 57 }, { 35, 50 }, { 36, 57 }, { 37, 50 }, { 38, 43 }, { 41, 47 }, { 46, 52 }, { 47, 57 }, { 55, 59 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 238.0, 0); } @Test @Category(SlowTests.class) public void testExactMinimumCover5() { int[] weightArray = { 1, 10, 13, 17, 0, 25, 4, 15, 8, 14, 20, 23, 10, 2, 21, 10, 4, 18, 4, 20, 25, 5, 20, 19, 11, 15, 18, 8, 19, 3, 3, 24, 3, 8, 6, 12, 8, 12, 2, 8, 2, 1, 5, 23, 11, 18, 22, 6, 19, 0, 19, 11, 4, 24, 24, 5, 11, 16, 24, 10 }; int[][] edges = { { 0, 30 }, { 3, 4 }, { 6, 15 }, { 7, 10 }, { 9, 56 }, { 13, 42 }, { 19, 49 }, { 22, 44 }, { 47, 58 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 50.0, 0); } @Test @Category(SlowTests.class) public void testExactMinimumCover6() { int[] weightArray = { 11, 11, 17, 25, 16, 9, 11, 5, 5, 18, 21, 3, 15, 12, 7, 14, 14, 10, 19, 12, 21, 17, 8, 0, 1, 3, 21, 8, 23, 0, 23, 7, 2, 1, 24, 18, 4, 25, 22, 6, 3, 10, 7, 4, 0, 24, 5, 16, 5, 8, 19, 11, 5, 14, 15, 19, 18, 3, 5, 3 }; int[][] edges = { { 0, 12 }, { 1, 14 }, { 1, 19 }, { 1, 24 }, { 1, 28 }, { 1, 49 }, { 1, 58 }, { 2, 46 }, { 3, 6 }, { 3, 27 }, { 4, 19 }, { 4, 29 }, { 4, 33 }, { 5, 48 }, { 5, 49 }, { 5, 53 }, { 6, 19 }, { 6, 40 }, { 7, 12 }, { 7, 21 }, { 7, 30 }, { 9, 10 }, { 9, 24 }, { 9, 26 }, { 10, 11 }, { 10, 24 }, { 10, 57 }, { 11, 29 }, { 13, 27 }, { 14, 44 }, { 15, 44 }, { 15, 51 }, { 17, 50 }, { 17, 56 }, { 18, 22 }, { 18, 31 }, { 18, 32 }, { 18, 44 }, { 19, 26 }, { 19, 32 }, { 19, 34 }, { 19, 59 }, { 20, 30 }, { 20, 31 }, { 21, 48 }, { 21, 51 }, { 22, 59 }, { 23, 41 }, { 24, 38 }, { 24, 45 }, { 25, 41 }, { 25, 42 }, { 26, 28 }, { 26, 35 }, { 27, 35 }, { 28, 32 }, { 28, 50 }, { 29, 35 }, { 29, 36 }, { 30, 33 }, { 30, 35 }, { 30, 50 }, { 31, 34 }, { 31, 38 }, { 31, 43 }, { 32, 36 }, { 33, 43 }, { 34, 36 }, { 34, 55 }, { 34, 57 }, { 35, 42 }, { 35, 45 }, { 35, 56 }, { 36, 58 }, { 37, 47 }, { 38, 45 }, { 38, 49 }, { 38, 58 }, { 39, 50 }, { 40, 49 }, { 42, 58 }, { 43, 58 }, { 50, 51 }, { 56, 59 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 286.0, 0); } @Test @Category(SlowTests.class) public void testExactMinimumCover7() { int[] weightArray = { 24, 13, 20, 22, 17, 18, 14, 3, 10, 10, 3, 13, 25, 3, 24, 7, 12, 24, 20, 11, 11, 14, 10, 7, 16, 0, 20, 16, 25, 24, 4, 3, 23, 14, 5, 7, 21, 17, 25, 24, 9, 22, 13, 19, 20, 21, 21, 24, 22, 20, 5, 12, 18, 14, 2, 4, 9, 24, 1, 1 }; int[][] edges = { { 0, 7 }, { 0, 8 }, { 0, 16 }, { 0, 26 }, { 0, 27 }, { 0, 32 }, { 0, 46 }, { 1, 5 }, { 1, 10 }, { 1, 13 }, { 1, 34 }, { 1, 43 }, { 1, 48 }, { 2, 15 }, { 2, 36 }, { 2, 49 }, { 3, 12 }, { 3, 33 }, { 3, 58 }, { 4, 7 }, { 4, 39 }, { 4, 40 }, { 4, 53 }, { 5, 35 }, { 5, 49 }, { 6, 21 }, { 7, 13 }, { 7, 24 }, { 7, 31 }, { 8, 19 }, { 8, 35 }, { 8, 48 }, { 9, 23 }, { 10, 12 }, { 10, 45 }, { 11, 20 }, { 11, 58 }, { 12, 21 }, { 12, 32 }, { 12, 40 }, { 12, 47 }, { 12, 53 }, { 13, 19 }, { 13, 24 }, { 13, 35 }, { 14, 21 }, { 14, 56 }, { 15, 27 }, { 17, 18 }, { 17, 44 }, { 18, 30 }, { 18, 40 }, { 18, 48 }, { 20, 22 }, { 20, 31 }, { 20, 34 }, { 20, 48 }, { 21, 38 }, { 22, 23 }, { 22, 25 }, { 22, 35 }, { 22, 59 }, { 23, 26 }, { 25, 36 }, { 25, 49 }, { 25, 56 }, { 26, 27 }, { 26, 41 }, { 26, 51 }, { 27, 29 }, { 27, 36 }, { 27, 38 }, { 27, 46 }, { 27, 50 }, { 27, 52 }, { 27, 58 }, { 27, 59 }, { 28, 29 }, { 28, 40 }, { 28, 50 }, { 29, 31 }, { 29, 56 }, { 30, 46 }, { 30, 55 }, { 31, 35 }, { 31, 55 }, { 32, 55 }, { 32, 57 }, { 32, 58 }, { 35, 40 }, { 35, 48 }, { 37, 39 }, { 37, 49 }, { 37, 51 }, { 38, 45 }, { 38, 55 }, { 40, 44 }, { 41, 46 }, { 41, 48 }, { 41, 59 }, { 42, 51 }, { 43, 56 }, { 47, 50 }, { 48, 52 }, { 49, 57 }, { 50, 51 }, { 50, 58 }, { 53, 58 }, { 54, 58 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 401.0, 0); } @Test @Category(SlowTests.class) public void testExactMinimumCover8() { int[] weightArray = { 19, 24, 0, 19, 17, 12, 15, 4, 22, 23, 6, 21, 19, 20, 3, 18, 22, 19, 2, 4, 19, 8, 23, 15, 21, 12, 4, 1, 21, 23, 11, 8, 18, 6, 11, 14, 0, 4, 11, 11, 22, 2, 1, 11, 0, 21, 20, 12, 13, 0, 16, 15, 24, 12, 15, 4, 24, 3, 20, 8 }; int[][] edges = { { 0, 6 }, { 0, 13 }, { 0, 47 }, { 0, 55 }, { 1, 5 }, { 1, 55 }, { 1, 57 }, { 2, 16 }, { 2, 24 }, { 3, 8 }, { 3, 26 }, { 3, 29 }, { 3, 53 }, { 3, 58 }, { 4, 16 }, { 5, 16 }, { 5, 45 }, { 5, 53 }, { 6, 7 }, { 6, 17 }, { 6, 27 }, { 6, 33 }, { 6, 34 }, { 7, 16 }, { 7, 17 }, { 7, 22 }, { 7, 25 }, { 7, 54 }, { 8, 29 }, { 8, 44 }, { 8, 59 }, { 9, 35 }, { 9, 51 }, { 9, 52 }, { 10, 11 }, { 10, 40 }, { 12, 45 }, { 13, 16 }, { 13, 31 }, { 13, 44 }, { 13, 48 }, { 13, 49 }, { 14, 35 }, { 14, 48 }, { 15, 41 }, { 15, 56 }, { 16, 23 }, { 17, 18 }, { 17, 33 }, { 18, 27 }, { 18, 49 }, { 18, 59 }, { 19, 24 }, { 19, 45 }, { 20, 29 }, { 20, 36 }, { 21, 58 }, { 22, 25 }, { 22, 49 }, { 22, 56 }, { 23, 26 }, { 23, 58 }, { 25, 36 }, { 25, 52 }, { 26, 34 }, { 26, 39 }, { 26, 40 }, { 26, 52 }, { 27, 31 }, { 27, 40 }, { 27, 44 }, { 27, 56 }, { 28, 44 }, { 29, 39 }, { 29, 40 }, { 29, 41 }, { 29, 42 }, { 30, 32 }, { 30, 49 }, { 30, 58 }, { 31, 32 }, { 32, 35 }, { 32, 39 }, { 32, 59 }, { 33, 47 }, { 33, 48 }, { 33, 54 }, { 34, 36 }, { 34, 47 }, { 35, 59 }, { 36, 37 }, { 36, 38 }, { 37, 45 }, { 37, 55 }, { 38, 45 }, { 38, 48 }, { 39, 40 }, { 39, 42 }, { 40, 49 }, { 40, 57 }, { 41, 42 }, { 41, 50 }, { 43, 53 }, { 43, 58 }, { 44, 46 }, { 45, 47 }, { 45, 48 }, { 48, 56 }, { 50, 56 }, { 51, 52 }, { 51, 53 }, { 51, 57 }, { 51, 58 }, { 53, 59 }, { 54, 55 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 336.0, 0); } @Test @Category(SlowTests.class) public void testExactMinimumCover9() { int[] weightArray = { 19, 0, 13, 1, 2, 18, 3, 17, 5, 13, 1, 17, 20, 7, 18, 21, 9, 13, 11, 23, 4, 8, 14, 22, 13, 10, 4, 17, 0, 8, 24, 6, 3, 3, 8, 25, 20, 4, 19, 19, 4, 11, 3, 2, 9, 18, 10, 23, 15, 2, 22, 14, 15, 3, 2, 15, 19, 5, 2, 11 }; int[][] edges = { { 0, 11 }, { 0, 28 }, { 0, 41 }, { 1, 5 }, { 1, 8 }, { 1, 13 }, { 1, 36 }, { 2, 18 }, { 2, 35 }, { 2, 54 }, { 2, 56 }, { 3, 8 }, { 3, 30 }, { 3, 41 }, { 3, 59 }, { 4, 58 }, { 5, 32 }, { 6, 14 }, { 6, 31 }, { 6, 41 }, { 6, 46 }, { 7, 47 }, { 9, 10 }, { 9, 26 }, { 9, 28 }, { 9, 50 }, { 10, 11 }, { 10, 28 }, { 10, 47 }, { 10, 56 }, { 11, 27 }, { 12, 55 }, { 13, 20 }, { 13, 45 }, { 13, 59 }, { 14, 37 }, { 16, 28 }, { 16, 40 }, { 17, 20 }, { 17, 39 }, { 17, 57 }, { 18, 34 }, { 18, 38 }, { 19, 53 }, { 19, 58 }, { 20, 24 }, { 20, 35 }, { 20, 41 }, { 20, 45 }, { 20, 54 }, { 21, 23 }, { 21, 27 }, { 22, 37 }, { 23, 37 }, { 23, 45 }, { 23, 47 }, { 25, 27 }, { 25, 29 }, { 25, 30 }, { 26, 31 }, { 26, 50 }, { 27, 29 }, { 27, 48 }, { 27, 50 }, { 29, 48 }, { 30, 42 }, { 30, 58 }, { 31, 49 }, { 32, 38 }, { 33, 45 }, { 33, 54 }, { 34, 40 }, { 34, 46 }, { 34, 59 }, { 35, 54 }, { 35, 57 }, { 36, 53 }, { 38, 41 }, { 40, 43 }, { 41, 54 }, { 42, 59 }, { 44, 49 }, { 47, 54 }, { 50, 56 }, { 51, 57 }, { 52, 58 }, { 53, 55 }, { 53, 56 }, { 55, 57 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 234.0, 0); } @Test @Category(SlowTests.class) public void testExactMinimumCover10() { int[] weightArray = { 0, 12, 13, 7, 23, 21, 8, 20, 12, 21, 23, 1, 16, 13, 2, 9, 18, 24, 18, 13, 0, 13, 4, 12, 16, 23, 5, 13, 15, 14, 15, 18, 23, 17, 23, 9, 12, 0, 16, 21, 7, 13, 9, 21, 16, 12, 22, 5, 16, 6, 5, 7, 8, 6, 21, 6, 13, 22, 4, 25 }; int[][] edges = { { 0, 18 }, { 0, 54 }, { 1, 18 }, { 4, 26 }, { 5, 7 }, { 5, 15 }, { 7, 20 }, { 8, 54 }, { 10, 28 }, { 10, 34 }, { 11, 14 }, { 11, 24 }, { 13, 19 }, { 14, 59 }, { 15, 19 }, { 16, 35 }, { 17, 39 }, { 19, 37 }, { 19, 38 }, { 22, 37 }, { 22, 42 }, { 22, 56 }, { 23, 33 }, { 23, 49 }, { 30, 57 }, { 31, 33 }, { 33, 47 }, { 34, 36 }, { 34, 46 }, { 34, 55 }, { 35, 52 }, { 37, 44 }, { 37, 45 }, { 37, 52 }, { 39, 45 }, { 48, 57 } }; Graph graph = new SimpleGraph<>(DefaultEdge.class); for (int[] edge : edges) Graphs.addEdgeWithVertices(graph, edge[0], edge[1]); HashMap weights = new HashMap<>(); for (int i = 0; i < weightArray.length; i++) weights.put(i, (double) weightArray[i]); VertexCoverAlgorithm mvc = createWeightedSolver(graph, weights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertEquals(vertexCover.getWeight(), 183.0, 0); } } WeightedVertexCoverTest.java000066400000000000000000000025561402514743400346300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import java.util.*; /** * Tests the weighted vertex cover algorithms * * @author Joris Kinable */ public interface WeightedVertexCoverTest { VertexCoverAlgorithm createWeightedSolver( Graph graph, Map vertexWeightMap); // ------- Helper methods ------ static Map getRandomVertexWeights(Graph graph) { Map vertexWeights = new HashMap<>(); for (Integer v : graph.vertexSet()) vertexWeights.put(v, 1.0 * VertexCoverTestUtils.RANDOM.nextInt(25)); return vertexWeights; } } WeightedVertexCoverTwoApproxTest.java000066400000000000000000000041701402514743400365060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/alg/vertexcover/* * (C) Copyright 2018-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.alg.vertexcover; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.jgrapht.alg.vertexcover.VertexCoverTestUtils.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests the weighted 2-approx vertex cover algorithms * * @author Joris Kinable */ public abstract class WeightedVertexCoverTwoApproxTest extends VertexCoverTwoApproxTest implements WeightedVertexCoverTest { // ------- Approximation algorithms ------ /** * Test 2-approximation algorithm for the minimum vertex cover problem. TODO: verify whether the * objective indeed is smaller than 2 times the optimum solution. */ @Test public void testFind2ApproximationWeightedCover() { for (int i = 0; i < TEST_REPEATS; i++) { Graph g = createRandomPseudoGraph(TEST_GRAPH_SIZE); Map vertexWeights = WeightedVertexCoverTest.getRandomVertexWeights(g); VertexCoverAlgorithm mvc = createWeightedSolver(Graphs.undirectedGraph(g), vertexWeights); VertexCoverAlgorithm.VertexCover vertexCover = mvc.getVertexCover(); assertTrue(isCover(g, vertexCover)); assertEquals( vertexCover.getWeight(), vertexCover.stream().mapToDouble(vertexWeights::get).sum(), 0); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/000077500000000000000000000000001402514743400257125ustar00rootroot00000000000000BarabasiAlbertForestGeneratorTest.java000066400000000000000000000125771402514743400352420ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Tests for {@link BarabasiAlbertForestGenerator}. * * @author Alexandru Valeanu */ public class BarabasiAlbertForestGeneratorTest { @Test public void testBadParameters() { try { new BarabasiAlbertForestGenerator<>(0, 10, 100); fail("Bad parameter"); } catch (IllegalArgumentException ignored) { } try { new BarabasiAlbertForestGenerator<>(-1, 10, 100); fail("Bad parameter"); } catch (IllegalArgumentException ignored) { } try { new BarabasiAlbertForestGenerator<>(10, 9, 100); fail("Bad parameter"); } catch (IllegalArgumentException ignored) { } } @Test public void testUndirected() { final long seed = 5; GraphGenerator gen = new BarabasiAlbertForestGenerator<>(5, 20, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(20, g.vertexSet().size()); assertEquals(5, new ConnectivityInspector<>(g).connectedSets().size()); } @Test public void testNoAdditionalNodes() { GraphGenerator gen = new BarabasiAlbertForestGenerator<>(20, 20); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(20, g.vertexSet().size()); assertEquals(20, new ConnectivityInspector<>(g).connectedSets().size()); } @Test public void testUndirectedWithOneInitialNode() { final long seed = 7; GraphGenerator gen = new BarabasiAlbertForestGenerator<>(1, 20, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(20, g.vertexSet().size()); assertEquals(1, new ConnectivityInspector<>(g).connectedSets().size()); } @Test(expected = IllegalArgumentException.class) public void testDirected() { final long seed = 5; GraphGenerator gen = new BarabasiAlbertForestGenerator<>(2, 10, seed); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(10, g.vertexSet().size()); } @Test(expected = IllegalArgumentException.class) public void testDirectedWithOneInitialNode() { final long seed = 13; GraphGenerator gen = new BarabasiAlbertForestGenerator<>(2, 20, seed); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(20, g.vertexSet().size()); } @Test(expected = IllegalArgumentException.class) public void testUndirectedWithGraphWhichAlreadyHasSomeVertices() { final long seed = 5; GraphGenerator gen = new BarabasiAlbertForestGenerator<>(3, 10, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); g.addVertex(1000); gen.generateGraph(g); assertEquals(10, g.vertexSet().size()); } @Test public void testRandomTrees() { Random random = new Random(0x88); final int NUM_TESTS = 10_000; for (int test = 0; test < NUM_TESTS; test++) { final int N = 10 + random.nextInt(100); final int T = 1 + random.nextInt(N); GraphGenerator gen = new BarabasiAlbertForestGenerator<>(T, N); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(N, g.vertexSet().size()); assertEquals(T, new ConnectivityInspector<>(g).connectedSets().size()); } } } BarabasiAlbertGraphGeneratorTest.java000066400000000000000000000077161402514743400350400ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Tests for {@link BarabasiAlbertGraphGenerator}. * * @author Dimitrios Michail */ public class BarabasiAlbertGraphGeneratorTest { @Test public void testBadParameters() { try { new BarabasiAlbertGraphGenerator<>(0, 10, 100); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new BarabasiAlbertGraphGenerator<>(2, 0, 100); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new BarabasiAlbertGraphGenerator<>(2, 3, 100); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new BarabasiAlbertGraphGenerator<>(3, 2, 2); fail("Bad parameter"); } catch (IllegalArgumentException e) { } } @Test public void testUndirected() { final long seed = 5; GraphGenerator gen = new BarabasiAlbertGraphGenerator<>(3, 2, 10, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(10, g.vertexSet().size()); } @Test public void testUndirectedWithOneInitialNode() { final long seed = 7; GraphGenerator gen = new BarabasiAlbertGraphGenerator<>(1, 1, 20, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(20, g.vertexSet().size()); } @Test public void testDirected() { final long seed = 5; GraphGenerator gen = new BarabasiAlbertGraphGenerator<>(3, 2, 10, seed); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(10, g.vertexSet().size()); } @Test public void testDirectedWithOneInitialNode() { final long seed = 13; GraphGenerator gen = new BarabasiAlbertGraphGenerator<>(1, 1, 20, seed); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(20, g.vertexSet().size()); } @Test public void testUndirectedWithGraphWhichAlreadyHasSomeVertices() { final long seed = 5; GraphGenerator gen = new BarabasiAlbertGraphGenerator<>(3, 2, 10, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); g.addVertex(1000); gen.generateGraph(g); assertEquals(11, g.vertexSet().size()); } } ComplementGraphGeneratorTest.java000066400000000000000000000101071402514743400342710ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for ComplementGraphGenerator * * @author Joris Kinable */ public class ComplementGraphGeneratorTest { @Test public void testEmptyGraph() { // Complement of a graph without edges is the complete graph Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); ComplementGraphGenerator cgg = new ComplementGraphGenerator<>(g); Graph target = new SimpleWeightedGraph<>(DefaultEdge.class); cgg.generateGraph(target); assertTrue(GraphTests.isComplete(target)); // complement of a complement graph is the original graph ComplementGraphGenerator cgg2 = new ComplementGraphGenerator<>(target); Graph target2 = new SimpleWeightedGraph<>(DefaultEdge.class); cgg2.generateGraph(target2); assertTrue(target2.edgeSet().isEmpty()); assertTrue(target2.vertexSet().equals(g.vertexSet())); } @Test public void testUndirectedGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2, 3)); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(0, 2); ComplementGraphGenerator cgg = new ComplementGraphGenerator<>(g); Graph target = new SimpleWeightedGraph<>(DefaultEdge.class); cgg.generateGraph(target); assertTrue(target.vertexSet().equals(Set.of(0, 1, 2, 3))); assertEquals(3, target.edgeSet().size()); assertTrue(target.containsEdge(0, 3)); assertTrue(target.containsEdge(2, 3)); assertTrue(target.containsEdge(1, 3)); } @Test public void testDirectedGraph() { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2)); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(0, 2); ComplementGraphGenerator cgg = new ComplementGraphGenerator<>(g); Graph target = new SimpleWeightedGraph<>(DefaultEdge.class); cgg.generateGraph(target); assertTrue(target.vertexSet().equals(Set.of(0, 1, 2))); assertEquals(3, target.edgeSet().size()); assertTrue(target.containsEdge(1, 0)); assertTrue(target.containsEdge(2, 1)); assertTrue(target.containsEdge(2, 0)); } @Test public void testGraphWithSelfLoops() { Graph g = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(0, 1, 2)); g.addEdge(0, 1); g.addEdge(1, 2); g.addEdge(0, 2); ComplementGraphGenerator cgg = new ComplementGraphGenerator<>(g, true); Graph target = new Pseudograph<>(DefaultEdge.class); cgg.generateGraph(target); assertTrue(target.vertexSet().equals(Set.of(0, 1, 2))); assertEquals(3, target.edgeSet().size()); for (Integer v : target.vertexSet()) assertTrue(target.containsEdge(v, v)); } } DirectedScaleFreeGraphGeneratorTest.java000066400000000000000000000156341402514743400354750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2019-2021, by Amr ALHOSSARY and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Tests for {@link DirectedScaleFreeGraphGenerator} * * @author Amr ALHOSSARY */ public class DirectedScaleFreeGraphGeneratorTest { private static final long SEED = 17L; @Test public void testBadParameters() { try { new DirectedScaleFreeGraphGenerator<>(-0.5f, 0.33f, 0.5f, 0.5f, 500, 500, SEED); fail("Bad alpha checking"); } catch (IllegalArgumentException e) { } try { new DirectedScaleFreeGraphGenerator<>(0.33f, -0.5f, 0.5f, 0.5f, 500, 500, SEED); fail("Bad gamma checking"); } catch (IllegalArgumentException e) { } try { new DirectedScaleFreeGraphGenerator<>(0.66f, 0.66f, 0.5f, 0.5f, 500, 500, SEED); fail("Bad alpha + gamma checking"); } catch (IllegalArgumentException e) { } try { new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, -0.5f, 0.5f, 500, 500, SEED); fail("Bad deltaIn checking"); } catch (IllegalArgumentException e) { } try { new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, -0.5f, 500, 500, SEED); fail("Bad deltaOut checking"); } catch (IllegalArgumentException e) { } try { new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, -1, -1, SEED); fail("Bad target checking"); } catch (IllegalArgumentException e) { } try { new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, 500, 500, null); fail("Bad random number generator checking"); } catch (NullPointerException e) { } } @Test public void testIncompatibleGraph() { DirectedScaleFreeGraphGenerator generator = new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, 1000, 0, SEED); generator.setAllowingMultipleEdges(true); generator.setAllowingSelfLoops(false); DefaultDirectedGraph g = new DefaultDirectedGraph( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); try { generator.generateGraph(g); fail("Bad checking for allowingMultipleEdges"); } catch (IllegalArgumentException e) { } generator = new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, 1000, 0, SEED); generator.setAllowingMultipleEdges(false); generator.setAllowingSelfLoops(true); DirectedMultigraph directedMultigraph = new DirectedMultigraph( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); try { generator.generateGraph(directedMultigraph); fail("Bad checking for allowingSelfLoops"); } catch (IllegalArgumentException e) { } } @Test public void testNumberOfEdges() { DirectedScaleFreeGraphGenerator generator = new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, 1000, 0, SEED); generator.setAllowingMultipleEdges(false); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(g); assertEquals(1000, g.edgeSet().size()); } @Test public void testNumberOfNodes() { DirectedScaleFreeGraphGenerator generator = new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, -1, 1000, SEED); generator.setAllowingMultipleEdges(false); Graph g = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(g); assertEquals(1000, g.vertexSet().size()); } @Test public void testZeroCases() { DirectedScaleFreeGraphGenerator generator = new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, -1, 0, SEED); generator.setAllowingMultipleEdges(false); DirectedPseudograph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(g); assertEquals(0, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); generator = new DirectedScaleFreeGraphGenerator<>(0.33f, 0.33f, 0.5f, 0.5f, 0, 0, SEED); g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(g); assertEquals(0, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } @Test public void testNoOutDegreeZero() { DirectedScaleFreeGraphGenerator generator = new DirectedScaleFreeGraphGenerator<>(0.3f, 0.0f, 0.5f, 0.5f, -1, 1000, SEED); generator.setAllowingMultipleEdges(false); Graph g = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(g); long outDegreeZero = g.vertexSet().stream().filter(v -> g.outDegreeOf(v) == 0).count(); assertEquals(0, outDegreeZero); } @Test public void testNoInDegreeZero() { DirectedScaleFreeGraphGenerator generator = new DirectedScaleFreeGraphGenerator<>(0.0f, 0.3f, 0.5f, 0.5f, -1, 1000, SEED); generator.setAllowingMultipleEdges(false); Graph g = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(g); long inDegreeZero = g.vertexSet().stream().filter(v -> g.inDegreeOf(v) == 0).count(); assertEquals(0, inDegreeZero); } } GeneralizedPetersenGraphGeneratorTest.java000066400000000000000000000067611402514743400361400ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for GeneralizedPetersenGraphGenerator * * @author Joris Kinable */ public class GeneralizedPetersenGraphGeneratorTest { @Test public void testCubicalGraph() { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GeneralizedPetersenGraphGenerator gpgg = new GeneralizedPetersenGraphGenerator<>(4, 1); gpgg.generateGraph(g); this.validateBasics(g, 8, 12, 3, 3, 4); assertTrue(GraphTests.isBipartite(g)); assertTrue(GraphTests.isCubic(g)); } @Test public void testPetersenGraph() { Graph g = NamedGraphGenerator.petersenGraph(); this.validateBasics(g, 10, 15, 2, 2, 5); assertTrue(GraphTests.isCubic(g)); } @Test public void testDürerGraphGraph() { Graph g = NamedGraphGenerator.dürerGraph(); this.validateBasics(g, 12, 18, 3, 4, 3); assertTrue(GraphTests.isCubic(g)); } @Test public void testDodecahedronGraphGraph() { Graph g = NamedGraphGenerator.dodecahedronGraph(); this.validateBasics(g, 20, 30, 5, 5, 5); assertTrue(GraphTests.isCubic(g)); } @Test public void testDesarguesGraphGraph() { Graph g = NamedGraphGenerator.desarguesGraph(); this.validateBasics(g, 20, 30, 5, 5, 6); assertTrue(GraphTests.isCubic(g)); assertTrue(GraphTests.isBipartite(g)); } @Test public void testNauruGraphGraph() { Graph g = NamedGraphGenerator.nauruGraph(); this.validateBasics(g, 24, 36, 4, 4, 6); assertTrue(GraphTests.isCubic(g)); assertTrue(GraphTests.isBipartite(g)); } @Test public void testMöbiusKantorGraph() { Graph g = NamedGraphGenerator.möbiusKantorGraph(); this.validateBasics(g, 16, 24, 4, 4, 6); assertTrue(GraphTests.isCubic(g)); assertTrue(GraphTests.isBipartite(g)); } private void validateBasics( Graph g, int vertices, int edges, int radius, int diameter, int girt) { assertEquals(vertices, g.vertexSet().size()); assertEquals(edges, g.edgeSet().size()); GraphMeasurer gm = new GraphMeasurer<>(g); assertEquals(radius, gm.getRadius(), 0.00000001); assertEquals(diameter, gm.getDiameter(), 0.00000001); assertEquals(girt, GraphMetrics.getGirth(g), 0.00000001); } } GnmRandomBipartiteGraphGeneratorTest.java000066400000000000000000000106241402514743400357200ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.*; /** * . * * @author Dimitrios Michail */ public class GnmRandomBipartiteGraphGeneratorTest { private static final long SEED = 5; @Test public void testZeroNodes() { GraphGenerator gen = new GnmRandomBipartiteGraphGenerator<>(0, 0, 10); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(0, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } @Test public void testBadParameters() { try { new GnmRandomBipartiteGraphGenerator<>(-1, 10, 1); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new GnmRandomBipartiteGraphGenerator<>(10, -1, 1); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new GnmRandomBipartiteGraphGenerator<>(10, 10, -5); fail("Bad parameter"); } catch (IllegalArgumentException e) { } } @Test public void testDirectedGraphGnm1() { GraphGenerator gen = new GnmRandomBipartiteGraphGenerator<>(4, 4, 20, SEED); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); int[][] edges = { { 3, 5 }, { 6, 3 }, { 2, 8 }, { 7, 2 }, { 6, 2 }, { 4, 5 }, { 7, 4 }, { 2, 5 }, { 6, 1 }, { 5, 1 }, { 2, 7 }, { 1, 7 }, { 2, 6 }, { 3, 6 }, { 1, 5 }, { 7, 3 }, { 1, 8 }, { 8, 3 }, { 4, 7 }, { 4, 8 } }; assertEquals(4 + 4, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testUndirectedGraphGnm1() { GraphGenerator gen = new GnmRandomBipartiteGraphGenerator<>(4, 4, 10, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); int[][] edges = { { 3, 5 }, { 1, 7 }, { 2, 8 }, { 2, 6 }, { 3, 8 }, { 4, 8 }, { 1, 6 }, { 4, 7 }, { 4, 6 }, { 4, 5 } }; assertEquals(4 + 4, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testGnmEdgesLimit() { try { GraphGenerator gen = new GnmRandomBipartiteGraphGenerator<>(4, 4, 17, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); fail("More edges than permitted"); } catch (IllegalArgumentException e) { } try { GraphGenerator gen = new GnmRandomBipartiteGraphGenerator<>(4, 4, 33, SEED); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); fail("More edges than permitted"); } catch (IllegalArgumentException e) { } } } GnmRandomGraphGeneratorTest.java000066400000000000000000000567151402514743400340670ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2005-2021, by Assaf Lehr, Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * . * * @author Assaf Lehr */ public class GnmRandomGraphGeneratorTest { private static final long SEED = 5; @Test public void testZeroNodes() { GraphGenerator gen = new GnmRandomGraphGenerator<>(0, 0); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(0, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } @Test public void testZeroEdge() { GraphGenerator gen = new GnmRandomGraphGenerator<>(10, 0); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(10, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } @Test public void testBadParameters() { try { new GnmRandomGraphGenerator<>(-10, 10); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new GnmRandomGraphGenerator<>(10, -10); fail("Bad parameter"); } catch (IllegalArgumentException e) { } } @Test public void testDirectedGraphGnp1() { GraphGenerator gen = new GnmRandomGraphGenerator<>(6, 18, SEED); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); int[][] edges = { { 6, 5 }, { 1, 6 }, { 5, 6 }, { 3, 4 }, { 6, 4 }, { 2, 1 }, { 3, 5 }, { 1, 2 }, { 1, 3 }, { 2, 5 }, { 4, 3 }, { 2, 3 }, { 5, 4 }, { 1, 4 }, { 2, 6 }, { 6, 1 }, { 4, 6 }, { 3, 1 } }; assertEquals(6, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testDirectedGraphGnp1WithLoops() { GraphGenerator gen = new GnmRandomGraphGenerator<>(6, 18, SEED, true, false); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); int[][] edges = { { 6, 5 }, { 3, 3 }, { 1, 6 }, { 5, 6 }, { 3, 4 }, { 6, 4 }, { 2, 1 }, { 3, 5 }, { 1, 2 }, { 1, 3 }, { 2, 5 }, { 4, 3 }, { 2, 3 }, { 2, 2 }, { 5, 4 }, { 2, 2 }, { 1, 4 }, { 5, 5 } }; assertEquals(6, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testDirectedGraphGnp1WithMultipleEdges() { GraphGenerator gen = new GnmRandomGraphGenerator<>(6, 18, SEED, false, true); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); int[][] edges = { { 6, 5 }, { 1, 6 }, { 5, 6 }, { 3, 4 }, { 6, 4 }, { 2, 1 }, { 3, 5 }, { 1, 2 }, { 6, 4 }, { 1, 6 }, { 1, 3 }, { 2, 5 }, { 3, 4 }, { 4, 3 }, { 2, 3 }, { 2, 3 }, { 5, 4 }, { 1, 6 } }; assertEquals(6, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testDirectedGraphGnp1WithLoopsAndMultipleEdges() { GraphGenerator gen = new GnmRandomGraphGenerator<>(6, 18, SEED, true, true); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); int[][] edges = { { 6, 5 }, { 3, 3 }, { 1, 6 }, { 5, 6 }, { 3, 4 }, { 6, 4 }, { 2, 1 }, { 3, 5 }, { 1, 2 }, { 6, 4 }, { 1, 6 }, { 1, 3 }, { 2, 5 }, { 3, 4 }, { 4, 3 }, { 2, 3 }, { 2, 2 }, { 2, 3 } }; assertEquals(6, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testUndirectedGraphGnp1() { GraphGenerator gen = new GnmRandomGraphGenerator<>(6, 15, SEED); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); int[][] edges = { { 6, 5 }, { 1, 6 }, { 3, 4 }, { 6, 4 }, { 2, 1 }, { 3, 5 }, { 1, 3 }, { 2, 5 }, { 2, 3 }, { 5, 4 }, { 1, 4 }, { 2, 6 }, { 5, 1 }, { 4, 2 }, { 6, 3 } }; assertEquals(6, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testUndirectedGraphGnp1WithLoops() { GraphGenerator gen = new GnmRandomGraphGenerator<>(6, 15, SEED, true, false); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); int[][] edges = { { 6, 5 }, { 3, 3 }, { 1, 6 }, { 3, 4 }, { 6, 4 }, { 2, 1 }, { 3, 5 }, { 1, 3 }, { 2, 5 }, { 2, 3 }, { 2, 2 }, { 5, 4 }, { 2, 2 }, { 1, 4 }, { 5, 5 } }; assertEquals(6, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testNotAllowedLoopsOrMultipleEdges() { try { GraphGenerator gen = new GnmRandomGraphGenerator<>(6, 18, SEED, true, false); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); fail("Exception expected"); } catch (IllegalArgumentException e) { } try { GraphGenerator gen = new GnmRandomGraphGenerator<>(6, 18, SEED, false, true); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); fail("Exception expected"); } catch (IllegalArgumentException e) { } } @Test public void testEdgeLimitsDirected() { try { GraphGenerator gen1 = new GnmRandomGraphGenerator<>(5, 21, SEED, false, false); Graph g1 = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen1.generateGraph(g1); fail("Exception expected"); } catch (IllegalArgumentException e) { } GraphGenerator gen2 = new GnmRandomGraphGenerator<>(5, 20, SEED, false, false); Graph g2 = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen2.generateGraph(g2); GraphGenerator gen3 = new GnmRandomGraphGenerator<>(5, 25, SEED, true, false); Graph g3 = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen3.generateGraph(g3); GraphGenerator gen4 = new GnmRandomGraphGenerator<>(5, 25, SEED, false, true); Graph g4 = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen4.generateGraph(g4); } @Test public void testEdgeLimitsUndirected() { try { GraphGenerator gen1 = new GnmRandomGraphGenerator<>(5, 11, SEED, false, false); Graph g1 = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen1.generateGraph(g1); fail("Exception expected"); } catch (IllegalArgumentException e) { } GraphGenerator gen2 = new GnmRandomGraphGenerator<>(5, 10, SEED, false, false); Graph g2 = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen2.generateGraph(g2); GraphGenerator gen3 = new GnmRandomGraphGenerator<>(5, 15, SEED, true, false); Graph g3 = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen3.generateGraph(g3); GraphGenerator gen4 = new GnmRandomGraphGenerator<>(5, 15, SEED, false, true); Graph g4 = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen4.generateGraph(g4); } @Test public void testMaximumAllowedEdges() { // undirected graphs boolean isDirected = false; assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(0, isDirected, false, false)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(0, isDirected, false, true)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(0, isDirected, true, false)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(0, isDirected, true, true)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(1, isDirected, false, false)); assertEquals( 1, GnmRandomGraphGenerator.computeMaximumAllowedEdges(1, isDirected, true, false)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(1, isDirected, false, true)); assertEquals( Integer.MAX_VALUE, GnmRandomGraphGenerator.computeMaximumAllowedEdges(1, isDirected, true, true)); assertEquals( 45, GnmRandomGraphGenerator.computeMaximumAllowedEdges(10, isDirected, false, false)); assertEquals( 55, GnmRandomGraphGenerator.computeMaximumAllowedEdges(10, isDirected, true, false)); assertEquals( Integer.MAX_VALUE, GnmRandomGraphGenerator.computeMaximumAllowedEdges(10, isDirected, false, true)); assertEquals( Integer.MAX_VALUE, GnmRandomGraphGenerator.computeMaximumAllowedEdges(10, isDirected, true, true)); assertEquals( Integer.MAX_VALUE, GnmRandomGraphGenerator.computeMaximumAllowedEdges(200000, isDirected, false, false)); // directed graphs isDirected = true; assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(0, isDirected, false, false)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(0, isDirected, false, true)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(0, isDirected, true, false)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(0, isDirected, true, true)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(1, isDirected, false, false)); assertEquals( 2, GnmRandomGraphGenerator.computeMaximumAllowedEdges(1, isDirected, true, false)); assertEquals( 0, GnmRandomGraphGenerator.computeMaximumAllowedEdges(1, isDirected, false, true)); assertEquals( Integer.MAX_VALUE, GnmRandomGraphGenerator.computeMaximumAllowedEdges(1, isDirected, true, true)); assertEquals( 90, GnmRandomGraphGenerator.computeMaximumAllowedEdges(10, isDirected, false, false)); assertEquals( 110, GnmRandomGraphGenerator.computeMaximumAllowedEdges(10, isDirected, true, false)); assertEquals( Integer.MAX_VALUE, GnmRandomGraphGenerator.computeMaximumAllowedEdges(10, isDirected, false, true)); assertEquals( Integer.MAX_VALUE, GnmRandomGraphGenerator.computeMaximumAllowedEdges(10, isDirected, true, true)); assertEquals( Integer.MAX_VALUE, GnmRandomGraphGenerator.computeMaximumAllowedEdges(200000, isDirected, false, false)); } @Test public void testGenerateDirectedGraph() { List> graphArray = new ArrayList<>(); for (int i = 0; i < 4; ++i) { graphArray .add( new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false)); } generateGraphs(graphArray, 11, 100); assertTrue( new EdgeTopologyCompare() .compare(graphArray.get(0), graphArray.get(2))); assertTrue( new EdgeTopologyCompare() .compare(graphArray.get(1), graphArray.get(3))); } @Test public void testGenerateListenableUndirectedGraph() { List> graphArray = new ArrayList<>(); for (int i = 0; i < 4; ++i) { graphArray .add( new DefaultListenableGraph<>( new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false))); } generateGraphs(graphArray, 11, 50); assertTrue( new EdgeTopologyCompare() .compare(graphArray.get(0), graphArray.get(2))); assertTrue( new EdgeTopologyCompare() .compare(graphArray.get(1), graphArray.get(3))); } @Test public void testBadVertexFactory() { GraphGenerator randomGen = new GnmRandomGraphGenerator<>(10, 3); Graph graph = new SimpleDirectedGraph<>( SupplierUtil.createSupplier(String.class), SupplierUtil.createDefaultEdgeSupplier(), false); try { randomGen.generateGraph(graph); fail("IllegalArgumentException expected"); } catch (IllegalArgumentException ex) { // expected } } /** * Generates 4 graphs with the same numOfVertex and numOfEdges. The first two are generated * using the same RandomGraphGenerator; the last two are generated using a new instance. * * @param graphs array of graphs to generate * @param numOfVertex number of vertices to generate per graph * @param numOfEdges number of edges to generate per graph */ private static void generateGraphs( List> graphs, int numOfVertex, int numOfEdges) { final int seed = 17; GraphGenerator randomGen = new GnmRandomGraphGenerator<>(numOfVertex, numOfEdges, seed); randomGen.generateGraph(graphs.get(0)); randomGen.generateGraph(graphs.get(1)); // use new randomGen here GraphGenerator newRandomGen = new GnmRandomGraphGenerator<>(numOfVertex, numOfEdges, seed); newRandomGen.generateGraph(graphs.get(2)); newRandomGen.generateGraph(graphs.get(3)); } static class EdgeTopologyCompare { /** * Compare topology of the two graphs. It does not compare the contents of the * vertexes/edges, but only the relationships between them. * * @param g1 * @param g2 */ public boolean compare(Graph g1, Graph g2) { boolean result; VertexOrdering lg1 = new VertexOrdering<>(g1); VertexOrdering lg2 = new VertexOrdering<>(g2); result = lg1.equalsByEdgeOrder(lg2); return result; } } static class VertexOrdering { /** * Holds a mapping between key=V(vertex) and value=Integer(vertex order). It can be used for * identifying the order of regular vertex/edge. */ private Map mapVertexToOrder; /** * Holds a HashSet of all LabelsGraph of the graph. */ private Set labelsEdgesSet; /** * Creates a new labels graph according to the regular graph. After its creation they will * no longer be linked, thus changes to one will not affect the other. * * @param regularGraph */ public VertexOrdering(Graph regularGraph) { this(regularGraph, regularGraph.vertexSet(), regularGraph.edgeSet()); } /** * Creates a new labels graph according to the regular graph. After its creation they will * no longer be linked, thus changes to one will not affect the other. * * @param regularGraph * @param vertexSet * @param edgeSet */ public VertexOrdering(Graph regularGraph, Set vertexSet, Set edgeSet) { init(regularGraph, vertexSet, edgeSet); } private void init(Graph g, Set vertexSet, Set edgeSet) { // create a map between vertex value to its order(1st,2nd,etc) // "CAT"=1 "DOG"=2 "RHINO"=3 this.mapVertexToOrder = CollectionUtil.newHashMapWithExpectedSize(vertexSet.size()); int counter = 0; for (V vertex : vertexSet) { mapVertexToOrder.put(vertex, counter); counter++; } // create a friendlier representation of an edge // by order, like 2nd->3rd instead of B->A // use the map to convert vertex to order // on directed graph, edge A->B must be (A,B) // on undirected graph, edge A-B can be (A,B) or (B,A) this.labelsEdgesSet = CollectionUtil.newHashSetWithExpectedSize(edgeSet.size()); for (E edge : edgeSet) { V sourceVertex = g.getEdgeSource(edge); int sourceLabel = mapVertexToOrder.get(sourceVertex); int targetLabel = mapVertexToOrder.get(g.getEdgeTarget(edge)); LabelsEdge lablesEdge = new LabelsEdge(sourceLabel, targetLabel); this.labelsEdgesSet.add(lablesEdge); if (g.getType().isUndirected()) { LabelsEdge oppositeEdge = new LabelsEdge(targetLabel, sourceLabel); this.labelsEdgesSet.add(oppositeEdge); } } } /** * Tests equality by order of edges */ public boolean equalsByEdgeOrder(VertexOrdering otherGraph) { return this.getLabelsEdgesSet().equals(otherGraph.getLabelsEdgesSet()); } public Set getLabelsEdgesSet() { return labelsEdgesSet; } /** * This is the format example: * *
             mapVertexToOrder=        labelsOrder=
             * 
    */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("mapVertexToOrder="); // vertex will be printed in their order Object[] vertexArray = new Object[this.mapVertexToOrder.size()]; Set keySet = this.mapVertexToOrder.keySet(); for (V currVertex : keySet) { Integer index = this.mapVertexToOrder.get(currVertex); vertexArray[index] = currVertex; } sb.append(Arrays.toString(vertexArray)); sb.append("labelsOrder=").append(this.labelsEdgesSet.toString()); return sb.toString(); } private static class LabelsEdge { private int source; private int target; private int hashCode; public LabelsEdge(int aSource, int aTarget) { this.source = aSource; this.target = aTarget; this.hashCode = (this.source + "" + this.target).hashCode(); } /** * Checks both source and target. Does not check class type to be fast, so it may throw * ClassCastException. Careful! * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; else if (!(obj instanceof VertexOrdering.LabelsEdge)) return false; LabelsEdge otherEdge = TypeUtil.uncheckedCast(obj); return (this.source == otherEdge.source) && (this.target == otherEdge.target); } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return this.hashCode; // filled on constructor } @Override public String toString() { return this.source + "->" + this.target; } } } } GnpRandomBipartiteGraphGeneratorTest.java000066400000000000000000000132451402514743400357250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.*; /** * . * * @author Dimitrios Michail */ public class GnpRandomBipartiteGraphGeneratorTest { private static final long SEED = 5; @Test public void testZeroNodes() { GraphGenerator gen = new GnpRandomBipartiteGraphGenerator<>(0, 0, 0.5); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(0, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } @Test public void testBadParameters() { try { new GnpRandomBipartiteGraphGenerator<>(-1, 0, 0.5); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new GnpRandomBipartiteGraphGenerator<>(10, -3, 0.5); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new GnpRandomBipartiteGraphGenerator<>(10, 10, -1.0); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new GnpRandomBipartiteGraphGenerator<>(10, 10, 2.0); fail("Bad parameter"); } catch (IllegalArgumentException e) { } } @Test public void testDirectedGraphGnp1() { GraphGenerator gen = new GnpRandomBipartiteGraphGenerator<>(4, 4, 0.5, SEED); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); int[][] edges = { { 5, 1 }, { 1, 6 }, { 6, 1 }, { 1, 7 }, { 1, 8 }, { 2, 5 }, { 6, 2 }, { 7, 2 }, { 2, 8 }, { 5, 3 }, { 3, 6 }, { 7, 3 }, { 3, 8 }, { 4, 5 }, { 5, 4 }, { 4, 6 }, { 6, 4 }, { 4, 7 }, { 4, 8 }, { 8, 4 } }; assertEquals(4 + 4, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testDirectedGraphGnp2() { GraphGenerator gen = new GnpRandomBipartiteGraphGenerator<>(4, 4, 1.0, SEED); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(4 + 4, g.vertexSet().size()); assertEquals(32, g.edgeSet().size()); } @Test public void testDirectedGraphGnp3() { GraphGenerator gen = new GnpRandomBipartiteGraphGenerator<>(4, 4, 0.1, SEED); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); int[][] edges = { { 5, 1 }, { 7, 3 }, { 3, 8 }, { 8, 4 } }; assertEquals(4 + 4, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testUndirectedGraphGnp1() { GraphGenerator gen = new GnpRandomBipartiteGraphGenerator<>(4, 4, 0.5, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); int[][] edges = { { 1, 6 }, { 1, 7 }, { 1, 8 }, { 2, 5 }, { 2, 7 }, { 3, 5 }, { 3, 8 }, { 4, 6 }, { 4, 7 } }; assertEquals(4 + 4, g.vertexSet().size()); for (int[] e : edges) { assertTrue(g.containsEdge(e[0], e[1])); } assertEquals(edges.length, g.edgeSet().size()); } @Test public void testUndirectedGraphGnp2() { GraphGenerator gen = new GnpRandomBipartiteGraphGenerator<>(4, 4, 1.0, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(4 + 4, g.vertexSet().size()); assertEquals(4 * 4, g.edgeSet().size()); } @Test public void testUndirectedGraphGnp3() { GraphGenerator gen = new GnpRandomBipartiteGraphGenerator<>(4, 4, 0.0, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(4 + 4, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } } GnpRandomGraphGeneratorTest.java000066400000000000000000000202271402514743400340570ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.*; /** * . * * @author Dimitrios Michail */ public class GnpRandomGraphGeneratorTest { private static final long SEED = 5; @Test public void testZeroNodes() { GraphGenerator gen = new GnpRandomGraphGenerator<>(0, 1d); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(0, g.edgeSet().size()); assertEquals(0, g.vertexSet().size()); } @Test public void testBadParameters() { try { new GnpRandomGraphGenerator<>(-10, 0); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new GnpRandomGraphGenerator<>(10, -1.0); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new GnpRandomGraphGenerator<>(10, 2.0); fail("Bad parameter"); } catch (IllegalArgumentException e) { } } @Test public void testDirectedGraphGnp1() { GraphGenerator gen = new GnpRandomGraphGenerator<>(6, 0.5, SEED); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertTrue(g.containsEdge(2, 1)); assertTrue(g.containsEdge(1, 3)); assertTrue(g.containsEdge(3, 1)); assertTrue(g.containsEdge(1, 4)); assertTrue(g.containsEdge(1, 5)); assertTrue(g.containsEdge(1, 6)); assertTrue(g.containsEdge(3, 2)); assertTrue(g.containsEdge(4, 2)); assertTrue(g.containsEdge(2, 5)); assertTrue(g.containsEdge(6, 2)); assertTrue(g.containsEdge(3, 4)); assertTrue(g.containsEdge(5, 3)); assertTrue(g.containsEdge(3, 6)); assertTrue(g.containsEdge(4, 5)); assertTrue(g.containsEdge(5, 4)); assertTrue(g.containsEdge(4, 6)); assertTrue(g.containsEdge(6, 4)); assertTrue(g.containsEdge(5, 6)); assertEquals(18, g.edgeSet().size()); } @Test public void testDirectedGraphGnp2() { GraphGenerator gen = new GnpRandomGraphGenerator<>(6, 1.0, SEED); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertEquals(30, g.edgeSet().size()); } @Test public void testDirectedGraphGnp3() { GraphGenerator gen = new GnpRandomGraphGenerator<>(6, 0.1, SEED); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertTrue(g.containsEdge(2, 1)); assertTrue(g.containsEdge(5, 3)); assertTrue(g.containsEdge(3, 6)); assertEquals(3, g.edgeSet().size()); } @Test public void testDirectedGraphGnp4WithLoops() { final boolean allowLoops = true; GraphGenerator gen = new GnpRandomGraphGenerator<>(6, 0.2, SEED, allowLoops); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertTrue(g.containsEdge(1, 1)); assertTrue(g.containsEdge(6, 2)); assertTrue(g.containsEdge(3, 3)); assertTrue(g.containsEdge(5, 3)); assertTrue(g.containsEdge(4, 4)); assertTrue(g.containsEdge(4, 5)); assertTrue(g.containsEdge(4, 6)); assertEquals(7, g.edgeSet().size()); } @Test public void testUndirectedGraphGnp1() { GraphGenerator gen = new GnpRandomGraphGenerator<>(6, 0.5, SEED); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertTrue(g.containsEdge(1, 3)); assertTrue(g.containsEdge(1, 4)); assertTrue(g.containsEdge(1, 5)); assertTrue(g.containsEdge(1, 6)); assertTrue(g.containsEdge(2, 4)); assertTrue(g.containsEdge(2, 6)); assertTrue(g.containsEdge(3, 6)); assertTrue(g.containsEdge(4, 6)); assertTrue(g.containsEdge(5, 6)); assertEquals(9, g.edgeSet().size()); } @Test public void testUndirectedGraphGnp2() { GraphGenerator gen = new GnpRandomGraphGenerator<>(6, 1.0, SEED); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertTrue(g.containsEdge(1, 2)); assertTrue(g.containsEdge(1, 3)); assertTrue(g.containsEdge(1, 4)); assertTrue(g.containsEdge(1, 5)); assertTrue(g.containsEdge(1, 6)); assertTrue(g.containsEdge(2, 3)); assertTrue(g.containsEdge(2, 4)); assertTrue(g.containsEdge(2, 5)); assertTrue(g.containsEdge(2, 6)); assertTrue(g.containsEdge(3, 4)); assertTrue(g.containsEdge(3, 5)); assertTrue(g.containsEdge(3, 6)); assertTrue(g.containsEdge(4, 5)); assertTrue(g.containsEdge(4, 6)); assertTrue(g.containsEdge(5, 6)); assertEquals(15, g.edgeSet().size()); } @Test public void testUndirectedGraphGnp3() { GraphGenerator gen = new GnpRandomGraphGenerator<>(6, 0.3, SEED); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertTrue(g.containsEdge(1, 3)); assertTrue(g.containsEdge(2, 4)); assertTrue(g.containsEdge(2, 6)); assertTrue(g.containsEdge(3, 6)); assertEquals(4, g.edgeSet().size()); } @Test public void testUndirectedGraphGnp4WithLoops() { final boolean allowLoops = true; GraphGenerator gen = new GnpRandomGraphGenerator<>(6, 0.3, SEED, allowLoops); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertTrue(g.containsEdge(1, 2)); assertTrue(g.containsEdge(2, 2)); assertTrue(g.containsEdge(2, 4)); assertTrue(g.containsEdge(3, 3)); assertTrue(g.containsEdge(4, 6)); assertTrue(g.containsEdge(5, 5)); assertEquals(6, g.edgeSet().size()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/GraphGeneratorTest.java000066400000000000000000000340361402514743400323330ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * . * * @author John V. Sichi */ public class GraphGeneratorTest { // ~ Static fields/initializers --------------------------------------------- private static final int SIZE = 10; // ~ Methods ---------------------------------------------------------------- /** * . */ @Test public void testEmptyGraphGenerator() { GraphGenerator gen = new EmptyGraphGenerator<>(SIZE); Graph g = new DefaultDirectedGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Map resultMap = new HashMap<>(); gen.generateGraph(g, resultMap); assertEquals(SIZE, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); assertTrue(resultMap.isEmpty()); } /** * . */ @Test public void testLinearGraphGenerator() { GraphGenerator gen = new LinearGraphGenerator<>(SIZE); Graph g = new DefaultDirectedGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Map resultMap = new HashMap<>(); gen.generateGraph(g, resultMap); assertEquals(SIZE, g.vertexSet().size()); assertEquals(SIZE - 1, g.edgeSet().size()); Object startVertex = resultMap.get(LinearGraphGenerator.START_VERTEX); Object endVertex = resultMap.get(LinearGraphGenerator.END_VERTEX); for (Object vertex : g.vertexSet()) { if (vertex == startVertex) { assertEquals(0, g.inDegreeOf(vertex)); assertEquals(1, g.outDegreeOf(vertex)); continue; } if (vertex == endVertex) { assertEquals(1, g.inDegreeOf(vertex)); assertEquals(0, g.outDegreeOf(vertex)); continue; } assertEquals(1, g.inDegreeOf(vertex)); assertEquals(1, g.outDegreeOf(vertex)); } } /** * . */ @Test public void testRingGraphGenerator() { GraphGenerator gen = new RingGraphGenerator<>(SIZE); Graph g = new DefaultDirectedGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Map resultMap = new HashMap<>(); gen.generateGraph(g, resultMap); assertEquals(SIZE, g.vertexSet().size()); assertEquals(SIZE, g.edgeSet().size()); Object startVertex = g.vertexSet().iterator().next(); assertEquals(1, g.outDegreeOf(startVertex)); Object nextVertex = startVertex; Set seen = new HashSet<>(); for (int i = 0; i < SIZE; ++i) { DefaultEdge nextEdge = g.outgoingEdgesOf(nextVertex).iterator().next(); nextVertex = g.getEdgeTarget(nextEdge); assertEquals(1, g.inDegreeOf(nextVertex)); assertEquals(1, g.outDegreeOf(nextVertex)); assertFalse(seen.contains(nextVertex)); seen.add(nextVertex); } // do you ever get the feeling you're going in circles? assertTrue(nextVertex == startVertex); assertTrue(resultMap.isEmpty()); } /** * . */ @Test public void testCompleteGraphGenerator() { Graph completeGraph = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator completeGenerator = new CompleteGraphGenerator<>(10); completeGenerator.generateGraph(completeGraph); // complete graph with 10 vertices has 10*(10-1)/2 = 45 edges assertEquals(45, completeGraph.edgeSet().size()); } @Test public void testCompleteGraphGeneratorWithDirectedGraph() { Graph completeGraph = new SimpleDirectedGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator completeGenerator = new CompleteGraphGenerator<>(10); completeGenerator.generateGraph(completeGraph); // complete graph with 10 vertices has 10*(10-1) = 90 edges assertEquals(90, completeGraph.edgeSet().size()); } @Test public void testCompleteGraphGeneratorWithPreexistingVertices() { Graph completeGraph = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); for (int i = 0; i < 10; i++) completeGraph.addVertex(); CompleteGraphGenerator completeGenerator = new CompleteGraphGenerator<>(); completeGenerator.generateGraph(completeGraph); // complete graph with 10 vertices has 10*(10-1)/2 = 45 edges assertEquals(45, completeGraph.edgeSet().size()); } /** * . */ @Test public void testScaleFreeGraphGenerator() { Graph graph = new DefaultDirectedGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); ScaleFreeGraphGenerator generator = new ScaleFreeGraphGenerator<>(500); generator.generateGraph(graph); ConnectivityInspector inspector = new ConnectivityInspector<>(graph); assertTrue("generated graph is not connected", inspector.isConnected()); try { new ScaleFreeGraphGenerator<>(-50); fail("IllegalArgumentException expected"); } catch (IllegalArgumentException e) { } try { new ScaleFreeGraphGenerator<>(-50, 31337); fail("IllegalArgumentException expected"); } catch (IllegalArgumentException e) { } generator = new ScaleFreeGraphGenerator<>(0); Graph empty = new DefaultDirectedGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(empty); assertTrue("non-empty graph generated", empty.vertexSet().isEmpty()); } /** * . */ @Test public void testCompleteBipartiteGraphGenerator() { Graph completeBipartiteGraph = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteBipartiteGraphGenerator completeBipartiteGenerator = new CompleteBipartiteGraphGenerator<>(10, 4); completeBipartiteGenerator.generateGraph(completeBipartiteGraph); // Complete bipartite graph with 10 and 4 vertices should have 14 // total vertices and 4*10=40 total edges assertEquals(14, completeBipartiteGraph.vertexSet().size()); assertEquals(40, completeBipartiteGraph.edgeSet().size()); } @Test public void testCompleteBipartiteGraphGeneratorWithPreexistingVertices() { Graph completeBipartiteGraph = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Set partitionA = new HashSet<>(); for (int i = 0; i < 10; i++) partitionA.add(completeBipartiteGraph.addVertex()); Set partitionB = new HashSet<>(); for (int i = 0; i < 4; i++) partitionB.add(completeBipartiteGraph.addVertex()); CompleteBipartiteGraphGenerator completeBipartiteGenerator = new CompleteBipartiteGraphGenerator<>(partitionA, partitionB); completeBipartiteGenerator.generateGraph(completeBipartiteGraph); // Complete bipartite graph with 10 and 4 vertices should have 14 // total vertices and 4*10=40 total edges assertEquals(14, completeBipartiteGraph.vertexSet().size()); assertEquals(40, completeBipartiteGraph.edgeSet().size()); } /** * . */ @Test public void testHyperCubeGraphGenerator() { Graph hyperCubeGraph = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); HyperCubeGraphGenerator hyperCubeGenerator = new HyperCubeGraphGenerator<>(4); hyperCubeGenerator.generateGraph(hyperCubeGraph); // Hypercube of 4 dimensions should have 2^4=16 vertices and // 4*2^(4-1)=32 total edges assertEquals(16, hyperCubeGraph.vertexSet().size()); assertEquals(32, hyperCubeGraph.edgeSet().size()); } /** * . */ @Test public void testStarGraphGenerator() { Map map = new HashMap<>(); Graph starGraph = new SimpleGraph<>( SupplierUtil.OBJECT_SUPPLIER, SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); StarGraphGenerator starGenerator = new StarGraphGenerator<>(10); starGenerator.generateGraph(starGraph, map); // Star graph of order 10 should have 10 vertices and 9 edges assertEquals(9, starGraph.edgeSet().size()); assertEquals(10, starGraph.vertexSet().size()); assertTrue(map.get(StarGraphGenerator.CENTER_VERTEX) != null); } /** * . */ @Test public void testGridGraphGenerator() { int rows = 3; int cols = 4; GridGraphGenerator generator = new GridGraphGenerator<>(rows, cols); Map resultMap = new HashMap<>(); // validating a directed and undirected graph Graph directedGridGraph = new DefaultDirectedGraph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.createStringSupplier(1), false); generator.generateGraph(directedGridGraph, resultMap); validateGridGraphGenerator(rows, cols, directedGridGraph, resultMap); resultMap.clear(); Graph undirectedGridGraph = new SimpleGraph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.createStringSupplier(1), false); generator.generateGraph(undirectedGridGraph, resultMap); validateGridGraphGenerator(rows, cols, undirectedGridGraph, resultMap); } public void validateGridGraphGenerator( int rows, int cols, Graph gridGraph, Map resultMap) { // graph structure validations int expectedVerticeNum = rows * cols; assertEquals( "number of vertices is wrong (" + gridGraph.vertexSet().size() + "), should be " + expectedVerticeNum, expectedVerticeNum, gridGraph.vertexSet().size()); int expectedEdgesNum = (((rows - 1) * cols) + ((cols - 1) * rows)) * ((gridGraph.getType().isUndirected()) ? 1 : 2); assertEquals( "number of edges is wrong (" + gridGraph.edgeSet().size() + "), should be " + expectedEdgesNum, expectedEdgesNum, gridGraph.edgeSet().size()); int cornerVertices = 0, borderVertices = 0, innerVertices = 0, neighborsSize; int expCornerVertices = 4; int expBorderVertices = Math.max(((rows - 2) * 2) + ((cols - 2) * 2), 0); int expInnerVertices = Math.max((rows - 2) * (cols - 2), 0); Set neighbors = new HashSet<>(); for (String v : gridGraph.vertexSet()) { neighbors.clear(); neighbors.addAll(Graphs.neighborListOf(gridGraph, v)); neighborsSize = neighbors.size(); assertTrue( "vertex with illegal number of neighbors (" + neighborsSize + ").", (neighborsSize == 2) || (neighborsSize == 3) || (neighborsSize == 4)); if (neighborsSize == 2) { cornerVertices++; } else if (neighborsSize == 3) { borderVertices++; } else if (neighborsSize == 4) { innerVertices++; } } assertEquals( "there should be exactly " + expCornerVertices + " corner (with two neighbors) vertices. " + " actual number is " + cornerVertices + ".", expCornerVertices, cornerVertices); assertEquals( "there should be exactly " + expBorderVertices + " border (with three neighbors) vertices. " + " actual number is " + borderVertices + ".", expBorderVertices, borderVertices); assertEquals( "there should be exactly " + expInnerVertices + " inner (with four neighbors) vertices. " + " actual number is " + innerVertices + ".", expInnerVertices, innerVertices); // result map validations Set keys = resultMap.keySet(); assertEquals( "result map contains should contains exactly 4 corner verices", 4, keys.size()); for (String key : keys) { neighbors.clear(); neighbors.addAll(Graphs.neighborListOf(gridGraph, resultMap.get(key))); neighborsSize = neighbors.size(); assertEquals("corner vertex should have exactly 2 neighbors", 2, neighborsSize); } } } KleinbergSmallWorldGraphGeneratorTest.java000066400000000000000000000062471402514743400361030ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * @author Dimitrios Michail */ public class KleinbergSmallWorldGraphGeneratorTest { @Test public void testBadParameters() { try { new KleinbergSmallWorldGraphGenerator<>(-1, 1, 1, 1); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new KleinbergSmallWorldGraphGenerator<>(5, 0, 1, 1); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new KleinbergSmallWorldGraphGenerator<>(5, 9, 1, 1); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new KleinbergSmallWorldGraphGenerator<>(5, 1, -1, 1); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new KleinbergSmallWorldGraphGenerator<>(5, 1, 1, -1); fail("Bad parameter"); } catch (IllegalArgumentException e) { } } @Test public void testUndirected() { final long seed = 5; GraphGenerator gen = new KleinbergSmallWorldGraphGenerator<>(5, 2, 3, 2, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(25, g.vertexSet().size()); } @Test public void testDirected() { final long seed = 5; GraphGenerator gen = new KleinbergSmallWorldGraphGenerator<>(5, 2, 3, 2, seed); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(25, g.vertexSet().size()); } @Test public void testDirectedWithUniform() { final long seed = 5; GraphGenerator gen = new KleinbergSmallWorldGraphGenerator<>(5, 2, 3, 0, seed); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(25, g.vertexSet().size()); } } LinearizedChordDiagramGraphGeneratorTest.java000066400000000000000000000114521402514743400365250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Tests for {@link LinearizedChordDiagramGraphGenerator}. * * @author Dimitrios Michail */ public class LinearizedChordDiagramGraphGeneratorTest { @Test public void testBadParameters() { try { new LinearizedChordDiagramGraphGenerator<>(0, 10); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new LinearizedChordDiagramGraphGenerator<>(-1, 10); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new LinearizedChordDiagramGraphGenerator<>(5, 0); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new LinearizedChordDiagramGraphGenerator<>(5, -1); fail("Bad parameter"); } catch (IllegalArgumentException e) { } } @Test(expected = IllegalArgumentException.class) public void testMultiGraph() { final long seed = 5; GraphGenerator gen = new LinearizedChordDiagramGraphGenerator<>(10, 2, seed); Graph g = new Multigraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); } @Test(expected = IllegalArgumentException.class) public void testSimpleGraph() { final long seed = 5; GraphGenerator gen = new LinearizedChordDiagramGraphGenerator<>(10, 2, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); } @Test(expected = IllegalArgumentException.class) public void testDirectedMultiGraph() { final long seed = 5; GraphGenerator gen = new LinearizedChordDiagramGraphGenerator<>(10, 2, seed); Graph g = new DirectedMultigraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); } @Test(expected = IllegalArgumentException.class) public void testDirectedSimpleGraph() { final long seed = 5; GraphGenerator gen = new LinearizedChordDiagramGraphGenerator<>(10, 2, seed); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); } @Test public void testUndirected() { final long seed = 5; GraphGenerator gen = new LinearizedChordDiagramGraphGenerator<>(20, 1, seed); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(20, g.vertexSet().size()); } @Test public void testUndirectedTwoEdges() { final long seed = 5; GraphGenerator gen = new LinearizedChordDiagramGraphGenerator<>(20, 2, seed); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(20, g.vertexSet().size()); } @Test public void testDirected() { final long seed = 5; GraphGenerator gen = new LinearizedChordDiagramGraphGenerator<>(20, 1, seed); Graph g = new DirectedPseudograph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(20, g.vertexSet().size()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/NamedGraphGeneratorTest.java000066400000000000000000000245241402514743400333010ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.alg.isomorphism.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for NamedGraphGenerator * * @author Joris Kinable */ public class NamedGraphGeneratorTest { @Test public void testDoyleGraph() { Graph g = NamedGraphGenerator.doyleGraph(); this.validateBasics(g, 27, 54, 3, 3, 5); assertTrue(GraphTests.isEulerian(g)); validateAutomorphismCount(g, 54); } @Test public void testBullGraph() { Graph g = NamedGraphGenerator.bullGraph(); this.validateBasics(g, 5, 5, 2, 3, 3); } @Test public void testClawGraph() { Graph g = NamedGraphGenerator.clawGraph(); this.validateBasics(g, 4, 3, 1, 2, Integer.MAX_VALUE); assertTrue(GraphTests.isBipartite(g)); } @Test public void testBuckyBallGraph() { Graph g = NamedGraphGenerator.buckyBallGraph(); this.validateBasics(g, 60, 90, 9, 9, 5); assertTrue(GraphTests.isCubic(g)); } @Test public void testClebschGraph() { Graph g = NamedGraphGenerator.clebschGraph(); this.validateBasics(g, 16, 40, 2, 2, 4); validateAutomorphismCount(g, 1920); } @Test public void testGrötzschGraph() { Graph g = NamedGraphGenerator.grötzschGraph(); this.validateBasics(g, 11, 20, 2, 2, 4); } @Test public void testBidiakisCubeGraph() { Graph g = NamedGraphGenerator.bidiakisCubeGraph(); this.validateBasics(g, 12, 18, 3, 3, 4); assertTrue(GraphTests.isCubic(g)); } @Test public void testBlanusaFirstSnarkGraph() { Graph g = NamedGraphGenerator.blanusaFirstSnarkGraph(); this.validateBasics(g, 18, 27, 4, 4, 5); assertTrue(GraphTests.isCubic(g)); } @Test public void testBlanusaSecondSnarkGraph() { Graph g = NamedGraphGenerator.blanusaSecondSnarkGraph(); this.validateBasics(g, 18, 27, 4, 4, 5); assertTrue(GraphTests.isCubic(g)); } @Test public void testDoubleStarSnarkGraph() { Graph g = NamedGraphGenerator.doubleStarSnarkGraph(); this.validateBasics(g, 30, 45, 4, 4, 6); } @Test public void testBrinkmannGraph() { Graph g = NamedGraphGenerator.brinkmannGraph(); this.validateBasics(g, 21, 42, 3, 3, 5); assertTrue(GraphTests.isEulerian(g)); } @Test public void testGossetGraph() { Graph g = NamedGraphGenerator.gossetGraph(); this.validateBasics(g, 56, 756, 3, 3, 3); } @Test public void testChvatalGraph() { Graph g = NamedGraphGenerator.chvatalGraph(); this.validateBasics(g, 12, 24, 2, 2, 4); assertTrue(GraphTests.isEulerian(g)); } @Test public void testKittellGraph() { Graph g = NamedGraphGenerator.kittellGraph(); this.validateBasics(g, 23, 63, 3, 4, 3); } @Test public void testCoxeterGraph() { Graph g = NamedGraphGenerator.coxeterGraph(); this.validateBasics(g, 28, 42, 4, 4, 7); assertTrue(GraphTests.isCubic(g)); validateAutomorphismCount(g, 336); } @Test public void testDiamondGraph() { Graph g = NamedGraphGenerator.diamondGraph(); this.validateBasics(g, 4, 5, 1, 2, 3); } @Test public void testEllinghamHorton54Graph() { Graph g = NamedGraphGenerator.ellinghamHorton54Graph(); this.validateBasics(g, 54, 81, 9, 10, 6); assertTrue(GraphTests.isCubic(g)); assertTrue(GraphTests.isBipartite(g)); validateAutomorphismCount(g, 32); } @Test public void testEllinghamHorton78Graph() { Graph g = NamedGraphGenerator.ellinghamHorton78Graph(); this.validateBasics(g, 78, 117, 7, 13, 6); assertTrue(GraphTests.isCubic(g)); assertTrue(GraphTests.isBipartite(g)); validateAutomorphismCount(g, 16); } @Test public void testErreraGraph() { Graph g = NamedGraphGenerator.erreraGraph(); this.validateBasics(g, 17, 45, 3, 4, 3); } @Test public void testFolkmanGraph() { Graph g = NamedGraphGenerator.folkmanGraph(); this.validateBasics(g, 20, 40, 3, 4, 4); assertTrue(GraphTests.isBipartite(g)); assertTrue(GraphTests.isEulerian(g)); validateAutomorphismCount(g, 3840); } @Test public void testFranklinGraph() { Graph g = NamedGraphGenerator.franklinGraph(); this.validateBasics(g, 12, 18, 3, 3, 4); assertTrue(GraphTests.isCubic(g)); assertTrue(GraphTests.isBipartite(g)); validateAutomorphismCount(g, 48); } @Test public void testFrughtGraph() { Graph g = NamedGraphGenerator.fruchtGraph(); this.validateBasics(g, 12, 18, 3, 4, 3); assertTrue(GraphTests.isCubic(g)); } @Test public void testGoldnerHararyGraph() { Graph g = NamedGraphGenerator.goldnerHararyGraph(); this.validateBasics(g, 11, 27, 2, 2, 3); } @Test public void testHeawoodGraph() { Graph g = NamedGraphGenerator.heawoodGraph(); this.validateBasics(g, 14, 21, 3, 3, 6); assertTrue(GraphTests.isCubic(g)); assertTrue(GraphTests.isBipartite(g)); validateAutomorphismCount(g, 336); } @Test public void testHerschelGraph() { Graph g = NamedGraphGenerator.herschelGraph(); this.validateBasics(g, 11, 18, 3, 4, 4); assertTrue(GraphTests.isBipartite(g)); validateAutomorphismCount(g, 12); } @Test public void testHoffmanGraph() { Graph g = NamedGraphGenerator.hoffmanGraph(); this.validateBasics(g, 16, 32, 3, 4, 4); assertTrue(GraphTests.isBipartite(g)); validateAutomorphismCount(g, 48); } @Test public void testKrackhardtKiteGraph() { Graph g = NamedGraphGenerator.krackhardtKiteGraph(); this.validateBasics(g, 10, 18, 2, 4, 3); } @Test public void testKlein3RegularGraph() { Graph g = NamedGraphGenerator.klein3RegularGraph(); this.validateBasics(g, 56, 84, 6, 6, 7); assertTrue(GraphTests.isCubic(g)); validateAutomorphismCount(g, 336); } @Test public void testKlein7RegularGraph() { Graph g = NamedGraphGenerator.klein7RegularGraph(); this.validateBasics(g, 24, 84, 3, 3, 3); validateAutomorphismCount(g, 336); } @Test public void testMoserSpindleGraph() { Graph g = NamedGraphGenerator.moserSpindleGraph(); this.validateBasics(g, 7, 11, 2, 2, 3); validateAutomorphismCount(g, 8); } @Test public void testPappusGraph() { Graph g = NamedGraphGenerator.pappusGraph(); this.validateBasics(g, 18, 27, 4, 4, 6); assertTrue(GraphTests.isCubic(g)); assertTrue(GraphTests.isBipartite(g)); validateAutomorphismCount(g, 216); } @Test public void testPoussinGraph() { Graph g = NamedGraphGenerator.poussinGraph(); this.validateBasics(g, 15, 39, 3, 3, 3); } @Test public void testSchläfliGraph() { Graph g = NamedGraphGenerator.schläfliGraph(); this.validateBasics(g, 27, 216, 2, 2, 3); } @Test public void testTietzeGraph() { Graph g = NamedGraphGenerator.tietzeGraph(); this.validateBasics(g, 12, 18, 3, 3, 3); assertTrue(GraphTests.isCubic(g)); } @Test public void testTutteGraph() { Graph g = NamedGraphGenerator.tutteGraph(); this.validateBasics(g, 46, 69, 5, 8, 4); assertTrue(GraphTests.isCubic(g)); } @Test public void testThomsenGraph() { Graph g = NamedGraphGenerator.thomsenGraph(); this.validateBasics(g, 6, 9, 2, 2, 4); assertTrue(GraphTests.isBipartite(g)); } private void validateBasics( Graph g, int vertices, int edges, int radius, int diameter, double girth) { assertEquals(vertices, g.vertexSet().size()); assertEquals(edges, g.edgeSet().size()); GraphMeasurer gm = new GraphMeasurer<>(g); assertEquals(radius, gm.getRadius(), 0.00000001); assertEquals(diameter, gm.getDiameter(), 0.00000001); assertEquals(girth, GraphMetrics.getGirth(g), 0.00000001); } private void validateAutomorphismCount(Graph g, int value) { VF2GraphIsomorphismInspector vf = new VF2GraphIsomorphismInspector<>(g, g); Iterator> iter = vf.getMappings(); int count = 0; while (iter.hasNext()) { count++; iter.next(); } assertEquals(count, value); } } PlantedPartitionGraphGeneratorTest.java000066400000000000000000000275511402514743400354620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2018-2021, by Emilio Cruciani and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * @author Emilio Cruciani */ public class PlantedPartitionGraphGeneratorTest { private final long SEED = 5; /* bad inputs */ @Test(expected = IllegalArgumentException.class) public void testNegativeL() { new PlantedPartitionGraphGenerator<>(-5, 10, 0.5, 0.1); } @Test(expected = IllegalArgumentException.class) public void testNegativeK() { new PlantedPartitionGraphGenerator<>(5, -10, 0.5, 0.1); } @Test(expected = IllegalArgumentException.class) public void testNegativeP() { new PlantedPartitionGraphGenerator<>(5, 10, -0.5, 0.1); } @Test(expected = IllegalArgumentException.class) public void testNegativeQ() { new PlantedPartitionGraphGenerator<>(5, 10, 0.5, -0.1); } @Test(expected = IllegalArgumentException.class) public void testTooLargeP() { new PlantedPartitionGraphGenerator<>(5, 10, 1.5, 0.1); } @Test(expected = IllegalArgumentException.class) public void testTooLargeQ() { new PlantedPartitionGraphGenerator<>(5, 10, 0.5, 1.1); } @Test public void testSelfLoopContradiction() { GraphGenerator gen = new PlantedPartitionGraphGenerator<>(5, 10, 0.5, 0.1, true); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); try { gen.generateGraph(g); fail("gen.generateGraph() did not throw an IllegalArgumentException as expected"); } catch (IllegalArgumentException e) { } } /* empty graphs */ @Test public void testZeroL() { int l = 0; int k = 10; double p = 0.5; double q = 0.1; GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(0, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } @Test public void testZeroK() { int l = 5; int k = 0; double p = 0.5; double q = 0.1; GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(0, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } /* simple graphs */ @Test public void testZeroPSimple() { int l = 5; int k = 10; double p = 0.0; double q = 0.1; int edges = k * k * l * (l - 1) / 2; GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); assertTrue(g.edgeSet().size() <= edges); } @Test public void testZeroQSimple() { int l = 5; int k = 10; double p = 0.5; double q = 0.0; int edges = l * k * (k - 1) / 2; GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); assertTrue(g.edgeSet().size() <= edges); } @Test public void testOnePSimple() { int l = 5; int k = 10; double p = 1.0; double q = 0.1; int edges = l * k * (k - 1) / 2; GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); assertTrue(g.edgeSet().size() >= edges); } @Test public void testOneQSimple() { int l = 5; int k = 10; double p = 0.5; double q = 1.0; int edges = k * k * l * (l - 1) / 2; GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); assertTrue(g.edgeSet().size() >= edges); } /* directed graphs */ @Test public void testZeroPDefault() { int l = 5; int k = 10; double p = 0.0; double q = 0.1; int edges = k * k * l * (l - 1); GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); assertTrue(g.edgeSet().size() <= edges); } @Test public void testZeroQDefault() { int l = 5; int k = 10; double p = 0.5; double q = 0.0; int edges = l * k * (k - 1); GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); assertTrue(g.edgeSet().size() <= edges); } @Test public void testOnePDefault() { int l = 5; int k = 10; double p = 1.0; double q = 0.1; int edges = l * k * (k - 1); GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); assertTrue(g.edgeSet().size() >= edges); } @Test public void testOneQDefault() { int l = 5; int k = 10; double p = 0.5; double q = 1.0; int edges = k * k * l * (l - 1); GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); assertTrue(g.edgeSet().size() >= edges); } /* complete graphs */ @Test public void testCompleteSimpleGraph() { int l = 5; int k = 10; double p = 1.0; double q = 1.0; int d = l * k - 1; GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); for (Integer v : g.vertexSet()) { assertEquals(d, g.degreeOf(v)); } } @Test public void testCompleteDefaultDirectedGraph() { int l = 5; int k = 10; double p = 1.0; double q = 1.0; int d = 2 * (l * k - 1); GraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(l * k, g.vertexSet().size()); for (Integer v : g.vertexSet()) { assertEquals(d, g.degreeOf(v)); } } /* test getCommunities() */ @Test public void testGetCommunities() { int l = 5; int k = 10; double p = 0.5; double q = 0.1; List> groundTruthCommunities = new ArrayList<>(l); for (int i = 0; i < l; i++) { groundTruthCommunities.add(CollectionUtil.newLinkedHashSetWithExpectedSize(k)); for (int j = 0; j < k; j++) { groundTruthCommunities.get(i).add(i * k + j); } } PlantedPartitionGraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(groundTruthCommunities, gen.getCommunities()); } @Test public void testCallGetCommunitiesBeforeGenerateGraph() { int l = 5; int k = 10; double p = 0.5; double q = 0.1; PlantedPartitionGraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); try { List> communities = gen.getCommunities(); fail("gen.getCommunities() did not throw an IllegalStateException as expected"); } catch (IllegalStateException e) { } } @Test public void testCallGetCommunitiesMoreThanOnce() { int l = 5; int k = 10; double p = 0.5; double q = 0.1; PlantedPartitionGraphGenerator gen = new PlantedPartitionGraphGenerator<>(l, k, p, q, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); Graph f = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); try { gen.generateGraph(f); fail("gen.getCommunities() did not throw an IllegalStateException as expected"); } catch (IllegalStateException e) { } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/PruferTreeGeneratorTest.java000066400000000000000000000136721402514743400333600ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; /** * Tests for {@link PruferTreeGenerator} * * @author Alexandru Valeanu */ public class PruferTreeGeneratorTest { @Test(expected = IllegalArgumentException.class) public void testNullPruferSequence() { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(null); } @Test public void testEmptyPruferSequence() { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(new int[] {}); generator.generateGraph(tree); Assert.assertEquals(2, tree.vertexSet().size()); } @Test(expected = IllegalArgumentException.class) public void testInvalidPruferSequence() { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(new int[] { 10 }); } @Test public void testPruferSequence() { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(new int[] { 4, 4, 4, 5 }); generator.generateGraph(tree); Assert.assertEquals(6, tree.vertexSet().size()); int[] degrees = tree.vertexSet().stream().mapToInt(tree::degreeOf).toArray(); Arrays.sort(degrees); Assert.assertArrayEquals(new int[] { 1, 1, 1, 1, 2, 4 }, degrees); } @Test(expected = IllegalArgumentException.class) public void testZeroVertices() { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(0); } @Test(expected = NullPointerException.class) public void testNullRNG() { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(100, null); } @Test(expected = IllegalArgumentException.class) public void testDirectedGraph() { Graph tree = new DirectedAcyclicGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(10); generator.generateGraph(tree); } @Test(expected = NullPointerException.class) public void testNullGraph() { PruferTreeGenerator generator = new PruferTreeGenerator<>(10); generator.generateGraph(null); } @Test public void testOneVertex() { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(1, 0x99); generator.generateGraph(tree); Assert.assertTrue(GraphTests.isTree(tree)); } @Test(expected = IllegalArgumentException.class) public void testExistingVertices() { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CompleteGraphGenerator completeGraphGenerator = new CompleteGraphGenerator<>(10); completeGraphGenerator.generateGraph(tree); PruferTreeGenerator generator = new PruferTreeGenerator<>(100, 0x99); generator.generateGraph(tree); } @Test public void testRandomSizes() { Random random = new Random(0x88); final int NUM_TESTS = 500; for (int test = 0; test < NUM_TESTS; test++) { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(1 + random.nextInt(5000), random); generator.generateGraph(tree); Assert.assertTrue(GraphTests.isTree(tree)); } } @Test public void testHugeSize() { Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); PruferTreeGenerator generator = new PruferTreeGenerator<>(100_000, 0x99); generator.generateGraph(tree); Assert.assertTrue(GraphTests.isTree(tree)); } } RandomRegularGraphGeneratorTest.java000066400000000000000000000120721402514743400347330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2018-2021, by Emilio Cruciani and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Tests for {@link RandomRegularGraphGenerator}. * * @author Emilio Cruciani */ public class RandomRegularGraphGeneratorTest { private final long SEED = 5; @Test(expected = IllegalArgumentException.class) public void testNegativeN() { new RandomRegularGraphGenerator<>(-10, 1); } @Test(expected = IllegalArgumentException.class) public void testNegativeD() { new RandomRegularGraphGenerator<>(10, -1); } @Test(expected = IllegalArgumentException.class) public void testDGreaterThanN() { new RandomRegularGraphGenerator<>(10, 15); } @Test(expected = IllegalArgumentException.class) public void testOddDTimesN() { new RandomRegularGraphGenerator<>(5, 3); } @Test public void testDirectedGraph() { GraphGenerator gen = new RandomRegularGraphGenerator<>(10, 2); Graph g = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); try { gen.generateGraph(g); fail("gen.generateGraph() did not throw an IllegalArgumentException as expected"); } catch (IllegalArgumentException e) { } } @Test public void testPseudograph() { int n = 100; int d = 20; GraphGenerator gen = new RandomRegularGraphGenerator<>(n, d, SEED); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); for (Integer v : g.vertexSet()) { assertEquals(d, g.degreeOf(v)); } } @Test public void testCompletePseudograph() { int n = 10; int d = n; GraphGenerator gen = new RandomRegularGraphGenerator<>(n, d, SEED); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); for (Integer v : g.vertexSet()) { assertEquals(d, g.degreeOf(v)); } } @Test public void testSimpleGraph() { int n = 100; int d = 20; GraphGenerator gen = new RandomRegularGraphGenerator<>(n, d, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); for (Integer v : g.vertexSet()) { assertEquals(d, g.degreeOf(v)); } } @Test public void testCompleteSimpleGraph() { int n = 10; int d = n - 1; GraphGenerator gen = new RandomRegularGraphGenerator<>(n, d, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); for (Integer v : g.vertexSet()) { assertEquals(d, g.degreeOf(v)); } } @Test public void testZeroNodes() { int n = 0; int d = 0; GraphGenerator gen = new RandomRegularGraphGenerator<>(n, d, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(0, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } @Test public void testZeroDegree() { int n = 10; int d = 0; GraphGenerator gen = new RandomRegularGraphGenerator<>(n, d, SEED); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gen.generateGraph(g); assertEquals(n, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); } } WattsStrogatzGraphGeneratorTest.java000066400000000000000000000140071402514743400350310ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * @author Dimitrios Michail */ public class WattsStrogatzGraphGeneratorTest { @Test(expected = IllegalArgumentException.class) public void testLessThan3Nodes() { new WattsStrogatzGraphGenerator<>(2, 1, 0.5); } @Test public void testBadParameters() { try { new WattsStrogatzGraphGenerator<>(-1, 2, 0.5); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new WattsStrogatzGraphGenerator<>(10, 9, 0.5); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new WattsStrogatzGraphGenerator<>(10, 9, 0.5); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new WattsStrogatzGraphGenerator<>(11, 11, 0.5); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new WattsStrogatzGraphGenerator<>(10, 2, -1.0); fail("Bad parameter"); } catch (IllegalArgumentException e) { } try { new WattsStrogatzGraphGenerator<>(10, 2, 2.0); fail("Bad parameter"); } catch (IllegalArgumentException e) { } } @Test public void test4RegularNoRewiring() { final long seed = 5; GraphGenerator gen = new WattsStrogatzGraphGenerator<>(6, 4, 0.0, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertEquals(12, g.edgeSet().size()); } @Test public void test4RegularSomeRewiring() { final long seed = 5; GraphGenerator gen = new WattsStrogatzGraphGenerator<>(6, 4, 0.5, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertEquals(12, g.edgeSet().size()); } @Test public void test4RegularMoreRewiring() { final long seed = 5; GraphGenerator gen = new WattsStrogatzGraphGenerator<>(6, 4, 0.8, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertEquals(12, g.edgeSet().size()); } @Test public void test4RegularAddShortcutInsteadOfRewiring() { final long seed = 5; GraphGenerator gen = new WattsStrogatzGraphGenerator<>(6, 4, 0.5, true, new Random(seed)); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); } @Test public void test6RegularNoRewiring() { final long seed = 5; GraphGenerator gen = new WattsStrogatzGraphGenerator<>(12, 6, 0.0, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(12, g.vertexSet().size()); assertEquals(36, g.edgeSet().size()); } @Test public void test6RegularSomeRewiring() { final long seed = 5; GraphGenerator gen = new WattsStrogatzGraphGenerator<>(12, 6, 0.7, seed); Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(12, g.vertexSet().size()); assertEquals(36, g.edgeSet().size()); } @Test public void test4RegularNoRewiringDirected() { final long seed = 5; GraphGenerator gen = new WattsStrogatzGraphGenerator<>(6, 4, 0.0, seed); Graph g = new SimpleDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(6, g.vertexSet().size()); assertEquals(12, g.edgeSet().size()); } @Test public void testNonIntegerVertices() { final long seed = 5; GraphGenerator gen = new WattsStrogatzGraphGenerator<>(10, 2, 0.1, seed); Graph g = new SimpleGraph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); gen.generateGraph(g); assertEquals(10, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); } } WindmillGraphsGeneratorTest.java000066400000000000000000000133711402514743400341360ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate; import org.jgrapht.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for GeneralizedPetersenGraphGenerator * * @author Joris Kinable */ public class WindmillGraphsGeneratorTest { @Test public void testCubicalGraph() { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); GeneralizedPetersenGraphGenerator gpgg = new GeneralizedPetersenGraphGenerator<>(4, 1); gpgg.generateGraph(g); this.validateBasics(g, 8, 12, 3, 3, 4); assertTrue(GraphTests.isBipartite(g)); assertTrue(GraphTests.isCubic(g)); } // --------------Tests for Windmill graphs --------------------- @Test public void testGraph1a() { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); new WindmillGraphsGenerator( WindmillGraphsGenerator.Mode.WINDMILL, 3, 4).generateGraph(g); assertEquals(10, g.vertexSet().size()); assertEquals(18, g.edgeSet().size()); this.verifyVertexDegree(g, WindmillGraphsGenerator.Mode.WINDMILL, 3, 4); } @Test public void testGraph2a() { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); new WindmillGraphsGenerator( WindmillGraphsGenerator.Mode.WINDMILL, 4, 3).generateGraph(g); assertEquals(9, g.vertexSet().size()); assertEquals(12, g.edgeSet().size()); this.verifyVertexDegree(g, WindmillGraphsGenerator.Mode.WINDMILL, 4, 3); } @Test public void testGraph3a() { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); new WindmillGraphsGenerator( WindmillGraphsGenerator.Mode.WINDMILL, 3, 5).generateGraph(g); assertEquals(13, g.vertexSet().size()); assertEquals(30, g.edgeSet().size()); this.verifyVertexDegree(g, WindmillGraphsGenerator.Mode.WINDMILL, 3, 5); } // --------------Tests for Dutch Windmill Graphs --------------- @Test public void testButterflyGraph() { Graph g = NamedGraphGenerator.butterflyGraph(); this.validateBasics(g, 5, 6, 1, 2, 3); this.verifyVertexDegree(g, WindmillGraphsGenerator.Mode.DUTCHWINDMILL, 2, 3); assertTrue(GraphTests.isEulerian(g)); } @Test public void testGraph2b() { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); new WindmillGraphsGenerator( WindmillGraphsGenerator.Mode.DUTCHWINDMILL, 4, 3).generateGraph(g); assertEquals(9, g.vertexSet().size()); assertEquals(12, g.edgeSet().size()); this.verifyVertexDegree(g, WindmillGraphsGenerator.Mode.DUTCHWINDMILL, 4, 3); } @Test public void testGraph3b() { Graph g = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); new WindmillGraphsGenerator( WindmillGraphsGenerator.Mode.DUTCHWINDMILL, 3, 5).generateGraph(g); assertEquals(13, g.vertexSet().size()); assertEquals(15, g.edgeSet().size()); this.verifyVertexDegree(g, WindmillGraphsGenerator.Mode.DUTCHWINDMILL, 3, 5); } private void validateBasics( Graph g, int vertices, int edges, int radius, int diameter, int girt) { assertEquals(vertices, g.vertexSet().size()); assertEquals(edges, g.edgeSet().size()); GraphMeasurer gm = new GraphMeasurer<>(g); assertEquals(radius, gm.getRadius(), 0.00000001); assertEquals(diameter, gm.getDiameter(), 0.00000001); assertEquals(girt, GraphMetrics.getGirth(g), 0.00000001); } private void verifyVertexDegree(Graph g, WindmillGraphsGenerator.Mode mode, int m, int n) { List vertices = new ArrayList<>(g.vertexSet()); if (mode == WindmillGraphsGenerator.Mode.DUTCHWINDMILL) { assertEquals(2 * m, g.degreeOf(vertices.get(0))); // degree of center vertex for (int i = 1; i < vertices.size(); i++) assertEquals(2, g.degreeOf(vertices.get(i))); // degree of other vertices } else { assertEquals(m * (n - 1), g.degreeOf(vertices.get(0))); // degree of center vertex for (int i = 1; i < vertices.size(); i++) assertEquals(n - 1, g.degreeOf(vertices.get(i))); // degree of other vertices } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/000077500000000000000000000000001402514743400271725ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/DistributorTest.java000066400000000000000000000141401402514743400332070ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; import org.junit.Test; import java.util.List; import java.util.Random; import java.util.stream.Collectors; import java.util.stream.IntStream; import static org.junit.Assert.*; /** * Tests for {@link Distributor}. * * @author Timofey Chudakov */ public class DistributorTest { private static final long SEED = 1; private final Random rng = new Random(SEED); @Test public void testDistributor_NoUpperBounds_OneValidDistribution() { Distributor distributor = new Distributor<>(rng); distributor.addLowerBound(element -> { switch (element) { case "a": return 3; case "b": return 4; case "c": return 2; default: return 0; } }); List distribution = distributor.getDistribution(List.of("a", "b", "c"), 9); assertEquals(List.of(3, 4, 2), distribution); } @Test(expected = IllegalArgumentException.class) public void testDistributor_NoUpperBounds_NoValidDistribution() { Distributor distributor = new Distributor<>(rng); distributor.addLowerBound(element -> { switch (element) { case 1: return 5; case 2: return 6; case 3: return 2; default: return 0; } }); List distribution = distributor.getDistribution(List.of(1, 2, 3), 12); } @Test public void testDistributor_NoLowerBounds_OneValidDistribution() { Distributor distributor = new Distributor<>(rng); distributor.addLowerBound(element -> { switch (element) { case 1: return 3; case 2: return 5; case 3: return 2; default: return 0; } }); List distribution = distributor.getDistribution(List.of(1, 2, 3), 10); assertEquals(List.of(3, 5, 2), distribution); } @Test(expected = IllegalArgumentException.class) public void testDistributor_NoLowerBounds_NoValidDistribution() { Distributor distributor = new Distributor<>(rng); distributor.addLowerBound(element -> { switch (element) { case 1: return 3; case 2: return 4; case 3: return 2; default: return 0; } }); List distribution = distributor.getDistribution(List.of(1, 2, 3), 8); } @Test public void testDistributor_AllBounds1() { Distributor distributor = new Distributor<>(rng); distributor.addLowerBound(element -> 5); distributor.addUpperBound(element -> 10); int elementNum = 10; int valueNum = 5 * elementNum; List dist = distributor .getDistribution( IntStream.range(0, elementNum).boxed().collect(Collectors.toList()), valueNum); int sum = dist.stream().mapToInt(i -> i).sum(); assertEquals(sum, valueNum); for (int assignedValues : dist) { assertEquals(5, assignedValues); } } @Test public void testDistributor_AllBounds2() { Distributor distributor = new Distributor<>(rng); distributor.addLowerBound(element -> 5); distributor.addUpperBound(element -> 10); int elementNum = 10; int valueNum = 10 * elementNum; List dist = distributor .getDistribution( IntStream.range(0, elementNum).boxed().collect(Collectors.toList()), valueNum); int sum = dist.stream().mapToInt(i -> i).sum(); assertEquals(sum, valueNum); for (int assignedValues : dist) { assertEquals(10, assignedValues); } } @Test public void testDistributor_AllBounds3() { Distributor distributor = new Distributor<>(rng); distributor.addLowerBound(element -> 5); distributor.addUpperBound(element -> 10); int elementNum = 10; int valueNum = 8 * elementNum; List dist = distributor .getDistribution( IntStream.range(0, elementNum).boxed().collect(Collectors.toList()), valueNum); int sum = dist.stream().mapToInt(i -> i).sum(); assertEquals(sum, valueNum); for (int assignedValues : dist) { assertTrue(assignedValues >= 5); assertTrue(assignedValues <= 10); } } @Test public void testDistributor_AllBounds_LargeBounds() { Distributor distributor = new Distributor<>(rng); int lb = 1000 * 1000; int ub = 2 * 1000 * 1000; distributor.addLowerBound(element -> lb); distributor.addUpperBound(element -> ub); int elementNum = 1000; int valueNum = ((lb + ub) / 2) * elementNum; List dist = distributor .getDistribution( IntStream.range(0, elementNum).boxed().collect(Collectors.toList()), valueNum); int sum = dist.stream().mapToInt(i -> i).sum(); assertEquals(sum, valueNum); for (int assignedValues : dist) { assertTrue(assignedValues >= lb); assertTrue(assignedValues <= ub); } } } NetworkGeneratorConfigBuilderTest.java000066400000000000000000000344741402514743400365670ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; import org.junit.Test; import static org.jgrapht.generate.netgen.NetworkGenerator.*; /** * Tests for {@link NetworkGeneratorConfigBuilder} * * @author Timofey Chudakov */ public class NetworkGeneratorConfigBuilderTest { private NetworkGeneratorConfigBuilder getBuilder( int nodeNum, int arcNum, int sourceNum, int sinkNum, int tSourceNum, int tSinkNum, int supply, int minCap, int maxCap, int minCost, int maxCost, int pCapacitated, int pWithInfCost) { return new NetworkGeneratorConfigBuilder() .setNodeNum(nodeNum).setArcNum(arcNum).setSourceNum(sourceNum).setSinkNum(sinkNum) .setTSourceNum(tSourceNum).setTSinkNum(tSinkNum).setTotalSupply(supply) .setMinCap(minCap).setMaxCap(maxCap).setMinCost(minCost).setMaxCost(maxCost) .setPercentCapacitated(pCapacitated).setPercentWithInfCost(pWithInfCost); } private NetworkGeneratorConfigBuilder getAssignmentBuilder() { return getBuilder(4, 4, 2, 2, 0, 0, 2, 1, 1, 0, 0, 100, 0); } private NetworkGeneratorConfigBuilder getMinCostFlowBuilder() { return getBuilder(10, 20, 2, 3, 1, 1, 50, 1, 10, 0, 10, 100, 0); } // -------------------------- node num tests -------------------------- @Test(expected = IllegalArgumentException.class) public void testNodeNum_NodeNumNotSet_IllegalArgumentException() { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setArcNum(20).setSourceNum(2).setSinkNum(3).setTSourceNum(1).setTSinkNum(1) .setTotalSupply(50).setMinCap(1).setMaxCap(10).setMinCost(0).setMaxCost(10) .setPercentCapacitated(100).setPercentWithInfCost(0).build(); } @Test(expected = IllegalArgumentException.class) public void testNodeNum_NegativeNodeNum_IllegalArgumentException() { getMinCostFlowBuilder().setNodeNum(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testNodeNum_TooHighNodeNum_IllegalArgumentException() { getMinCostFlowBuilder().setNodeNum(MAX_NODE_NUM + 1).build(); } // -------------------------- arc num tests -------------------------- @Test(expected = IllegalArgumentException.class) public void testArcNum_ArcNumNotSet_IllegalArgumentException() { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setNodeNum(10).setSourceNum(2).setSinkNum(3).setTSourceNum(1).setTSinkNum(1) .setTotalSupply(50).setMinCap(1).setMaxCap(10).setMinCost(0).setMaxCost(10) .setPercentCapacitated(100).setPercentWithInfCost(0).build(); } @Test(expected = IllegalArgumentException.class) public void testArcNum_NegativeArcNum_IllegalArgumentException() { getMinCostFlowBuilder().setArcNum(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testArcNum_TooHighArcNum_IllegalArgumentException() { getMinCostFlowBuilder().setArcNum(MAX_ARC_NUM + 1).build(); } @Test(expected = IllegalArgumentException.class) public void testArcNum_TooFewArcsInAssignmentProblem_IllegalArgumentException() { getAssignmentBuilder().setArcNum(1).build(); } @Test public void testArcNum_MinimumNumberOfArcsInAssignmentProblem_Ok() { getAssignmentBuilder().setArcNum(2).build(); } @Test(expected = IllegalArgumentException.class) public void testArcNum_TooManyArcsInAssignmentProblem_IllegalArgumentException() { getAssignmentBuilder().setArcNum(5).build(); } @Test public void testArcNum_MaximumNumberOfArcsInAssignmentProblem_Ok() { getAssignmentBuilder().setArcNum(4).build(); } @Test(expected = IllegalArgumentException.class) public void testArcNum_TooFewArcsInMinCostFlowProblem_IllegalArgumentException() { getMinCostFlowBuilder().setArcNum(7).build(); } @Test public void testArcNum_MinimumNumberOfArcsInMinCostFlowProblem_Ok() { getMinCostFlowBuilder().setArcNum(8).build(); } @Test(expected = IllegalArgumentException.class) public void testArcNum_TooManyArcsInMinCostFlowProblem_IllegalArgumentException() { getMinCostFlowBuilder().setArcNum(9 + 8 + 40 + 8 + 1).build(); } @Test public void testArcNum_MaximumNumberOfArcsInMinCostFlowProblem_Ok() { getMinCostFlowBuilder().setArcNum(9 + 8 + 40 + 8).build(); } // -------------------------- source and sink node num tests -------------------------- @Test(expected = IllegalArgumentException.class) public void testSourceNum_SourceNumNotSet_IllegalArgumentException() { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setNodeNum(10).setArcNum(20).setSinkNum(3).setTSourceNum(1).setTSinkNum(1) .setTotalSupply(50).setMinCap(1).setMaxCap(10).setMinCost(0).setMaxCost(10) .setPercentCapacitated(100).setPercentWithInfCost(0).build(); } @Test(expected = IllegalArgumentException.class) public void testSourceNum_NegativeSourceNum_IllegalArgumentException() { getMinCostFlowBuilder().setSourceNum(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testSourceNum_SourceNumGreaterThanNodeNum_IllegalArgumentException() { getMinCostFlowBuilder().setSourceNum(11).build(); } @Test(expected = IllegalArgumentException.class) public void testSourceNum_SinkNumNotSet_IllegalArgumentException() { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setNodeNum(10).setArcNum(20).setSourceNum(2).setTSourceNum(1).setTSinkNum(1) .setTotalSupply(50).setMinCap(1).setMaxCap(10).setMinCost(0).setMaxCost(10) .setPercentCapacitated(100).setPercentWithInfCost(0).build(); } @Test(expected = IllegalArgumentException.class) public void testSinkNum_NegativeSinkNum_IllegalArgumentException() { getMinCostFlowBuilder().setSinkNum(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testSinkNum_SinkNumGreaterThanNodeNum_IllegalArgumentException() { getMinCostFlowBuilder().setSinkNum(11).build(); } @Test(expected = IllegalArgumentException.class) public void testSourceSinkNum_SourceNumPlusSinkNumGreaterThanTheNodeNum_IllegalArgumentException() { getMinCostFlowBuilder().setSourceNum(5).setSinkNum(6).build(); } // -------------------------- transshipment source and sinks test -------------------------- @Test(expected = IllegalArgumentException.class) public void testTransshipmentSourceNum_NegativeTransshipmentSourceNum_IllegalArgumentException() { getMinCostFlowBuilder().setTSourceNum(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testTransshipmentSourceNum_TransshipmentSourceNumGreaterThanSourceNum_IllegalArgumentException() { getMinCostFlowBuilder().setTSourceNum(3).build(); } @Test(expected = IllegalArgumentException.class) public void testTransshipmentSinkNum_NegativeTransshipmentSinkNum_IllegalArgumentException() { getMinCostFlowBuilder().setTSinkNum(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testTransshipmentSinkNum_TransshipmentSinkNumGreaterThanSourceNum_IllegalArgumentException() { getMinCostFlowBuilder().setTSinkNum(4).build(); } // -------------------------- supply tests -------------------------- @Test(expected = IllegalArgumentException.class) public void testSupply_SupplyNotSet_IllegalArgumentException() { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setNodeNum(10).setArcNum(20).setSourceNum(2).setSinkNum(3).setTSourceNum(1) .setTSinkNum(1).setMinCap(1).setMaxCap(10).setMinCost(0).setMaxCost(10) .setPercentCapacitated(100).setPercentWithInfCost(0).build(); } @Test(expected = IllegalArgumentException.class) public void testSupply_NegativeSupply_IllegalArgumentException() { getMinCostFlowBuilder().setTotalSupply(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testSupply_TooHighSupply_IllegalArgumentException() { getMinCostFlowBuilder().setTotalSupply(MAX_SUPPLY + 1).build(); } @Test public void testSupply_MaximumSupply_Ok() { getMinCostFlowBuilder().setTotalSupply(MAX_SUPPLY).build(); } @Test(expected = IllegalArgumentException.class) public void testSupply_SupplySmallerThanSourceNodeNum_IllegalArgumentException() { getMinCostFlowBuilder().setTotalSupply(1).build(); } // -------------------------- capacities tests -------------------------- @Test(expected = IllegalArgumentException.class) public void testCapacities_NegativeMinimumCapacity_IllegalArgumentException() { getMinCostFlowBuilder().setMinCap(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testCapacities_NegativeMaximumCapacity_IllegalArgumentException() { getMinCostFlowBuilder().setMaxCap(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testCapacities_MinimumCapacityNotSet_IllegalArgumentException() { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setNodeNum(10).setArcNum(20).setSourceNum(2).setSinkNum(3).setTSourceNum(1) .setTSinkNum(1).setTotalSupply(50).setMaxCap(10).setMinCost(0).setMaxCost(10) .setPercentCapacitated(100).setPercentWithInfCost(0).build(); } @Test(expected = IllegalArgumentException.class) public void testCapacities_MaximumCapacityNotSet_IllegalArgumentException() { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setNodeNum(10).setArcNum(20).setSourceNum(2).setSinkNum(3).setTSourceNum(1) .setTSinkNum(1).setTotalSupply(50).setMaxCap(10).setMinCost(0).setMaxCost(10) .setPercentCapacitated(100).setPercentWithInfCost(0).build(); } @Test(expected = IllegalArgumentException.class) public void testCapacities_TooHighMinimumCapacity_IllegalArgumentException() { getMinCostFlowBuilder().setMinCap(CAPACITY_COST_BOUND + 1); } @Test public void testCapacities_MaximumMinimumCapacity_Ok() { getMinCostFlowBuilder() .setMinCap(CAPACITY_COST_BOUND).setMaxCap(CAPACITY_COST_BOUND).build(); } @Test(expected = IllegalArgumentException.class) public void testCapacities_TooHighMaximumCapacity_IllegalArgumentException() { getMinCostFlowBuilder().setMaxCap(CAPACITY_COST_BOUND + 1); } @Test public void testCapacities_MaximumMaximumCapacity_Ok() { getMinCostFlowBuilder().setMaxCap(CAPACITY_COST_BOUND).build(); } @Test(expected = IllegalArgumentException.class) public void testCapacities_MinimumCapacityGreaterThatMaximumCapacity_IllegalArgumentException() { getMinCostFlowBuilder().setMinCap(10).setMaxCap(9).build(); } // -------------------------- costs tests -------------------------- @Test(expected = IllegalArgumentException.class) public void testCosts_TooLowMinimumCost_IllegalArgumentException() { getMinCostFlowBuilder().setMinCost(-CAPACITY_COST_BOUND - 1).build(); } @Test(expected = IllegalArgumentException.class) public void testCosts_TooLowMaximumCost_IllegalArgumentException() { getMinCostFlowBuilder().setMaxCost(-CAPACITY_COST_BOUND - 1).build(); } @Test(expected = IllegalArgumentException.class) public void testCosts_TooHighMinimumCost_IllegalArgumentException() { getMinCostFlowBuilder().setMinCost(CAPACITY_COST_BOUND + 1).build(); } @Test(expected = IllegalArgumentException.class) public void testCosts_TooHighMaximumCost_IllegalArgumentException() { getMinCostFlowBuilder().setMaxCost(CAPACITY_COST_BOUND + 1).build(); } @Test(expected = IllegalArgumentException.class) public void testCosts_MinimumCostGreaterThanMaximumCost_IllegalArgumentException() { getMinCostFlowBuilder().setMinCost(10).setMaxCost(9).build(); } // -------------------------- percent capacitated tests -------------------------- @Test(expected = IllegalArgumentException.class) public void testPercentCapacitated_NegativeValue_IllegalArgumentException() { getMinCostFlowBuilder().setPercentCapacitated(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testPercentCapacitated_TooHighValue_IllegalArgumentException() { getMinCostFlowBuilder().setPercentCapacitated(101).build(); } // -------------------------- percent with inf cost tests -------------------------- @Test(expected = IllegalArgumentException.class) public void testPercentWithInfCost_NegativeValue_IllegalArgumentException() { getMinCostFlowBuilder().setPercentWithInfCost(-1).build(); } @Test(expected = IllegalArgumentException.class) public void testPercentWithInfCost_TooHighValue_IllegalArgumentException() { getMinCostFlowBuilder().setPercentWithInfCost(101).build(); } // -------------------------- positive tests -------------------------- @Test public void testAssignmentConfig_Ok() { getAssignmentBuilder().build(); } @Test public void testMinCostFlowConfig_Ok() { getMinCostFlowBuilder().build(); } @Test public void test() { NetworkGeneratorConfig config = getAssignmentBuilder().build(); System.out.println(config.getMaximumArcNum()); } } NetworkGeneratorTest.java000066400000000000000000000512651402514743400341270ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/generate/netgen/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.generate.netgen; import org.jgrapht.Graph; import org.jgrapht.alg.flow.PushRelabelMFImpl; import org.jgrapht.alg.flow.mincost.MinimumCostFlowProblem; import org.jgrapht.alg.interfaces.MatchingAlgorithm; import org.jgrapht.alg.interfaces.MaximumFlowAlgorithm; import org.jgrapht.alg.matching.HopcroftKarpMaximumCardinalityBipartiteMatching; import org.jgrapht.graph.AsUndirectedGraph; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DefaultDirectedWeightedGraph; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.util.SupplierUtil; import org.junit.Test; import java.util.*; import java.util.function.Function; import static org.jgrapht.generate.netgen.NetworkGenerator.MAX_SUPPLY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for the {@link NetworkGenerator} * * @author Timofey Chudakov */ public class NetworkGeneratorTest { private static final long SEED = 1; private static final double EPS = 1e-9; private final Random rng = new Random(SEED); private static void validateNetwork( Graph network, NetworkInfo networkInfo, NetworkGeneratorConfig config) { List pureSources = networkInfo.getPureSources(); assertEquals(config.getPureSourceNum(), pureSources.size()); List tSources = networkInfo.getTransshipmentSources(); assertEquals(config.getTransshipSourceNum(), tSources.size()); List tNodes = networkInfo.getTransshipmentNodes(); assertEquals(config.getTransshipNodeNum(), tNodes.size()); List pureSinks = networkInfo.getPureSinks(); assertEquals(config.getPureSinkNum(), pureSinks.size()); List tSinks = networkInfo.getTransshipmentSinks(); assertEquals(config.getTransshipSinkNum(), tSinks.size()); List> vertexClasses = new ArrayList<>(); vertexClasses.add(pureSources); vertexClasses.add(tSources); vertexClasses.add(tNodes); vertexClasses.add(pureSinks); vertexClasses.add(tSinks); // validate that none of the vertices is of 2 types at the same time for (int i = 1; i < vertexClasses.size(); i++) { for (int j = 0; j < i; j++) { List firstList = vertexClasses.get(i); List secondList = vertexClasses.get(j); assertTrue(Collections.disjoint(firstList, secondList)); } } // validate that every vertex belongs to the network for (List vertexList : vertexClasses) { for (V vertex : vertexList) { assertTrue(network.containsVertex(vertex)); } } // validate arc num constraint assertEquals(config.getArcNum(), network.edgeSet().size()); // validate that none of the pure sources has incoming arcs for (V pureSource : pureSources) { assertTrue(network.incomingEdgesOf(pureSource).isEmpty()); } // validate that none of the pure sinks has outgoing arcs for (V pureSink : pureSinks) { assertTrue(network.outgoingEdgesOf(pureSink).isEmpty()); } } private static void validateCapacities( Graph graph, Function capacities, NetworkInfo info, NetworkGeneratorConfig config) { Set skeletonArcs = new HashSet<>(info.getSkeletonArcs()); for (E edge : graph.edgeSet()) { if (!skeletonArcs.contains(edge)) { assertTrue(capacities.apply(edge).doubleValue() + EPS >= config.getMinCap()); assertTrue(capacities.apply(edge).doubleValue() - EPS <= config.getMaxCap()); } } } private static void validateCosts( Graph graph, Function costs, NetworkGeneratorConfig config) { for (E edge : graph.edgeSet()) { assertTrue(costs.apply(edge).doubleValue() >= config.getMinCost() - EPS); assertTrue(costs.apply(edge).doubleValue() <= config.getMaxCost() + EPS); } } private static void validateSupplies(MinimumCostFlowProblem problem, NetworkInfo info) { Function supplyFunction = problem.getNodeSupply(); for (V source : info.getSources()) { assertTrue(supplyFunction.apply(source) > 0); } for (V sink : info.getSinks()) { assertTrue(supplyFunction.apply(sink) < 0); } } private static void compareFunctions( Graph firstGraph, Graph secondGraph, Function firstFunc, Function secondFunc) { for (E firstArc : firstGraph.edgeSet()) { V source = firstGraph.getEdgeSource(firstArc); V target = firstGraph.getEdgeTarget(firstArc); E secondArc = secondGraph.getEdge(source, target); assertEquals(firstFunc.apply(firstArc), secondFunc.apply(secondArc)); } } private static void assertBipartiteMatchingProblemsAreEqual( BipartiteMatchingProblem firstProblem, BipartiteMatchingProblem secondProblem) { Graph firstGraph = firstProblem.getGraph(); Graph secondGraph = secondProblem.getGraph(); assertGraphsAreEqual(firstGraph, secondGraph); assertEquals(firstProblem.getPartition1(), secondProblem.getPartition1()); assertEquals(firstProblem.getPartition2(), secondProblem.getPartition2()); compareFunctions( firstGraph, secondGraph, firstProblem.getCosts(), secondProblem.getCosts()); } private static void assertMaxFlowProblemsAreEqual( MaximumFlowProblem firstProblem, MaximumFlowProblem secondProblem) { Graph firstGraph = firstProblem.getGraph(); Graph secondGraph = secondProblem.getGraph(); assertGraphsAreEqual(firstGraph, secondGraph); assertEquals(firstProblem.getSources(), secondProblem.getSources()); assertEquals(firstProblem.getSinks(), secondProblem.getSinks()); compareFunctions( firstGraph, secondGraph, firstProblem.getCapacities(), secondProblem.getCapacities()); } private static void assertMinCostFlowProblemsAreEqual( MinimumCostFlowProblem firstProblem, MinimumCostFlowProblem secondProblem) { Graph firstGraph = firstProblem.getGraph(); Graph secondGraph = secondProblem.getGraph(); assertGraphsAreEqual(firstGraph, secondGraph); Function firstSupply = firstProblem.getNodeSupply(); Function secondSupply = secondProblem.getNodeSupply(); for (V vertex : firstGraph.vertexSet()) { assertEquals(firstSupply.apply(vertex), secondSupply.apply(vertex)); } compareFunctions( firstGraph, secondGraph, firstProblem.getArcCapacityLowerBounds(), secondProblem.getArcCapacityLowerBounds()); compareFunctions( firstGraph, secondGraph, firstProblem.getArcCapacityUpperBounds(), secondProblem.getArcCapacityUpperBounds()); compareFunctions( firstGraph, secondGraph, firstProblem.getArcCosts(), secondProblem.getArcCosts()); } private static void assertGraphsAreEqual(Graph firstGraph, Graph secondGraph) { assertEquals(firstGraph.vertexSet(), firstGraph.vertexSet()); assertEquals(firstGraph.edgeSet().size(), secondGraph.edgeSet().size()); for (E firstEdge : firstGraph.edgeSet()) { E secondEdge = secondGraph .getEdge(firstGraph.getEdgeSource(firstEdge), firstGraph.getEdgeTarget(firstEdge)); assertEquals( firstGraph.getEdgeWeight(firstEdge), secondGraph.getEdgeWeight(secondEdge), EPS); } } private static void assertIsFeasible(BipartiteMatchingProblem problem) { Graph graph = problem.getGraph(); Graph undirectedGraph = new AsUndirectedGraph<>(graph); MatchingAlgorithm matchingAlgorithm = new HopcroftKarpMaximumCardinalityBipartiteMatching<>( undirectedGraph, problem.getPartition1(), problem.getPartition2()); MatchingAlgorithm.Matching matching = matchingAlgorithm.getMatching(); assertEquals(graph.vertexSet().size(), 2 * matching.getEdges().size()); } private static double getMaxFlowValue(MaximumFlowProblem problem) { MaximumFlowProblem convertedProblem = problem.toSingleSourceSingleSinkProblem(); Graph graph = convertedProblem.getGraph(); convertedProblem.dumpCapacities(); MaximumFlowAlgorithm algorithm = new PushRelabelMFImpl<>(graph); MaximumFlowAlgorithm.MaximumFlow flow = algorithm.getMaximumFlow(convertedProblem.getSource(), convertedProblem.getSink()); return flow.getValue(); } private MinimumCostFlowProblem generateMinCostFlowProblem( NetworkGeneratorConfig config, long seed) { Graph graph = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), true); NetworkGenerator generator = new NetworkGenerator<>(config, seed); MinimumCostFlowProblem problem = generator.generateMinimumCostFlowProblem(graph); NetworkInfo info = generator.getNetworkInfo(); validateNetwork(graph, info, config); validateSupplies(problem, info); validateCapacities(graph, problem.getArcCapacityUpperBounds(), info, config); validateCosts(graph, problem.getArcCosts(), config); return problem; } private MaximumFlowProblem generateMaxFlowProblem( NetworkGeneratorConfig config, long seed) { Graph graph = new DefaultDirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), true); NetworkGenerator generator = new NetworkGenerator<>(config, seed); MaximumFlowProblem problem = generator.generateMaxFlowProblem(graph); NetworkInfo info = generator.getNetworkInfo(); validateNetwork(graph, info, config); validateCapacities(graph, problem.getCapacities(), info, config); return problem; } private static BipartiteMatchingProblem generateBipartiteMatchingProblem( NetworkGeneratorConfig config, long seed) { Graph graph = new DefaultDirectedWeightedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier()); NetworkGenerator generator = new NetworkGenerator<>(config, seed); BipartiteMatchingProblem problem = generator.generateBipartiteMatchingProblem(graph); NetworkInfo info = generator.getNetworkInfo(); validateNetwork(graph, info, config); validateCosts(graph, problem.getCosts(), config); return problem; } private void testMinCostFlowProblem(NetworkGeneratorConfig config, long seed) { MinimumCostFlowProblem firstProblem = generateMinCostFlowProblem(config, seed); MinimumCostFlowProblem secondProblem = generateMinCostFlowProblem(config, seed); assertMinCostFlowProblemsAreEqual(firstProblem, secondProblem); } private void testMaxFlowProblem(NetworkGeneratorConfig config, long seed) { MaximumFlowProblem firstProblem = generateMaxFlowProblem(config, seed); MaximumFlowProblem secondProblem = generateMaxFlowProblem(config, seed); assertMaxFlowProblemsAreEqual(firstProblem, secondProblem); double maxFlow = getMaxFlowValue(firstProblem); assertTrue(maxFlow + EPS > config.getTotalSupply()); } private void testBipartiteMatchingProblem(NetworkGeneratorConfig config, long seed) { BipartiteMatchingProblem firstProblem = generateBipartiteMatchingProblem(config, seed); BipartiteMatchingProblem secondProblem = generateBipartiteMatchingProblem(config, seed); assertIsFeasible(firstProblem); assertBipartiteMatchingProblemsAreEqual(firstProblem, secondProblem); } @Test public void testMinCostFlow_MinimumArcNum() { List tNodes = List.of(0, 1, 2, 5, 10, 20, 30); for (int sourceNum = 1; sourceNum < 4; sourceNum++) { for (int tSourceNum = 0; tSourceNum <= sourceNum; tSourceNum++) { for (int sinkNum = 1; sinkNum < 4; sinkNum++) { for (int tSinkNum = 0; tSinkNum <= sinkNum; tSinkNum++) { for (int tNodeNum : tNodes) { int arcNum = (int) NetworkGeneratorConfig .getMinimumArcNum(sourceNum, tNodeNum, sinkNum); NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setParams( sourceNum + tNodeNum + sinkNum, arcNum, sourceNum, sinkNum, tSourceNum, tSinkNum, Math.max(sourceNum, sinkNum), 1, 100, 1, 100, 100, 0) .build(); testMinCostFlowProblem(config, rng.nextLong()); } } } } } } @Test public void testMinCostFlow_MaximumArcNum() { List tNodes = List.of(0, 1, 2, 5, 10, 20, 30); for (int sourceNum = 1; sourceNum < 4; sourceNum++) { for (int tSourceNum = 0; tSourceNum <= sourceNum; tSourceNum++) { for (int sinkNum = 1; sinkNum < 4; sinkNum++) { for (int tSinkNum = 0; tSinkNum <= sinkNum; tSinkNum++) { for (int tNodeNum : tNodes) { int arcNum = (int) NetworkGeneratorConfig .getMaximumArcNum( sourceNum, tSourceNum, tNodeNum, tSinkNum, sinkNum); NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setParams( sourceNum + tNodeNum + sinkNum, arcNum, sourceNum, sinkNum, tSourceNum, tSinkNum, 10 * sourceNum, 1, 100, 1, 100, 100, 0) .build(); testMinCostFlowProblem(config, rng.nextLong()); } } } } } } // @Test public void test() { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setParams( 100 * 1000, 2000 * 1000, 1000, 1000, 500, 500, MAX_SUPPLY, 1, 100000, 1, 100000, 100, 0) .build(); testMinCostFlowProblem(config, rng.nextLong()); } @Test public void testMinCostFlow_MinimumArcNumA() { int sourceNum = 2; int tSourceNum = 0; int tNodeNum = 1; int tSinkNum = 0; int sinkNum = 3; int arcNum = (int) NetworkGeneratorConfig.getMinimumArcNum(sourceNum, tNodeNum, sinkNum); NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setParams( sourceNum + tNodeNum + sinkNum, arcNum, sourceNum, sinkNum, tSourceNum, tSinkNum, 10 * sourceNum, 1, 100, 1, 100, 100, 0) .build(); testMinCostFlowProblem(config, rng.nextLong()); } @Test public void testMaxFlow_MinimumNumberOfArcs() { for (int sourceNum = 1; sourceNum < 5; sourceNum++) { for (int sinkNum = 1; sinkNum < 5; sinkNum++) { for (int tNodeNum = 0; tNodeNum < 30; tNodeNum++) { int arcNum = (int) NetworkGeneratorConfig.getMinimumArcNum(sourceNum, tNodeNum, sinkNum); NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setMaximumFlowProblemParams( sourceNum + tNodeNum + sinkNum, arcNum, 10 * sourceNum, 1, 100, sourceNum, sinkNum) .build(); testMaxFlowProblem(config, rng.nextLong()); } } } } @Test public void testMaxFlow_MaximumNumberOfArcs() { for (int sourceNum = 1; sourceNum < 5; sourceNum++) { for (int sinkNum = 1; sinkNum < 5; sinkNum++) { for (int tNodeNum = 0; tNodeNum < 30; tNodeNum++) { int arcNum = (int) NetworkGeneratorConfig.getMaximumArcNum(sourceNum, tNodeNum, sinkNum); NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setMaximumFlowProblemParams( sourceNum + tNodeNum + sinkNum, arcNum, 10 * sourceNum, 1, 100, sourceNum, sinkNum) .build(); testMaxFlowProblem(config, rng.nextLong()); } } } } @Test public void testMaxFlow_RandomNumberOfArcs() { for (int sourceNum = 1; sourceNum < 5; sourceNum++) { for (int sinkNum = 1; sinkNum < 5; sinkNum++) { for (int tNodeNum = 0; tNodeNum < 30; tNodeNum++) { int lB = (int) NetworkGeneratorConfig.getMinimumArcNum(sourceNum, tNodeNum, sinkNum); int uB = (int) NetworkGeneratorConfig.getMaximumArcNum(sourceNum, tNodeNum, sinkNum); int arcNum = rng.nextInt(uB - lB + 1) + lB; NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setMaximumFlowProblemParams( sourceNum + tNodeNum + sinkNum, arcNum, 10 * sourceNum, 1, 100, sourceNum, sinkNum) .build(); testMaxFlowProblem(config, rng.nextLong()); } } } } @Test public void testBipartiteMatchingProblem_MinimumNumberOfArcs() { for (int i = 1; i < 50; i++) { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setBipartiteMatchingProblemParams(2 * i, i, 1, 100).build(); testBipartiteMatchingProblem(config, rng.nextLong()); } } @Test public void testBipartiteMatchingProblem_MaximumNumberOfArcs() { for (int i = 1; i < 50; i++) { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setBipartiteMatchingProblemParams(2 * i, i * i, 1, 100).build(); testBipartiteMatchingProblem(config, rng.nextLong()); } } @Test public void testBipartiteMatchingProblem_RandomNumberOfArcs() { for (int i = 1; i < 50; i++) { int max = i * i; int arcNum = rng.nextInt(max - i + 1) + i; NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setBipartiteMatchingProblemParams(2 * i, arcNum, 1, 100).build(); testBipartiteMatchingProblem(config, rng.nextLong()); } } @Test public void testBipartiteMatchingProblem_LargeProblem() { NetworkGeneratorConfig config = new NetworkGeneratorConfigBuilder() .setBipartiteMatchingProblemParams(1000, 250000, 1, 100).build(); testBipartiteMatchingProblem(config, rng.nextLong()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/000077500000000000000000000000001402514743400252215ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/AsGraphUnionTest.java000066400000000000000000000330711402514743400312660ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Unit test for the {@link AsGraphUnion} class. * * @author Joris Kinable */ public class AsGraphUnionTest { // ~ Instance fields -------------------------------------------------------- private String v0 = "v0"; private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; private DefaultWeightedEdge e1 = new DefaultWeightedEdge(); // (v0,v1); private DefaultWeightedEdge e2 = new DefaultWeightedEdge(); // (v1,v4); private DefaultWeightedEdge e3 = new DefaultWeightedEdge(); // (v4,v0); private DefaultWeightedEdge e4 = new DefaultWeightedEdge(); // (v1,v2); private DefaultWeightedEdge e5 = new DefaultWeightedEdge(); // (v2,v3); private DefaultWeightedEdge e6 = new DefaultWeightedEdge(); // (v3,v4); private DefaultWeightedEdge e7 = new DefaultWeightedEdge(); // (v4,v1); private DefaultWeightedEdge e8 = new DefaultWeightedEdge(); // (v4,v4); Graph undirectedGraph1; Graph undirectedGraph2; Graph directedGraph1; Graph directedGraph2; // ~ Methods ---------------------------------------------------------------- @Before public void setUp() { undirectedGraph1 = new WeightedPseudograph<>(DefaultWeightedEdge.class); undirectedGraph2 = new WeightedPseudograph<>(DefaultWeightedEdge.class); directedGraph1 = new DirectedPseudograph<>(DefaultWeightedEdge.class); directedGraph2 = new DirectedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(undirectedGraph1, Arrays.asList(v0, v1, v4)); Graphs.addAllVertices(undirectedGraph2, Arrays.asList(v1, v2, v3, v4)); Graphs.addAllVertices(directedGraph1, Arrays.asList(v0, v1, v4)); Graphs.addAllVertices(directedGraph2, Arrays.asList(v1, v2, v3, v4)); undirectedGraph1.addEdge(v0, v1, e1); undirectedGraph1.addEdge(v1, v4, e2); undirectedGraph1.addEdge(v4, v0, e3); undirectedGraph1.addEdge(v4, v4, e8); directedGraph1.addEdge(v0, v1, e1); directedGraph1.addEdge(v1, v4, e2); directedGraph1.addEdge(v4, v0, e3); directedGraph1.addEdge(v4, v4, e8); undirectedGraph2.addEdge(v4, v1, e7); undirectedGraph2.addEdge(v1, v2, e4); undirectedGraph2.addEdge(v2, v3, e5); undirectedGraph2.addEdge(v3, v4, e6); directedGraph2.addEdge(v4, v1, e7); directedGraph2.addEdge(v1, v2, e4); directedGraph2.addEdge(v2, v3, e5); directedGraph2.addEdge(v3, v4, e6); } /** * Create and test the union of two Undirected Graphs */ @Test public void testUndirectedGraphUnion() { Graph graphUnion = new AsGraphUnion<>(undirectedGraph1, undirectedGraph2); assertTrue(graphUnion.getType().isUndirected()); assertTrue(graphUnion.getType().isWeighted()); assertFalse(graphUnion.getType().isModifiable()); assertEquals(Set.of(v0, v1, v2, v3, v4), graphUnion.vertexSet()); assertEquals(Set.of(e1, e2, e3, e4, e5, e6, e7, e8), graphUnion.edgeSet()); assertEquals(Set.of(e1, e3), graphUnion.edgesOf(v0)); assertEquals(Set.of(e1, e2, e4, e7), graphUnion.edgesOf(v1)); assertEquals(Set.of(e4, e5), graphUnion.edgesOf(v2)); assertEquals(Set.of(e5, e6), graphUnion.edgesOf(v3)); assertEquals(Set.of(e2, e3, e6, e7, e8), graphUnion.edgesOf(v4)); assertEquals(2, graphUnion.degreeOf(v0)); assertEquals(4, graphUnion.degreeOf(v1)); assertEquals(2, graphUnion.degreeOf(v2)); assertEquals(2, graphUnion.degreeOf(v3)); assertEquals(6, graphUnion.degreeOf(v4)); assertEquals(Set.of(e1, e3), graphUnion.incomingEdgesOf(v0)); assertEquals(Set.of(e1, e2, e4, e7), graphUnion.incomingEdgesOf(v1)); assertEquals(Set.of(e4, e5), graphUnion.incomingEdgesOf(v2)); assertEquals(Set.of(e5, e6), graphUnion.incomingEdgesOf(v3)); assertEquals(Set.of(e2, e3, e6, e7, e8), graphUnion.incomingEdgesOf(v4)); assertEquals(2, graphUnion.inDegreeOf(v0)); assertEquals(4, graphUnion.inDegreeOf(v1)); assertEquals(2, graphUnion.inDegreeOf(v2)); assertEquals(2, graphUnion.inDegreeOf(v3)); assertEquals(6, graphUnion.inDegreeOf(v4)); assertEquals(Set.of(e1, e3), graphUnion.outgoingEdgesOf(v0)); assertEquals(Set.of(e1, e2, e4, e7), graphUnion.outgoingEdgesOf(v1)); assertEquals(Set.of(e4, e5), graphUnion.outgoingEdgesOf(v2)); assertEquals(Set.of(e5, e6), graphUnion.outgoingEdgesOf(v3)); assertEquals(Set.of(e2, e3, e6, e7, e8), graphUnion.outgoingEdgesOf(v4)); assertEquals(2, graphUnion.outDegreeOf(v0)); assertEquals(4, graphUnion.outDegreeOf(v1)); assertEquals(2, graphUnion.outDegreeOf(v2)); assertEquals(2, graphUnion.outDegreeOf(v3)); assertEquals(6, graphUnion.outDegreeOf(v4)); assertTrue(graphUnion.getEdge(v1, v4) == e2); assertTrue(graphUnion.getEdge(v4, v1) == e2); } /** * Create and test the union of two Directed Graphs */ @Test public void testDirectedGraphUnion() { Graph graphUnion = new AsGraphUnion<>(directedGraph1, directedGraph2); assertTrue(graphUnion.getType().isDirected()); assertTrue(graphUnion.getType().isWeighted()); assertFalse(graphUnion.getType().isModifiable()); assertEquals(Set.of(v0, v1, v2, v3, v4), graphUnion.vertexSet()); assertEquals(Set.of(e1, e2, e3, e4, e5, e6, e7, e8), graphUnion.edgeSet()); assertEquals(Set.of(e1, e3), graphUnion.edgesOf(v0)); assertEquals(Set.of(e1, e2, e4, e7), graphUnion.edgesOf(v1)); assertEquals(Set.of(e4, e5), graphUnion.edgesOf(v2)); assertEquals(Set.of(e5, e6), graphUnion.edgesOf(v3)); assertEquals(Set.of(e2, e3, e6, e7, e8), graphUnion.edgesOf(v4)); assertEquals(2, graphUnion.degreeOf(v0)); assertEquals(4, graphUnion.degreeOf(v1)); assertEquals(2, graphUnion.degreeOf(v2)); assertEquals(2, graphUnion.degreeOf(v3)); assertEquals(6, graphUnion.degreeOf(v4)); assertEquals(Set.of(e3), graphUnion.incomingEdgesOf(v0)); assertEquals(Set.of(e1, e7), graphUnion.incomingEdgesOf(v1)); assertEquals(Set.of(e4), graphUnion.incomingEdgesOf(v2)); assertEquals(Set.of(e5), graphUnion.incomingEdgesOf(v3)); assertEquals(Set.of(e2, e6, e8), graphUnion.incomingEdgesOf(v4)); assertEquals(1, graphUnion.inDegreeOf(v0)); assertEquals(2, graphUnion.inDegreeOf(v1)); assertEquals(1, graphUnion.inDegreeOf(v2)); assertEquals(1, graphUnion.inDegreeOf(v3)); assertEquals(3, graphUnion.inDegreeOf(v4)); assertEquals(Set.of(e1), graphUnion.outgoingEdgesOf(v0)); assertEquals(Set.of(e2, e4), graphUnion.outgoingEdgesOf(v1)); assertEquals(Set.of(e5), graphUnion.outgoingEdgesOf(v2)); assertEquals(Set.of(e6), graphUnion.outgoingEdgesOf(v3)); assertEquals(Set.of(e3, e7, e8), graphUnion.outgoingEdgesOf(v4)); assertEquals(1, graphUnion.outDegreeOf(v0)); assertEquals(2, graphUnion.outDegreeOf(v1)); assertEquals(1, graphUnion.outDegreeOf(v2)); assertEquals(1, graphUnion.outDegreeOf(v3)); assertEquals(3, graphUnion.outDegreeOf(v4)); assertFalse(directedGraph1.containsEdge(v4, v1)); assertFalse(directedGraph2.containsEdge(v1, v4)); assertTrue(graphUnion.getEdge(v1, v4) == e2); assertTrue(graphUnion.getEdge(v4, v1) == e7); } /** * Create and test a Mixed-Graph, obtained by taking the union of a undirected and a directed * graph */ @Test public void testMixedGraphUnion() { Graph graphUnion = new AsGraphUnion<>(undirectedGraph1, directedGraph2); assertTrue(graphUnion.getType().isMixed()); assertTrue(graphUnion.getType().isWeighted()); assertFalse(graphUnion.getType().isModifiable()); assertEquals(Set.of(v0, v1, v2, v3, v4), graphUnion.vertexSet()); assertEquals(Set.of(e1, e2, e3, e4, e5, e6, e7, e8), graphUnion.edgeSet()); assertEquals(Set.of(e1, e3), graphUnion.edgesOf(v0)); assertEquals(Set.of(e1, e2, e4, e7), graphUnion.edgesOf(v1)); assertEquals(Set.of(e4, e5), graphUnion.edgesOf(v2)); assertEquals(Set.of(e5, e6), graphUnion.edgesOf(v3)); assertEquals(Set.of(e2, e3, e6, e7, e8), graphUnion.edgesOf(v4)); assertEquals(2, graphUnion.degreeOf(v0)); assertEquals(4, graphUnion.degreeOf(v1)); assertEquals(2, graphUnion.degreeOf(v2)); assertEquals(2, graphUnion.degreeOf(v3)); assertEquals(6, graphUnion.degreeOf(v4)); assertEquals(Set.of(e1, e3), graphUnion.incomingEdgesOf(v0)); assertEquals(Set.of(e1, e2, e7), graphUnion.incomingEdgesOf(v1)); assertEquals(Set.of(e4), graphUnion.incomingEdgesOf(v2)); assertEquals(Set.of(e5), graphUnion.incomingEdgesOf(v3)); assertEquals(Set.of(e2, e3, e6, e8), graphUnion.incomingEdgesOf(v4)); assertEquals(2, graphUnion.inDegreeOf(v0)); assertEquals(3, graphUnion.inDegreeOf(v1)); assertEquals(1, graphUnion.inDegreeOf(v2)); assertEquals(1, graphUnion.inDegreeOf(v3)); assertEquals(5, graphUnion.inDegreeOf(v4)); assertEquals(Set.of(e1, e3), graphUnion.outgoingEdgesOf(v0)); assertEquals(Set.of(e1, e2, e4), graphUnion.outgoingEdgesOf(v1)); assertEquals(Set.of(e5), graphUnion.outgoingEdgesOf(v2)); assertEquals(Set.of(e6), graphUnion.outgoingEdgesOf(v3)); assertEquals(Set.of(e2, e3, e7, e8), graphUnion.outgoingEdgesOf(v4)); assertEquals(2, graphUnion.outDegreeOf(v0)); assertEquals(3, graphUnion.outDegreeOf(v1)); assertEquals(1, graphUnion.outDegreeOf(v2)); assertEquals(1, graphUnion.outDegreeOf(v3)); assertEquals(5, graphUnion.outDegreeOf(v4)); assertTrue(graphUnion.containsEdge(v0, v1)); // undirected edge assertTrue(graphUnion.containsEdge(v1, v0)); // undirected edge assertTrue(graphUnion.containsEdge(v3, v4)); // directed edge assertFalse(graphUnion.containsEdge(v4, v3)); // directed edge } /** * Test the weight combiner for graphs having an edge in common. */ @Test public void testWeightCombiner() { // Create two graphs, both having the same vertices {0,1} and the same weighted edge (0,1) SimpleWeightedGraph g1 = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g1, Arrays.asList(0, 1)); DefaultWeightedEdge edge = g1.addEdge(0, 1); g1.setEdgeWeight(edge, 10); SimpleWeightedGraph g2 = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(g2, Arrays.asList(0, 1)); g2.addEdge(0, 1, edge); // We need to create a mask of the second graph if we want to store the edge with a // different weight. Simply setting g2.setEdgeWeight(edge,20) would override the edge weight // for the same edge in g1 as well! Map weightMap = new HashMap<>(); weightMap.put(edge, 20.0); Graph g2Masked = new AsWeightedGraph<>(g2, weightMap); Graph graphUnionSum = new AsGraphUnion<>(g1, g2Masked, WeightCombiner.SUM); assertEquals(30.0, graphUnionSum.getEdgeWeight(edge), 0); Graph graphUnionFirst = new AsGraphUnion<>(g1, g2Masked, WeightCombiner.FIRST); assertEquals(10.0, graphUnionFirst.getEdgeWeight(edge), 0); Graph graphUnionSecond = new AsGraphUnion<>(g1, g2Masked, WeightCombiner.SECOND); assertEquals(20.0, graphUnionSecond.getEdgeWeight(edge), 0); Graph graphUnionMax = new AsGraphUnion<>(g1, g2Masked, WeightCombiner.MAX); assertEquals(20.0, graphUnionMax.getEdgeWeight(edge), 0); Graph graphUnionMin = new AsGraphUnion<>(g1, g2Masked, WeightCombiner.MIN); assertEquals(10.0, graphUnionMin.getEdgeWeight(edge), 0); Graph graphUnionMult = new AsGraphUnion<>(g1, g2Masked, WeightCombiner.MULT); assertEquals(200.0, graphUnionMult.getEdgeWeight(edge), 0); assertEquals(10.0, g1.getEdgeWeight(edge), 0); assertEquals(10.0, g2.getEdgeWeight(edge), 0); assertEquals(20.0, g2Masked.getEdgeWeight(edge), 0); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/AsSubgraphTest.java000066400000000000000000000254301402514743400307670ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Michael Behrisch and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; /** * Unit test for {@link AsSubgraph} class. * * @author Michael Behrisch */ public class AsSubgraphTest { // ~ Instance fields -------------------------------------------------------- private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; /** * . */ @Test public void testInducedSubgraphListener() { Graph g = init(true); Graph sub = new AsSubgraph<>(g, null, null); assertEquals(g.vertexSet(), sub.vertexSet()); assertEquals(g.edgeSet(), sub.edgeSet()); g.addEdge(v3, v4); assertEquals(g.vertexSet(), sub.vertexSet()); assertEquals(g.edgeSet(), sub.edgeSet()); } /** * Tests Subgraph. */ @Test public void testSubgraph() { Graph g = init(false); Graph sub = new AsSubgraph<>(g, null, null); assertEquals(g.vertexSet(), sub.vertexSet()); assertEquals(g.edgeSet(), sub.edgeSet()); Set vset = new HashSet<>(g.vertexSet()); g.removeVertex(v1); assertEquals(vset, sub.vertexSet()); // losing track g = init(false); vset = new HashSet<>(); vset.add(v1); sub = new AsSubgraph<>(g, vset, null); assertEquals(vset, sub.vertexSet()); assertEquals(0, sub.degreeOf(v1)); assertEquals(Collections.EMPTY_SET, sub.edgeSet()); vset.add(v2); vset.add(v3); sub = new AsSubgraph<>(g, vset, new HashSet<>(g.getAllEdges(v1, v2))); assertEquals(vset, sub.vertexSet()); assertEquals(1, sub.edgeSet().size()); } /** * . */ @Test public void testSubgraphListener() { Graph g = init(true); Graph sub = new AsSubgraph<>(g, null, null); assertEquals(g.vertexSet(), sub.vertexSet()); assertEquals(g.edgeSet(), sub.edgeSet()); Set vset = new HashSet<>(g.vertexSet()); g.removeVertex(v1); vset.remove(v1); assertEquals(vset, sub.vertexSet()); // not losing track assertEquals(g.edgeSet(), sub.edgeSet()); } private Graph init(boolean listenable) { Graph g; if (listenable) { g = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); } else { g = new SimpleGraph<>(DefaultEdge.class); } g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); g.addVertex(v4); g.addEdge(v1, v2); g.addEdge(v2, v3); g.addEdge(v3, v1); g.addEdge(v1, v4); return g; } @Test public void testInducedSubgraphUnderlyingEdgeAddition() { ListenableGraph baseGraph = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); baseGraph.addVertex(v1); baseGraph.addVertex(v2); Set initialVertexes = new LinkedHashSet<>(); initialVertexes.add(v1); Graph subgraph = new AsSubgraph<>(baseGraph, initialVertexes, null); baseGraph.addEdge(v1, v2); assertFalse(subgraph.containsEdge(v1, v2)); } @Test public void testEdges() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); g.addEdge(1, 2); g.addEdge(1, 2); g.addEdge(1, 3); DefaultEdge e14 = g.addEdge(1, 4); g.addEdge(2, 3); g.addEdge(2, 1); g.addEdge(3, 3); g.addEdge(4, 5); g.addEdge(5, 5); g.addEdge(5, 2); Graph sg = new AsSubgraph<>(g); assertEquals(10, sg.edgeSet().size()); sg.removeVertex(2); assertEquals(5, sg.edgeSet().size()); assertEquals(2, sg.edgesOf(1).size()); assertFalse(sg.containsVertex(2)); assertEquals(2, sg.edgesOf(3).size()); assertEquals(2, sg.edgesOf(4).size()); assertEquals(2, sg.edgesOf(5).size()); sg.removeEdge(e14); assertEquals(4, sg.edgeSet().size()); assertEquals(1, sg.edgesOf(1).size()); assertEquals(2, sg.edgesOf(3).size()); assertEquals(1, sg.edgesOf(4).size()); assertEquals(2, sg.edgesOf(5).size()); assertEquals(10, g.edgeSet().size()); } @Test public void testNonValidVerticesFilter() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); g.addEdge(1, 2); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(1, 4); g.addEdge(2, 3); g.addEdge(2, 1); g.addEdge(3, 3); g.addEdge(4, 5); g.addEdge(5, 5); g.addEdge(5, 2); Graph sg = new AsSubgraph<>(g, Set.of(1, 3, 100, 200, 300, 500, 800, 1000)); assertEquals(2, sg.edgeSet().size()); assertEquals(2, sg.vertexSet().size()); } @Test public void testNonValidEdgesFilter() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); DefaultEdge e1 = g.addEdge(1, 2); g.addEdge(1, 2); DefaultEdge e2 = g.addEdge(1, 3); DefaultEdge e3 = g.addEdge(1, 4); g.addEdge(2, 3); g.addEdge(2, 1); g.addEdge(3, 3); DefaultEdge e4 = g.addEdge(4, 5); DefaultEdge e5 = g.addEdge(5, 5); g.addEdge(5, 2); DefaultEdge nonValid1 = g.addEdge(5, 1); g.removeEdge(nonValid1); DefaultEdge nonValid2 = g.addEdge(5, 1); g.removeEdge(nonValid2); Graph sg = new AsSubgraph<>(g, null, Set.of(e1, e2, e3, e4, e5, nonValid1, nonValid2)); assertEquals(5, sg.edgeSet().size()); assertEquals(5, sg.vertexSet().size()); } @Test public void testInOutEdgesUndirected() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e13 = g.addEdge(1, 3); DefaultEdge e23 = g.addEdge(2, 3); DefaultEdge e24_1 = g.addEdge(2, 4); DefaultEdge e24_2 = g.addEdge(2, 4); g.addEdge(2, 4); g.addEdge(3, 5); DefaultEdge e44 = g.addEdge(4, 4); g.addEdge(4, 5); Graph sg = new AsSubgraph<>(g, Set.of(1, 2, 3, 4), Set.of(e12, e13, e23, e24_1, e24_2, e44)); assertEquals(6, sg.edgeSet().size()); assertEquals(Set.of(e12, e13), sg.edgesOf(1)); assertEquals(Set.of(e12, e24_1, e24_2, e23), sg.edgesOf(2)); assertEquals(Set.of(e13, e23), sg.edgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.edgesOf(4)); assertEquals(2, sg.degreeOf(1)); assertEquals(4, sg.degreeOf(2)); assertEquals(2, sg.degreeOf(3)); assertEquals(4, sg.degreeOf(4)); assertEquals(Set.of(e12, e13), sg.incomingEdgesOf(1)); assertEquals(Set.of(e12, e24_1, e24_2, e23), sg.incomingEdgesOf(2)); assertEquals(Set.of(e13, e23), sg.incomingEdgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.incomingEdgesOf(4)); assertEquals(2, sg.inDegreeOf(1)); assertEquals(4, sg.inDegreeOf(2)); assertEquals(2, sg.inDegreeOf(3)); assertEquals(4, sg.inDegreeOf(4)); assertEquals(Set.of(e12, e13), sg.outgoingEdgesOf(1)); assertEquals(Set.of(e12, e24_1, e24_2, e23), sg.outgoingEdgesOf(2)); assertEquals(Set.of(e13, e23), sg.outgoingEdgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.outgoingEdgesOf(4)); assertEquals(2, sg.outDegreeOf(1)); assertEquals(4, sg.outDegreeOf(2)); assertEquals(2, sg.outDegreeOf(3)); assertEquals(4, sg.outDegreeOf(4)); } @Test public void testInOutEdgesDirected() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e13 = g.addEdge(1, 3); DefaultEdge e23 = g.addEdge(2, 3); DefaultEdge e24_1 = g.addEdge(2, 4); DefaultEdge e24_2 = g.addEdge(2, 4); g.addEdge(2, 4); g.addEdge(3, 5); DefaultEdge e44 = g.addEdge(4, 4); g.addEdge(4, 5); Graph sg = new AsSubgraph<>(g, Set.of(1, 2, 3, 4), Set.of(e12, e13, e23, e24_1, e24_2, e44)); assertEquals(6, sg.edgeSet().size()); assertEquals(Set.of(e12, e13), sg.edgesOf(1)); assertEquals(Set.of(e12, e24_1, e24_2, e23), sg.edgesOf(2)); assertEquals(Set.of(e13, e23), sg.edgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.edgesOf(4)); assertEquals(2, sg.degreeOf(1)); assertEquals(4, sg.degreeOf(2)); assertEquals(2, sg.degreeOf(3)); assertEquals(4, sg.degreeOf(4)); assertEquals(Set.of(), sg.incomingEdgesOf(1)); assertEquals(Set.of(e12), sg.incomingEdgesOf(2)); assertEquals(Set.of(e13, e23), sg.incomingEdgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.incomingEdgesOf(4)); assertEquals(0, sg.inDegreeOf(1)); assertEquals(1, sg.inDegreeOf(2)); assertEquals(2, sg.inDegreeOf(3)); assertEquals(3, sg.inDegreeOf(4)); assertEquals(Set.of(e12, e13), sg.outgoingEdgesOf(1)); assertEquals(Set.of(e24_1, e24_2, e23), sg.outgoingEdgesOf(2)); assertEquals(Set.of(), sg.outgoingEdgesOf(3)); assertEquals(Set.of(e44), sg.outgoingEdgesOf(4)); assertEquals(2, sg.outDegreeOf(1)); assertEquals(3, sg.outDegreeOf(2)); assertEquals(0, sg.outDegreeOf(3)); assertEquals(1, sg.outDegreeOf(4)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/AsUndirectedGraphTest.java000066400000000000000000000133311402514743400322610ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * A unit test for the AsDirectedGraph view. * * @author John V. Sichi */ public class AsUndirectedGraphTest { // ~ Instance fields -------------------------------------------------------- private Graph directed; private DefaultEdge loop; private DefaultEdge e12; private DefaultEdge e23; private DefaultEdge e24; private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; private Graph undirected; /** * . */ @Test public void testAddEdge() { try { undirected.addEdge(v3, v4); Assert.fail(); // should not get here } catch (UnsupportedOperationException e) { } assertEquals( "([v1, v2, v3, v4], [{v1,v2}, {v2,v3}, {v2,v4}, {v4,v4}])", undirected.toString()); } /** * . */ @Test public void testAddVertex() { String v5 = "v5"; undirected.addVertex(v5); assertEquals(true, undirected.containsVertex(v5)); assertEquals(true, directed.containsVertex(v5)); } /** * . */ @Test public void testDegreeOf() { assertEquals(1, undirected.degreeOf(v1)); assertEquals(3, undirected.degreeOf(v2)); assertEquals(1, undirected.degreeOf(v3)); assertEquals(3, undirected.degreeOf(v4)); } /** * . */ @Test public void testEdgesOf() { assertEquals(Set.of(e12), undirected.edgesOf(v1)); assertEquals(Set.of(e12, e23, e24), undirected.edgesOf(v2)); assertEquals(Set.of(e23), undirected.edgesOf(v3)); assertEquals(Set.of(e24, loop), undirected.edgesOf(v4)); } /** * . */ @Test public void testInDegreeOf() { assertEquals(1, undirected.inDegreeOf(v1)); assertEquals(3, undirected.inDegreeOf(v2)); assertEquals(1, undirected.inDegreeOf(v3)); assertEquals(3, undirected.inDegreeOf(v4)); } /** * . */ @Test public void testIncomingEdgesOf() { assertEquals(Set.of(e12), undirected.incomingEdgesOf(v1)); assertEquals(Set.of(e12, e23, e24), undirected.incomingEdgesOf(v2)); assertEquals(Set.of(e23), undirected.incomingEdgesOf(v3)); assertEquals(Set.of(e24, loop), undirected.incomingEdgesOf(v4)); } /** * . */ @Test public void testOutDegreeOf() { assertEquals(1, undirected.outDegreeOf(v1)); assertEquals(3, undirected.outDegreeOf(v2)); assertEquals(1, undirected.outDegreeOf(v3)); assertEquals(3, undirected.outDegreeOf(v4)); } /** * . */ @Test public void testOutgoingEdgesOf() { assertEquals(Set.of(e12), undirected.outgoingEdgesOf(v1)); assertEquals(Set.of(e12, e23, e24), undirected.outgoingEdgesOf(v2)); assertEquals(Set.of(e23), undirected.outgoingEdgesOf(v3)); assertEquals(Set.of(e24, loop), undirected.outgoingEdgesOf(v4)); } /** * . */ @Test public void testGetAllEdges() { Set edges = undirected.getAllEdges(v3, v2); assertEquals(1, edges.size()); assertEquals(directed.getEdge(v2, v3), edges.iterator().next()); edges = undirected.getAllEdges(v4, v4); assertEquals(1, edges.size()); assertEquals(loop, edges.iterator().next()); } /** * . */ @Test public void testGetEdge() { assertEquals(directed.getEdge(v1, v2), undirected.getEdge(v1, v2)); assertEquals(directed.getEdge(v1, v2), undirected.getEdge(v2, v1)); assertEquals(directed.getEdge(v4, v4), undirected.getEdge(v4, v4)); } /** * . */ @Test public void testRemoveEdge() { undirected.removeEdge(loop); assertEquals(false, undirected.containsEdge(loop)); assertEquals(false, directed.containsEdge(loop)); } /** * . */ @Test public void testRemoveVertex() { undirected.removeVertex(v4); assertEquals(false, undirected.containsVertex(v4)); assertEquals(false, directed.containsVertex(v4)); } /** * . */ @Test public void testToString() { assertEquals( "([v1, v2, v3, v4], [(v1,v2), (v2,v3), (v2,v4), (v4,v4)])", directed.toString()); assertEquals( "([v1, v2, v3, v4], [{v1,v2}, {v2,v3}, {v2,v4}, {v4,v4}])", undirected.toString()); } /** * . */ @Before public void setUp() { directed = new DefaultDirectedGraph<>(DefaultEdge.class); undirected = new AsUndirectedGraph<>(directed); directed.addVertex(v1); directed.addVertex(v2); directed.addVertex(v3); directed.addVertex(v4); e12 = directed.addEdge(v1, v2); e23 = directed.addEdge(v2, v3); e24 = directed.addEdge(v2, v4); loop = directed.addEdge(v4, v4); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/AsUnweightedGraphTest.java000066400000000000000000000063631402514743400323050ustar00rootroot00000000000000/* * (C) Copyright 2018, by Lukas Harzenetter and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.junit.*; import static junit.framework.TestCase.fail; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; public class AsUnweightedGraphTest { private DefaultWeightedEdge loop; private DefaultWeightedEdge e12; private DefaultWeightedEdge e23; private DefaultWeightedEdge e24; private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; private Graph unweightedGraph; /** * Similar set up as created by {@link AsUndirectedGraphTest}. */ @Before public void setUp() { Graph undirectedWeightedGraph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); this.unweightedGraph = new AsUnweightedGraph<>(undirectedWeightedGraph); undirectedWeightedGraph.addVertex(v1); undirectedWeightedGraph.addVertex(v2); undirectedWeightedGraph.addVertex(v3); undirectedWeightedGraph.addVertex(v4); e12 = Graphs.addEdge(undirectedWeightedGraph, v1, v2, 6d); e23 = Graphs.addEdge(undirectedWeightedGraph, v2, v3, 456d); e24 = Graphs.addEdge(undirectedWeightedGraph, v2, v4, 0.587d); loop = Graphs.addEdge(undirectedWeightedGraph, v4, v4, 6781234453486d); } @Test public void getEdgeWeightOfE12() { assertEquals(Graph.DEFAULT_EDGE_WEIGHT, this.unweightedGraph.getEdgeWeight(e12), 0); } @Test public void getEdgeWeightOfE23() { assertEquals(Graph.DEFAULT_EDGE_WEIGHT, this.unweightedGraph.getEdgeWeight(e23), 0); } @Test public void getEdgeWeightOfE24() { assertEquals(Graph.DEFAULT_EDGE_WEIGHT, this.unweightedGraph.getEdgeWeight(e24), 0); } @Test public void getEdgeWeightOfLoop() { assertEquals(Graph.DEFAULT_EDGE_WEIGHT, this.unweightedGraph.getEdgeWeight(loop), 0); } @Test public void setEdgeWeight() { try { this.unweightedGraph.setEdgeWeight(e23, 81); } catch (UnsupportedOperationException e) { assertThat(e.getMessage(), is("Edge weight is not supported")); } } @Test public void getType() { assertFalse(this.unweightedGraph.getType().isWeighted()); } @Test public void failOnCreationOfUnweightedGraph() { try { new AsUnweightedGraph<>(null); fail("Expected an NullPointerException to be thrown"); } catch (NullPointerException e) { assertNotNull(e); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/CloneTest.java000066400000000000000000000063151402514743400277710ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.junit.*; import static org.junit.Assert.*; /** * A unit test for a cloning bug, adapted from a forum entry from Linda Buisman. * * @author John V. Sichi */ public class CloneTest { /** * Test graph cloning. */ @SuppressWarnings("unchecked") @Test public void testCloneSpecificsBug() { SimpleGraph g1 = new SimpleGraph<>(DefaultEdge.class); String one = "1"; String two = "2"; String three = "3"; g1.addVertex(one); g1.addVertex(two); g1.addVertex(three); g1.addEdge(one, two); g1.addEdge(two, three); SimpleGraph g2 = (SimpleGraph) g1.clone(); // Type-safety // warning // OK // with // clone assertEquals(2, g2.edgeSet().size()); assertNotNull(g2.getEdge(one, two)); assertTrue(g2.removeEdge(g2.getEdge(one, two))); assertNotNull(g2.removeEdge("2", "3")); assertTrue(g2.edgeSet().isEmpty()); } /** * Tests usage of {@link ParanoidGraph} for detecting broken vertex implementations. */ @Test public void testParanoidGraph() { BrokenVertex v1 = new BrokenVertex(1); BrokenVertex v2 = new BrokenVertex(2); BrokenVertex v3 = new BrokenVertex(1); SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); ParanoidGraph pg = new ParanoidGraph<>(g); pg.addVertex(v1); pg.addVertex(v2); try { pg.addVertex(v3); Assert.fail(); // should not get here } catch (IllegalArgumentException ex) { // expected, swallow } } // ~ Inner Classes ---------------------------------------------------------- private class BrokenVertex { private int x; BrokenVertex(int x) { this.x = x; } @Override public boolean equals(Object other) { return other instanceof BrokenVertex && x == ((BrokenVertex) other).x; } @Override public int hashCode() { return super.hashCode(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/DirectedAcyclicGraphTest.java000066400000000000000000000526141402514743400327310ustar00rootroot00000000000000/* * (C) Copyright 2008-2021, by Peter Giles and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.generate.*; import org.jgrapht.graph.builder.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.*; /** * Unit tests for the DirectedAcyclicGraph, a dynamic DAG implementation. * * @author Peter Giles */ public class DirectedAcyclicGraphTest { /** * Tests the cycle detection capabilities of DirectedAcyclicGraph by building a parallel * SimpleDirectedGraph and using a CycleDetector to check for cycles, and comparing the results. */ @Test public void testCycleDetectionInRandomGraphBuild() { for (int i = 0; i < 50; i++) { // test with 50 random graph // configurations Graph sourceGraph = setUpWithSeed(20, 200, i); DirectedAcyclicGraph dag = new DirectedAcyclicGraph<>(DefaultEdge.class); SimpleDirectedGraph compareGraph = new SimpleDirectedGraph<>(DefaultEdge.class); for (Long vertex : sourceGraph.vertexSet()) { dag.addVertex(vertex); compareGraph.addVertex(vertex); } for (DefaultEdge edge : sourceGraph.edgeSet()) { Long edgeSource = sourceGraph.getEdgeSource(edge); Long edgeTarget = sourceGraph.getEdgeTarget(edge); boolean dagRejectedEdge = false; try { dag.addEdge(edgeSource, edgeTarget); } catch (IllegalArgumentException e) { // okay, it did't add that edge dagRejectedEdge = true; } DefaultEdge compareEdge = compareGraph.addEdge(edgeSource, edgeTarget); CycleDetector cycleDetector = new CycleDetector<>(compareGraph); boolean cycleDetected = cycleDetector.detectCycles(); assertTrue(dagRejectedEdge == cycleDetected); if (cycleDetected) { // remove the edge from the compareGraph so the graphs // remain in sync compareGraph.removeEdge(compareEdge); } } // after all this, our graphs must be equal assertEquals(compareGraph.vertexSet(), dag.vertexSet()); // for some reason comparing vertex sets doesn't work, so doing it // the hard way: for (Long sourceVertex : compareGraph.vertexSet()) { for (DefaultEdge outgoingEdge : compareGraph.outgoingEdgesOf(sourceVertex)) { Long targetVertex = compareGraph.getEdgeTarget(outgoingEdge); assertTrue(dag.containsEdge(sourceVertex, targetVertex)); } } } } /** * trivial test of topological order using a linear graph */ @Test public void testTopoIterationOrderLinearGraph() { DirectedAcyclicGraph dag = new DirectedAcyclicGraph<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); LinearGraphGenerator graphGen = new LinearGraphGenerator<>(100); graphGen.generateGraph(dag); Iterator internalTopoIter = dag.iterator(); TopologicalOrderIterator comparTopoIter = new TopologicalOrderIterator<>(dag); while (comparTopoIter.hasNext()) { Long compareNext = comparTopoIter.next(); Long myNext = null; if (internalTopoIter.hasNext()) { myNext = internalTopoIter.next(); } assertSame(compareNext, myNext); assertEquals(comparTopoIter.hasNext(), internalTopoIter.hasNext()); } } /** * more rigorous test of topological iteration order, by assuring that each visited vertex * adheres to the definition of topological order, that is that it doesn't have a path leading * to any of its predecessors. */ @Test public void testTopoIterationOrderComplexGraph() { for (int seed = 0; seed < 20; seed++) { DirectedAcyclicGraph dag = new DirectedAcyclicGraph<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); RepeatableRandomGraphGenerator graphGen = new RepeatableRandomGraphGenerator<>(100, 500, seed); graphGen.generateGraph(dag); ConnectivityInspector connectivityInspector = new ConnectivityInspector<>(dag); Iterator internalTopoIter = dag.iterator(); List previousVertices = new ArrayList<>(); while (internalTopoIter.hasNext()) { Long vertex = internalTopoIter.next(); for (Long previousVertex : previousVertices) { connectivityInspector.pathExists(vertex, previousVertex); } previousVertices.add(vertex); } } } @Test public void testIterationBehaviors() { int vertexCount = 100; DirectedAcyclicGraph dag = new DirectedAcyclicGraph<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); RepeatableRandomGraphGenerator graphGen = new RepeatableRandomGraphGenerator<>(vertexCount, 500, 2); graphGen.generateGraph(dag); Iterator dagIter = dag.iterator(); // Scroll through all the elements, then make sure things happen as // should when an iterator is all used up for (int i = 0; i < vertexCount; i++) { assertTrue(dagIter.hasNext()); dagIter.next(); } assertFalse(dagIter.hasNext()); try { dagIter.next(); fail(); } catch (NoSuchElementException e) { // good, we already looked at all of the elements } assertFalse(dagIter.hasNext()); dagIter = dag.iterator(); // replace dagIter; assertNotNull(dagIter.next()); // make sure it works on first element // even if hasNext() wasn't called // Test that ConcurrentModificationExceptionS happen as they should when // the topology is modified during iteration // remove a random vertex dag.removeVertex(dag.vertexSet().iterator().next()); // now we expect exceptions since the topological order has been // modified (albeit trivially) try { dagIter.next(); fail(); // fail, no exception was thrown } catch (ConcurrentModificationException e) { // good, this is expected } try { dagIter.hasNext(); fail(); // fail, no exception was thrown } catch (ConcurrentModificationException e) { // good, this is expected } try { dagIter.remove(); fail(); // fail, no exception was thrown } catch (ConcurrentModificationException e) { // good, this is expected } } @Test public void testWhenVertexIsNotInGraph_Then_ThrowException() { DirectedAcyclicGraph dag = new DirectedAcyclicGraph<>(DefaultEdge.class); try { dag.addEdge(1l, 2l); } catch (IllegalArgumentException e) { return; } fail("No exception 'IllegalArgumentException' catched"); } //@formatter:off /** * Input: * * +--> C * | * A +--> B +--+ * | * +--> D * * Expected output when determining ancestors of C * (order does not matter): * * B, A */ //@formatter:on @Test public void testDetermineAncestors00() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Set expectedAncestors = new HashSet<>(); expectedAncestors.add("B"); expectedAncestors.add("A"); Set ancestors = graph.getAncestors("C"); assertEquals(expectedAncestors, ancestors); } //@formatter:off /** * Input: * * +--> C * | * A +--> B +--+ * | * +--> D * * Expected output when determining ancestors of A: * * an empty list */ //@formatter:on @Test public void testDetermineAncestors01() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Set expectedAncestors = new HashSet<>(); Set ancestors = graph.getAncestors("A"); assertEquals(expectedAncestors, ancestors); } //@formatter:off /** * Input: * * A +--> B +--> C * | ^ * | | * +-------------+ * * Expected output when determining ancestors of A * (order does not matter): * * B, A */ //@formatter:on @Test public void testDetermineAncestors02() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(a, c); Set expectedAncestors = new HashSet<>(); expectedAncestors.add("B"); expectedAncestors.add("A"); Set ancestors = graph.getAncestors("C"); assertEquals(expectedAncestors, ancestors); } //@formatter:off /** * Input: * * +--> C * | * A +--> B +--+ * | * +--> D * * Expected output when determining descendents of B * (order does not matter): * * C, D */ //@formatter:on @Test public void testDetermineDescendants00() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Set expectedDescendents = new HashSet<>(); expectedDescendents.add("C"); expectedDescendents.add("D"); Set ancestors = graph.getDescendants("B"); assertEquals(expectedDescendents, ancestors); } //@formatter:off /** * Input: * * +--> C * | * A +--> B +--+ * | * +--> D * * Expected output when determining descendents of C: * * an empty list */ //@formatter:on @Test public void testDetermineDescendants01() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addVertex(d); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, d); Set expectedDescendents = new HashSet<>(); Set ancestors = graph.getDescendants("C"); assertEquals(expectedDescendents, ancestors); } //@formatter:off /** * Input: * * A +--> B +--> C * | ^ * | | * +-------------+ * * Expected output when determining ancestors of A * (order does not matter): * * B, C */ //@formatter:on @Test public void testDetermineDescendants02() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph(TestEdge.class); String a = "A"; String b = "B"; String c = "C"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(a, c); Set expectedAncestors = new HashSet<>(); expectedAncestors.add("B"); expectedAncestors.add("C"); Set ancestors = graph.getDescendants("A"); assertEquals(expectedAncestors, ancestors); } @Test public void testRemoveAllVerticesShouldNotDeleteTopologyIfTheGraphHasVerticesLeft() { // Given DirectedAcyclicGraph dag = new DirectedAcyclicGraph<>(TestEdge.class); List vertices = Arrays.asList("a", "b", "c", "d", "e"); vertices.forEach(dag::addVertex); dag.addEdge("e", "a"); dag.addEdge("e", "b"); dag.addEdge("a", "d"); dag.addEdge("b", "c"); // When dag.removeAllVertices(vertices.subList(0, vertices.size() - 2)); // Then assertTrue(dag.iterator().hasNext()); } //@formatter:off /** * Input: * * A +--> B +--> C * | ^ * | | * +-------------+ * * Expected output when determining ancestors of A * (order does not matter): * * B, C */ //@formatter:on @Test public void testMultipleEdges01() { DirectedAcyclicGraph graph = new DirectedAcyclicGraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false, true); String a = "A"; String b = "B"; String c = "C"; graph.addVertex(a); graph.addVertex(b); graph.addVertex(c); graph.addEdge(a, b); graph.addEdge(a, b); graph.addEdge(b, c); graph.addEdge(b, c); graph.addEdge(a, c); graph.addEdge(a, c); Set expectedAncestors = new HashSet<>(); expectedAncestors.add("B"); expectedAncestors.add("C"); Set ancestors = graph.getDescendants("A"); assertEquals(expectedAncestors, ancestors); Iterator it = graph.iterator(); assertEquals(a, it.next()); assertEquals(b, it.next()); assertEquals(c, it.next()); assertFalse(it.hasNext()); } @Test public void testMultipleEdges02() { Random rng = new Random(17); // create random DAG with multiple edges Graph sourceGraph = setUpDagWithMultipleEdges(20, 20, 0.5, rng); replayAndTestDAG(sourceGraph, rng); } @Test public void testMultipleEdges03() { // allow different tests per time Random rng = new Random(); for (double p : Arrays.asList(0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7)) { // create random DAG with multiple edges Graph sourceGraph = setUpDagWithMultipleEdges(20, 20, p, rng); replayAndTestDAG(sourceGraph, rng); } } // ~ Private Methods ---------------------------------------------------------- private Graph setUpWithSeed(int vertices, int edges, long seed) { GraphGenerator randomGraphGenerator = new RepeatableRandomGraphGenerator<>(vertices, edges, seed); Graph sourceGraph = new SimpleDirectedGraph<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); randomGraphGenerator.generateGraph(sourceGraph); return sourceGraph; } private Graph setUpDagWithMultipleEdges( int levels, int verticesPerLevel, double edgeProb, Random rng) { Graph g = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createLongSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); Long[][] vertices = new Long[levels][verticesPerLevel]; for (int i = 0; i < levels; i++) { for (int j = 0; j < verticesPerLevel; j++) { vertices[i][j] = g.addVertex(); } if (i == 0) { continue; } for (int k = 0; k < verticesPerLevel; k++) { for (int l = 0; l < verticesPerLevel; l++) { if (rng.nextDouble() < edgeProb) { g.addEdge(vertices[i - 1][k], vertices[i][l]); } // sometimes we add the edge twice if (rng.nextDouble() < edgeProb) { g.addEdge(vertices[i - 1][k], vertices[i][l]); } } } } return g; } private void replayAndTestDAG(Graph sourceGraph, Random rng) { // extract edges and shuffle List edgeList = sourceGraph.edgeSet().stream().collect(Collectors.toList()); Collections.shuffle(edgeList, rng); // create DAG which allows multiple edges DirectedAcyclicGraph graph = new DirectedAcyclicGraph<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false, true); for (Long v : sourceGraph.vertexSet()) { graph.addVertex(v); } for (DefaultEdge e : edgeList) { Long s = sourceGraph.getEdgeSource(e); Long t = sourceGraph.getEdgeTarget(e); graph.addEdge(s, t); } Map topo = new HashMap<>(); int i = 0; for (Long v : graph) { topo.put(v, i++); } for (DefaultEdge e : graph.edgeSet()) { Long s = graph.getEdgeSource(e); Long t = graph.getEdgeTarget(e); assertTrue(topo.get(s) < topo.get(t)); } } // ~ Inner Classes ---------------------------------------------------------- // it is nice for tests to be easily repeatable, so we use a graph generator // that we can seed for specific configurations public static class RepeatableRandomGraphGenerator implements GraphGenerator { private Random randomizer; private int numOfVertexes; private int numOfEdges; public RepeatableRandomGraphGenerator(int vertices, int edges, long seed) { this.numOfVertexes = vertices; this.numOfEdges = edges; this.randomizer = new Random(seed); } @Override public void generateGraph(Graph graph, Map namedVerticesMap) { List vertices = new ArrayList<>(numOfVertexes); Set edgeGeneratorIds = new HashSet<>(); for (int i = 0; i < numOfVertexes; i++) { vertices.add(graph.addVertex()); } for (int i = 0; i < numOfEdges; i++) { Integer edgeGeneratorId; do { edgeGeneratorId = randomizer.nextInt(numOfVertexes * (numOfVertexes - 1)); } while (edgeGeneratorIds.contains(edgeGeneratorId)); int fromVertexId = edgeGeneratorId / numOfVertexes; int toVertexId = edgeGeneratorId % (numOfVertexes - 1); if (toVertexId >= fromVertexId) { ++toVertexId; } try { graph.addEdge(vertices.get(fromVertexId), vertices.get(toVertexId)); } catch (IllegalArgumentException e) { // okay, that's fine; omit cycle } } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/DirectedGraphTest.java000066400000000000000000000117701402514743400314370ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * A unit test for directed multigraph. * * @author Barak Naveh */ public class DirectedGraphTest { // ~ Instance fields -------------------------------------------------------- private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; // ~ Methods ---------------------------------------------------------------- /** * . */ @Test public void testEdgeSetFactory() { DirectedMultigraph g = new LinkedHashSetDirectedMultigraph<>(DefaultEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); DefaultEdge e1 = g.addEdge(v1, v2); DefaultEdge e2 = g.addEdge(v2, v1); DefaultEdge e3 = g.addEdge(v2, v3); DefaultEdge e4 = g.addEdge(v3, v1); Iterator iter = g.edgeSet().iterator(); assertEquals(e1, iter.next()); assertEquals(e2, iter.next()); assertEquals(e3, iter.next()); assertEquals(e4, iter.next()); assertFalse(iter.hasNext()); assertEquals("([v1, v2, v3], [(v1,v2), (v2,v1), (v2,v3), (v3,v1)])", g.toString()); } /** * . */ @Test public void testEdgeOrderDeterminism() { Graph g = new DirectedMultigraph<>(DefaultEdge.class); g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); DefaultEdge e1 = g.addEdge(v1, v2); DefaultEdge e2 = g.addEdge(v2, v3); DefaultEdge e3 = g.addEdge(v3, v1); Iterator iter = g.edgeSet().iterator(); assertEquals(e1, iter.next()); assertEquals(e2, iter.next()); assertEquals(e3, iter.next()); // some bonus tests assertTrue(Graphs.testIncidence(g, e1, v1)); assertTrue(Graphs.testIncidence(g, e1, v2)); assertFalse(Graphs.testIncidence(g, e1, v3)); assertEquals(v2, Graphs.getOppositeVertex(g, e1, v1)); assertEquals(v1, Graphs.getOppositeVertex(g, e1, v2)); assertEquals("([v1, v2, v3], [(v1,v2), (v2,v3), (v3,v1)])", g.toString()); } /** * . */ @Test public void testEdgesOf() { Graph g = createMultiTriangle(); assertEquals(3, g.edgesOf(v1).size()); assertEquals(3, g.edgesOf(v2).size()); assertEquals(2, g.edgesOf(v3).size()); } /** * . */ @Test public void testInDegreeOf() { Graph g = createMultiTriangle(); assertEquals(2, g.inDegreeOf(v1)); assertEquals(1, g.inDegreeOf(v2)); assertEquals(1, g.inDegreeOf(v3)); } /** * . */ @Test public void testOutDegreeOf() { Graph g = createMultiTriangle(); assertEquals(1, g.outDegreeOf(v1)); assertEquals(2, g.outDegreeOf(v2)); assertEquals(1, g.outDegreeOf(v3)); } /** * . */ @Test public void testVertexOrderDeterminism() { Graph g = createMultiTriangle(); Iterator iter = g.vertexSet().iterator(); assertEquals(v1, iter.next()); assertEquals(v2, iter.next()); assertEquals(v3, iter.next()); } private Graph createMultiTriangle() { Graph g = new DirectedMultigraph<>(DefaultEdge.class); initMultiTriangle(g); return g; } private void initMultiTriangle(Graph g) { g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); g.addEdge(v1, v2); g.addEdge(v2, v1); g.addEdge(v2, v3); g.addEdge(v3, v1); } // ~ Inner Classes ---------------------------------------------------------- /** * A graph implementation with an edge factory using linked hash sets. * * @param the graph vertex type * @param the graph edge type */ private class LinkedHashSetDirectedMultigraph extends DirectedMultigraph { private static final long serialVersionUID = -1826738982402033648L; public LinkedHashSetDirectedMultigraph(Class edgeClass) { super(edgeClass); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/EqualsAndHashCodeTest.java000066400000000000000000000252431402514743400322060ustar00rootroot00000000000000/* * (C) Copyright 2012-2021, by Vladimir Kostyukov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; public class EqualsAndHashCodeTest { // ~ Instance fields -------------------------------------------------------- private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; /** * Tests equals/hashCode methods for directed graphs. */ @Test public void testDefaultDirectedGraph() { Graph g1 = new DefaultDirectedGraph<>(DefaultEdge.class); g1.addVertex(v1); g1.addVertex(v2); g1.addVertex(v3); g1.addVertex(v4); DefaultEdge e12 = g1.addEdge(v1, v2); DefaultEdge e23 = g1.addEdge(v2, v3); DefaultEdge e31 = g1.addEdge(v3, v1); Graph g2 = new DefaultDirectedGraph<>(DefaultEdge.class); g2.addVertex(v4); g2.addVertex(v3); g2.addVertex(v2); g2.addVertex(v1); g2.addEdge(v3, v1, e31); g2.addEdge(v2, v3, e23); g2.addEdge(v1, v2, e12); Graph g3 = new DefaultDirectedGraph<>(DefaultEdge.class); g3.addVertex(v4); g3.addVertex(v3); g3.addVertex(v2); g3.addVertex(v1); g3.addEdge(v3, v1, e31); g3.addEdge(v2, v3, e23); assertTrue(g2.equals(g1)); assertFalse(g3.equals(g2)); assertEquals(g2.hashCode(), g1.hashCode()); } /** * Tests equals/hashCode methods for undirected graphs. */ @Test public void testSimpleGraph() { Graph g1 = new SimpleGraph<>(DefaultEdge.class); g1.addVertex(v1); g1.addVertex(v2); g1.addVertex(v3); g1.addVertex(v4); DefaultEdge e12 = g1.addEdge(v1, v2); DefaultEdge e23 = g1.addEdge(v2, v3); DefaultEdge e31 = g1.addEdge(v3, v1); Graph g2 = new SimpleGraph<>(DefaultEdge.class); g2.addVertex(v4); g2.addVertex(v3); g2.addVertex(v2); g2.addVertex(v1); g2.addEdge(v3, v1, e31); g2.addEdge(v2, v3, e23); g2.addEdge(v1, v2, e12); Graph g3 = new SimpleGraph<>(DefaultEdge.class); g3.addVertex(v4); g3.addVertex(v3); g3.addVertex(v2); g3.addVertex(v1); g3.addEdge(v3, v1, e31); g3.addEdge(v2, v3, e23); assertTrue(g2.equals(g1)); assertFalse(g3.equals(g2)); assertEquals(g2.hashCode(), g1.hashCode()); } /** * Tests equals/hashCode methods for graphs with non-Intrusive edges. */ @Test public void testGraphsWithNonIntrusiveEdge() { Graph g1 = new DefaultDirectedGraph<>(String.class); g1.addVertex(v1); g1.addVertex(v2); g1.addVertex(v3); g1.addEdge(v1, v2, v1 + v2); g1.addEdge(v3, v1, v3 + v1); Graph g2 = new DefaultDirectedGraph<>(String.class); g2.addVertex(v3); g2.addVertex(v2); g2.addVertex(v1); g2.addEdge(v3, v1, v3 + v1); g2.addEdge(v1, v2, v1 + v2); Graph g3 = new DefaultDirectedGraph<>(String.class); g3.addVertex(v3); g3.addVertex(v2); g3.addVertex(v1); g3.addEdge(v3, v1, v3 + v1); g3.addEdge(v1, v2, v1 + v2); g3.addEdge(v2, v3, v2 + v3); assertTrue(g1.equals(g2)); assertFalse(g2.equals(g3)); assertEquals(g2.hashCode(), g1.hashCode()); } /** * Tests equals/hashCode methods for graphs with multiple edges and loops. */ @Test public void testPseudograph() { Graph g1 = new Pseudograph<>(DefaultEdge.class); g1.addVertex(v1); g1.addVertex(v2); g1.addVertex(v3); DefaultEdge e121 = g1.addEdge(v1, v2); DefaultEdge e23 = g1.addEdge(v2, v3); DefaultEdge e31 = g1.addEdge(v3, v1); DefaultEdge e122 = g1.addEdge(v1, v2); DefaultEdge e11 = g1.addEdge(v1, v1); Graph g2 = new Pseudograph<>(DefaultEdge.class); g2.addVertex(v3); g2.addVertex(v2); g2.addVertex(v1); g2.addEdge(v1, v1, e11); g2.addEdge(v1, v2, e121); g2.addEdge(v3, v1, e31); g2.addEdge(v2, v3, e23); g2.addEdge(v1, v2, e122); Graph g3 = new Pseudograph<>(DefaultEdge.class); g3.addVertex(v3); g3.addVertex(v2); g3.addVertex(v1); g3.addEdge(v1, v1, e11); g3.addEdge(v1, v2, e121); g3.addEdge(v3, v1, e31); g3.addEdge(v2, v3, e23); assertTrue(g1.equals(g2)); assertFalse(g2.equals(g3)); assertEquals(g2.hashCode(), g1.hashCode()); } /** * Tests equals/hashCode methods for graphs with custom edges. */ @Test public void testGrapshWithCustomEdges() { Graph g1 = new SimpleGraph<>(CustomEdge.class); g1.addVertex(v1); g1.addVertex(v2); g1.addVertex(v3); g1.addEdge(v1, v2, new CustomEdge("v1-v2")); g1.addEdge(v3, v1, new CustomEdge("v3-v1")); Graph g2 = new SimpleGraph<>(CustomEdge.class); g2.addVertex(v1); g2.addVertex(v2); g2.addVertex(v3); g2.addEdge(v1, v2, new CustomEdge("v1-v2")); g2.addEdge(v3, v1, new CustomEdge("v3-v1")); Graph g3 = new SimpleGraph<>(CustomEdge.class); g3.addVertex(v1); g3.addVertex(v2); g3.addVertex(v3); g3.addEdge(v1, v2, new CustomEdge("v1::v2")); g3.addEdge(v3, v1, new CustomEdge("v3-v1")); assertTrue(g1.equals(g2)); assertFalse(g2.equals(g3)); assertEquals(g2.hashCode(), g1.hashCode()); } /** * Tests equals/hashCode for graphs transformed to weighted. */ @Test public void testAsWeightedGraphs() { Graph g1 = new SimpleGraph<>(DefaultEdge.class); g1.addVertex(v1); g1.addVertex(v2); g1.addVertex(v3); DefaultEdge e12 = g1.addEdge(v1, v2); DefaultEdge e23 = g1.addEdge(v2, v3); DefaultEdge e31 = g1.addEdge(v3, v1); Graph g2 = new SimpleGraph<>(DefaultEdge.class); g2.addVertex(v1); g2.addVertex(v2); g2.addVertex(v3); g2.addEdge(v1, v2, e12); g2.addEdge(v2, v3, e23); g2.addEdge(v3, v1, e31); Map weightMap1 = new HashMap<>(); weightMap1.put(e12, 10.0); weightMap1.put(e23, 20.0); weightMap1.put(e31, 30.0); Graph g3 = new AsWeightedGraph<>(g1, weightMap1); Map weightMap2 = new HashMap<>(); weightMap2.put(e12, 10.0); weightMap2.put(e23, 20.0); weightMap2.put(e31, 30.0); Graph g4 = new AsWeightedGraph<>(g2, weightMap2); Map weightMap3 = new HashMap<>(); weightMap3.put(e12, 100.0); weightMap3.put(e23, 200.0); weightMap3.put(e31, 300.0); Graph g5 = new AsWeightedGraph<>(g2, weightMap3); assertTrue(g1.equals(g2)); assertEquals(g2.hashCode(), g1.hashCode()); assertTrue(g3.equals(g4)); assertEquals(g4.hashCode(), g3.hashCode()); assertFalse(g4.equals(g5)); } @Test public void testHashcodeEquals() { Graph g = GraphTypeBuilder. directed().weighted(true).buildGraph(); g.addVertex(0); g.addVertex(1); g.addEdge(0, 1, 1); g.setEdgeWeight(1, 2 + 1e-08); Graph h = GraphTypeBuilder. directed().weighted(true).buildGraph(); h.addVertex(0); h.addVertex(1); h.addEdge(0, 1, 1); h.setEdgeWeight(1, 2 - 1e-08); assertNotEquals(g, h); } @Test public void testUndirectedEquality() { Graph g = GraphTypeBuilder. undirected().buildGraph(); g.addVertex(0); g.addVertex(1); g.addEdge(0, 1, 1); Graph h = GraphTypeBuilder. undirected().buildGraph(); h.addVertex(0); h.addVertex(1); h.addEdge(1, 0, 1); assertEquals(g.hashCode(), h.hashCode()); assertEquals(g, h); } @Test public void testDirectedEquality() { Graph g = GraphTypeBuilder. directed().buildGraph(); g.addVertex(0); g.addVertex(1); g.addEdge(0, 1, 1); Graph h = GraphTypeBuilder. directed().buildGraph(); h.addVertex(0); h.addVertex(1); h.addEdge(1, 0, 1); assertNotEquals(g.hashCode(), h.hashCode()); assertNotEquals(g, h); } /** * Simple custom edge class. */ public static class CustomEdge extends DefaultEdge { private static final long serialVersionUID = 1L; private String label; public CustomEdge() { } public CustomEdge(String label) { this.label = label; } @Override public int hashCode() { return label.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof CustomEdge)) { return false; } CustomEdge edge = (CustomEdge) obj; return label.equals(edge.label); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/GenericGraphsTest.java000066400000000000000000000127441402514743400314550ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by HartmutBenz and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; /** * A unit test for graph generic vertex/edge parameters. * * @author Hartmut Benz */ public class GenericGraphsTest { // ~ Instance fields -------------------------------------------------------- Graph objectGraph; Graph fooFooGraph; Graph barBarGraph; @Test public void testLegalInsertStringGraph() { String v1 = "Vertex1"; Object v2 = "Vertex2"; objectGraph.addVertex(v1); objectGraph.addVertex(v2); objectGraph.addEdge(v1, v2); } @Test public void testLegalInsertFooGraph() { FooVertex v1 = new FooVertex(); FooVertex v2 = new FooVertex(); BarVertex vb1 = new BarVertex(); BarVertex vb2 = new BarVertex(); fooFooGraph.addVertex(v1); fooFooGraph.addVertex(v2); fooFooGraph.addVertex(vb1); fooFooGraph.addVertex(vb2); fooFooGraph.addEdge(v1, v2); fooFooGraph.addEdge(vb1, vb2); fooFooGraph.addEdge(v1, vb2); fooFooGraph.addEdge(v1, v2, new BarEdge()); fooFooGraph.addEdge(v1, vb2, new BarEdge()); fooFooGraph.addEdge(vb1, vb2, new BarEdge()); } @Test public void testLegalInsertBarGraph() { BarVertex v1 = new BarVertex(); BarVertex v2 = new BarVertex(); barBarGraph.addVertex(v1); barBarGraph.addVertex(v2); barBarGraph.addEdge(v1, v2); } @Test public void testLegalInsertFooBarGraph() { FooVertex v1 = new FooVertex(); FooVertex v2 = new FooVertex(); BarVertex vb1 = new BarVertex(); BarVertex vb2 = new BarVertex(); fooFooGraph.addVertex(v1); fooFooGraph.addVertex(v2); fooFooGraph.addVertex(vb1); fooFooGraph.addVertex(vb2); fooFooGraph.addEdge(v1, v2); fooFooGraph.addEdge(vb1, vb2); fooFooGraph.addEdge(v1, vb2); } @Test public void testAlissaHacker() { Graph g = new DefaultDirectedGraph<>(CustomEdge.class); g.addVertex("a"); g.addVertex("b"); g.addEdge("a", "b"); CustomEdge custom = g.getEdge("a", "b"); String s = custom.toString(); assertEquals("Alissa P. Hacker approves the edge from a to b", s); } @Test public void testEqualButNotSameVertex() { EquivVertex v1 = new EquivVertex(); EquivVertex v2 = new EquivVertex(); EquivGraph g = new EquivGraph(); g.addVertex(v1); g.addVertex(v2); g.addEdge(v1, v2, new DefaultEdge()); assertEquals(2, g.degreeOf(v1)); assertEquals(2, g.degreeOf(v2)); } /** * . */ @Before public void setUp() { objectGraph = new DefaultDirectedGraph<>(DefaultEdge.class); fooFooGraph = new SimpleGraph<>(FooEdge.class); barBarGraph = new SimpleGraph<>(BarEdge.class); } // ~ Inner Classes ---------------------------------------------------------- public static class CustomEdge extends DefaultEdge { private static final long serialVersionUID = 1L; @Override public String toString() { return "Alissa P. Hacker approves the edge from " + getSource() + " to " + getTarget(); } } public static class EquivVertex { @Override public boolean equals(Object o) { return true; } @Override public int hashCode() { return 1; } } public static class EquivGraph extends AbstractBaseGraph { private static final long serialVersionUID = 8647217182401022498L; public EquivGraph() { super( SupplierUtil.createSupplier(EquivVertex.class), SupplierUtil.createSupplier(DefaultEdge.class), DefaultGraphType.directedPseudograph().asUnweighted()); } } public static class FooEdge extends DefaultEdge { private static final long serialVersionUID = 1L; } private class FooVertex { String str; public FooVertex() { super(); str = "empty foo"; } public FooVertex(String s) { str = s; } public String toString() { return str; } } public static class BarEdge extends FooEdge { private static final long serialVersionUID = 1L; } private class BarVertex extends FooVertex { public BarVertex() { super("empty bar"); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/GraphWalkTest.java000066400000000000000000000312441402514743400306100ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; /** * * @author Joris Kinable */ public class GraphWalkTest { @Test(expected = IllegalArgumentException.class) public void testInvalidPath1() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); graph.addEdge(0, 0); // Invalid: the path's edgeList should contain the edge (0,0) new GraphWalk<>(graph, 0, 0, Arrays.asList(0, 0), Collections.emptyList(), 0); } @Test(expected = IllegalArgumentException.class) public void testInvalidPath2() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(0); // Invalid: the path's vertexList and edgeList cannot both be empty new GraphWalk<>(graph, 0, 0, Collections.emptyList(), Collections.emptyList(), 0); } @Test(expected = InvalidGraphWalkException.class) public void testInvalidPath3() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex(0); // Invalid: The graph does not contain a self loop from 0 to 0. GraphWalk gw = new GraphWalk<>(graph, 0, 0, Arrays.asList(0, 0), null, 0); gw.verify(); } @Test(expected = InvalidGraphWalkException.class) public void testInvalidPath4() { Graph graph = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 3); // Invalid: The graph does not contain an edge from 1 to 3 GraphWalk gw = new GraphWalk<>(graph, 0, 2, Arrays.asList(0, 1, 3, 2), null, 0); gw.verify(); } @Test(expected = InvalidGraphWalkException.class) public void testInvalidPath5() { Graph graph = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); DefaultEdge e1 = graph.addEdge(0, 1); graph.addEdge(1, 2); DefaultEdge e3 = graph.addEdge(2, 3); // Invalid: the path jumps from vertex 1 to vertex 2 (edge (1,2) is skipped) GraphWalk gw = new GraphWalk<>(graph, 0, 2, null, Arrays.asList(e1, e3), 0); gw.verify(); } @Test public void testValidPaths() { Graph graph = new SimpleGraph<>(DefaultEdge.class); graph.addVertex(0); // empty path GraphWalk gw1 = new GraphWalk<>(graph, null, null, Collections.emptyList(), Collections.emptyList(), 0); gw1.verify(); GraphWalk gw2 = new GraphWalk<>(graph, null, null, null, Collections.emptyList(), 0); gw2.verify(); GraphWalk gw3 = new GraphWalk<>(graph, null, null, Collections.emptyList(), null, 0); gw3.verify(); // singleton path GraphWalk gw4 = new GraphWalk<>(graph, 0, 0, Collections.singletonList(0), Collections.emptyList(), 0); gw4.verify(); GraphWalk gw5 = new GraphWalk<>(graph, Collections.singletonList(0), 0); gw5.verify(); } @Test public void testEmptyPath() { Graph graph = new SimpleGraph<>(DefaultEdge.class); List> paths = new ArrayList<>(); paths.add(new GraphWalk<>(graph, null, null, Collections.emptyList(), 0)); paths.add(new GraphWalk<>(graph, Collections.emptyList(), 0)); for (GraphWalk path : paths) { Assert.assertEquals(0, path.getLength()); Assert.assertEquals(Collections.emptyList(), path.getVertexList()); Assert.assertEquals(Collections.emptyList(), path.getEdgeList()); Assert.assertTrue(path.isEmpty()); Assert.assertEquals(GraphWalk.emptyWalk(graph), path); } } @Test public void testNonSimplePath() { CompleteGraphGenerator completeGraphGenerator = new CompleteGraphGenerator<>(5); Graph completeGraph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); completeGraphGenerator.generateGraph(completeGraph); List vertexList = Arrays.asList(0, 1, 2, 3, 2, 3, 4); List edgeList = new ArrayList<>(); for (int i = 0; i < vertexList.size() - 1; i++) edgeList.add(completeGraph.getEdge(vertexList.get(i), vertexList.get(i + 1))); GraphPath p1 = new GraphWalk<>(completeGraph, 0, 4, edgeList, 10); Assert.assertEquals(0, p1.getStartVertex().intValue()); Assert.assertEquals(4, p1.getEndVertex().intValue()); Assert.assertEquals(vertexList, p1.getVertexList()); Assert.assertEquals(edgeList.size(), p1.getLength()); Assert.assertEquals(10.0, p1.getWeight(), 0.0000000001); GraphPath p2 = new GraphWalk<>(completeGraph, vertexList, 10); Assert.assertEquals(0, p2.getStartVertex().intValue()); Assert.assertEquals(4, p2.getEndVertex().intValue()); Assert.assertEquals(edgeList, p2.getEdgeList()); Assert.assertEquals(edgeList.size(), p2.getLength()); Assert.assertEquals(10.0, p2.getWeight(), 0.0000000001); } @Test public void testReversePathUndirected() { Graph graph = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); DefaultWeightedEdge e1 = Graphs.addEdge(graph, 0, 1, 2); DefaultWeightedEdge e2 = Graphs.addEdge(graph, 1, 2, 3); DefaultWeightedEdge e3 = Graphs.addEdge(graph, 2, 3, 4); GraphWalk gw1 = new GraphWalk<>(graph, 0, 3, Arrays.asList(0, 1, 2, 3), null, 9); GraphWalk gw2 = new GraphWalk<>(graph, 0, 3, null, Arrays.asList(e1, e2, e3), 9); GraphWalk rev1 = gw1.reverse(gw -> gw1.getWeight()); rev1.verify(); GraphWalk rev2 = gw2.reverse(gw -> gw2.getWeight()); rev2.verify(); GraphWalk revPath = new GraphWalk<>(graph, 3, 0, null, Arrays.asList(e3, e2, e1), 9); Assert.assertEquals(revPath, rev1); Assert.assertEquals(revPath, rev2); rev1 = gw1.reverse(); Assert.assertEquals(9.0, gw1.getWeight(), 0.0000000001); rev2 = gw2.reverse(); Assert.assertEquals(9.0, gw2.getWeight(), 0.0000000001); } @Test(expected = InvalidGraphWalkException.class) public void testReverseInvalidPathDirected() { Graph graph = new SimpleDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); graph.addEdge(0, 1); graph.addEdge(1, 2); graph.addEdge(2, 3); GraphWalk gw1 = new GraphWalk<>(graph, 0, 3, Arrays.asList(0, 1, 2, 3), null, 0); // Walk cannot be reversed since reverse arcs do not exist gw1.reverse(gw -> gw.edgeList.stream().mapToDouble(gw.graph::getEdgeWeight).sum()); } @Test public void testReversePathDirected() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); Graphs.addEdge(graph, 0, 1, 1); Graphs.addEdge(graph, 1, 2, 2); Graphs.addEdge(graph, 2, 3, 3); DefaultWeightedEdge e1 = Graphs.addEdge(graph, 3, 2, 4); DefaultWeightedEdge e2 = Graphs.addEdge(graph, 2, 1, 5); DefaultWeightedEdge e3 = Graphs.addEdge(graph, 1, 0, 6); GraphWalk gw1 = new GraphWalk<>(graph, 0, 3, Arrays.asList(0, 1, 2, 3), null, 0); GraphWalk rev1 = gw1.reverse(gw -> gw.getEdgeList().stream().mapToDouble(gw.graph::getEdgeWeight).sum()); rev1.verify(); GraphWalk revPath = new GraphWalk<>(graph, 3, 0, null, Arrays.asList(e1, e2, e3), 15); Assert.assertEquals(revPath, rev1); Assert.assertEquals(15, rev1.getWeight(), 0.00000001); GraphWalk rev2 = gw1.reverse(); Assert.assertEquals(15, rev2.getWeight(), 0.00000001); } /** * Cannot extend empty path */ @Test(expected = IllegalArgumentException.class) public void testIllegalConcatPath1() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultEdge.class); graph.addVertex(0); GraphWalk gw1 = GraphWalk.emptyWalk(graph); GraphWalk gw2 = GraphWalk.singletonWalk(graph, 0, 10); gw1.concat(gw2, gw -> gw1.getWeight() + gw2.getWeight()); } /** * Cannot concat two paths which do not end/start at the same vertex */ @Test(expected = IllegalArgumentException.class) public void testIllegalConcatPath2() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultEdge.class); graph.addVertex(0); graph.addVertex(1); GraphWalk gw1 = GraphWalk.singletonWalk(graph, 0, 10); GraphWalk gw2 = GraphWalk.singletonWalk(graph, 1, 12); gw1.concat(gw2, gw -> gw1.getWeight() + gw2.getWeight()); } @Test public void testConcatPath1() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); graph.addEdge(0, 1); graph.addEdge(1, 2); DefaultEdge e3 = graph.addEdge(2, 3); DefaultEdge e4 = graph.addEdge(3, 1); GraphWalk gw1 = new GraphWalk<>(graph, 0, 2, Arrays.asList(0, 1, 2), null, 5); GraphWalk gw2 = new GraphWalk<>(graph, 2, 1, null, Arrays.asList(e3, e4), 7); GraphWalk gw3 = gw1.concat(gw2, gw -> gw1.getWeight() + gw2.getWeight()); gw3.verify(); GraphWalk expected = new GraphWalk<>(graph, 0, 1, Arrays.asList(0, 1, 2, 3, 1), null, 12); Assert.assertEquals(expected, gw3); Assert.assertEquals(12, gw3.getWeight(), 0.00000001); } @Test public void testConcatPathWithSingleton() { Graph graph = new SimpleDirectedWeightedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1)); graph.addEdge(0, 1); GraphWalk gw1 = new GraphWalk<>(graph, 0, 1, Arrays.asList(0, 1), null, 5); GraphWalk gw2 = GraphWalk.singletonWalk(graph, 1, 10); GraphWalk gw3 = gw1.concat(gw2, gw -> gw1.getWeight() + gw2.getWeight()); gw3.verify(); // Concatenation with singleton shouldn't result in a different path. Assert.assertEquals(gw1, gw3); } @Test public void testFirstEmptyWalkEquality() { Graph graph1 = new SimpleGraph<>(DefaultEdge.class); GraphWalk gw1 = GraphWalk.emptyWalk(graph1); Graph graph2 = new SimpleGraph<>(DefaultEdge.class); graph2.addVertex(0); GraphWalk gw2 = GraphWalk.singletonWalk(graph2, 0); Assert.assertNotEquals(gw1, gw2); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/IncomingOutgoingEdgesTest.java000066400000000000000000000272471402514743400331670ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.graph.BaseIntrusiveEdgesSpecifics.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.*; /** * Check Incoming/Outgoing edges in directed and undirected graphs. * * @author Dimitrios Michail */ public class IncomingOutgoingEdgesTest { public static void testAddDuplicateEdgeDirectedGraph( Supplier> graphSupplier) { Graph g = graphSupplier.get(); assertTrue(g.getType().isDirected()); g.addVertex(1); g.addVertex(2); g.addVertex(3); DefaultEdge e = new DefaultEdge(); g.addEdge(1, 2, e); assertTrue(g.edgeSet().size() == 1); assertEquals(Collections.emptySet(), g.incomingEdgesOf(1)); assertEquals(0, g.inDegreeOf(1)); assertEquals(Set.of(e), g.outgoingEdgesOf(1)); assertEquals(1, g.outDegreeOf(1)); assertEquals(Set.of(e), g.incomingEdgesOf(2)); assertEquals(1, g.inDegreeOf(2)); assertEquals(Collections.emptySet(), g.outgoingEdgesOf(2)); assertEquals(0, g.outDegreeOf(2)); assertEquals(Collections.emptySet(), g.incomingEdgesOf(3)); assertEquals(0, g.inDegreeOf(3)); assertEquals(Collections.emptySet(), g.outgoingEdgesOf(3)); assertEquals(0, g.outDegreeOf(3)); assertThrows(IntrusiveEdgeException.class, () -> g.addEdge(1, 3, e)); assertTrue(g.edgeSet().size() == 1); assertEquals(Collections.emptySet(), g.incomingEdgesOf(1)); assertEquals(0, g.inDegreeOf(1)); assertEquals(Set.of(e), g.outgoingEdgesOf(1)); assertEquals(1, g.outDegreeOf(1)); assertEquals(Set.of(e), g.incomingEdgesOf(2)); assertEquals(1, g.inDegreeOf(2)); assertEquals(Collections.emptySet(), g.outgoingEdgesOf(2)); assertEquals(0, g.outDegreeOf(2)); assertEquals(Collections.emptySet(), g.incomingEdgesOf(3)); assertEquals(0, g.inDegreeOf(3)); assertEquals(Collections.emptySet(), g.outgoingEdgesOf(3)); assertEquals(0, g.outDegreeOf(3)); } public static void testDirectedGraph(Supplier> graphSupplier) { Graph g = graphSupplier.get(); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); assertEquals(5, g.vertexSet().size()); assertTrue(g.containsVertex(1)); assertTrue(g.containsVertex(2)); assertTrue(g.containsVertex(3)); assertTrue(g.containsVertex(4)); assertTrue(g.containsVertex(5)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e23_1 = g.addEdge(2, 3); DefaultEdge e23_2 = g.addEdge(2, 3); DefaultEdge e24 = g.addEdge(2, 4); DefaultEdge e44 = g.addEdge(4, 4); DefaultEdge e55_1 = g.addEdge(5, 5); DefaultEdge e52 = g.addEdge(5, 2); DefaultEdge e55_2 = g.addEdge(5, 5); assertEquals(1, g.degreeOf(1)); assertEquals(5, g.degreeOf(2)); assertEquals(2, g.degreeOf(3)); assertEquals(3, g.degreeOf(4)); assertEquals(5, g.degreeOf(5)); assertEquals(Set.of(e12), g.edgesOf(1)); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.edgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.edgesOf(3)); assertEquals(Set.of(e24, e44), g.edgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.edgesOf(5)); assertEquals(0, g.inDegreeOf(1)); assertEquals(2, g.inDegreeOf(2)); assertEquals(2, g.inDegreeOf(3)); assertEquals(2, g.inDegreeOf(4)); assertEquals(2, g.inDegreeOf(5)); assertEquals(Set.of(), g.incomingEdgesOf(1)); assertEquals(Set.of(e12, e52), g.incomingEdgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.incomingEdgesOf(3)); assertEquals(Set.of(e24, e44), g.incomingEdgesOf(4)); assertEquals(Set.of(e55_1, e55_2), g.incomingEdgesOf(5)); assertEquals(1, g.outDegreeOf(1)); assertEquals(3, g.outDegreeOf(2)); assertEquals(0, g.outDegreeOf(3)); assertEquals(1, g.outDegreeOf(4)); assertEquals(3, g.outDegreeOf(5)); assertEquals(Set.of(e12), g.outgoingEdgesOf(1)); assertEquals(Set.of(e23_1, e23_2, e24), g.outgoingEdgesOf(2)); assertEquals(Set.of(), g.outgoingEdgesOf(3)); assertEquals(Set.of(e44), g.outgoingEdgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.outgoingEdgesOf(5)); } /** * Test the most general version of the directed graph. */ @Test public void testDirectedGraph() { testDirectedGraph(() -> new DirectedPseudograph<>(DefaultEdge.class)); testAddDuplicateEdgeDirectedGraph( () -> GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeDirectedGraph( () -> GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeDirectedGraph( () -> GraphTypeBuilder .directed().allowingMultipleEdges(false).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeDirectedGraph( () -> GraphTypeBuilder .directed().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); } public static void testAddDuplicateEdgeUndirectedGraph( Supplier> graphSupplier) { Graph g = graphSupplier.get(); assertTrue(g.getType().isUndirected()); g.addVertex(1); g.addVertex(2); g.addVertex(3); DefaultEdge e = new DefaultEdge(); g.addEdge(1, 2, e); assertTrue(g.edgeSet().size() == 1); assertEquals(Set.of(e), g.edgesOf(1)); assertEquals(1, g.degreeOf(1)); assertEquals(Set.of(e), g.edgesOf(2)); assertEquals(1, g.degreeOf(2)); assertEquals(Collections.emptySet(), g.edgesOf(3)); assertEquals(0, g.degreeOf(3)); assertThrows(IntrusiveEdgeException.class, () -> g.addEdge(1, 3, e)); assertTrue(g.edgeSet().size() == 1); assertEquals(Set.of(e), g.edgesOf(1)); assertEquals(1, g.degreeOf(1)); assertEquals(Set.of(e), g.edgesOf(2)); assertEquals(1, g.degreeOf(2)); assertEquals(Collections.emptySet(), g.edgesOf(3)); assertEquals(0, g.degreeOf(3)); } /** * Test the most general version of the undirected graph. */ public static void testUndirectedGraph(Supplier> graphSupplier) { Graph g = graphSupplier.get(); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); assertEquals(5, g.vertexSet().size()); assertTrue(g.containsVertex(1)); assertTrue(g.containsVertex(2)); assertTrue(g.containsVertex(3)); assertTrue(g.containsVertex(4)); assertTrue(g.containsVertex(5)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e23_1 = g.addEdge(2, 3); DefaultEdge e23_2 = g.addEdge(2, 3); DefaultEdge e24 = g.addEdge(2, 4); DefaultEdge e44 = g.addEdge(4, 4); DefaultEdge e55_1 = g.addEdge(5, 5); DefaultEdge e52 = g.addEdge(5, 2); DefaultEdge e55_2 = g.addEdge(5, 5); assertEquals(1, g.degreeOf(1)); assertEquals(5, g.degreeOf(2)); assertEquals(2, g.degreeOf(3)); assertEquals(3, g.degreeOf(4)); assertEquals(5, g.degreeOf(5)); assertEquals(Set.of(e12), g.edgesOf(1)); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.edgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.edgesOf(3)); assertEquals(Set.of(e24, e44), g.edgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.edgesOf(5)); assertEquals(1, g.inDegreeOf(1)); assertEquals(5, g.inDegreeOf(2)); assertEquals(2, g.inDegreeOf(3)); assertEquals(3, g.inDegreeOf(4)); assertEquals(5, g.inDegreeOf(5)); assertEquals(Set.of(e12), g.incomingEdgesOf(1)); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.incomingEdgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.incomingEdgesOf(3)); assertEquals(Set.of(e24, e44), g.incomingEdgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.incomingEdgesOf(5)); assertEquals(1, g.outDegreeOf(1)); assertEquals(5, g.outDegreeOf(2)); assertEquals(2, g.outDegreeOf(3)); assertEquals(3, g.outDegreeOf(4)); assertEquals(5, g.outDegreeOf(5)); assertEquals(Set.of(e12), g.outgoingEdgesOf(1)); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.outgoingEdgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.outgoingEdgesOf(3)); assertEquals(Set.of(e24, e44), g.outgoingEdgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.outgoingEdgesOf(5)); } /** * Test the most general version of the undirected graph. */ @Test public void testUndirectedGraph() { testUndirectedGraph(() -> new Pseudograph<>(DefaultEdge.class)); testAddDuplicateEdgeUndirectedGraph( () -> GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeUndirectedGraph( () -> GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeUndirectedGraph( () -> GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeUndirectedGraph( () -> GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/ListenableGraphTest.java000066400000000000000000000220001402514743400317620ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.event.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Unit test for {@link ListenableGraph} class. * * @author Barak Naveh */ public class ListenableGraphTest { // ~ Instance fields -------------------------------------------------------- Object lastAddedEdge; Object lastRemovedEdge; Object lastAddedVertex; Object lastRemovedVertex; Double lastWeightUpdate; /** * Tests GraphListener listener. */ @Test public void testGraphListener() { init(); ListenableGraph g = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); GraphListener listener = new MyGraphListener<>(); g.addGraphListener(listener); String v1 = "v1"; String v2 = "v2"; // test vertex notification g.addVertex(v1); assertEquals(v1, lastAddedVertex); assertEquals(null, lastRemovedVertex); init(); g.removeVertex(v1); assertEquals(v1, lastRemovedVertex); assertEquals(null, lastAddedVertex); // test edge notification g.addVertex(v1); g.addVertex(v2); init(); DefaultEdge e = g.addEdge(v1, v2); assertEquals(e, lastAddedEdge); assertEquals(null, lastRemovedEdge); init(); assertTrue(g.removeEdge(e)); assertEquals(e, lastRemovedEdge); assertEquals(null, lastAddedEdge); g.removeVertex(v1); g.removeVertex(v2); // // test notification stops when removing listener // g.removeGraphListener(listener); init(); g.addVertex(v1); g.addVertex(v2); e = g.addEdge(v1, v2); g.removeEdge(e); assertEquals(null, lastAddedEdge); assertEquals(null, lastAddedVertex); assertEquals(null, lastRemovedEdge); assertEquals(null, lastRemovedVertex); } /** * Tests VertexSetListener listener. */ @Test public void testVertexSetListener() { init(); ListenableGraph g = new DefaultListenableGraph<>(new SimpleGraph<>(DefaultEdge.class)); VertexSetListener listener = new MyGraphListener<>(); g.addVertexSetListener(listener); String v1 = "v1"; String v2 = "v2"; // test vertex notification g.addVertex(v1); assertEquals(v1, lastAddedVertex); assertEquals(null, lastRemovedVertex); init(); g.removeVertex(v1); assertEquals(v1, lastRemovedVertex); assertEquals(null, lastAddedVertex); // test edge notification g.addVertex(v1); g.addVertex(v2); init(); DefaultEdge e = g.addEdge(v1, v2); assertEquals(null, lastAddedEdge); assertEquals(null, lastRemovedEdge); init(); assertTrue(g.removeEdge(e)); assertEquals(null, lastRemovedEdge); assertEquals(null, lastAddedEdge); g.removeVertex(v1); g.removeVertex(v2); // // test notification stops when removing listener // g.removeVertexSetListener(listener); init(); g.addVertex(v1); g.addVertex(v2); e = g.addEdge(v1, v2); g.removeEdge(e); assertEquals(null, lastAddedEdge); assertEquals(null, lastAddedVertex); assertEquals(null, lastRemovedEdge); assertEquals(null, lastRemovedVertex); } /** * Tests VertexSetListener listener. (Issue #887). */ @Test public void testWithVertexSupplier() { Graph wrappedGraph = GraphTypeBuilder .undirected().weighted(false).edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createStringSupplier(15)).buildGraph(); ListenableGraph g = new DefaultListenableGraph<>(wrappedGraph); SimpleVertexListener listener = new SimpleVertexListener<>(); g.addVertexSetListener(listener); g.addVertex(); assertEquals("15", listener.getLastVertex()); String other = "other"; g.addVertex(other); assertEquals("other", listener.getLastVertex()); g.addVertex(); assertEquals("16", listener.getLastVertex()); } /** * Tests that the combination of weights plus listener works. */ @Test public void testListenableDirectedWeightedGraph() { init(); ListenableGraph g = new DefaultListenableGraph<>( new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class)); GraphListener listener = new MyGraphListener<>(); g.addGraphListener(listener); String v1 = "v1"; String v2 = "v2"; g.addVertex(v1); assertEquals(v1, lastAddedVertex); assertEquals(null, lastRemovedVertex); g.addVertex(v2); init(); DefaultWeightedEdge e = g.addEdge(v1, v2); g.setEdgeWeight(e, 10.0); assertEquals(10.0, g.getEdgeWeight(e), 0); assertEquals(e, lastAddedEdge); assertEquals(null, lastRemovedEdge); } @Test public void testListenableDirectedWeightedGraphWithCustomEdge() { init(); ListenableGraph g = new DefaultListenableGraph<>(new DefaultDirectedWeightedGraph<>(DefaultEdge.class)); GraphListener listener = new MyGraphListener<>(); g.addGraphListener(listener); String v1 = "v1"; String v2 = "v2"; g.addVertex(v1); assertEquals(v1, lastAddedVertex); assertEquals(null, lastRemovedVertex); g.addVertex(v2); init(); DefaultEdge e = g.addEdge(v1, v2); g.setEdgeWeight(e, 10.0); assertEquals(10.0, g.getEdgeWeight(e), 0); assertEquals(e, lastAddedEdge); assertEquals(null, lastRemovedEdge); init(); g.setEdgeWeight(e, 5.5d); assertEquals(5.5, g.getEdgeWeight(e), 1e-9); assertEquals(null, lastAddedEdge); assertEquals(null, lastRemovedEdge); assertEquals(5.5, lastWeightUpdate, 1e-9); g.setEdgeWeight(e, 20.5d); assertEquals(20.5, g.getEdgeWeight(e), 1e-9); assertEquals(null, lastAddedEdge); assertEquals(null, lastRemovedEdge); assertEquals(20.5, lastWeightUpdate, 1e-9); } public void init() { lastAddedEdge = null; lastAddedVertex = null; lastRemovedEdge = null; lastRemovedVertex = null; lastWeightUpdate = null; } // ~ Inner Classes ---------------------------------------------------------- /** * A listener on the tested graph. * * @author Barak Naveh */ private class MyGraphListener implements GraphListener { @Override public void edgeAdded(GraphEdgeChangeEvent e) { lastAddedEdge = e.getEdge(); } @Override public void edgeRemoved(GraphEdgeChangeEvent e) { lastRemovedEdge = e.getEdge(); } @Override public void vertexAdded(GraphVertexChangeEvent e) { lastAddedVertex = e.getVertex(); } @Override public void vertexRemoved(GraphVertexChangeEvent e) { lastRemovedVertex = e.getVertex(); } @Override public void edgeWeightUpdated(GraphEdgeChangeEvent e) { lastWeightUpdate = e.getEdgeWeight(); } } /** * A listener on the tested graph. */ private class SimpleVertexListener implements VertexSetListener { private V lastVertex; @Override public void vertexAdded(GraphVertexChangeEvent e) { lastVertex = e.getVertex(); } @Override public void vertexRemoved(GraphVertexChangeEvent e) { lastVertex = e.getVertex(); } public V getLastVertex() { return lastVertex; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/MaskEdgeSetTest.java000066400000000000000000000055651402514743400310730ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Andrew Gainer-Dewar and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Unit tests for MaskEdgeSet. * * @author Andrew Gainer-Dewar */ public class MaskEdgeSetTest { private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; private DefaultEdge e1, e2, e3, loop1, loop2; private MaskEdgeSet testMaskedEdgeSet; private DefaultDirectedGraph directed; @Before public void setUp() { directed = new DefaultDirectedGraph<>(DefaultEdge.class); directed.addVertex(v1); directed.addVertex(v2); directed.addVertex(v3); directed.addVertex(v4); e1 = directed.addEdge(v1, v2); e2 = directed.addEdge(v2, v3); e3 = directed.addEdge(v2, v4); loop1 = directed.addEdge(v1, v1); loop2 = directed.addEdge(v4, v4); testMaskedEdgeSet = new MaskEdgeSet<>(directed, directed.edgeSet(), v -> v == v1, e -> e == e2); } @Test public void testContains() { assertFalse(testMaskedEdgeSet.contains(e1)); assertFalse(testMaskedEdgeSet.contains(e2)); assertTrue(testMaskedEdgeSet.contains(e3)); assertFalse(testMaskedEdgeSet.contains(loop1)); assertTrue(testMaskedEdgeSet.contains(loop2)); assertFalse(testMaskedEdgeSet.contains(v1)); } @Test public void testSize() { assertEquals(2, testMaskedEdgeSet.size()); } @Test public void testIterator() { Iterator it = testMaskedEdgeSet.iterator(); assertTrue(it.hasNext()); assertEquals(e3, it.next()); assertTrue(it.hasNext()); assertEquals(loop2, it.next()); assertFalse(it.hasNext()); } @Test public void testIsEmpty() { assertFalse(testMaskedEdgeSet.isEmpty()); testMaskedEdgeSet = new MaskEdgeSet<>(directed, directed.edgeSet(), v -> v.equals(v1), e -> true); assertTrue(testMaskedEdgeSet.isEmpty()); testMaskedEdgeSet = new MaskEdgeSet<>(directed, directed.edgeSet(), v -> true, e -> e == e2); assertTrue(testMaskedEdgeSet.isEmpty()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/MaskSubgraphTest.java000066400000000000000000000121451402514743400313160ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; /** * Unit tests for {@link MaskSubgraph} class. * * @author Dimitrios Michail */ public class MaskSubgraphTest { @Test public void testUnmodifiable() { Graph g = new Pseudograph<>(DefaultEdge.class); assertFalse(new MaskSubgraph<>(g, v -> v.equals(5), e -> false).getType().isModifiable()); } @Test public void testInOutEdgesUndirected() { Graph g = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e13 = g.addEdge(1, 3); DefaultEdge e23 = g.addEdge(2, 3); DefaultEdge e24_1 = g.addEdge(2, 4); DefaultEdge e24_2 = g.addEdge(2, 4); DefaultEdge e24_3 = g.addEdge(2, 4); g.addEdge(3, 5); DefaultEdge e44 = g.addEdge(4, 4); g.addEdge(4, 5); Graph sg = new MaskSubgraph<>(g, v -> v.equals(5), e -> e.equals(e24_3)); assertEquals(6, sg.edgeSet().size()); assertEquals(Set.of(e12, e13), sg.edgesOf(1)); assertEquals(Set.of(e12, e24_1, e24_2, e23), sg.edgesOf(2)); assertEquals(Set.of(e13, e23), sg.edgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.edgesOf(4)); assertEquals(2, sg.degreeOf(1)); assertEquals(4, sg.degreeOf(2)); assertEquals(2, sg.degreeOf(3)); assertEquals(4, sg.degreeOf(4)); assertEquals(Set.of(e12, e13), sg.incomingEdgesOf(1)); assertEquals(Set.of(e12, e24_1, e24_2, e23), sg.incomingEdgesOf(2)); assertEquals(Set.of(e13, e23), sg.incomingEdgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.incomingEdgesOf(4)); assertEquals(2, sg.inDegreeOf(1)); assertEquals(4, sg.inDegreeOf(2)); assertEquals(2, sg.inDegreeOf(3)); assertEquals(4, sg.inDegreeOf(4)); assertEquals(Set.of(e12, e13), sg.outgoingEdgesOf(1)); assertEquals(Set.of(e12, e24_1, e24_2, e23), sg.outgoingEdgesOf(2)); assertEquals(Set.of(e13, e23), sg.outgoingEdgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.outgoingEdgesOf(4)); assertEquals(2, sg.outDegreeOf(1)); assertEquals(4, sg.outDegreeOf(2)); assertEquals(2, sg.outDegreeOf(3)); assertEquals(4, sg.outDegreeOf(4)); } @Test public void testInOutEdgesDirected() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(g, Arrays.asList(1, 2, 3, 4, 5)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e13 = g.addEdge(1, 3); DefaultEdge e23 = g.addEdge(2, 3); DefaultEdge e24_1 = g.addEdge(2, 4); DefaultEdge e24_2 = g.addEdge(2, 4); DefaultEdge e24_3 = g.addEdge(2, 4); g.addEdge(3, 5); DefaultEdge e44 = g.addEdge(4, 4); g.addEdge(4, 5); Graph sg = new MaskSubgraph<>(g, v -> v.equals(5), e -> e.equals(e24_3)); assertEquals(6, sg.edgeSet().size()); assertEquals(Set.of(e12, e13), sg.edgesOf(1)); assertEquals(Set.of(e12, e24_1, e24_2, e23), sg.edgesOf(2)); assertEquals(Set.of(e13, e23), sg.edgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.edgesOf(4)); assertEquals(2, sg.degreeOf(1)); assertEquals(4, sg.degreeOf(2)); assertEquals(2, sg.degreeOf(3)); assertEquals(4, sg.degreeOf(4)); assertEquals(new HashSet<>(), sg.incomingEdgesOf(1)); assertEquals(Set.of(e12), sg.incomingEdgesOf(2)); assertEquals(Set.of(e13, e23), sg.incomingEdgesOf(3)); assertEquals(Set.of(e24_1, e24_2, e44), sg.incomingEdgesOf(4)); assertEquals(0, sg.inDegreeOf(1)); assertEquals(1, sg.inDegreeOf(2)); assertEquals(2, sg.inDegreeOf(3)); assertEquals(3, sg.inDegreeOf(4)); assertEquals(Set.of(e12, e13), sg.outgoingEdgesOf(1)); assertEquals(Set.of(e24_1, e24_2, e23), sg.outgoingEdgesOf(2)); assertEquals(Set.of(), sg.outgoingEdgesOf(3)); assertEquals(Set.of(e44), sg.outgoingEdgesOf(4)); assertEquals(2, sg.outDegreeOf(1)); assertEquals(3, sg.outDegreeOf(2)); assertEquals(0, sg.outDegreeOf(3)); assertEquals(1, sg.outDegreeOf(4)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/MaskVertexSetTest.java000066400000000000000000000046251402514743400315000ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Andrew Gainer-Dewar and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Unit tests for MaskVertexSet. * * @author Andrew Gainer-Dewar */ public class MaskVertexSetTest { private Graph directed; private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; private DefaultEdge e1; private MaskVertexSet testMaskVertexSet; @Before public void setUp() { directed = new DefaultDirectedGraph<>(DefaultEdge.class); directed.addVertex(v1); directed.addVertex(v2); directed.addVertex(v3); directed.addVertex(v4); e1 = directed.addEdge(v1, v2); directed.addEdge(v2, v3); testMaskVertexSet = new MaskVertexSet<>(directed.vertexSet(), v -> v.equals(v1)); } @Test public void testContains() { assertFalse(testMaskVertexSet.contains(v1)); assertTrue(testMaskVertexSet.contains(v2)); assertFalse(testMaskVertexSet.contains(e1)); } @Test public void testSize() { assertEquals(3, testMaskVertexSet.size()); } @Test public void testIterator() { Iterator it = testMaskVertexSet.iterator(); assertTrue(it.hasNext()); assertEquals(v2, it.next()); assertTrue(it.hasNext()); assertEquals(v3, it.next()); assertTrue(it.hasNext()); assertEquals(v4, it.next()); assertFalse(it.hasNext()); } @Test public void testIsEmpty() { assertFalse(testMaskVertexSet.isEmpty()); testMaskVertexSet = new MaskVertexSet<>(directed.vertexSet(), v -> true); assertTrue(testMaskVertexSet.isEmpty()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/SerializationTest.java000066400000000000000000000513341402514743400315470ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.jgrapht.graph.SerializationTestUtils.serializeAndDeserialize; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * SerializationTest tests serialization and deserialization of JGraphT objects. *

    * The following classes are tested here: *

      *
    • {@link SimpleGraph}
    • *
    • {@link Multigraph}
    • *
    • {@link Pseudograph}
    • *
    • {@link DefaultUndirectedGraph}
    • * *
    • {@link SimpleWeightedGraph}
    • *
    • {@link WeightedMultigraph}
    • *
    • {@link WeightedPseudograph}
    • *
    • {@link DefaultUndirectedWeightedGraph}
    • * *
    • {@link SimpleDirectedGraph}
    • *
    • {@link DirectedMultigraph}
    • *
    • {@link DirectedPseudograph}
    • *
    • {@link DefaultDirectedGraph}
    • * *
    • {@link SimpleDirectedWeightedGraph}
    • *
    • {@link DirectedWeightedMultigraph}
    • *
    • {@link DirectedWeightedPseudograph}
    • *
    • {@link DefaultDirectedWeightedGraph}
    • *
    * * @author John V. Sichi */ public class SerializationTest { private static final String V1 = "V1"; private static final String V2 = "V2"; private static final String V3 = "V3"; private static final List VERTEX_LIST = Arrays.asList(V1, V2, V3); private static final List> VERTEX_PAIRS = Arrays .asList( Arrays.asList(V1, V2), Arrays.asList(V2, V1), Arrays.asList(V1, V3), Arrays.asList(V3, V1), Arrays.asList(V2, V3), Arrays.asList(V3, V2)); public static void assertContainsAllVertices(Graph graph, List vertices) { for (V v : vertices) { assertTrue(graph.containsVertex(v)); } } public static void checkEdgesOf(Graph graph, List edges, List vertices) { if (edges.size() != vertices.size()) { throw new IllegalArgumentException( "the size of list of #edges and vertices should match"); } for (int i = 0; i < edges.size(); i++) { assertEquals(edges.get(i).intValue(), graph.edgesOf(vertices.get(i)).size()); } } public static void assertAllEdges(Graph graph1, Graph graph2) { for (int i = 0; i < VERTEX_PAIRS.size(); i++) { String a = VERTEX_PAIRS.get(i).get(0); String b = VERTEX_PAIRS.get(i).get(1); assertEquals(graph1.getAllEdges(a, b).size(), graph2.getAllEdges(a, b).size()); assertEquals(graph1.containsEdge(a, b), graph2.containsEdge(a, b)); } } private static void verifyBasic( Graph graph1, Graph graph2, List numberOfEdges) { assertContainsAllVertices(graph2, VERTEX_LIST); assertContainsAllVertices(graph1, VERTEX_LIST); assertAllEdges(graph1, graph2); checkEdgesOf(graph2, numberOfEdges, VERTEX_LIST); checkEdgesOf(graph1, numberOfEdges, VERTEX_LIST); assertEquals(graph1.toString(), graph2.toString()); } private static void assertWeight( Graph graph1, Graph graph2, List weights, String vertex1, String vertex2) { assertWeight(graph1, weights, vertex1, vertex2); assertWeight(graph2, weights, vertex1, vertex2); } private static void assertWeight( Graph graph, List weights, String vertex1, String vertex2) { Set edgeSet = graph.getAllEdges(vertex1, vertex2); for (E e : edgeSet) assertTrue(e instanceof DefaultWeightedEdge); assertEquals( new HashSet<>(weights), edgeSet .stream().map(e -> (DefaultWeightedEdge) e).map(DefaultWeightedEdge::getWeight) .collect(Collectors.toSet())); } /** * Tests serialization of {@link SimpleGraph}. *

    * undirected no self-loop no multiple-edges unweighted */ @Test public void testSimpleGraph() throws Exception { SimpleGraph graph1 = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); graph1.addEdge(V1, V2); graph1.addEdge(V2, V3); graph1.addEdge(V1, V3); SimpleGraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(2, 2, 2)); } /** * Tests serialization of {@link Multigraph}. undirected no self-loop multiple-edges unweighted */ @Test public void testMultiGraph() throws Exception { Multigraph graph1 = new Multigraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); graph1.addEdge(V1, V2); graph1.addEdge(V1, V2); graph1.addEdge(V2, V3); graph1.addEdge(V1, V3); Multigraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(3, 3, 2)); } /** * Tests serialization of {@link Pseudograph}. undirected self-loop multiple-edges unweighted */ @Test public void testPseudograph() throws Exception { Pseudograph graph1 = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); graph1.addEdge(V1, V2); graph1.addEdge(V1, V2); // multiple edge graph1.addEdge(V1, V1); // self loop graph1.addEdge(V2, V3); graph1.addEdge(V1, V3); Pseudograph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(4, 3, 2)); } /** * Tests serialization of {@link DefaultUndirectedGraph} *

    * undirected self-loops no multiple edges unweighted */ @Test public void testDefaultUndirectedGraph() throws Exception { DefaultUndirectedGraph graph1 = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); graph1.addEdge(V1, V2); graph1.addEdge(V1, V1); graph1.addEdge(V2, V3); graph1.addEdge(V3, V1); DefaultUndirectedGraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(3, 2, 2)); } /** * Tests serialization of {@link SimpleWeightedGraph} *

    * undirected no self-loops no-multiple edges weighted */ @Test public void testSimpleWeightedGraph() throws Exception { SimpleWeightedGraph graph1 = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); DefaultWeightedEdge e12 = graph1.addEdge(V1, V2); DefaultWeightedEdge e23 = graph1.addEdge(V2, V3); DefaultWeightedEdge e31 = graph1.addEdge(V3, V1); graph1.setEdgeWeight(e12, 1.0); graph1.setEdgeWeight(e23, 2.0); graph1.setEdgeWeight(e31, 3.0); SimpleWeightedGraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(2, 2, 2)); assertWeight(graph1, graph2, Arrays.asList(1.0), V1, V2); assertWeight(graph1, graph2, Arrays.asList(2.0), V3, V2); assertWeight(graph1, graph2, Arrays.asList(3.0), V1, V3); } /** * Tests serialization of {@link WeightedMultigraph} *

    * undirected no self-loops multiple edges weighted */ @Test public void testWeightedMultigraph() throws Exception { WeightedMultigraph graph1 = new WeightedMultigraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); DefaultWeightedEdge e12a = graph1.addEdge(V1, V2); DefaultWeightedEdge e12b = graph1.addEdge(V1, V2); DefaultWeightedEdge e23 = graph1.addEdge(V2, V3); DefaultWeightedEdge e31 = graph1.addEdge(V3, V1); graph1.setEdgeWeight(e12a, 1.0); graph1.setEdgeWeight(e12b, 10.0); graph1.setEdgeWeight(e23, 2.0); graph1.setEdgeWeight(e31, 3.0); WeightedMultigraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(3, 3, 2)); assertEquals(2, graph1.getAllEdges(V1, V2).size()); assertEquals(2, graph2.getAllEdges(V1, V2).size()); assertWeight(graph1, graph2, Arrays.asList(1.0, 10.0), V1, V2); assertWeight(graph1, graph2, Arrays.asList(2.0), V3, V2); assertWeight(graph1, graph2, Arrays.asList(3.0), V1, V3); } /** * Tests serialization of {@link WeightedPseudograph} *

    * undirected self-loops multiple edges weighted */ @Test public void testWeightedPseudograph() throws Exception { WeightedPseudograph graph1 = new WeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); DefaultWeightedEdge e11 = graph1.addEdge(V1, V1); DefaultWeightedEdge e12a = graph1.addEdge(V1, V2); DefaultWeightedEdge e12b = graph1.addEdge(V1, V2); DefaultWeightedEdge e23 = graph1.addEdge(V2, V3); DefaultWeightedEdge e31 = graph1.addEdge(V3, V1); graph1.setEdgeWeight(e11, 100.0); graph1.setEdgeWeight(e12a, 1.0); graph1.setEdgeWeight(e12b, 10.0); graph1.setEdgeWeight(e23, 2.0); graph1.setEdgeWeight(e31, 3.0); WeightedPseudograph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(4, 3, 2)); assertEquals(2, graph1.getAllEdges(V1, V2).size()); assertEquals(2, graph2.getAllEdges(V1, V2).size()); assertWeight(graph1, graph2, Arrays.asList(100.0), V1, V1); assertWeight(graph1, graph2, Arrays.asList(1.0, 10.0), V1, V2); assertWeight(graph1, graph2, Arrays.asList(2.0), V3, V2); assertWeight(graph1, graph2, Arrays.asList(3.0), V1, V3); } /** * Tests serialization of {@link DefaultUndirectedWeightedGraph} *

    * undirected self-loops no multiple edges weighted */ @Test public void testDefaultUndirectedWeightedGraph() throws Exception { DefaultUndirectedWeightedGraph graph1 = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); DefaultWeightedEdge e11 = graph1.addEdge(V1, V1); DefaultWeightedEdge e12 = graph1.addEdge(V1, V2); DefaultWeightedEdge e23 = graph1.addEdge(V2, V3); DefaultWeightedEdge e31 = graph1.addEdge(V3, V1); graph1.setEdgeWeight(e11, 100.0); graph1.setEdgeWeight(e12, 1.0); graph1.setEdgeWeight(e23, 2.0); graph1.setEdgeWeight(e31, 3.0); DefaultUndirectedWeightedGraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(3, 2, 2)); assertWeight(graph1, graph2, Arrays.asList(100.0), V1, V1); assertWeight(graph1, graph2, Arrays.asList(1.0), V1, V2); assertWeight(graph1, graph2, Arrays.asList(2.0), V3, V2); assertWeight(graph1, graph2, Arrays.asList(3.0), V1, V3); } /** * Tests serialization of {@link SimpleDirectedGraph} directed no self-loop no multiple-edges * unweighted */ @Test public void testSimpleDirectedGraph() throws Exception { SimpleDirectedGraph graph1 = new SimpleDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); graph1.addEdge(V1, V2); graph1.addEdge(V2, V3); graph1.addEdge(V1, V3); SimpleDirectedGraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(2, 2, 2)); } /** * Tests serialization of {@link DirectedMultigraph} *

    * directed no-self loops multiple edges unweighted */ @Test public void testDirectedMultigraph() throws Exception { DirectedMultigraph graph1 = new DirectedMultigraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); graph1.addEdge(V1, V2); graph1.addEdge(V2, V3); graph1.addEdge(V2, V3); DirectedMultigraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(1, 3, 2)); } /** * Tests serialization of {@link DirectedPseudograph} *

    * directed self-loops multiple-edges unweighted */ @Test public void testDirectedPseudograph() throws Exception { DirectedPseudograph graph1 = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); graph1.addEdge(V1, V2); graph1.addEdge(V1, V2); // multi-edge graph1.addEdge(V2, V3); graph1.addEdge(V1, V1); // self-loop graph1.addEdge(V1, V3); DirectedPseudograph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(4, 3, 2)); } /** * Tests serialization of {@link DefaultDirectedGraph} *

    * directed self-loops no multiple-edges unweighted */ @Test public void testDefaultDirectedGraph() throws Exception { DefaultDirectedGraph graph1 = new DefaultDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); graph1.addEdge(V1, V1); graph1.addEdge(V1, V2); graph1.addEdge(V2, V3); graph1.addEdge(V3, V1); DefaultDirectedGraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(3, 2, 2)); } /** * Tests serialization of {@link SimpleDirectedWeightedGraph} *

    * directed no self-loops no multiple edges weighted */ @Test public void testSimpleDirectedWeightedGraph() throws Exception { SimpleDirectedWeightedGraph graph1 = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); DefaultWeightedEdge e12 = graph1.addEdge(V1, V2); DefaultWeightedEdge e23 = graph1.addEdge(V2, V3); DefaultWeightedEdge e31 = graph1.addEdge(V3, V1); graph1.setEdgeWeight(e12, 1.0); graph1.setEdgeWeight(e23, 2.0); graph1.setEdgeWeight(e31, 3.0); SimpleDirectedWeightedGraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(2, 2, 2)); assertWeight(graph1, graph2, Arrays.asList(1.0), V1, V2); assertWeight(graph1, graph2, Arrays.asList(2.0), V2, V3); assertWeight(graph1, graph2, Arrays.asList(3.0), V3, V1); } /** * Tests serialization of {@link DirectedWeightedMultigraph} *

    * directed no self-loops multiple edges weighted */ @Test public void testDirectedWeightedMultiGraph() throws Exception { DirectedWeightedMultigraph graph1 = new DirectedWeightedMultigraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); DefaultWeightedEdge e12a = graph1.addEdge(V1, V2); DefaultWeightedEdge e12b = graph1.addEdge(V1, V2); DefaultWeightedEdge e23 = graph1.addEdge(V2, V3); DefaultWeightedEdge e31 = graph1.addEdge(V3, V1); graph1.setEdgeWeight(e12a, 1.0); graph1.setEdgeWeight(e12b, 10.0); graph1.setEdgeWeight(e23, 2.0); graph1.setEdgeWeight(e31, 3.0); DirectedWeightedMultigraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(3, 3, 2)); assertEquals(2, graph2.getAllEdges(V1, V2).size()); assertEquals(2, graph1.getAllEdges(V1, V2).size()); assertWeight(graph1, graph2, Arrays.asList(1.0, 10.0), V1, V2); assertWeight(graph1, graph2, Arrays.asList(2.0), V2, V3); assertWeight(graph1, graph2, Arrays.asList(3.0), V3, V1); } /** * Tests serialization of {@link DirectedWeightedPseudograph} *

    * directed self-loops multiple edges weighted */ @Test public void testDirectedWeightedPseudograph() throws Exception { DirectedWeightedPseudograph graph1 = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); DefaultWeightedEdge e11 = graph1.addEdge(V1, V1); DefaultWeightedEdge e12a = graph1.addEdge(V1, V2); DefaultWeightedEdge e12b = graph1.addEdge(V1, V2); DefaultWeightedEdge e23 = graph1.addEdge(V2, V3); DefaultWeightedEdge e31 = graph1.addEdge(V3, V1); graph1.setEdgeWeight(e11, 100.0); graph1.setEdgeWeight(e12a, 1.0); graph1.setEdgeWeight(e12b, 10.0); graph1.setEdgeWeight(e23, 2.0); graph1.setEdgeWeight(e31, 3.0); DirectedWeightedPseudograph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(4, 3, 2)); assertEquals(2, graph2.getAllEdges(V1, V2).size()); assertEquals(2, graph1.getAllEdges(V1, V2).size()); assertWeight(graph1, graph2, Arrays.asList(100.0), V1, V1); assertWeight(graph1, graph2, Arrays.asList(1.0, 10.0), V1, V2); assertWeight(graph1, graph2, Arrays.asList(2.0), V2, V3); assertWeight(graph1, graph2, Arrays.asList(3.0), V3, V1); } /** * Tests serialization of {@link DefaultDirectedWeightedGraph} *

    * directed self-loops no multiple edges weighted */ @Test public void testDefaultDirectedWeightedGraph() throws Exception { DefaultDirectedWeightedGraph graph1 = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addAllVertices(graph1, VERTEX_LIST); DefaultWeightedEdge e11 = graph1.addEdge(V1, V1); DefaultWeightedEdge e12 = graph1.addEdge(V1, V2); DefaultWeightedEdge e23 = graph1.addEdge(V2, V3); DefaultWeightedEdge e31 = graph1.addEdge(V3, V1); graph1.setEdgeWeight(e11, 100.0); graph1.setEdgeWeight(e12, 1.0); graph1.setEdgeWeight(e23, 2.0); graph1.setEdgeWeight(e31, 3.0); DefaultDirectedWeightedGraph graph2 = serializeAndDeserialize(graph1); verifyBasic(graph1, graph2, Arrays.asList(3, 2, 2)); assertWeight(graph1, graph2, Arrays.asList(100.0), V1, V1); assertWeight(graph1, graph2, Arrays.asList(1.0), V1, V2); assertWeight(graph1, graph2, Arrays.asList(2.0), V2, V3); assertWeight(graph1, graph2, Arrays.asList(3.0), V3, V1); } /** * Test Serialization of {@link AsGraphUnion} * * @throws Exception */ @Test public void testAsGraphUnion() throws Exception { Graph graph1 = new DirectedPseudograph<>(DefaultEdge.class); Graph graph2 = new DirectedPseudograph<>(DefaultEdge.class); graph1.addVertex(V1); graph1.addVertex(V2); graph1.addVertex(V3); graph2.addVertex(V1); graph2.addVertex(V2); graph2.addVertex(V3); graph1.addEdge(V1, V2); graph1.addEdge(V1, V3); graph2.addEdge(V2, V3); AsGraphUnion graph3 = new AsGraphUnion<>(graph1, graph2); AsGraphUnion graph4 = serializeAndDeserialize(graph3); verifyBasic(graph3, graph4, Arrays.asList(2, 2, 2)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/SerializationTestUtils.java000066400000000000000000000026431402514743400325670ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import java.io.*; /** * Serialization test utils for the serialization and deserialization of JGraphT objects. * * @author John V. Sichi */ public class SerializationTestUtils { // don't instantiate this class private SerializationTestUtils() { } @SuppressWarnings("unchecked") public static T serializeAndDeserialize(T obj) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); out.writeObject(obj); out.flush(); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream in = new ObjectInputStream(bin); obj = (T) in.readObject(); return obj; } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/SimpleDirectedGraphTest.java000066400000000000000000000441401402514743400326060ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.graph.BaseIntrusiveEdgesSpecifics.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.*; /** * A unit test for simple directed graph. * * @author Barak Naveh */ public class SimpleDirectedGraphTest { // ~ Instance fields -------------------------------------------------------- Graph gEmpty; private Graph g1; private Graph g2; private Graph g3; private Graph g4; private DefaultEdge eLoop; private Supplier eSupplier; private final String v1 = "v1"; private final String v2 = "v2"; private final String v3 = "v3"; private final String v4 = "v4"; private DefaultEdge e12_1; private DefaultEdge e12_2; private DefaultEdge e12_3; private DefaultEdge e21_1; private DefaultEdge e21_2; private DefaultEdge e13_1; private DefaultEdge e23_1; private DefaultEdge e31_1; private DefaultEdge e32_1; private DefaultEdge e23_2; private DefaultEdge e34_1; private DefaultEdge e41_1; /** * Class to test for boolean addEdge(V, V, E) */ @Test public void testAddEdgeEdge() { // loops not allowed assertThrows(IllegalArgumentException.class, () -> g1.addEdge(v1, v1, eLoop)); assertThrows(NullPointerException.class, () -> g3.addEdge(v1, v1, null)); DefaultEdge e = eSupplier.get(); // no such vertex in graph assertThrows(IllegalArgumentException.class, () -> g1.addEdge("ya", "ya", e)); // supplied edge already in another graph with differing touching vertices assertThrows(IntrusiveEdgeException.class, () -> g4.addEdge(v1, v3, e12_1)); assertFalse(g2.addEdge(v2, v1, e)); assertFalse(g3.addEdge(v2, v1, e)); assertTrue(g4.addEdge(v2, v1, e)); } /** * Class to test for Edge addEdge(Object, Object) */ @Test public void testAddEdgeObjectObject() { // loops not allowed assertThrows(IllegalArgumentException.class, () -> g1.addEdge(v1, v1)); assertThrows(NullPointerException.class, () -> g3.addEdge(null, null)); // no such vertex in graph assertThrows(IllegalArgumentException.class, () -> g1.addEdge(v2, v1)); // supplied edge already in another graph with differing touching vertices Graph g5 = new SimpleDirectedGraph<>(null, () -> this.e12_1, false); g5.addVertex(v1); g5.addVertex(v3); assertThrows(IntrusiveEdgeException.class, () -> g5.addEdge(v1, v3)); assertNull(g2.addEdge(v2, v1)); assertNull(g3.addEdge(v2, v1)); assertNotNull(g4.addEdge(v2, v1)); } /** * . */ @Test public void testAddVertex() { assertEquals(1, g1.vertexSet().size()); assertEquals(2, g2.vertexSet().size()); assertEquals(3, g3.vertexSet().size()); assertEquals(4, g4.vertexSet().size()); assertFalse(g1.addVertex(v1)); assertTrue(g1.addVertex(v2)); assertEquals(2, g1.vertexSet().size()); } /** * Class to test for boolean containsEdge(Edge) */ @Test public void testContainsEdgeEdge() { assertTrue(g2.containsEdge(e12_1)); assertTrue(g2.containsEdge(e21_1)); assertTrue(g3.containsEdge(e12_2)); assertTrue(g3.containsEdge(e21_2)); assertTrue(g3.containsEdge(e23_1)); assertTrue(g3.containsEdge(e32_1)); assertTrue(g3.containsEdge(e31_1)); assertTrue(g3.containsEdge(e13_1)); assertTrue(g4.containsEdge(e12_3)); assertTrue(g4.containsEdge(e23_2)); assertTrue(g4.containsEdge(e34_1)); assertTrue(g4.containsEdge(e41_1)); } /** * Class to test for boolean containsEdge(Object, Object) */ @Test public void testContainsEdgeObjectObject() { assertFalse(g1.containsEdge(v1, v2)); assertFalse(g1.containsEdge(v1, v1)); assertTrue(g2.containsEdge(v1, v2)); assertTrue(g2.containsEdge(v2, v1)); assertTrue(g3.containsEdge(v1, v2)); assertTrue(g3.containsEdge(v2, v1)); assertTrue(g3.containsEdge(v3, v2)); assertTrue(g3.containsEdge(v2, v3)); assertTrue(g3.containsEdge(v1, v3)); assertTrue(g3.containsEdge(v3, v1)); assertFalse(g4.containsEdge(v1, v4)); g4.addEdge(v1, v4); assertTrue(g4.containsEdge(v1, v4)); assertFalse(g3.containsEdge(v4, v2)); assertFalse(g3.containsEdge(null, null)); } /** * . */ @Test public void testContainsVertex() { assertTrue(g1.containsVertex(v1)); assertFalse(g1.containsVertex(v2)); assertTrue(g2.containsVertex(v1)); assertTrue(g2.containsVertex(v2)); assertFalse(g2.containsVertex(v3)); assertTrue(g3.containsVertex(v1)); assertTrue(g3.containsVertex(v2)); assertTrue(g3.containsVertex(v3)); assertFalse(g3.containsVertex(v4)); assertTrue(g4.containsVertex(v1)); assertTrue(g4.containsVertex(v2)); assertTrue(g4.containsVertex(v3)); assertTrue(g4.containsVertex(v4)); } /** * . */ @Test public void testEdgeSet() { assertEquals(0, g1.edgeSet().size()); assertEquals(2, g2.edgeSet().size()); assertTrue(g2.containsEdge(e12_1)); assertTrue(g2.containsEdge(e21_1)); assertEquals(6, g3.edgeSet().size()); assertTrue(g3.containsEdge(e12_2)); assertTrue(g3.containsEdge(e21_2)); assertTrue(g3.containsEdge(e23_1)); assertTrue(g3.containsEdge(e32_1)); assertTrue(g3.containsEdge(e31_1)); assertTrue(g3.containsEdge(e13_1)); assertEquals(4, g4.edgeSet().size()); assertTrue(g4.containsEdge(e12_3)); assertTrue(g4.containsEdge(e23_2)); assertTrue(g4.containsEdge(e34_1)); assertTrue(g4.containsEdge(e41_1)); } /** * . */ @Test public void testEdgesOf() { assertEquals(g4.edgesOf(v1).size(), 2); assertEquals(g3.edgesOf(v1).size(), 4); Iterator iter = g3.edgesOf(v1).iterator(); int count = 0; while (iter.hasNext()) { iter.next(); count++; } assertEquals(count, 4); } /** * . */ @Test public void testGetAllEdges() { assertEquals(1, g3.getAllEdges(v1, v2).size()); assertTrue(g3.getAllEdges(v1, v2).contains(e12_2)); assertEquals(1, g3.getAllEdges(v2, v1).size()); assertTrue(g3.getAllEdges(v2, v1).contains(e21_2)); } /** * . */ @Test public void testGetEdge() { assertEquals(e12_1, g2.getEdge(v1, v2)); assertEquals(e21_1, g2.getEdge(v2, v1)); assertEquals(e12_2, g3.getEdge(v1, v2)); assertEquals(e21_2, g3.getEdge(v2, v1)); assertEquals(e21_2, g3.getEdge(v2, v1)); assertEquals(e32_1, g3.getEdge(v3, v2)); assertEquals(e31_1, g3.getEdge(v3, v1)); assertEquals(e13_1, g3.getEdge(v1, v3)); assertEquals(e12_3, g4.getEdge(v1, v2)); assertEquals(e23_2, g4.getEdge(v2, v3)); assertEquals(e34_1, g4.getEdge(v3, v4)); assertEquals(e41_1, g4.getEdge(v4, v1)); } /** * . */ @Test public void testGetEdgeSupplier() { assertNotNull(g1.getEdgeSupplier()); Supplier es = g1.getEdgeSupplier(); DefaultEdge e = es.get(); assertNotNull(e); assertNull(g1.getEdgeSource(e)); assertNull(g1.getEdgeTarget(e)); } /** * . */ @Test public void testGetVertexSupplier() { assertNotNull(g1.getVertexSupplier()); Supplier vs = g1.getVertexSupplier(); String v = vs.get(); assertNotNull(v); } /** * . */ @Test public void testInDegreeOf() { assertEquals(0, g1.inDegreeOf(v1)); assertEquals(1, g2.inDegreeOf(v1)); assertEquals(1, g2.inDegreeOf(v2)); assertEquals(2, g3.inDegreeOf(v1)); assertEquals(2, g3.inDegreeOf(v2)); assertEquals(2, g3.inDegreeOf(v3)); assertEquals(1, g4.inDegreeOf(v1)); assertEquals(1, g4.inDegreeOf(v2)); assertEquals(1, g4.inDegreeOf(v3)); assertEquals(1, g4.inDegreeOf(v4)); try { g3.inDegreeOf(new String()); Assert.fail("Should not get here."); } catch (IllegalArgumentException e) { } try { g3.inDegreeOf(null); Assert.fail("Should not get here."); } catch (NullPointerException e) { } } /** * . */ @Test public void testIncomingOutgoingEdgesOf() { Set e1to2 = g2.outgoingEdgesOf(v1); Set e2from1 = g2.incomingEdgesOf(v2); assertEquals(e1to2, e2from1); } /** * . */ @Test public void testOutDegreeOf() { assertEquals(1, g2.outDegreeOf(v1)); assertEquals(1, g2.outDegreeOf(v2)); assertEquals(2, g3.outDegreeOf(v1)); assertEquals(2, g3.outDegreeOf(v2)); assertEquals(2, g3.outDegreeOf(v3)); assertEquals(1, g4.outDegreeOf(v1)); assertEquals(1, g4.outDegreeOf(v2)); assertEquals(1, g4.outDegreeOf(v3)); assertEquals(1, g4.outDegreeOf(v4)); } /** * . */ @Test public void testOutgoingEdgesOf() { assertEquals(0, g1.outgoingEdgesOf(v1).size()); assertEquals(1, g2.outgoingEdgesOf(v1).size()); assertTrue(g2.outgoingEdgesOf(v1).contains(e12_1)); assertEquals(1, g2.outgoingEdgesOf(v2).size()); assertTrue(g2.outgoingEdgesOf(v2).contains(e21_1)); assertEquals(2, g3.outgoingEdgesOf(v1).size()); assertTrue(g3.outgoingEdgesOf(v1).contains(e12_2)); assertTrue(g3.outgoingEdgesOf(v1).contains(e13_1)); assertEquals(2, g3.outgoingEdgesOf(v2).size()); assertTrue(g3.outgoingEdgesOf(v2).contains(e23_1)); assertTrue(g3.outgoingEdgesOf(v2).contains(e21_2)); assertEquals(2, g3.outgoingEdgesOf(v3).size()); assertTrue(g3.outgoingEdgesOf(v3).contains(e31_1)); assertTrue(g3.outgoingEdgesOf(v3).contains(e32_1)); assertEquals(1, g4.outgoingEdgesOf(v1).size()); assertTrue(g4.outgoingEdgesOf(v1).contains(e12_3)); assertEquals(1, g4.outgoingEdgesOf(v2).size()); assertTrue(g4.outgoingEdgesOf(v2).contains(e23_2)); assertEquals(1, g4.outgoingEdgesOf(v3).size()); assertTrue(g4.outgoingEdgesOf(v3).contains(e34_1)); assertEquals(1, g4.outgoingEdgesOf(v4).size()); assertTrue(g4.outgoingEdgesOf(v4).contains(e41_1)); } /** * Class to test for boolean removeEdge(Edge) */ @Test public void testRemoveEdgeEdge() { assertEquals(g4.edgeSet().size(), 4); g4.removeEdge(v1, v2); assertEquals(g4.edgeSet().size(), 3); assertFalse(g4.removeEdge(eLoop)); assertTrue(g4.removeEdge(g4.getEdge(v2, v3))); assertEquals(g4.edgeSet().size(), 2); } /** * Class to test for Edge removeEdge(Object, Object) */ @Test public void testRemoveEdgeObjectObject() { assertEquals(g4.edgeSet().size(), 4); g4.removeEdge(v1, v2); assertEquals(g4.edgeSet().size(), 3); assertFalse(g4.removeEdge(eLoop)); assertTrue(g4.removeEdge(g4.getEdge(v2, v3))); assertEquals(g4.edgeSet().size(), 2); } @Test public void testRemoveAllEdgesObjectObject() { assertEquals(2, g2.edgeSet().size()); assertTrue(g2.containsEdge(v1, v2)); Set edges = g2.getAllEdges(v1, v2); assertEquals(edges, g2.removeAllEdges(v1, v2)); assertEquals(1, g2.edgeSet().size()); assertFalse(g2.containsEdge(v1, v2)); assertEquals(4, g4.edgeSet().size()); edges = g4.getAllEdges(v3, v4); assertEquals(edges, g4.removeAllEdges(v3, v4)); assertEquals(3, g4.edgeSet().size()); assertFalse(g4.containsEdge(v3, v4)); // No edge to remove. assertEquals(Collections.emptySet(), g4.removeAllEdges(v3, v2)); assertEquals(3, g4.edgeSet().size()); // Missing vertex. assertEquals(null, g4.removeAllEdges(v1, "v5")); } /** * . */ @Test public void testRemoveVertex() { assertEquals(4, g4.vertexSet().size()); assertTrue(g4.removeVertex(v1)); assertEquals(3, g4.vertexSet().size()); assertEquals(2, g4.edgeSet().size()); assertFalse(g4.removeVertex(v1)); assertTrue(g4.removeVertex(v2)); assertEquals(1, g4.edgeSet().size()); assertTrue(g4.removeVertex(v3)); assertEquals(0, g4.edgeSet().size()); assertEquals(1, g4.vertexSet().size()); assertTrue(g4.removeVertex(v4)); assertEquals(0, g4.vertexSet().size()); } /** * . */ @Test public void testVertexSet() { assertEquals(1, g1.vertexSet().size()); assertTrue(g1.containsVertex(v1)); assertEquals(2, g2.vertexSet().size()); assertTrue(g2.containsVertex(v1)); assertTrue(g2.containsVertex(v2)); assertEquals(3, g3.vertexSet().size()); assertTrue(g3.containsVertex(v1)); assertTrue(g3.containsVertex(v2)); assertTrue(g3.containsVertex(v3)); assertEquals(4, g4.vertexSet().size()); assertTrue(g4.containsVertex(v1)); assertTrue(g4.containsVertex(v2)); assertTrue(g4.containsVertex(v3)); assertTrue(g4.containsVertex(v4)); } @Test public void testReversedView() { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); Graph r = new EdgeReversedGraph<>(g); g.addVertex(v1); g.addVertex(v2); DefaultEdge e = g.addEdge(v1, v2); verifyReversal(g, r, e); // We have implicitly verified that r is backed by g for additive // operations (since we constructed it before adding anything to g). // Now verify for deletion. g.removeEdge(e); assertTrue(r.edgeSet().isEmpty()); assertEquals(0, r.inDegreeOf(v1)); assertEquals(0, r.outDegreeOf(v1)); assertEquals(0, r.inDegreeOf(v2)); assertEquals(0, r.outDegreeOf(v2)); assertTrue(r.incomingEdgesOf(v1).isEmpty()); assertTrue(r.outgoingEdgesOf(v1).isEmpty()); assertTrue(r.incomingEdgesOf(v2).isEmpty()); assertTrue(r.outgoingEdgesOf(v2).isEmpty()); } private void verifyReversal( Graph g, Graph r, DefaultEdge e) { assertTrue(r.containsVertex(v1)); assertTrue(r.containsVertex(v2)); assertEquals(g.vertexSet(), r.vertexSet()); assertEquals(g.edgeSet(), r.edgeSet()); assertTrue(r.containsEdge(v2, v1)); assertSame(e, r.getEdge(v2, v1)); assertFalse(r.containsEdge(v1, v2)); assertNull(r.getEdge(v1, v2)); Set s = r.getAllEdges(v1, v2); assertEquals(0, s.size()); s = r.getAllEdges(v2, v1); assertEquals(1, s.size()); assertSame(e, s.iterator().next()); assertEquals(1, r.inDegreeOf(v1)); assertEquals(0, r.inDegreeOf(v2)); assertEquals(0, r.outDegreeOf(v1)); assertEquals(1, r.outDegreeOf(v2)); assertEquals(g.edgeSet(), r.incomingEdgesOf(v1)); assertTrue(r.outgoingEdgesOf(v1).isEmpty()); assertTrue(r.incomingEdgesOf(v2).isEmpty()); assertEquals(g.edgeSet(), r.outgoingEdgesOf(v2)); assertSame(v2, r.getEdgeSource(e)); assertSame(v1, r.getEdgeTarget(e)); assertEquals("([v1, v2], [(v2,v1)])", r.toString()); } @Before public void setUp() { gEmpty = new SimpleDirectedGraph<>( SupplierUtil.createRandomUUIDStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); g1 = new SimpleDirectedGraph<>( SupplierUtil.createRandomUUIDStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); g2 = new SimpleDirectedGraph<>( SupplierUtil.createRandomUUIDStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); g3 = new SimpleDirectedGraph<>( SupplierUtil.createRandomUUIDStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); g4 = new SimpleDirectedGraph<>( SupplierUtil.createRandomUUIDStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); eSupplier = g1.getEdgeSupplier(); eLoop = eSupplier.get(); g1.addVertex(v1); g2.addVertex(v1); g2.addVertex(v2); e12_1 = g2.addEdge(v1, v2); e21_1 = g2.addEdge(v2, v1); g3.addVertex(v1); g3.addVertex(v2); g3.addVertex(v3); e12_2 = g3.addEdge(v1, v2); e21_2 = g3.addEdge(v2, v1); e23_1 = g3.addEdge(v2, v3); e32_1 = g3.addEdge(v3, v2); e31_1 = g3.addEdge(v3, v1); e13_1 = g3.addEdge(v1, v3); g4.addVertex(v1); g4.addVertex(v2); g4.addVertex(v3); g4.addVertex(v4); e12_3 = g4.addEdge(v1, v2); e23_2 = g4.addEdge(v2, v3); e34_1 = g4.addEdge(v3, v4); e41_1 = g4.addEdge(v4, v1); } } SimpleIdentityDirectedGraphTest.java000066400000000000000000000473501402514743400342470ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.jgrapht.graph.specifics.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.*; /** * A unit test for simple directed graph when the backing map is an IdentityHashMap * */ public class SimpleIdentityDirectedGraphTest { public static class Holder { T t; public Holder(T t) { this.t = t; } public T getT() { return t; } public void setT(T t) { this.t = t; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Holder holder = TypeUtil.uncheckedCast(o); return !(t != null ? !t.equals(holder.t) : holder.t != null); } @Override public int hashCode() { return t != null ? t.hashCode() : 0; } } public static class SimpleIdentityDirectedGraph extends AbstractBaseGraph { private static final long serialVersionUID = 4600490314100246989L; public SimpleIdentityDirectedGraph(Class edgeClass) { super( null, SupplierUtil.createSupplier(edgeClass), DefaultGraphType.directedSimple(), new IdentitySpecificsStrategy<>()); } } private static class IdentitySpecificsStrategy implements GraphSpecificsStrategy { private static final long serialVersionUID = 1L; @Override public Function> getIntrusiveEdgesSpecificsFactory() { return (Function> & Serializable) (type) -> { if (type.isWeighted()) { return new WeightedIntrusiveEdgesSpecifics(new IdentityHashMap<>()); } else { return new UniformIntrusiveEdgesSpecifics<>(new IdentityHashMap<>()); } }; } @Override public BiFunction, GraphType, Specifics> getSpecificsFactory() { return (BiFunction, GraphType, Specifics> & Serializable) (graph, type) -> { if (type.isDirected()) { return new DirectedSpecifics( graph, new IdentityHashMap<>(), getEdgeSetFactory()); } else { return new UndirectedSpecifics<>( graph, new IdentityHashMap<>(), getEdgeSetFactory()); } }; } } // ~ Instance fields -------------------------------------------------------- Graph, DefaultEdge> gEmpty; private Graph, DefaultEdge> g1; private Graph, DefaultEdge> g2; private Graph, DefaultEdge> g3; private Graph, DefaultEdge> g4; private DefaultEdge eLoop; private Supplier eSupplier; private Holder v1 = new Holder<>("v1"); private Holder v2 = new Holder<>("v2"); private Holder v3 = new Holder<>("v3"); private Holder v4 = new Holder<>("v4"); private DefaultEdge e12_1; private DefaultEdge e12_2; private DefaultEdge e12_3; private DefaultEdge e21_1; private DefaultEdge e21_2; private DefaultEdge e13_1; private DefaultEdge e23_1; private DefaultEdge e31_1; private DefaultEdge e32_1; private DefaultEdge e23_2; private DefaultEdge e34_1; private DefaultEdge e41_1; /** * Class to test for boolean addEdge(V, V, E) */ @Test public void testAddEdgeEdge() { try { g1.addEdge(v1, v1, eLoop); // loops not allowed Assert.fail("Should not get here."); } catch (IllegalArgumentException e) { } try { g3.addEdge(v1, v1, null); Assert.fail("Should not get here."); } catch (NullPointerException e) { } DefaultEdge e = eSupplier.get(); try { g1.addEdge(new Holder<>("ya"), new Holder<>("ya"), e); // no such vertex in graph Assert.fail("Should not get here."); } catch (IllegalArgumentException ile) { } assertFalse(g2.addEdge(v2, v1, e)); assertFalse(g3.addEdge(v2, v1, e)); assertTrue(g4.addEdge(v2, v1, e)); } /** * Class to test for Edge addEdge(Object, Object) */ @Test public void testAddEdgeObjectObject() { try { g1.addEdge(v1, v1); // loops not allowed Assert.fail("Should not get here."); } catch (IllegalArgumentException e) { } try { g3.addEdge(null, null); Assert.fail("Should not get here."); } catch (NullPointerException e) { } try { g1.addEdge(v2, v1); // no such vertex in graph Assert.fail("Should not get here."); } catch (IllegalArgumentException ile) { } assertNull(g2.addEdge(v2, v1)); assertNull(g3.addEdge(v2, v1)); assertNotNull(g4.addEdge(v2, v1)); } /** * . */ @Test public void testAddVertex() { assertEquals(1, g1.vertexSet().size()); assertEquals(2, g2.vertexSet().size()); assertEquals(3, g3.vertexSet().size()); assertEquals(4, g4.vertexSet().size()); assertFalse(g1.addVertex(v1)); assertTrue(g1.addVertex(v2)); assertEquals(2, g1.vertexSet().size()); } /** * Class to test for boolean containsEdge(Edge) */ @Test public void testContainsEdgeEdge() { assertTrue(g2.containsEdge(e12_1)); assertTrue(g2.containsEdge(e21_1)); assertTrue(g3.containsEdge(e12_2)); assertTrue(g3.containsEdge(e21_2)); assertTrue(g3.containsEdge(e23_1)); assertTrue(g3.containsEdge(e32_1)); assertTrue(g3.containsEdge(e31_1)); assertTrue(g3.containsEdge(e13_1)); assertTrue(g4.containsEdge(e12_3)); assertTrue(g4.containsEdge(e23_2)); assertTrue(g4.containsEdge(e34_1)); assertTrue(g4.containsEdge(e41_1)); } /** * Class to test for boolean containsEdge(Object, Object) */ @Test public void testContainsEdgeObjectObject() { assertFalse(g1.containsEdge(v1, v2)); assertFalse(g1.containsEdge(v1, v1)); assertTrue(g2.containsEdge(v1, v2)); assertTrue(g2.containsEdge(v2, v1)); assertTrue(g3.containsEdge(v1, v2)); assertTrue(g3.containsEdge(v2, v1)); assertTrue(g3.containsEdge(v3, v2)); assertTrue(g3.containsEdge(v2, v3)); assertTrue(g3.containsEdge(v1, v3)); assertTrue(g3.containsEdge(v3, v1)); assertFalse(g4.containsEdge(v1, v4)); g4.addEdge(v1, v4); assertTrue(g4.containsEdge(v1, v4)); assertFalse(g3.containsEdge(v4, v2)); assertFalse(g3.containsEdge(null, null)); } /** * . */ @Test public void testContainsVertex() { assertTrue(g1.containsVertex(v1)); v1.setT("V1"); assertTrue(g1.containsVertex(v1)); // shows #hashCode is bypassed } /** * . */ @Test public void testEdgeSet() { assertEquals(0, g1.edgeSet().size()); assertEquals(2, g2.edgeSet().size()); assertTrue(g2.containsEdge(e12_1)); assertTrue(g2.containsEdge(e21_1)); assertEquals(6, g3.edgeSet().size()); assertTrue(g3.containsEdge(e12_2)); assertTrue(g3.containsEdge(e21_2)); assertTrue(g3.containsEdge(e23_1)); assertTrue(g3.containsEdge(e32_1)); assertTrue(g3.containsEdge(e31_1)); assertTrue(g3.containsEdge(e13_1)); assertEquals(4, g4.edgeSet().size()); assertTrue(g4.containsEdge(e12_3)); assertTrue(g4.containsEdge(e23_2)); assertTrue(g4.containsEdge(e34_1)); assertTrue(g4.containsEdge(e41_1)); } /** * . */ @Test public void testEdgesOf() { assertEquals(g4.edgesOf(v1).size(), 2); assertEquals(g3.edgesOf(v1).size(), 4); Iterator iter = g3.edgesOf(v1).iterator(); int count = 0; while (iter.hasNext()) { iter.next(); count++; } assertEquals(count, 4); } /** * . */ @Test public void testGetAllEdges() { assertEquals(1, g3.getAllEdges(v1, v2).size()); assertTrue(g3.getAllEdges(v1, v2).contains(e12_2)); assertEquals(1, g3.getAllEdges(v2, v1).size()); assertTrue(g3.getAllEdges(v2, v1).contains(e21_2)); } /** * . */ @Test public void testGetEdge() { assertEquals(e12_1, g2.getEdge(v1, v2)); assertEquals(e21_1, g2.getEdge(v2, v1)); assertEquals(e12_2, g3.getEdge(v1, v2)); assertEquals(e21_2, g3.getEdge(v2, v1)); assertEquals(e21_2, g3.getEdge(v2, v1)); assertEquals(e32_1, g3.getEdge(v3, v2)); assertEquals(e31_1, g3.getEdge(v3, v1)); assertEquals(e13_1, g3.getEdge(v1, v3)); assertEquals(e12_3, g4.getEdge(v1, v2)); assertEquals(e23_2, g4.getEdge(v2, v3)); assertEquals(e34_1, g4.getEdge(v3, v4)); assertEquals(e41_1, g4.getEdge(v4, v1)); } /** * . */ @Test public void testGetEdgeSupplier() { assertNotNull(g1.getEdgeSupplier()); Supplier es = g1.getEdgeSupplier(); DefaultEdge e = es.get(); assertNotNull(e); assertNull(g1.getEdgeSource(e)); assertNull(g1.getEdgeTarget(e)); } /** * . */ @Test public void testInDegreeOf() { assertEquals(0, g1.inDegreeOf(v1)); assertEquals(1, g2.inDegreeOf(v1)); assertEquals(1, g2.inDegreeOf(v2)); assertEquals(2, g3.inDegreeOf(v1)); assertEquals(2, g3.inDegreeOf(v2)); assertEquals(2, g3.inDegreeOf(v3)); assertEquals(1, g4.inDegreeOf(v1)); assertEquals(1, g4.inDegreeOf(v2)); assertEquals(1, g4.inDegreeOf(v3)); assertEquals(1, g4.inDegreeOf(v4)); try { g3.inDegreeOf(new Holder<>("")); Assert.fail("Should not get here."); } catch (IllegalArgumentException e) { } try { g3.inDegreeOf(null); Assert.fail("Should not get here."); } catch (NullPointerException e) { } } /** * . */ @Test public void testIncomingOutgoingEdgesOf() { Set e1to2 = g2.outgoingEdgesOf(v1); Set e2from1 = g2.incomingEdgesOf(v2); assertEquals(e1to2, e2from1); } /** * . */ @Test public void testOutDegreeOf() { assertEquals(1, g2.outDegreeOf(v1)); assertEquals(1, g2.outDegreeOf(v2)); assertEquals(2, g3.outDegreeOf(v1)); assertEquals(2, g3.outDegreeOf(v2)); assertEquals(2, g3.outDegreeOf(v3)); assertEquals(1, g4.outDegreeOf(v1)); assertEquals(1, g4.outDegreeOf(v2)); assertEquals(1, g4.outDegreeOf(v3)); assertEquals(1, g4.outDegreeOf(v4)); } /** * . */ @Test public void testOutgoingEdgesOf() { assertEquals(0, g1.outgoingEdgesOf(v1).size()); assertEquals(1, g2.outgoingEdgesOf(v1).size()); assertTrue(g2.outgoingEdgesOf(v1).contains(e12_1)); assertEquals(1, g2.outgoingEdgesOf(v2).size()); assertTrue(g2.outgoingEdgesOf(v2).contains(e21_1)); assertEquals(2, g3.outgoingEdgesOf(v1).size()); assertTrue(g3.outgoingEdgesOf(v1).contains(e12_2)); assertTrue(g3.outgoingEdgesOf(v1).contains(e13_1)); assertEquals(2, g3.outgoingEdgesOf(v2).size()); assertTrue(g3.outgoingEdgesOf(v2).contains(e23_1)); assertTrue(g3.outgoingEdgesOf(v2).contains(e21_2)); assertEquals(2, g3.outgoingEdgesOf(v3).size()); assertTrue(g3.outgoingEdgesOf(v3).contains(e31_1)); assertTrue(g3.outgoingEdgesOf(v3).contains(e32_1)); assertEquals(1, g4.outgoingEdgesOf(v1).size()); assertTrue(g4.outgoingEdgesOf(v1).contains(e12_3)); assertEquals(1, g4.outgoingEdgesOf(v2).size()); assertTrue(g4.outgoingEdgesOf(v2).contains(e23_2)); assertEquals(1, g4.outgoingEdgesOf(v3).size()); assertTrue(g4.outgoingEdgesOf(v3).contains(e34_1)); assertEquals(1, g4.outgoingEdgesOf(v4).size()); assertTrue(g4.outgoingEdgesOf(v4).contains(e41_1)); } /** * Class to test for boolean removeEdge(Edge) */ @Test public void testRemoveEdgeEdge() { assertEquals(g4.edgeSet().size(), 4); g4.removeEdge(v1, v2); assertEquals(g4.edgeSet().size(), 3); assertFalse(g4.removeEdge(eLoop)); assertTrue(g4.removeEdge(g4.getEdge(v2, v3))); assertEquals(g4.edgeSet().size(), 2); } /** * Class to test for Edge removeEdge(Object, Object) */ @Test public void testRemoveEdgeObjectObject() { assertEquals(g4.edgeSet().size(), 4); g4.removeEdge(v1, v2); assertEquals(g4.edgeSet().size(), 3); assertFalse(g4.removeEdge(eLoop)); assertTrue(g4.removeEdge(g4.getEdge(v2, v3))); assertEquals(g4.edgeSet().size(), 2); } @Test public void testRemoveAllEdgesObjectObject() { assertEquals(2, g2.edgeSet().size()); assertTrue(g2.containsEdge(v1, v2)); Set edges = g2.getAllEdges(v1, v2); assertEquals(edges, g2.removeAllEdges(v1, v2)); assertEquals(1, g2.edgeSet().size()); assertFalse(g2.containsEdge(v1, v2)); assertEquals(4, g4.edgeSet().size()); edges = g4.getAllEdges(v3, v4); assertEquals(edges, g4.removeAllEdges(v3, v4)); assertEquals(3, g4.edgeSet().size()); assertFalse(g4.containsEdge(v3, v4)); // No edge to remove. assertEquals(Collections.emptySet(), g4.removeAllEdges(v3, v2)); assertEquals(3, g4.edgeSet().size()); // Missing vertex. assertEquals(null, g4.removeAllEdges(v1, new Holder<>("v5"))); } /** * . */ @Test public void testRemoveVertex() { assertEquals(4, g4.vertexSet().size()); assertTrue(g4.removeVertex(v1)); assertEquals(3, g4.vertexSet().size()); assertEquals(2, g4.edgeSet().size()); assertFalse(g4.removeVertex(v1)); assertTrue(g4.removeVertex(v2)); assertEquals(1, g4.edgeSet().size()); assertTrue(g4.removeVertex(v3)); assertEquals(0, g4.edgeSet().size()); assertEquals(1, g4.vertexSet().size()); assertTrue(g4.removeVertex(v4)); assertEquals(0, g4.vertexSet().size()); } /** * . */ @Test public void testVertexSet() { assertEquals(1, g1.vertexSet().size()); assertTrue(g1.containsVertex(v1)); assertEquals(2, g2.vertexSet().size()); assertTrue(g2.containsVertex(v1)); assertTrue(g2.containsVertex(v2)); assertEquals(3, g3.vertexSet().size()); assertTrue(g3.containsVertex(v1)); assertTrue(g3.containsVertex(v2)); assertTrue(g3.containsVertex(v3)); assertEquals(4, g4.vertexSet().size()); assertTrue(g4.containsVertex(v1)); assertTrue(g4.containsVertex(v2)); assertTrue(g4.containsVertex(v3)); assertTrue(g4.containsVertex(v4)); } @Test public void testReversedView() { Graph, DefaultEdge> g = new SimpleIdentityDirectedGraph<>(DefaultEdge.class); Graph, DefaultEdge> r = new EdgeReversedGraph<>(g); g.addVertex(v1); g.addVertex(v2); DefaultEdge e = g.addEdge(v1, v2); verifyReversal(g, r, e); // We have implicitly verified that r is backed by g for additive // operations (since we constructed it before adding anything to g). // Now verify for deletion. g.removeEdge(e); assertTrue(r.edgeSet().isEmpty()); assertEquals(0, r.inDegreeOf(v1)); assertEquals(0, r.outDegreeOf(v1)); assertEquals(0, r.inDegreeOf(v2)); assertEquals(0, r.outDegreeOf(v2)); assertTrue(r.incomingEdgesOf(v1).isEmpty()); assertTrue(r.outgoingEdgesOf(v1).isEmpty()); assertTrue(r.incomingEdgesOf(v2).isEmpty()); assertTrue(r.outgoingEdgesOf(v2).isEmpty()); } private void verifyReversal( Graph, DefaultEdge> g, Graph, DefaultEdge> r, DefaultEdge e) { assertTrue(r.containsVertex(v1)); assertTrue(r.containsVertex(v2)); assertEquals(g.vertexSet(), r.vertexSet()); assertEquals(g.edgeSet(), r.edgeSet()); assertTrue(r.containsEdge(v2, v1)); assertSame(e, r.getEdge(v2, v1)); assertFalse(r.containsEdge(v1, v2)); assertNull(r.getEdge(v1, v2)); Set s = r.getAllEdges(v1, v2); assertEquals(0, s.size()); s = r.getAllEdges(v2, v1); assertEquals(1, s.size()); assertSame(e, s.iterator().next()); assertEquals(1, r.inDegreeOf(v1)); assertEquals(0, r.inDegreeOf(v2)); assertEquals(0, r.outDegreeOf(v1)); assertEquals(1, r.outDegreeOf(v2)); assertEquals(g.edgeSet(), r.incomingEdgesOf(v1)); assertTrue(r.outgoingEdgesOf(v1).isEmpty()); assertTrue(r.incomingEdgesOf(v2).isEmpty()); assertEquals(g.edgeSet(), r.outgoingEdgesOf(v2)); assertSame(v2, r.getEdgeSource(e)); assertSame(v1, r.getEdgeTarget(e)); } @Before public void setUp() { gEmpty = new SimpleIdentityDirectedGraph<>(DefaultEdge.class); g1 = new SimpleIdentityDirectedGraph<>(DefaultEdge.class); g2 = new SimpleIdentityDirectedGraph<>(DefaultEdge.class); g3 = new SimpleIdentityDirectedGraph<>(DefaultEdge.class); g4 = new SimpleIdentityDirectedGraph<>(DefaultEdge.class); eSupplier = g1.getEdgeSupplier(); eLoop = eSupplier.get(); g1.addVertex(v1); g2.addVertex(v1); g2.addVertex(v2); e12_1 = g2.addEdge(v1, v2); e21_1 = g2.addEdge(v2, v1); g3.addVertex(v1); g3.addVertex(v2); g3.addVertex(v3); e12_2 = g3.addEdge(v1, v2); e21_2 = g3.addEdge(v2, v1); e23_1 = g3.addEdge(v2, v3); e32_1 = g3.addEdge(v3, v2); e31_1 = g3.addEdge(v3, v1); e13_1 = g3.addEdge(v1, v3); g4.addVertex(v1); g4.addVertex(v2); g4.addVertex(v3); g4.addVertex(v4); e12_3 = g4.addEdge(v1, v2); e23_2 = g4.addEdge(v2, v3); e34_1 = g4.addEdge(v3, v4); e41_1 = g4.addEdge(v4, v1); // change vertex values v1.setT("_v1"); v2.setT("_v2"); v3.setT("_v3"); v4.setT("_v4"); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/TestEdge.java000066400000000000000000000027641402514743400276010ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Christoph Zauner and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import java.util.Objects; /** * {@link org.jgrapht.graph.DefaultEdge} does not implement hashCode() or equals(). Therefore * comparing two graphs does not work as expected out of the box. * * @author Christoph Zauner */ public class TestEdge extends DefaultEdge { private static final long serialVersionUID = 1L; public TestEdge() { super(); } @Override public int hashCode() { return Objects.hash(source, target); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; TestEdge other = (TestEdge) obj; return Objects.equals(source, other.source) && Objects.equals(target, other.target); } } UnweightedGraphAsWeightedGraphTest.java000066400000000000000000000067761402514743400347010ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/* * (C) Copyright 2018-2021, by Lukas Harzenetter and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.junit.*; import java.util.*; import static junit.framework.TestCase.fail; import static org.jgrapht.Graph.DEFAULT_EDGE_WEIGHT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class UnweightedGraphAsWeightedGraphTest { private DefaultWeightedEdge loop; private DefaultWeightedEdge e12; private final double e12Weight = -123.54d; private DefaultWeightedEdge e23; private final double e23Weight = 89d; private DefaultWeightedEdge e24; private final double e24Weight = 3d; private final String v1 = "v1"; private final String v2 = "v2"; private final String v3 = "v3"; private final String v4 = "v4"; private Graph weightedGraph; /** * Similar set up as created by {@link AsUndirectedGraphTest}. */ @Before public void setUp() { Graph graph = new DefaultUndirectedGraph<>(DefaultWeightedEdge.class); graph.addVertex(v1); graph.addVertex(v2); graph.addVertex(v3); graph.addVertex(v4); e12 = graph.addEdge(v1, v2); e23 = graph.addEdge(v2, v3); e24 = graph.addEdge(v2, v4); loop = graph.addEdge(v4, v4); Map graphWeights = new HashMap<>(); graphWeights.put(e12, e12Weight); graphWeights.put(e23, e23Weight); graphWeights.put(e24, e24Weight); this.weightedGraph = new AsWeightedGraph<>(graph, graphWeights); } @Test public void testSetEdgeWeight() { double newEdgeWeight = -999; this.weightedGraph.setEdgeWeight(e12, newEdgeWeight); assertEquals(newEdgeWeight, this.weightedGraph.getEdgeWeight(e12), 0); } @Test public void testGetEdgeWeight() { assertEquals(e23Weight, this.weightedGraph.getEdgeWeight(e23), 0); } @Test public void testGetDefaultEdgeWeight() { assertEquals(DEFAULT_EDGE_WEIGHT, this.weightedGraph.getEdgeWeight(loop), 0); } @Test public void testGetEdgeWeightOfNull() { try { this.weightedGraph.getEdgeWeight(null); fail("Expected a NullPointerException"); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } } @Test public void testGetType() { assertTrue(this.weightedGraph.getType().isWeighted()); } @Test public void createAsWeightedGraphWithWeightPropagationOnAnUnweightedGraph() { try { new AsWeightedGraph<>( new DefaultUndirectedGraph<>(String.class), new HashMap<>(), true); fail("Expected a IllegalArgumentException"); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } } } WeightedGraphAsWeightedGraphTest.java000066400000000000000000000132371402514743400343240ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/* * (C) Copyright 2018-2021, by Lukas Harzenetter and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.jgrapht.*; import org.junit.*; import java.util.*; import java.util.function.*; import static junit.framework.TestCase.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class WeightedGraphAsWeightedGraphTest { private Graph backingGraph; private DefaultWeightedEdge loop; private final double defaultLoopWeight = 6781234453486d; private DefaultWeightedEdge e12; private final double defaultE12Weight = 6d; private final double e12Weight = -123.54d; private DefaultWeightedEdge e23; private final double defaultE23Weight = 456d; private final double e23Weight = 89d; private DefaultWeightedEdge e24; private final double defaultE24Weight = 0.587d; private final double e24Weight = 3d; private final String v1 = "v1"; private final String v2 = "v2"; private final String v3 = "v3"; private final String v4 = "v4"; private Graph weightedGraph; /** * Set up using default writeWeightsThrough setting (false) for AsWeightedGraph. */ private void setUp() { Map graphWeights = createBackingGraph(); this.weightedGraph = new AsWeightedGraph<>(this.backingGraph, graphWeights); } /** * Set up using explicit writeWeightsThrough setting (false) for AsWeightedGraph. */ private void setUp(boolean writeWeightsThrough) { Map graphWeights = createBackingGraph(); this.weightedGraph = new AsWeightedGraph<>(this.backingGraph, graphWeights, writeWeightsThrough); } private Map createBackingGraph() { this.backingGraph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); this.backingGraph.addVertex(v1); this.backingGraph.addVertex(v2); this.backingGraph.addVertex(v3); this.backingGraph.addVertex(v4); e12 = Graphs.addEdge(this.backingGraph, v1, v2, defaultE12Weight); e23 = Graphs.addEdge(this.backingGraph, v2, v3, defaultE23Weight); e24 = Graphs.addEdge(this.backingGraph, v2, v4, defaultE24Weight); loop = Graphs.addEdge(this.backingGraph, v4, v4, defaultLoopWeight); Map graphWeights = new HashMap<>(); graphWeights.put(e12, e12Weight); graphWeights.put(e23, e23Weight); graphWeights.put(e24, e24Weight); return graphWeights; } @Test public void testSetEdgeWeight() { this.setUp(false); double newEdgeWeight = -999; this.weightedGraph.setEdgeWeight(e12, newEdgeWeight); assertEquals(newEdgeWeight, this.weightedGraph.getEdgeWeight(e12), 0); assertEquals(this.defaultE12Weight, this.backingGraph.getEdgeWeight(e12), 0); } @Test public void testSetEdgeWeightDefaultPropagation() { this.setUp(); double newEdgeWeight = -999; this.weightedGraph.setEdgeWeight(e12, newEdgeWeight); assertEquals(newEdgeWeight, this.weightedGraph.getEdgeWeight(e12), 0); assertEquals(newEdgeWeight, this.backingGraph.getEdgeWeight(e12), 0); } @Test public void testSetEdgePropagatesChangesToBackingGraph() { this.setUp(true); double newEdgeWeight = -999; this.weightedGraph.setEdgeWeight(e12, newEdgeWeight); assertEquals(newEdgeWeight, this.weightedGraph.getEdgeWeight(e12), 0); assertEquals(newEdgeWeight, this.backingGraph.getEdgeWeight(e12), 0); } @Test public void testGetEdgeWeight() { this.setUp(false); assertEquals(e23Weight, this.weightedGraph.getEdgeWeight(e23), 0); } @Test public void testGetDefaultEdgeWeight() { this.setUp(false); assertEquals(defaultLoopWeight, this.weightedGraph.getEdgeWeight(loop), 0); } @Test public void testGetEdgeWeightOfNull() { this.setUp(false); try { this.weightedGraph.getEdgeWeight(null); fail("Expected a NullPointerException"); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } } @Test public void testWeightFunction() { Graph g1 = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); Graphs.addEdgeWithVertices(g1, 0, 1, 2); Graphs.addEdgeWithVertices(g1, 1, 2, 3); Graphs.addEdgeWithVertices(g1, 2, 0, 4); Function weightFunction = e -> Math.pow(g1.getEdgeWeight(e), 2); Graph g2 = new AsWeightedGraph<>(g1, weightFunction, true, false); // Repeat twice to trigger caching for (int i = 0; i < 2; i++) for (DefaultWeightedEdge edge : g1.edgeSet()) assertEquals( g1.getEdgeWeight(edge) * g1.getEdgeWeight(edge), g2.getEdgeWeight(edge), 0); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/WeightedGraphTest.java000066400000000000000000000061151402514743400314510ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph; import org.junit.*; import static org.junit.Assert.*; /** * Tests for different edge types on weighted graphs. * * @author Dimitrios Michail */ public class WeightedGraphTest { @Test public void testDefaultWeightedEdge() { WeightedPseudograph g = new WeightedPseudograph(DefaultWeightedEdge.class); g.addVertex(1); g.addVertex(2); DefaultWeightedEdge e = g.addEdge(1, 2); assertEquals(g.getEdgeWeight(e), 1d, 1e-9); g.setEdgeWeight(e, 3d); assertEquals(g.getEdgeWeight(e), 3d, 1e-9); } @Test public void testStringAsWeightedEdge() { WeightedPseudograph g = new WeightedPseudograph(String.class); g.addVertex(1); g.addVertex(2); assertTrue(g.addEdge(1, 2, "1-2")); assertEquals(g.getEdgeWeight("1-2"), 1d, 1e-9); g.setEdgeWeight("1-2", 3d); assertEquals(g.getEdgeWeight("1-2"), 3d, 1e-9); assertTrue(g.containsEdge("1-2")); g.removeEdge("1-2"); assertFalse(g.containsEdge("1-2")); } @Test(expected = IllegalArgumentException.class) public void testInvalidEdgeOnWeightedGraph() { WeightedPseudograph g = new WeightedPseudograph(String.class); g.getEdgeWeight("1-2"); } @Test(expected = IllegalArgumentException.class) public void testInvalidEdgeOnWeightedGraphSet() { WeightedPseudograph g = new WeightedPseudograph(String.class); g.setEdgeWeight("1-2", 2d); } public void testInvalidEdgeOnUnweightedGraph() { Pseudograph g = new Pseudograph(String.class); assertEquals(1d, g.getEdgeWeight("1-2"), 1e-9); } @Test public void testDefaultEdgeOnWeightedGraphs() { WeightedPseudograph g = new WeightedPseudograph(DefaultEdge.class); g.addVertex(1); g.addVertex(2); DefaultEdge e = g.addEdge(1, 2); assertEquals(g.getEdgeWeight(e), 1d, 1e-9); g.setEdgeWeight(e, 3d); assertEquals(g.getEdgeWeight(e), 3d, 1e-9); assertEquals(Integer.valueOf(1), g.getEdgeSource(e)); assertEquals(Integer.valueOf(2), g.getEdgeTarget(e)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/builder/000077500000000000000000000000001402514743400266475ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/builder/GraphBuilderTest.java000066400000000000000000000126351402514743400327310ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Andrew Chen and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.builder; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class GraphBuilderTest { // ~ Instance fields -------------------------------------------------------- private String v1 = "v1"; private String v2 = "v2"; private String v3 = "v3"; private String v4 = "v4"; private String v5 = "v5"; private String v6 = "v6"; private String v7 = "v7"; private String v8 = "v8"; @Test public void testAddVertex() { Graph g = new GraphBuilder<>(new DefaultDirectedGraph(DefaultEdge.class)) .addVertex(v1).addVertices(v2, v3).build(); assertEquals(3, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); assertTrue(g.vertexSet().containsAll(Arrays.asList(v1, v2, v3))); } @Test public void testAddEdge() { DefaultWeightedEdge e1 = new DefaultWeightedEdge(); DefaultWeightedEdge e2 = new DefaultWeightedEdge(); Graph g = new GraphBuilder<>( new DefaultDirectedWeightedGraph( DefaultWeightedEdge.class)) .addEdge(v1, v2).addEdgeChain(v3, v4, v5, v6).addEdge(v7, v8, 10.0) .addEdge(v1, v7, e1).addEdge(v1, v8, e2, 42.0).buildAsUnmodifiable(); assertEquals(8, g.vertexSet().size()); assertEquals(7, g.edgeSet().size()); assertTrue(g.vertexSet().containsAll(Arrays.asList(v1, v2, v3, v4, v5, v6, v7, v8))); assertTrue(g.containsEdge(v1, v2)); assertTrue(g.containsEdge(v3, v4)); assertTrue(g.containsEdge(v4, v5)); assertTrue(g.containsEdge(v5, v6)); assertTrue(g.containsEdge(v7, v8)); assertTrue(g.containsEdge(v1, v7)); assertTrue(g.containsEdge(v1, v8)); assertEquals(e1, g.getEdge(v1, v7)); assertEquals(e2, g.getEdge(v1, v8)); assertEquals(10.0, g.getEdgeWeight(g.getEdge(v7, v8)), 0); assertEquals(42.0, g.getEdgeWeight(g.getEdge(v1, v8)), 0); } @Test public void testAddGraph() { Graph g1 = DefaultDirectedGraph . createBuilder(DefaultEdge.class).addVertex(v1) .addEdge(v2, v3).buildAsUnmodifiable(); Graph g2 = new GraphBuilder<>(new DefaultDirectedGraph(DefaultEdge.class)) .addGraph(g1).addEdge(v1, v4).build(); assertEquals(4, g2.vertexSet().size()); assertEquals(2, g2.edgeSet().size()); assertTrue(g2.vertexSet().containsAll(Arrays.asList(v1, v2, v3, v3))); assertTrue(g2.containsEdge(v2, v3)); assertTrue(g2.containsEdge(v1, v4)); } @Test public void testRemoveVertex() { Graph g1 = new GraphBuilder<>(new DefaultDirectedGraph(DefaultEdge.class)) .addEdge(v1, v3).addEdgeChain(v2, v3, v4, v5).buildAsUnmodifiable(); Graph g2 = new GraphBuilder<>(new DefaultDirectedGraph(DefaultEdge.class)) .addGraph(g1).removeVertex(v2).removeVertices(v4, v5).build(); assertEquals(2, g2.vertexSet().size()); assertEquals(1, g2.edgeSet().size()); assertTrue(g2.vertexSet().containsAll(Arrays.asList(v1, v3))); assertTrue(g2.containsEdge(v1, v3)); } @Test public void testRemoveEdge() { DefaultEdge e = new DefaultEdge(); Graph g1 = new GraphBuilder<>(new DefaultDirectedGraph(DefaultEdge.class)) .addEdgeChain(v1, v2, v3, v4).addEdge(v1, v4, e).buildAsUnmodifiable(); Graph g2 = new GraphBuilder<>(new DefaultDirectedGraph(DefaultEdge.class)) .addGraph(g1).removeEdge(v2, v3).removeEdge(e).build(); assertEquals(4, g2.vertexSet().size()); assertEquals(2, g2.edgeSet().size()); assertTrue(g2.vertexSet().containsAll(Arrays.asList(v1, v2, v3, v4))); assertTrue(g2.containsEdge(v1, v2)); assertTrue(g2.containsEdge(v3, v4)); } @Test public void testAddVertexPseudograph() { Pseudograph g = Pseudograph . createBuilder(DefaultEdge.class).addVertex(v1).build(); assertEquals(1, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); assertTrue(g.vertexSet().containsAll(Collections.singletonList(v1))); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/builder/GraphTypeBuilderTest.java000066400000000000000000000073511402514743400335720ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.builder; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import static org.junit.Assert.*; /** * Tests for the graph type builder. * * @author Dimitrios Michail */ public class GraphTypeBuilderTest { @Test public void testGraphTypeBuilder() { Graph graph = GraphTypeBuilder . directed().allowingMultipleEdges(true) .allowingSelfLoops(true).edgeClass(DefaultEdge.class).buildGraph(); assertTrue(graph.getType().isDirected()); assertTrue(graph.getType().isAllowingMultipleEdges()); assertTrue(graph.getType().isAllowingSelfLoops()); assertNotNull(graph.getEdgeSupplier()); assertNull(graph.getVertexSupplier()); } @Test public void testGraphTypeBuilderWithEdgeSupplier() { Graph graph = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true) .edgeSupplier(() -> new DefaultWeightedEdge()) .vertexSupplier(SupplierUtil.createIntegerSupplier()).buildGraph(); assertTrue(graph.getType().isDirected()); assertTrue(graph.getType().isAllowingMultipleEdges()); assertTrue(graph.getType().isAllowingSelfLoops()); assertNotNull(graph.getEdgeSupplier()); assertNotNull(graph.getVertexSupplier()); } @Test public void testGraphTypeBuilderWithVertexClass() { Graph graph = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexClass(Integer.class).edgeClass(DefaultEdge.class).buildGraph(); assertTrue(graph.getType().isDirected()); assertTrue(graph.getType().isAllowingMultipleEdges()); assertTrue(graph.getType().isAllowingSelfLoops()); assertNotNull(graph.getEdgeSupplier()); assertNotNull(graph.getVertexSupplier()); } @Test public void testGraphTypeBuilderUndirected() { Graph graph = GraphTypeBuilder . undirected().allowingMultipleEdges(true) .allowingSelfLoops(false).edgeClass(DefaultEdge.class).buildGraph(); assertTrue(graph.getType().isUndirected()); assertTrue(graph.getType().isAllowingMultipleEdges()); assertFalse(graph.getType().isAllowingSelfLoops()); assertNotNull(graph.getEdgeSupplier()); } @Test public void testGraphTypeBuilderFromGraph() { Graph graph = new Pseudograph<>(DefaultEdge.class); Graph graph1 = GraphTypeBuilder.forGraph(graph).buildGraph(); assertTrue(graph1.getType().isUndirected()); assertTrue(graph1.getType().isAllowingMultipleEdges()); assertTrue(graph1.getType().isAllowingSelfLoops()); assertNotNull(graph1.getEdgeSupplier()); assertEquals(graph.getEdgeSupplier(), graph1.getEdgeSupplier()); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/concurrent/000077500000000000000000000000001402514743400274035ustar00rootroot00000000000000AsSynchronizedGraphTest.java000066400000000000000000000376351402514743400347720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/graph/concurrent/* * (C) Copyright 2008-2021, by CHEN Kui and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.concurrent; import junit.extensions.*; import junit.framework.*; import junit.textui.*; import org.jgrapht.graph.*; import org.junit.Test; import java.util.*; import static org.junit.Assert.*; /** * Test class AsSynchronizedGraph. * * @author CHEN Kui */ public class AsSynchronizedGraphTest { private ArrayList vertices; private ArrayList edges; private AsSynchronizedGraph g; private Vector> ordersList; @Test public void testAddVertex() { g = new AsSynchronizedGraph.Builder() .build(new SimpleGraph<>(DefaultEdge.class)); ordersList = new Vector<>(); for (int i = 0; i < 20; i++) { ordersList.add(new ArrayList<>()); } for (int i = 0; i < 1000; i++) { int index = (int) (Math.random() * ordersList.size()); ordersList.get(index).add(new AddV(i)); } TestSuite ts = new ActiveTestSuite(); for (int i = 0; i < ordersList.size(); i++) ts.addTest(new TestThread("runAsThread")); TestRunner.run(ts); assertEquals(1000, g.vertexSet().size()); for (int i = 0; i < 1000; i++) { assertTrue(g.containsVertex(i)); } assertEquals(1000, iteratorCnt(g.vertexSet().iterator())); g.addVertex(1000); assertEquals(1001, g.vertexSet().size()); assertEquals(1001, iteratorCnt(g.vertexSet().iterator())); } @Test public void testAddEdge() { g = new AsSynchronizedGraph.Builder() .cacheEnable().build(new SimpleGraph<>(DefaultEdge.class)); ArrayList list = new ArrayList<>(); for (int i = 0; i < 1000; i++) g.addVertex(i); ordersList = new Vector<>(); for (int i = 0; i < 20; i++) { ordersList.add(new ArrayList<>()); } for (int i = 0; i < 1000; i++) { int index = (int) (Math.random() * ordersList.size()); DefaultEdge e = new DefaultEdge(); ordersList.get(index).add(new AddE(i, (i + 1) % 1000, e)); list.add(e); } TestSuite ts = new ActiveTestSuite(); for (int i = 0; i < ordersList.size(); i++) ts.addTest(new TestThread("runAsThread")); TestRunner.run(ts); assertEquals(1000, g.edgeSet().size()); for (int i = 0; i < 1000; i++) assertTrue(g.containsEdge(list.get(i))); assertEquals(1000, iteratorCnt(g.edgeSet().iterator())); assertEquals(2, g.edgesOf(3).size()); assertEquals(2, g.incomingEdgesOf(3).size()); assertEquals(2, g.outgoingEdgesOf(3).size()); g.addEdge(1, 3); assertEquals(1001, g.edgeSet().size()); assertEquals(1001, iteratorCnt(g.edgeSet().iterator())); assertEquals(3, g.edgesOf(3).size()); assertEquals(3, g.incomingEdgesOf(3).size()); assertEquals(3, g.outgoingEdgesOf(3).size()); } @Test public void testRemoveEdge() { g = new AsSynchronizedGraph.Builder() .cacheEnable().build(new SimpleGraph<>(DefaultEdge.class)); edges = new ArrayList<>(); TestSuite ts = new ActiveTestSuite(); for (int i = 0; i < 1000; i++) { g.addVertex(i); } for (int i = 0; i < 1000; i++) { DefaultEdge e = new DefaultEdge(); g.addEdge(i, (i + 1) % 1000, e); edges.add(e); } for (int i = 0; i < 5; i++) { ts.addTest(new TestThread("removeEdge")); } TestRunner.run(ts); assertEquals(400, g.edgeSet().size()); assertEquals(400, iteratorCnt(g.edgeSet().iterator())); g.removeEdge(edges.get(0)); g.removeEdge(edges.get(1)); assertEquals(398, g.edgeSet().size()); assertEquals(398, iteratorCnt(g.edgeSet().iterator())); } @Test public void testRemoveVertex() { g = new AsSynchronizedGraph.Builder() .cacheEnable().build(new DirectedPseudograph<>(DefaultEdge.class)); vertices = new ArrayList<>(); TestSuite ts = new ActiveTestSuite(); for (int i = 0; i < 100; i++) { g.addVertex(i); vertices.add(i); } for (int i = 0; i < 100; i++) for (int j = 0; j < 100; j++) g.addEdge(i, j); ts.addTest(new TestThread("removeVertex")); ts.addTest(new TestThread("removeVertex")); ts.addTest(new TestThread("removeVertex")); TestRunner.run(ts); assertEquals(10, g.vertexSet().size()); assertEquals(10, iteratorCnt(g.vertexSet().iterator())); assertEquals(100, g.edgeSet().size()); assertEquals(100, iteratorCnt(g.edgeSet().iterator())); assertEquals(10, g.incomingEdgesOf(vertices.get(0)).size()); assertEquals(10, g.outgoingEdgesOf(vertices.get(0)).size()); assertEquals(19, g.edgesOf(vertices.get(0)).size()); g.removeVertex(vertices.get(1)); assertEquals(9, g.vertexSet().size()); assertEquals(9, iteratorCnt(g.vertexSet().iterator())); assertEquals(81, g.edgeSet().size()); assertEquals(81, iteratorCnt(g.edgeSet().iterator())); assertEquals(9, g.incomingEdgesOf(vertices.get(0)).size()); assertEquals(9, g.outgoingEdgesOf(vertices.get(0)).size()); assertEquals(17, g.edgesOf(vertices.get(0)).size()); } @Test public void testOthers() { g = new AsSynchronizedGraph.Builder() .cacheDisable().build(new Pseudograph<>(DefaultEdge.class)); Set vertSet = g.vertexSet(); Set edgeSet = g.edgeSet(); g.addVertex(1); g.addVertex(2); assertEquals(2, vertSet.size()); assertEquals(2, iteratorCnt(vertSet.iterator())); g.addVertex(3); g.addVertex(4); assertEquals(4, vertSet.size()); assertEquals(4, iteratorCnt(vertSet.iterator())); assertEquals(0, edgeSet.size()); assertEquals(0, iteratorCnt(edgeSet.iterator())); g.addEdge(1, 2); assertEquals(1, g.edgesOf(2).size()); assertEquals(1, iteratorCnt(g.edgesOf(2).iterator())); assertEquals(1, g.outgoingEdgesOf(1).size()); assertEquals(1, g.incomingEdgesOf(2).size()); g.addEdge(2, 3); assertEquals(2, edgeSet.size()); assertEquals(2, iteratorCnt(edgeSet.iterator())); assertEquals(2, g.edgesOf(2).size()); assertEquals(2, iteratorCnt(g.edgesOf(2).iterator())); assertEquals(2, g.outgoingEdgesOf(2).size()); assertEquals(2, g.incomingEdgesOf(2).size()); assertFalse(g.isCacheEnabled()); g.setCache(true); assertTrue(g.isCacheEnabled()); g.addEdge(2, 4); assertEquals(3, g.edgesOf(2).size()); assertEquals(3, iteratorCnt(g.edgesOf(2).iterator())); assertEquals(3, g.outgoingEdgesOf(2).size()); assertEquals(3, g.incomingEdgesOf(2).size()); g.addEdge(2, 2); assertEquals(4, g.edgesOf(2).size()); assertEquals(4, iteratorCnt(g.edgesOf(2).iterator())); assertEquals(4, g.outgoingEdgesOf(2).size()); assertEquals(4, g.incomingEdgesOf(2).size()); g.removeVertex(3); assertEquals(3, vertSet.size()); assertEquals(3, iteratorCnt(vertSet.iterator())); assertEquals(3, edgeSet.size()); assertEquals(3, iteratorCnt(edgeSet.iterator())); assertEquals(3, g.edgeSet().size()); assertEquals(3, g.incomingEdgesOf(2).size()); assertEquals(3, g.outgoingEdgesOf(2).size()); } @Test public void testScenario() { g = new AsSynchronizedGraph<>(new SimpleGraph(DefaultEdge.class)); TestSuite ts = new ActiveTestSuite(); ArrayList order1 = new ArrayList<>(); ArrayList order2 = new ArrayList<>(); ArrayList order3 = new ArrayList<>(); ArrayList order4 = new ArrayList<>(); for (int i = 0; i < 10; i++) order1.add(new AddV(i)); createOrder(order1, 2, 9, true); // add 21 edges createOrder(order1, 4, 7, false); // rm 3 edges for (int i = 10; i < 20; i++) order2.add(new AddV(i)); createOrder(order2, 10, 20, true); // add 45 edges order2.add(new SetCache()); createOrder(order2, 14, 18, false); // rm 6 edges for (int i = 20; i < 30; i++) order3.add(new AddV(i)); order3.add(new SetCache()); createOrder(order3, 25, 30, true); // add 15 edges createOrder(order3, 25, 30, false); // rm 15 edges for (int i = 30; i < 60; i++) order4.add(new AddV(i)); createOrder(order4, 30, 60, true); // add 435 edges ordersList = new Vector<>(); ordersList.add(order1); ordersList.add(order2); ordersList.add(order3); ordersList.add(order4); for (int i = 0; i < ordersList.size(); i++) ts.addTest(new TestThread("runAsThread")); TestRunner.run(ts); assertFalse(g.isCacheEnabled()); assertEquals(60, g.vertexSet().size()); assertEquals(60, iteratorCnt(g.vertexSet().iterator())); for (int i = 0; i < 60; i++) assertTrue(g.containsVertex(i)); assertEquals(21 - 3 + 45 - 6 + 435, g.edgeSet().size()); assertEquals(21 - 3 + 45 - 6 + 435, iteratorCnt(g.edgeSet().iterator())); assertEquals(6, g.outgoingEdgesOf(2).size()); assertEquals(6, g.incomingEdgesOf(2).size()); assertEquals(6, g.edgesOf(2).size()); assertEquals(9, g.edgesOf(10).size()); new SetCache().execute(); order1.clear(); createOrder(order1, 1, 10, false); // rm 18 edges order2.clear(); createOrder(order2, 10, 20, false); // rm 39 edges order3.clear(); for (int i = 3; i < 15; i++) // rm 12 vertices, 0 edges order3.add(new RmV(i)); for (int i = 30; i < 40; i++) // rm 10 vertices order3.add(new RmV(i)); ts = new ActiveTestSuite(); ordersList.clear(); ordersList.add(order1); ordersList.add(order2); ordersList.add(order3); for (int i = 0; i < ordersList.size(); i++) ts.addTest(new TestThread("runAsThread")); TestRunner.run(ts); assertEquals(38, g.vertexSet().size()); assertEquals(190, g.edgeSet().size()); assertEquals(0, g.edgesOf(2).size()); assertEquals(19, g.edgesOf(41).size()); } @Test public void testCopyless() { g = new AsSynchronizedGraph.Builder() .setCopyless().build(new Pseudograph<>(DefaultEdge.class)); vertices = new ArrayList<>(); for (int i = 0; i < 1000; i++) { g.addVertex(i); vertices.add(i); } edges = new ArrayList<>(); for (int i = 0; i < 1000; i++) { DefaultEdge e = new DefaultEdge(); g.addEdge(i, (i + 1) % 1000, e); edges.add(e); } TestSuite ts = new ActiveTestSuite(); ts.addTest(new TestThread("verifyEdges")); ts.addTest(new TestThread("removeEdge")); TestRunner.run(ts); } private void createOrder(ArrayList list, int start, int end, boolean add) { for (int i = start; i < end - 1; i++) { for (int j = i + 1; j < end; j++) { if (add) list.add(new AddE(i, j, new DefaultEdge())); else list.add(new RmE(i, j)); } } } private interface Order { void execute(); } private class AddV implements Order { int vertex; public AddV(int v) { vertex = v; } @Override public void execute() { g.addVertex(vertex); } } private class AddE implements Order { DefaultEdge e; int s, t; public AddE(int s, int t, DefaultEdge e) { this.e = e; this.s = s; this.t = t; } @Override public void execute() { g.addEdge(s, t, e); } } private class SetCache implements Order { @Override public void execute() { synchronized (g) { g.setCache(!g.isCacheEnabled()); } } } private class RmV implements Order { int v; public RmV(int v) { this.v = v; } @Override public void execute() { g.removeVertex(v); } } private class RmE implements Order { int s, t; public RmE(int s, int t) { this.s = s; this.t = t; } @Override public void execute() { g.removeEdge(s, t); } } private int iteratorCnt(Iterator it) { int count = 0; while (it.hasNext()) { it.next(); count++; } return count; } public class TestThread extends TestCase { public TestThread(String s) { super(s); } public void addVertex() { while (true) { int id; synchronized (vertices) { if (vertices.size() != 0) id = vertices.remove(0); else return; } g.addVertex(id); } } public void removeEdge() { while (true) { DefaultEdge e; synchronized (edges) { if (edges.size() > 400) e = edges.remove(0); else return; } g.removeEdge(e); } } public void verifyEdges() { while (true) { int c; synchronized (vertices) { if (vertices.size() > 10) { c = vertices.remove(0); } else { return; } } g.getLock().readLock().lock(); try { for (DefaultEdge e : g.edgesOf(c)) { assertTrue(g.containsEdge(e)); } } finally { g.getLock().readLock().unlock(); } } } public void removeVertex() { while (true) { int c; synchronized (vertices) { if (vertices.size() > 10) c = vertices.remove(0); else return; } g.removeVertex(c); } } public void runAsThread() { List orders = ordersList.remove(0); for (Order o : orders) { o.execute(); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/interfaces/000077500000000000000000000000001402514743400262435ustar00rootroot00000000000000AStarAdmissibleHeuristicTest.java000066400000000000000000000073121402514743400345610ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/interfaces/* * (C) Copyright 2019-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.interfaces; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.graph.*; import org.junit.*; import static junit.framework.TestCase.assertTrue; /** * This class tests default implementation of the * {@link org.jgrapht.alg.interfaces.AStarAdmissibleHeuristic#isConsistent(Graph)} method. */ public class AStarAdmissibleHeuristicTest extends BaseHeuristicSearchTest { @Test(expected = IllegalArgumentException.class) public void testNullValue() { AStarAdmissibleHeuristic heuristic = (sourceVertex, targetVertex) -> 0; heuristic.isConsistent(null); } @Test public void testEmptyGraph() { Graph graph = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); AStarAdmissibleHeuristic heuristic = (sourceVertex, targetVertex) -> 0; assertTrue(heuristic.isConsistent(graph)); } @Test public void testZeroValueHeuristic() { Graph graph = getMultigraph(); AStarAdmissibleHeuristic heuristic = (sourceVertex, targetVertex) -> 0; assertTrue(heuristic.isConsistent(graph)); } @Test public void testEuclideanHeuristic() { AStarAdmissibleHeuristic heuristic = new EuclideanDistance(); this.readLabyrinth(labyrinth1); assertTrue(heuristic.isConsistent(graph)); this.readLabyrinth(labyrinth2); assertTrue(heuristic.isConsistent(graph)); Graph multigraph = getMultigraph(); assertTrue(heuristic.isConsistent(multigraph)); } @Test public void testManhattanHeuristic() { AStarAdmissibleHeuristic heuristic = new ManhattanDistance(); this.readLabyrinth(labyrinth1); assertTrue(heuristic.isConsistent(graph)); this.readLabyrinth(labyrinth2); assertTrue(heuristic.isConsistent(graph)); Graph multigraph = getMultigraph(); assertTrue(heuristic.isConsistent(multigraph)); } /** * Does not override {@link AStarAdmissibleHeuristic#isConsistent(Graph)} method. */ public static class ManhattanDistance implements AStarAdmissibleHeuristic { @Override public double getCostEstimate(Node sourceVertex, Node targetVertex) { return Math.abs(sourceVertex.x - targetVertex.x) + Math.abs(sourceVertex.y - targetVertex.y); } } /** * Does not override {@link AStarAdmissibleHeuristic#isConsistent(Graph)} method. */ public static class EuclideanDistance implements AStarAdmissibleHeuristic { @Override public double getCostEstimate(Node sourceVertex, Node targetVertex) { return Math .sqrt( Math.pow(sourceVertex.x - targetVertex.x, 2) + Math.pow(sourceVertex.y - targetVertex.y, 2)); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/000077500000000000000000000000001402514743400250545ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/clique/000077500000000000000000000000001402514743400263365ustar00rootroot00000000000000MaximalCliqueEnumerationPerformanceTest.java000066400000000000000000000076001402514743400371110ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/clique/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.clique; import org.jgrapht.*; import org.jgrapht.alg.clique.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.*; import org.openjdk.jmh.runner.options.*; import java.util.*; import java.util.concurrent.*; /** * A small benchmark comparing maximal clique enumeration algorithms. * * @author Dimitrios Michail */ public class MaximalCliqueEnumerationPerformanceTest { public static final int PERF_BENCHMARK_VERTICES_COUNT = 75; public static final double PERF_BENCHMARK_EDGES_PROP = 0.8; @State(Scope.Benchmark) private static abstract class RandomGraphBenchmarkBase { public static final long SEED = 13l; private GraphGenerator generator = null; private Graph graph; abstract Iterable> createSolver(Graph graph); @Setup(Level.Iteration) public void setup() { if (generator == null) { // lazily construct generator generator = new GnpRandomGraphGenerator<>( PERF_BENCHMARK_VERTICES_COUNT, PERF_BENCHMARK_EDGES_PROP, SEED, false); } graph = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(graph); } @Benchmark public void run() { Iterator> it = createSolver(graph).iterator(); while (it.hasNext()) { it.next(); } } } public static class BronKerboschRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override Iterable> createSolver(Graph graph) { return new BronKerboschCliqueFinder<>(graph); } } public static class PivotBronKerboschRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override Iterable> createSolver(Graph graph) { return new PivotBronKerboschCliqueFinder<>(graph); } } public static class DegeneracyBronKerboschRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override Iterable> createSolver(Graph graph) { return new DegeneracyBronKerboschCliqueFinder<>(graph); } } @Test public void testMaximalCliqueRandomGraphBenchmark() throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + BronKerboschRandomGraphBenchmark.class.getSimpleName() + ".*") .include(".*" + PivotBronKerboschRandomGraphBenchmark.class.getSimpleName() + ".*") .include(".*" + DegeneracyBronKerboschRandomGraphBenchmark.class.getSimpleName() + ".*") .mode(Mode.SingleShotTime).timeUnit(TimeUnit.MILLISECONDS).warmupIterations(5) .measurementIterations(10).forks(1).shouldFailOnError(true).shouldDoGC(true).build(); new Runner(opt).run(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/connectivity/000077500000000000000000000000001402514743400275725ustar00rootroot00000000000000TreeDynamicConnectivityPerformanceTest.java000066400000000000000000000112211402514743400402000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/connectivity/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.connectivity; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import org.openjdk.jmh.annotations.*; import java.util.*; import java.util.concurrent.*; /** * Performance test for {@link TreeDynamicConnectivity} * * @author Timofey Chudakov */ @Fork(value = 3, warmups = 0) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Warmup(iterations = 1, time = 1) @Measurement(iterations = 5, time = 1) public class TreeDynamicConnectivityPerformanceTest { private static final Random RANDOM = new Random(17L); @Benchmark public List testTreeDynamicConnectivity(Data data) { List res = new ArrayList<>(); for (int v1 : data.firstSet) { for (int v2 : data.secondSet) { data.connectivity.link(data.connectNode1, data.connectNode2); res.add(data.connectivity.connected(v1, v2)); data.connectivity.cut(data.connectNode1, data.connectNode2); } } return res; } @Benchmark public List testTreeNaiveConnectivity(Data data) { List res = new ArrayList<>(); for (int v1 : data.firstSet) { for (int v2 : data.secondSet) { DefaultEdge edge = data.forest.addEdge(data.connectNode1, data.connectNode2); res.add(isConnected(data.forest, v1, v2)); data.forest.removeEdge(edge); } } return res; } private boolean isConnected(Graph graph, int v1, int v2) { BreadthFirstIterator iterator = new BreadthFirstIterator<>(graph, v1); while (iterator.hasNext()) { if (iterator.next().equals(v2)) { return true; } } return false; } @State(Scope.Benchmark) public static class Data { @Param({ "10", "100", "1000", "10000", "100000", "1000000" }) public int treeSize; public Graph forest; public TreeDynamicConnectivity connectivity; public Set firstSet; public Set secondSet; int connectNode1; int connectNode2; @Setup(Level.Iteration) public void init() { BarabasiAlbertForestGenerator gen = new BarabasiAlbertForestGenerator<>(1, treeSize, RANDOM); forest = new DefaultUndirectedGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); Graph secondTree = new DefaultUndirectedGraph<>( SupplierUtil.createIntegerSupplier(treeSize), SupplierUtil.createDefaultEdgeSupplier(), false); connectivity = new TreeDynamicConnectivity<>(); gen.generateGraph(forest); gen.generateGraph(secondTree); Graphs.addGraph(forest, secondTree); for (int v : forest.vertexSet()) { connectivity.add(v); } for (DefaultEdge e : forest.edgeSet()) { int from = forest.getEdgeSource(e); int to = forest.getEdgeTarget(e); connectivity.link(to, from); } assert !connectivity.connected(0, treeSize); firstSet = gen(0, treeSize, 5); secondSet = gen(treeSize, 2 * treeSize, 5); connectNode1 = RANDOM.nextInt(treeSize); connectNode2 = RANDOM.nextInt(treeSize) + treeSize; } private Set gen(int from, int to, int num) { Set res = new HashSet<>(); int dist = to - from; while (res.size() < num) { res.add(RANDOM.nextInt(dist) + from); } return res; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/flow/000077500000000000000000000000001402514743400260235ustar00rootroot00000000000000MaximumFlowAlgorithmPerformanceTest.java000066400000000000000000000104331402514743400357460ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/flow/* * (C) Copyright 2015-2021, by Alexey Kudinkin and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.flow; import org.jgrapht.*; import org.jgrapht.alg.flow.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.*; import org.openjdk.jmh.runner.options.*; import java.util.*; import java.util.concurrent.*; public class MaximumFlowAlgorithmPerformanceTest { public static final int NUMBER_OF_GRAPHS = 20; public static final int PERF_BENCHMARK_VERTICES_COUNT = 1000; public static final int PERF_BENCHMARK_EDGES_COUNT = 100000; @State(Scope.Benchmark) private static abstract class RandomGraphBenchmarkBase { public static final long SEED = 1446523573696201013L; private List> graphs; abstract MaximumFlowAlgorithm createSolver( Graph network); @Setup public void setup() { graphs = new ArrayList<>(); GraphGenerator rgg = new GnmRandomGraphGenerator<>( PERF_BENCHMARK_VERTICES_COUNT, PERF_BENCHMARK_EDGES_COUNT, SEED); for (int i = 0; i < NUMBER_OF_GRAPHS; i++) { SimpleDirectedWeightedGraph network = new SimpleDirectedWeightedGraph<>( SupplierUtil.createIntegerSupplier(0), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); rgg.generateGraph(network); graphs.add(network); } } @Benchmark public void run() { for (Graph g : graphs) { createSolver(g).getMaximumFlow(0, g.vertexSet().size() - 1); } } } public static class EdmondsKarpMaximumFlowRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override MaximumFlowAlgorithm createSolver( Graph network) { return new EdmondsKarpMFImpl<>(network); } } public static class PushRelabelMaximumFlowRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override MaximumFlowAlgorithm createSolver( Graph network) { return new PushRelabelMFImpl<>(network); } } public static class DinicMaximumFlowRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override MaximumFlowAlgorithm createSolver( Graph network) { return new DinicMFImpl<>(network); } } @Test public void testRandomGraphBenchmark() throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + EdmondsKarpMaximumFlowRandomGraphBenchmark.class.getSimpleName() + ".*") .include(".*" + PushRelabelMaximumFlowRandomGraphBenchmark.class.getSimpleName() + ".*") .include(".*" + DinicMaximumFlowRandomGraphBenchmark.class.getSimpleName() + ".*") .mode(Mode.AverageTime).timeUnit(TimeUnit.NANOSECONDS).warmupTime(TimeValue.seconds(1)) .warmupIterations(3).measurementTime(TimeValue.seconds(1)).measurementIterations(5) .forks(1).shouldFailOnError(true).shouldDoGC(true).build(); new Runner(opt).run(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/graph/000077500000000000000000000000001402514743400261555ustar00rootroot00000000000000DirectedAcyclicGraphPerformanceTest.java000066400000000000000000000162111402514743400357610ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/graph/* * (C) Copyright 2008-2021, by Peter Giles and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.graph; import org.jgrapht.graph.*; import org.jgrapht.graph.DirectedAcyclicGraphTest.*; import org.jgrapht.util.*; import org.junit.*; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.*; import org.openjdk.jmh.runner.options.*; import java.util.concurrent.*; import java.util.function.*; /** * A small benchmark comparing the different dag implementations. * * @author Peter Giles * @author Dimitrios Michail */ public class DirectedAcyclicGraphPerformanceTest { @State(Scope.Benchmark) private static abstract class RandomGraphBenchmarkBase { abstract DirectedAcyclicGraph createDAG(); @Setup(Level.Iteration) public void setup() { } @Benchmark public void run() { int trialsPerConfiguration = 10; int maxVertices = 1024; int maxConnectednessFactor = 4; for (int numVertices = 64; numVertices <= maxVertices; numVertices *= 2) { for (int connectednessFactor = 1; (connectednessFactor <= maxConnectednessFactor) && (connectednessFactor < (numVertices - 1)); connectednessFactor *= 2) { for (int seed = 0; seed < trialsPerConfiguration; seed++) { // test with random // graph // configurations RepeatableRandomGraphGenerator gen = new RepeatableRandomGraphGenerator<>( numVertices, numVertices * connectednessFactor, seed); DirectedAcyclicGraph dag = createDAG(); gen.generateGraph(dag); } } } } } public static class ArrayDAGRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override DirectedAcyclicGraph createDAG() { return new ArrayDAG<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER); } } public static class ArrayListDAGRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override DirectedAcyclicGraph createDAG() { return new ArrayListDAG<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER); } } public static class HashSetDAGRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override DirectedAcyclicGraph createDAG() { return new HashSetDAG<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER); } } public static class BitSetDAGRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override DirectedAcyclicGraph createDAG() { return new BitSetDAG<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER); } } @Test public void testDirectedAcyclicGraphRandomGraphBenchmark() throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + ArrayDAGRandomGraphBenchmark.class.getSimpleName() + ".*") .include(".*" + ArrayListDAGRandomGraphBenchmark.class.getSimpleName() + ".*") .include(".*" + HashSetDAGRandomGraphBenchmark.class.getSimpleName() + ".*") .include(".*" + BitSetDAGRandomGraphBenchmark.class.getSimpleName() + ".*") .mode(Mode.SingleShotTime).timeUnit(TimeUnit.MILLISECONDS).warmupIterations(5) .measurementIterations(10).forks(1).shouldFailOnError(true).shouldDoGC(true).build(); new Runner(opt).run(); } /** * A DAG using the array visited strategy */ private static class ArrayDAG extends DirectedAcyclicGraph { private static final long serialVersionUID = 1L; /** * Construct a directed acyclic graph. * * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public ArrayDAG(Supplier vertexSupplier, Supplier edgeSupplier) { super( vertexSupplier, edgeSupplier, new VisitedArrayImpl(), new TopoVertexBiMap<>(), false); } } /** * A DAG using the array list visited strategy */ private static class ArrayListDAG extends DirectedAcyclicGraph { private static final long serialVersionUID = 1L; /** * Construct a directed acyclic graph. * * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public ArrayListDAG(Supplier vertexSupplier, Supplier edgeSupplier) { super( vertexSupplier, edgeSupplier, new VisitedArrayListImpl(), new TopoVertexBiMap<>(), false); } } /** * A DAG using the hash set visited strategy */ private static class HashSetDAG extends DirectedAcyclicGraph { private static final long serialVersionUID = 1L; /** * Construct a directed acyclic graph. * * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public HashSetDAG(Supplier vertexSupplier, Supplier edgeSupplier) { super( vertexSupplier, edgeSupplier, new VisitedHashSetImpl(), new TopoVertexBiMap<>(), false); } } /** * A DAG using the bitset visited strategy */ private static class BitSetDAG extends DirectedAcyclicGraph { private static final long serialVersionUID = 1L; /** * Construct a directed acyclic graph. * * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public BitSetDAG(Supplier vertexSupplier, Supplier edgeSupplier) { super( vertexSupplier, edgeSupplier, new VisitedBitSetImpl(), new TopoVertexBiMap<>(), false); } } } DirectedAcyclicGraphVSStaticGraphPerformanceTest.java000066400000000000000000000155161402514743400403730ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/graph/* * (C) Copyright 2008-2021, by Peter Giles and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.graph; import org.jgrapht.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.graph.*; import org.jgrapht.graph.DirectedAcyclicGraphTest.*; import org.jgrapht.util.*; import org.junit.*; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.*; import org.openjdk.jmh.runner.options.*; import java.util.concurrent.*; /** * A somewhat frivolous test of the performance difference between doing a full cycle detection * (non-dynamic algorithm) for each edge added versus the dynamic algorithm used by * DirectedAcyclicGraph. * * @author Peter Giles * @author Dimitrios Michail */ public class DirectedAcyclicGraphVSStaticGraphPerformanceTest { @State(Scope.Benchmark) public static class DynamicCycleDetectorRandomGraphBenchmark { @Setup(Level.Iteration) public void setup() { } @Benchmark public void run() { int trialsPerConfiguration = 10; int maxVertices = 1024; int maxConnectednessFactor = 4; for (int numVertices = 1024; numVertices <= maxVertices; numVertices *= 2) { for (int connectednessFactor = 1; (connectednessFactor <= maxConnectednessFactor) && (connectednessFactor < (numVertices - 1)); connectednessFactor *= 2) { for (int seed = 0; seed < trialsPerConfiguration; seed++) { // test with random // graph // configurations Graph sourceGraph = new SimpleDirectedGraph<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); RepeatableRandomGraphGenerator gen = new RepeatableRandomGraphGenerator<>( numVertices, numVertices * connectednessFactor, seed); gen.generateGraph(sourceGraph); DirectedAcyclicGraph dag = new DirectedAcyclicGraph<>(DefaultEdge.class); for (Long vertex : sourceGraph.vertexSet()) { dag.addVertex(vertex); } for (DefaultEdge edge : sourceGraph.edgeSet()) { Long edgeSource = sourceGraph.getEdgeSource(edge); Long edgeTarget = sourceGraph.getEdgeTarget(edge); try { dag.addEdge(edgeSource, edgeTarget); } catch (IllegalArgumentException doNothing) { } } } } } } } @State(Scope.Benchmark) public static class StaticGraphWithCycleDetectorRandomGraphBenchmark { @Setup(Level.Iteration) public void setup() { } @Benchmark public void run() { int trialsPerConfiguration = 10; int maxVertices = 1024; int maxConnectednessFactor = 4; for (int numVertices = 1024; numVertices <= maxVertices; numVertices *= 2) { for (int connectednessFactor = 1; (connectednessFactor <= maxConnectednessFactor) && (connectednessFactor < (numVertices - 1)); connectednessFactor *= 2) { for (int seed = 0; seed < trialsPerConfiguration; seed++) { // test with random // graph // configurations Graph sourceGraph = new SimpleDirectedGraph<>( SupplierUtil.createLongSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); RepeatableRandomGraphGenerator gen = new RepeatableRandomGraphGenerator<>( numVertices, numVertices * connectednessFactor, seed); gen.generateGraph(sourceGraph); SimpleDirectedGraph compareGraph = new SimpleDirectedGraph<>(DefaultEdge.class); for (Long vertex : sourceGraph.vertexSet()) { compareGraph.addVertex(vertex); } for (DefaultEdge edge : sourceGraph.edgeSet()) { Long edgeSource = sourceGraph.getEdgeSource(edge); Long edgeTarget = sourceGraph.getEdgeTarget(edge); DefaultEdge compareEdge = compareGraph.addEdge(edgeSource, edgeTarget); CycleDetector cycleDetector = new CycleDetector<>(compareGraph); boolean cycleDetected = cycleDetector.detectCycles(); if (cycleDetected) { // remove the edge from the compareGraph compareGraph.removeEdge(compareEdge); } } } } } } } @Test public void testDirectedAcyclicGraphVSStaticGraphRandomGraphBenchmark() throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + DynamicCycleDetectorRandomGraphBenchmark.class.getSimpleName() + ".*") .include( ".*" + StaticGraphWithCycleDetectorRandomGraphBenchmark.class.getSimpleName() + ".*") .mode(Mode.SingleShotTime).timeUnit(TimeUnit.MILLISECONDS).warmupIterations(5) .measurementIterations(10).forks(1).shouldFailOnError(true).shouldDoGC(true).build(); new Runner(opt).run(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/graph/GraphPerformanceTest.java000066400000000000000000000210171402514743400331040ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.graph; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.flow.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.*; import org.openjdk.jmh.runner.*; import org.openjdk.jmh.runner.options.*; import java.util.*; import java.util.concurrent.*; import java.util.function.*; /** * Benchmark class to compare different graph implementations. The benchmark creates a graph, runs * various algorithms on the graph and finally destroys (part of) the graph. This is an attempt to * simulate common usage of the graph. * * Note: Currently the tests are performed on a single graph. It would be better to run it on * multiple graphs. Not sure how to achieve that through the JMH framework. */ public class GraphPerformanceTest { public static final int PERF_BENCHMARK_VERTICES_COUNT = 1000; public static final int PERF_BENCHMARK_EDGES_COUNT = 100000; public static final long SEED = 1446523573696201013l; public static final int NR_GRAPHS = 5; // Number of unique graphs on which the tests are // repeated @State(Scope.Benchmark) private static abstract class DirectedGraphBenchmarkBase { private Blackhole blackhole; protected GnmRandomGraphGenerator rgg; private SimpleDirectedWeightedGraph graph; /** * Creates a random graph using the Random Graph Generator * * @return random graph */ abstract SimpleDirectedWeightedGraph constructGraph(); @Setup public void setup() { blackhole = new Blackhole( "Today's password is swordfish. I understand instantiating Blackholes directly is dangerous."); } /** * Benchmark 1: graph construction */ @Benchmark public void generateGraphBenchmark() { for (int i = 0; i < NR_GRAPHS; i++) { rgg = new GnmRandomGraphGenerator<>( PERF_BENCHMARK_VERTICES_COUNT, PERF_BENCHMARK_EDGES_COUNT, SEED + i); // Create a graph graph = constructGraph(); } } /** * Benchmark 2: Simulate graph usage: Create a graph, perform various algorithms, partially * destroy graph */ @Benchmark public void graphPerformanceBenchmark() { for (int i = 0; i < NR_GRAPHS; i++) { rgg = new GnmRandomGraphGenerator<>( PERF_BENCHMARK_VERTICES_COUNT, PERF_BENCHMARK_EDGES_COUNT, SEED + i); // Create a graph graph = constructGraph(); Integer[] vertices = graph.vertexSet().toArray(new Integer[graph.vertexSet().size()]); Integer source = vertices[0]; Integer sink = vertices[vertices.length - 1]; // Run various algorithms on the graph double length = this.calculateShorestPath(graph, source, sink); blackhole.consume(length); double maxFlow = this.calculateMaxFlow(graph, source, sink); blackhole.consume(maxFlow); boolean isStronglyConnected = this.isStronglyConnected(graph); blackhole.consume(isStronglyConnected); // Destroy some random edges in the graph destroyRandomEdges(graph); } } private double calculateShorestPath( SimpleDirectedWeightedGraph graph, Integer source, Integer sink) { DijkstraShortestPath shortestPathAlg = new DijkstraShortestPath<>(graph); return shortestPathAlg.getPath(source, sink).getWeight(); } private double calculateMaxFlow( SimpleDirectedWeightedGraph graph, Integer source, Integer sink) { EdmondsKarpMFImpl maximumFlowAlg = new EdmondsKarpMFImpl<>(graph); return maximumFlowAlg.getMaximumFlow(source, sink).getValue(); } private boolean isStronglyConnected( SimpleDirectedWeightedGraph graph) { StrongConnectivityAlgorithm strongConnectivityAlg = new GabowStrongConnectivityInspector<>(graph); return strongConnectivityAlg.isStronglyConnected(); } private void destroyRandomEdges( SimpleDirectedWeightedGraph graph) { int nrVertices = graph.vertexSet().size(); Random rand = new Random(SEED); for (int i = 0; i < PERF_BENCHMARK_EDGES_COUNT / 2; i++) { int u = rand.nextInt(nrVertices); int v = rand.nextInt(nrVertices); graph.removeEdge(u, v); } } } /** * Graph class which relies on the (legacy) DirectedSpecifics implementation. This class is * optimized for low memory usage, but performs edge retrieval operations fairly slow. */ public static class MemoryEfficientDirectedGraphBenchmark extends DirectedGraphBenchmarkBase { @Override SimpleDirectedWeightedGraph constructGraph() { SimpleDirectedWeightedGraph graph = new MemoryEfficientDirectedWeightedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); rgg.generateGraph(graph); return graph; } } /** * Graph class which relies on the FastLookupDirectedSpecifics. This class is optimized to * perform quick edge retrievals. */ public static class FastLookupDirectedGraphBenchmark extends DirectedGraphBenchmarkBase { @Override SimpleDirectedWeightedGraph constructGraph() { SimpleDirectedWeightedGraph graph = new SimpleDirectedWeightedGraph<>( SupplierUtil.createIntegerSupplier(1), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); rgg.generateGraph(graph); return graph; } } @Test public void testRandomGraphBenchmark() throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + MemoryEfficientDirectedGraphBenchmark.class.getSimpleName() + ".*") .include(".*" + FastLookupDirectedGraphBenchmark.class.getSimpleName() + ".*") .mode(Mode.AverageTime).timeUnit(TimeUnit.MILLISECONDS) // .warmupTime(TimeValue.seconds(1)) .warmupIterations(3) // .measurementTime(TimeValue.seconds(1)) .measurementIterations(5).forks(1).shouldFailOnError(true).shouldDoGC(true).build(); new Runner(opt).run(); } /** * Creates an memory efficient graph implementation. * * @param the graph vertex type * @param the graph edge type */ public static class MemoryEfficientDirectedWeightedGraph extends SimpleDirectedWeightedGraph { private static final long serialVersionUID = -1826738982402033648L; public MemoryEfficientDirectedWeightedGraph( Supplier vertexSupplier, Supplier edgeSupplier) { super(vertexSupplier, edgeSupplier); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/lca/000077500000000000000000000000001402514743400256135ustar00rootroot00000000000000LowestCommonAncestorAlgorithmPerformanceTest.java000066400000000000000000000204741402514743400374240ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/lca/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.lca; import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.lca.*; import org.jgrapht.alg.util.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.*; import org.openjdk.jmh.runner.options.*; import java.util.*; import java.util.concurrent.*; import java.util.stream.*; public class LowestCommonAncestorAlgorithmPerformanceTest { public static final int PERF_BENCHMARK_VERTICES_COUNT = 100_000; public static final int PERF_BENCHMARK_QUERIES_COUNT = 300_000; @State(Scope.Benchmark) private static abstract class RandomTreeBenchmarkBase { public static final long SEED = 111222111; private LowestCommonAncestorAlgorithm solver; private List> queries; abstract LowestCommonAncestorAlgorithm createSolver( Graph tree, Integer root); @Setup public void setup() { Random random = new Random(SEED); Graph tree = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(0), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>(1, PERF_BENCHMARK_VERTICES_COUNT, random); generator.generateGraph(tree, null); solver = createSolver(tree, tree.vertexSet().iterator().next()); queries = LCATreeTestBase .generateQueries( PERF_BENCHMARK_QUERIES_COUNT, new ArrayList<>(tree.vertexSet()), random); } @Benchmark public void run() { solver.getBatchLCA(queries); } } @State(Scope.Benchmark) private static abstract class RandomForestBenchmarkBase { public static final int NUMBER_TREES = 10 * PERF_BENCHMARK_VERTICES_COUNT / 100; // 10% public static final long SEED = 111222111; private LowestCommonAncestorAlgorithm solver; private List> queries; abstract LowestCommonAncestorAlgorithm createSolver( Graph tree, Set roots); @Setup public void setup() { Random random = new Random(SEED); Graph forest = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(0), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); BarabasiAlbertForestGenerator generator = new BarabasiAlbertForestGenerator<>( NUMBER_TREES, PERF_BENCHMARK_VERTICES_COUNT, random); generator.generateGraph(forest, null); Set roots = new ConnectivityInspector<>(forest) .connectedSets().stream().map(x -> x.iterator().next()).collect(Collectors.toSet()); assert roots.size() == NUMBER_TREES; solver = createSolver(forest, roots); queries = LCATreeTestBase .generateQueries( PERF_BENCHMARK_QUERIES_COUNT, new ArrayList<>(forest.vertexSet()), random); } @Benchmark public void run() { solver.getBatchLCA(queries); } } public static class BinaryLiftingLCARandomTreeBenchmark extends RandomTreeBenchmarkBase { @Override LowestCommonAncestorAlgorithm createSolver( Graph tree, Integer root) { return new BinaryLiftingLCAFinder<>(tree, root); } } public static class EulerTourRMQLCARandomTreeBenchmark extends RandomTreeBenchmarkBase { @Override LowestCommonAncestorAlgorithm createSolver( Graph tree, Integer root) { return new EulerTourRMQLCAFinder<>(tree, root); } } public static class TarjanLCARandomTreeBenchmark extends RandomTreeBenchmarkBase { @Override LowestCommonAncestorAlgorithm createSolver( Graph tree, Integer root) { return new TarjanLCAFinder<>(tree, root); } } public static class HeavyPathRandomTreeBenchmark extends RandomTreeBenchmarkBase { @Override LowestCommonAncestorAlgorithm createSolver( Graph tree, Integer root) { return new HeavyPathLCAFinder<>(tree, root); } } public static class BinaryLiftingLCARandomForestBenchmark extends RandomForestBenchmarkBase { @Override LowestCommonAncestorAlgorithm createSolver( Graph tree, Set roots) { return new BinaryLiftingLCAFinder<>(tree, roots); } } public static class EulerTourRMQLCARandomForestBenchmark extends RandomForestBenchmarkBase { @Override LowestCommonAncestorAlgorithm createSolver( Graph tree, Set roots) { return new EulerTourRMQLCAFinder<>(tree, roots); } } public static class TarjanLCARandomForestBenchmark extends RandomForestBenchmarkBase { @Override LowestCommonAncestorAlgorithm createSolver( Graph tree, Set roots) { return new TarjanLCAFinder<>(tree, roots); } } public static class HeavyPathRandomForestBenchmark extends RandomForestBenchmarkBase { @Override LowestCommonAncestorAlgorithm createSolver( Graph tree, Set roots) { return new HeavyPathLCAFinder<>(tree, roots); } } @Test public void testRandomTreeBenchmark() throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + BinaryLiftingLCARandomTreeBenchmark.class.getSimpleName() + ".*") .include(".*" + EulerTourRMQLCARandomTreeBenchmark.class.getSimpleName() + ".*") .include(".*" + TarjanLCARandomTreeBenchmark.class.getSimpleName() + ".*") .include(".*" + HeavyPathRandomTreeBenchmark.class.getSimpleName() + ".*") .mode(Mode.AverageTime).timeUnit(TimeUnit.NANOSECONDS).warmupTime(TimeValue.seconds(1)) .warmupIterations(3).measurementTime(TimeValue.seconds(1)).measurementIterations(5) .forks(1).shouldFailOnError(true).shouldDoGC(true).build(); new Runner(opt).run(); } @Test public void testRandomForestBenchmark() throws RunnerException { Options opt = new OptionsBuilder() .include(".*" + BinaryLiftingLCARandomForestBenchmark.class.getSimpleName() + ".*") .include(".*" + EulerTourRMQLCARandomForestBenchmark.class.getSimpleName() + ".*") .include(".*" + TarjanLCARandomForestBenchmark.class.getSimpleName() + ".*") .include(".*" + HeavyPathRandomForestBenchmark.class.getSimpleName() + ".*") .mode(Mode.AverageTime).timeUnit(TimeUnit.NANOSECONDS).warmupTime(TimeValue.seconds(1)) .warmupIterations(3).measurementTime(TimeValue.seconds(1)).measurementIterations(5) .forks(1).shouldFailOnError(true).shouldDoGC(true).build(); new Runner(opt).run(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/matching/000077500000000000000000000000001402514743400266465ustar00rootroot00000000000000MaximumCardinalityBipartiteMatchingPerformanceTest.java000066400000000000000000000111441402514743400415750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/matching/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.matching.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.*; import org.openjdk.jmh.runner.options.*; import java.util.*; import java.util.concurrent.*; /** * A small benchmark comparing matching algorithms for bipartite graphs. * * @author Joris Kinable */ public class MaximumCardinalityBipartiteMatchingPerformanceTest { public static final int PERF_BENCHMARK_VERTICES_COUNT = 2000; public static final double PERF_BENCHMARK_EDGES_PROP = 0.7; @State(Scope.Benchmark) private static abstract class RandomGraphBenchmarkBase { public static final long SEED = 13l; private GnpRandomBipartiteGraphGenerator generator = null; private Graph graph; private Set firstPartition; private Set secondPartition; abstract MatchingAlgorithm createSolver( Graph graph, Set firstPartition, Set secondPartition); @Setup(Level.Iteration) public void setup() { if (generator == null) { // lazily construct generator generator = new GnpRandomBipartiteGraphGenerator<>( PERF_BENCHMARK_VERTICES_COUNT, PERF_BENCHMARK_VERTICES_COUNT / 2, PERF_BENCHMARK_EDGES_PROP, SEED); } graph = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(graph); firstPartition = generator.getFirstPartition(); secondPartition = generator.getSecondPartition(); } @Benchmark public void run() { long time = System.currentTimeMillis(); MatchingAlgorithm.Matching m = createSolver(graph, firstPartition, secondPartition).getMatching(); time = System.currentTimeMillis() - time; System.out .println( "time: " + time + " obj :" + m.getEdges().size() + " vertices: " + graph.vertexSet().size() + " edges: " + graph.edgeSet().size()); } } public static class EdmondsMaxCardinalityBipartiteMatchingBenchmark extends RandomGraphBenchmarkBase { @Override MatchingAlgorithm createSolver( Graph graph, Set firstPartition, Set secondPartition) { return new SparseEdmondsMaximumCardinalityMatching<>(graph); } } public static class HopcroftKarpMaximumCardinalityBipartiteMatchingBenchmark extends RandomGraphBenchmarkBase { @Override MatchingAlgorithm createSolver( Graph graph, Set firstPartition, Set secondPartition) { return new HopcroftKarpMaximumCardinalityBipartiteMatching<>( graph, firstPartition, secondPartition); } } @Test public void testRandomGraphBenchmark() throws RunnerException { Options opt = new OptionsBuilder() .include( ".*" + EdmondsMaxCardinalityBipartiteMatchingBenchmark.class.getSimpleName() + ".*") .include( ".*" + HopcroftKarpMaximumCardinalityBipartiteMatchingBenchmark.class .getSimpleName() + ".*") .mode(Mode.SingleShotTime).timeUnit(TimeUnit.MILLISECONDS).warmupIterations(5) .measurementIterations(10).forks(1).shouldFailOnError(true).shouldDoGC(true).build(); new Runner(opt).run(); } } PathGrowingWeightedMatchingPerformanceTest.java000066400000000000000000000110361402514743400400420ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/matching/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.matching; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.matching.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.runner.*; import org.openjdk.jmh.runner.options.*; import java.util.concurrent.*; /** * A small benchmark comparing matching algorithms. * * @author Dimitrios Michail */ public class PathGrowingWeightedMatchingPerformanceTest { public static final int PERF_BENCHMARK_VERTICES_COUNT = 1000; public static final double PERF_BENCHMARK_EDGES_PROP = 0.8; @State(Scope.Benchmark) private static abstract class RandomGraphBenchmarkBase { public static final long SEED = 13l; private GraphGenerator generator = null; private Graph graph; abstract MatchingAlgorithm createSolver( Graph graph); @Setup(Level.Iteration) public void setup() { if (generator == null) { // lazily construct generator generator = new GnpRandomGraphGenerator<>( PERF_BENCHMARK_VERTICES_COUNT, PERF_BENCHMARK_EDGES_PROP, SEED, false); } graph = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); generator.generateGraph(graph); } @Benchmark public void run() { createSolver(graph).getMatching(); } } public static class PathGrowingWeightedMatchingRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override MatchingAlgorithm createSolver(Graph graph) { return new PathGrowingWeightedMatching<>(graph); } } public static class PathGrowingWeightedMatchingNoHeuristicsRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override MatchingAlgorithm createSolver(Graph graph) { final boolean useHeuristics = false; return new PathGrowingWeightedMatching<>(graph, useHeuristics); } } public static class GreedyWeightedMatchingRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override MatchingAlgorithm createSolver(Graph graph) { return new GreedyWeightedMatching<>(graph, false); } } public static class EdmondsMaximumCardinalityMatchingRandomGraphBenchmark extends RandomGraphBenchmarkBase { @Override MatchingAlgorithm createSolver(Graph graph) { return new SparseEdmondsMaximumCardinalityMatching<>(graph); } } @Test public void testPathGrowingRandomGraphBenchmark() throws RunnerException { Options opt = new OptionsBuilder() .include( ".*" + PathGrowingWeightedMatchingRandomGraphBenchmark.class.getSimpleName() + ".*") .include( ".*" + PathGrowingWeightedMatchingNoHeuristicsRandomGraphBenchmark.class .getSimpleName() + ".*") .include(".*" + GreedyWeightedMatchingRandomGraphBenchmark.class.getSimpleName() + ".*") .include( ".*" + EdmondsMaximumCardinalityMatchingRandomGraphBenchmark.class.getSimpleName() + ".*") .mode(Mode.SingleShotTime).timeUnit(TimeUnit.MILLISECONDS).warmupIterations(5) .measurementIterations(10).forks(1).shouldFailOnError(true).shouldDoGC(true).build(); new Runner(opt).run(); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/matching/blossom/000077500000000000000000000000001402514743400303245ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/matching/blossom/v5/000077500000000000000000000000001402514743400306565ustar00rootroot00000000000000KolmogorovMinimumWeightPerfectMatchingPerformanceTest.java000066400000000000000000000044101402514743400443110ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/matching/blossom/v5/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.matching.blossom.v5; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.matching.blossom.v5.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.openjdk.jmh.annotations.*; import java.util.concurrent.*; @Fork(value = 5, warmups = 0) @BenchmarkMode(Mode.SampleTime) @OutputTimeUnit(TimeUnit.MILLISECONDS) @Warmup(iterations = 2, time = 5) @Measurement(iterations = 10, time = 8) public class KolmogorovMinimumWeightPerfectMatchingPerformanceTest { @Benchmark public MatchingAlgorithm.Matching testBlossomV(Data data) { KolmogorovWeightedPerfectMatching matching = new KolmogorovWeightedPerfectMatching<>(data.graph, data.options[data.optionNum]); return matching.getMatching(); } @State(Scope.Benchmark) public static class Data { public BlossomVOptions[] options = BlossomVOptions.ALL_OPTIONS; @Param({ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23" }) public int optionNum; @Param({ "300", "500" }) public int graphSize; Graph graph; @Setup(Level.Iteration) public void generate() { this.graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); CompleteGraphGenerator generator = new CompleteGraphGenerator<>(graphSize); generator.generateGraph(graph); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/000077500000000000000000000000001402514743400276045ustar00rootroot00000000000000DeltaSteppingShortestPathPerformance.java000066400000000000000000000234651402514743400377000ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/* * (C) Copyright 2018-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.openjdk.jmh.annotations.*; import java.util.concurrent.*; /** * A benchmark comparing {@link DeltaSteppingShortestPath} to * {@link org.jgrapht.alg.shortestpath.DijkstraShortestPath} and * {@link org.jgrapht.alg.shortestpath.BellmanFordShortestPath}. The benchmark test the algorithms * on random, dense and sparse graphs. * * @author Semen Chudakov */ @BenchmarkMode(Mode.AverageTime) @Fork(value = 1, warmups = 0) @Warmup(iterations = 3, time = 10) @Measurement(iterations = 8, time = 10) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class DeltaSteppingShortestPathPerformance { @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDeltaSteppingGnm(GnmState data) { return new DeltaSteppingShortestPath<>(data.graph, 1.0 / data.edgeDegree, data.executor) .getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDijkstraGnm( GnmState data) { return new DijkstraShortestPath<>(data.graph).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testBellmanFordGnm( GnmState data) { return new BellmanFordShortestPath<>(data.graph).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDeltaSteppingGnp(GnpState data) { return new DeltaSteppingShortestPath<>( data.graph, 1.0 / (1 + (data.p * data.numOfVertices)), data.executor).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDijkstraGnp( GnpState data) { return new DijkstraShortestPath<>(data.graph).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testBellmanFordGnp( GnpState data) { return new BellmanFordShortestPath<>(data.graph).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDeltaSteppingBarabasiAlbert(BarabasiAlbertState data) { return new DeltaSteppingShortestPath<>(data.graph, 1.0 / data.m0, data.executor) .getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDijkstraBarabasiAlbert(BarabasiAlbertState data) { return new DijkstraShortestPath<>(data.graph).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testBellmanFordBarabasiAlbert(BarabasiAlbertState data) { return new BellmanFordShortestPath<>(data.graph).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDeltaSteppingWattsStogatz(WattsStogatzState data) { return new DeltaSteppingShortestPath<>(data.graph, 1.0 / data.k, data.executor).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDijkstraWattsStogatz(WattsStogatzState data) { return new DijkstraShortestPath<>(data.graph).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testBellmanFordWattsStogatz(WattsStogatzState data) { return new BellmanFordShortestPath<>(data.graph).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDeltaSteppingComplete(CompleteGraphState data) { return new DeltaSteppingShortestPath<>(data.graph, 1.0 / data.numOfVertices, data.executor) .getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testDijkstraComplete(CompleteGraphState data) { return new DijkstraShortestPath<>(data.graph).getPaths(0); } @Benchmark public ShortestPathAlgorithm.SingleSourcePaths testBellmanFordComplete(CompleteGraphState data) { return new BellmanFordShortestPath<>(data.graph).getPaths(0); } @State(Scope.Benchmark) public abstract static class BaseState { DefaultUndirectedWeightedGraph graph; public ThreadPoolExecutor executor; @Setup public void createExecutor() { executor = ConcurrencyUtil .createThreadPoolExecutor(Runtime.getRuntime().availableProcessors()); } @TearDown public void shutdownExecutor() throws InterruptedException { ConcurrencyUtil.shutdownExecutionService(executor); } public abstract void generateGraph(); void makeConnected(Graph graph) { Object[] vertices = graph.vertexSet().toArray(); for (int i = 0; i < vertices.length - 1; i++) { graph.addEdge((Integer) vertices[i], (Integer) vertices[i + 1]); } } void addEdgeWeights(Graph graph) { for (DefaultWeightedEdge edge : graph.edgeSet()) { graph.setEdgeWeight(edge, Math.random()); } } } @State(Scope.Benchmark) public static class GnmState extends BaseState { @Param({ "10000" }) int numOfVertices; @Param({ "50", "500" }) int edgeDegree; @Setup(Level.Trial) public void generateGraph() { graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); GraphGenerator generator = new GnmRandomGraphGenerator<>( numOfVertices, numOfVertices * edgeDegree - numOfVertices + 1); generator.generateGraph(graph); makeConnected(graph); addEdgeWeights(graph); } } @State(Scope.Benchmark) public static class GnpState extends BaseState { @Param({ "10000" }) int numOfVertices; @Param({ "0.01", "0.05" }) double p; @Setup(Level.Trial) public void generateGraph() { graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); GraphGenerator generator = new GnpRandomGraphGenerator<>(numOfVertices, p); generator.generateGraph(graph); makeConnected(graph); addEdgeWeights(graph); } } @State(Scope.Benchmark) public static class BarabasiAlbertState extends BaseState { @Param({ "1000" }) int m0; @Param({ "10000" }) int numOfVertices; @Param({ "50", "500" }) int m; @Setup(Level.Trial) public void generateGraph() { graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); GraphGenerator generator = new BarabasiAlbertGraphGenerator<>(m0, m, numOfVertices); generator.generateGraph(graph); makeConnected(graph); addEdgeWeights(graph); } } @State(Scope.Benchmark) public static class WattsStogatzState extends BaseState { @Param({ "10000" }) int numOfVertices; @Param({ "100", "1000" }) int k; @Param({ "0.05", "0.5" }) double p; @Setup(Level.Trial) public void generateGraph() { graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); GraphGenerator generator = new WattsStrogatzGraphGenerator<>(numOfVertices, k, p); generator.generateGraph(graph); addEdgeWeights(graph); } } @State(Scope.Benchmark) public static class CompleteGraphState extends BaseState { @Param({ "1000", "2000", "3000" }) int numOfVertices; @Setup(Level.Trial) public void generateGraph() { graph = new DefaultUndirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); CompleteGraphGenerator generator = new CompleteGraphGenerator<>(numOfVertices); generator.generateGraph(graph); addEdgeWeights(graph); } } } DijkstraShortestPathPerformanceTest.java000066400000000000000000000264071402514743400375470ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.concurrent.*; import java.util.function.*; /** * A small benchmark comparing Dijkstra like algorithms. The benchmark creates a random graph and * computes all-pairs shortest paths. * * @author Dimitrios Michail */ public class DijkstraShortestPathPerformanceTest { private static final int PERF_BENCHMARK_VERTICES_COUNT = 250; private static final double PERF_BENCHMARK_EDGES_PROP = 0.3; private static final int WARMUP_REPEAT = 5; private static final int REPEAT = 10; private static final long SEED = 13l; private static abstract class BenchmarkBase { protected Random rng = new Random(SEED); protected GraphGenerator generator = null; protected Graph graph; abstract ShortestPathAlgorithm createSolver( Graph graph); public void setup() { if (generator == null) { // lazily construct generator generator = new GnpRandomGraphGenerator<>( PERF_BENCHMARK_VERTICES_COUNT, PERF_BENCHMARK_EDGES_PROP, rng, false); } this.graph = GraphTypeBuilder .directed().weighted(true).edgeClass(DefaultWeightedEdge.class) .vertexSupplier(SupplierUtil.createIntegerSupplier()).allowingMultipleEdges(true) .allowingSelfLoops(true).buildGraph(); generator.generateGraph(graph); for (DefaultWeightedEdge e : graph.edgeSet()) { graph.setEdgeWeight(e, rng.nextDouble()); } } public void run() { ShortestPathAlgorithm sp = createSolver(graph); for (Integer v : graph.vertexSet()) { for (Integer u : graph.vertexSet()) { sp.getPath(v, u); } } } } public static class DijkstraBenchmark extends BenchmarkBase { @Override ShortestPathAlgorithm createSolver( Graph graph) { return new DijkstraShortestPath<>(graph); } @Override public String toString() { return "Dijkstra"; } } public static class BFSShortestPathBenchmark extends BenchmarkBase { @Override ShortestPathAlgorithm createSolver( Graph graph) { return new BFSShortestPath<>(graph); } @Override public String toString() { return "BFSShortestPath"; } } public static class ClosestFirstIteratorBenchmark extends BenchmarkBase { @Override ShortestPathAlgorithm createSolver( Graph graph) { return new ShortestPathAlgorithm() { @Override public GraphPath getPath(Integer source, Integer sink) { /* * We do not really return a result here, just reach the target. */ ClosestFirstIterator iter = new ClosestFirstIterator<>(graph, source, Double.POSITIVE_INFINITY); while (iter.hasNext()) { Integer vertex = iter.next(); if (vertex.equals(sink)) { return null; } } return null; } @Override public double getPathWeight(Integer source, Integer sink) { GraphPath p = getPath(source, sink); if (p == null) { return Double.POSITIVE_INFINITY; } else { return p.getWeight(); } } public org.jgrapht.alg.interfaces.ShortestPathAlgorithm.SingleSourcePaths getPaths(Integer source) { throw new UnsupportedOperationException(); } }; } @Override public String toString() { return "Dijkstra with ClosestFirstIterator"; } } public static class BidirectionalDijkstraBenchmark extends BenchmarkBase { @Override ShortestPathAlgorithm createSolver( Graph graph) { return new BidirectionalDijkstraShortestPath<>(graph); } @Override public String toString() { return "Bidirectional Dijkstra"; } } public static class AStarNoHeuristicBenchmark extends BenchmarkBase { @Override ShortestPathAlgorithm createSolver( Graph graph) { return new AStarShortestPath<>(graph, (u, t) -> 0d); } @Override public String toString() { return "A* no heuristic"; } } public static class AStarALTBenchmark extends BenchmarkBase { private int totalLandmarks; AStarALTBenchmark(int totalLandmarks) { this.totalLandmarks = totalLandmarks; } @Override ShortestPathAlgorithm createSolver( Graph graph) { Integer[] vertices = graph.vertexSet().toArray(new Integer[0]); Set landmarks = new HashSet<>(); while (landmarks.size() < totalLandmarks) { landmarks.add(vertices[rng.nextInt(graph.vertexSet().size())]); } return new AStarShortestPath<>(graph, new ALTAdmissibleHeuristic<>(graph, landmarks)); } @Override public String toString() { return "A* with ALT heuristic (" + totalLandmarks + " random landmarks)"; } } public static class BidirectionalAStarNoHeuristicBenchmark extends BenchmarkBase { @Override ShortestPathAlgorithm createSolver( Graph graph) { return new BidirectionalAStarShortestPath<>(graph, (u, t) -> 0d); } @Override public String toString() { return "Bidirectional A* no heuristic"; } } public static class BidirectionalAStarALTBenchmark extends BenchmarkBase { private int totalLandmarks; BidirectionalAStarALTBenchmark(int totalLandmarks) { this.totalLandmarks = totalLandmarks; } @Override ShortestPathAlgorithm createSolver( Graph graph) { Integer[] vertices = graph.vertexSet().toArray(new Integer[0]); Set landmarks = new HashSet<>(); while (landmarks.size() < totalLandmarks) { landmarks.add(vertices[rng.nextInt(graph.vertexSet().size())]); } AStarAdmissibleHeuristic heuristic = new ALTAdmissibleHeuristic<>(graph, landmarks); return new BidirectionalAStarShortestPath<>(graph, heuristic); } @Override public String toString() { return "Bidirectional A* with ALT heuristic (" + totalLandmarks + " random landmarks)"; } } @Test public void testBenchmark() { System.out.println("All-Pairs Shortest Paths Benchmark"); System.out.println("---------"); System.out .println( "Using G(n,p) random graph with n = " + PERF_BENCHMARK_VERTICES_COUNT + ", p = " + PERF_BENCHMARK_EDGES_PROP); System.out.println("Warmup phase " + WARMUP_REPEAT + " executions"); System.out.println("Averaging results over " + REPEAT + " executions"); List> algFactory = new ArrayList<>(); algFactory.add(() -> new ClosestFirstIteratorBenchmark()); algFactory.add(() -> new DijkstraBenchmark()); algFactory.add(() -> new AStarNoHeuristicBenchmark()); algFactory.add(() -> new AStarALTBenchmark(1)); algFactory.add(() -> new AStarALTBenchmark(5)); algFactory.add(() -> new BidirectionalDijkstraBenchmark()); algFactory.add(() -> new BFSShortestPathBenchmark()); algFactory.add(() -> new BidirectionalAStarALTBenchmark(1)); algFactory.add(() -> new BidirectionalAStarALTBenchmark(5)); algFactory.add(() -> new BidirectionalAStarNoHeuristicBenchmark()); for (Supplier alg : algFactory) { System.gc(); StopWatch watch = new StopWatch(); BenchmarkBase benchmark = alg.get(); System.out.printf("%-50s :", benchmark.toString()); for (int i = 0; i < WARMUP_REPEAT; i++) { System.out.print("-"); benchmark.setup(); benchmark.run(); } double avgGraphCreate = 0d; double avgExecution = 0d; for (int i = 0; i < REPEAT; i++) { System.out.print("+"); watch.start(); benchmark.setup(); avgGraphCreate += watch.getElapsed(TimeUnit.MILLISECONDS); watch.start(); benchmark.run(); avgExecution += watch.getElapsed(TimeUnit.MILLISECONDS); } avgGraphCreate /= REPEAT; avgExecution /= REPEAT; System.out.print(" -> "); System.out .printf("setup %.3f (ms) | execution %.3f (ms)\n", avgGraphCreate, avgExecution); } } } KShortestPathsPerformance.java000066400000000000000000000147011402514743400355030ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/shortestpath/* * (C) Copyright 2018-2021, by Semen Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.shortestpath; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.alg.util.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.openjdk.jmh.annotations.*; import java.util.*; import java.util.concurrent.*; @BenchmarkMode(Mode.AverageTime) @Fork(value = 1, warmups = 0, jvmArgs = "--illegal-access=permit") @Warmup(iterations = 3, time = 10) @Measurement(iterations = 8, time = 10) @OutputTimeUnit(TimeUnit.MILLISECONDS) public class KShortestPathsPerformance { private static final Random RANDOM = new Random(19L); @Benchmark public List>> testYenKShortestPaths(YenState state) { return computeResult(new YenKShortestPath<>(state.graph, state.pathValidator), state); } @Benchmark public List>> testEppsteinKShortestPaths( RandomGraphState state) { return computeResult(new EppsteinKShortestPath<>(state.graph), state); } @Benchmark public List>> testBhandariKDisjointShortestPaths( RandomGraphState state) { return computeResult(new BhandariKDisjointShortestPaths<>(state.graph), state); } @Benchmark public List>> testSuurballeKDisjointShortestPaths( RandomGraphState state) { return computeResult(new SuurballeKDisjointShortestPaths<>(state.graph), state); } private List>> computeResult( KShortestPathAlgorithm algorithm, RandomGraphState state) { List>> result = new ArrayList<>(state.numberOfQueries); for (Pair query : state.queries) { int source = query.getFirst(); int target = query.getSecond(); result.add(algorithm.getPaths(source, target, state.k)); } return result; } @State(Scope.Benchmark) public static class RandomGraphState { @Param({ "100" }) int n; @Param({ "0.3", "0.5" }) double p; @Param({ "50" }) int k; @Param({ "20" }) int numberOfQueries; GraphGenerator generator; SimpleDirectedWeightedGraph graph; List> queries; @Setup(Level.Iteration) public void generateGraph() { generateGraphAndQueries(); } protected void generateGraphAndQueries() { generator = new GnpRandomGraphGenerator<>(n, p); graph = new SimpleDirectedWeightedGraph<>(DefaultWeightedEdge.class); graph.setVertexSupplier(SupplierUtil.createIntegerSupplier()); generator.generateGraph(graph); makeConnected(graph); addEdgeWeights(graph); queries = selectQueries(); } private List> selectQueries() { Set> result = new HashSet<>(numberOfQueries); Object[] vertices = graph.vertexSet().toArray(); while (result.size() < numberOfQueries) { int sourceIndex = (int) (Math.random() * vertices.length); int targetIndex = (int) (Math.random() * vertices.length); while (sourceIndex == targetIndex) { targetIndex = (int) (Math.random() * vertices.length); } Integer source = (Integer) vertices[sourceIndex]; Integer target = (Integer) vertices[targetIndex]; result.add(Pair.of(source, target)); } return new ArrayList<>(result); } private void makeConnected(Graph graph) { Object[] vertices = graph.vertexSet().toArray(); for (int i = 0; i < vertices.length - 1; i++) { graph.addEdge((Integer) vertices[i], (Integer) vertices[i + 1]); } } private void addEdgeWeights(Graph graph) { for (DefaultWeightedEdge edge : graph.edgeSet()) { double weight = Math.abs(RANDOM.nextInt(Integer.MAX_VALUE)); graph.setEdgeWeight(edge, weight); } } } public static class YenState extends RandomGraphState { @Param({ "true", "false" }) boolean createPathValidator; @Param({ "20" }) int numberOfRandomEdges; PathValidator pathValidator; @Override @Setup(Level.Iteration) public void generateGraph() { generateGraphAndQueries(); if (createPathValidator) { pathValidator = getPathValidator(); } else { pathValidator = null; } } private PathValidator getPathValidator() { Set randomEdges = getRandomEdges(); return (path, edge) -> !randomEdges.contains(edge); } private Set getRandomEdges() { Set result = CollectionUtil.newHashSetWithExpectedSize(numberOfRandomEdges); Object[] edges = graph.edgeSet().toArray(); while (result.size() != numberOfQueries) { int index = (int) (Math.random() * edges.length); result.add((DefaultWeightedEdge) edges[index]); } return result; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/spanning/000077500000000000000000000000001402514743400266715ustar00rootroot00000000000000MinimumSpanningTreePerformanceTest.java000066400000000000000000000221561402514743400364360ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/perf/spanning/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.perf.spanning; import org.jgrapht.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.spanning.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.concurrent.*; import java.util.function.*; /** * A small benchmark comparing spanning tree algorithms on random graphs. * * @author Dimitrios Michail * @author Alexandru Valeanu */ public class MinimumSpanningTreePerformanceTest { private static final int PERF_BENCHMARK_VERTICES_COUNT_DENSE = 1500; private static final double PERF_BENCHMARK_EDGES_PROP_DENSE = 0.65; private static final int PERF_BENCHMARK_VERTICES_COUNT_SPARSE = 100_000; private static final int PERF_BENCHMARK_EDGES_COUNT_SPARSE = 500_000; private static final int WARMUP_REPEAT = 5; private static final int REPEAT = 10; private static final long SEED = 13L; private static abstract class BenchmarkBase { protected Random rng = new Random(SEED); protected GraphGenerator generatorSparseGraphs = null; protected GraphGenerator generatorDenseGraphs = null; protected Graph sparseGraph, denseGraph; abstract SpanningTreeAlgorithm createSolver( Graph graph); public void setupDense() { if (generatorDenseGraphs == null) { // lazily construct generators generatorDenseGraphs = new GnpRandomGraphGenerator<>( PERF_BENCHMARK_VERTICES_COUNT_DENSE, PERF_BENCHMARK_EDGES_PROP_DENSE, rng, false); } DirectedWeightedPseudograph weightedDenseGraph = new DirectedWeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); this.denseGraph = weightedDenseGraph; generatorDenseGraphs.generateGraph(weightedDenseGraph); for (DefaultWeightedEdge e : weightedDenseGraph.edgeSet()) { weightedDenseGraph.setEdgeWeight(e, rng.nextDouble()); } } public void setupSparse() { if (generatorSparseGraphs == null) { // lazily construct generator generatorSparseGraphs = new GnmRandomGraphGenerator<>( PERF_BENCHMARK_VERTICES_COUNT_SPARSE, PERF_BENCHMARK_EDGES_COUNT_SPARSE); } DirectedWeightedPseudograph weightedSparseGraph = new DirectedWeightedPseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); this.sparseGraph = weightedSparseGraph; generatorSparseGraphs.generateGraph(sparseGraph); for (DefaultWeightedEdge e : weightedSparseGraph.edgeSet()) { weightedSparseGraph.setEdgeWeight(e, rng.nextDouble()); } } public void runDense() { SpanningTreeAlgorithm algo = createSolver(denseGraph); algo.getSpanningTree(); } public void runSparse() { SpanningTreeAlgorithm algo = createSolver(sparseGraph); algo.getSpanningTree(); } } public static class PrimBenchmark extends BenchmarkBase { @Override SpanningTreeAlgorithm createSolver( Graph graph) { return new PrimMinimumSpanningTree<>(graph); } @Override public String toString() { return "Prim"; } } public static class KruskalBenchmark extends BenchmarkBase { @Override SpanningTreeAlgorithm createSolver( Graph graph) { return new KruskalMinimumSpanningTree<>(graph); } @Override public String toString() { return "Kruskal"; } } public static class BoruvkaBenchmark extends BenchmarkBase { @Override SpanningTreeAlgorithm createSolver( Graph graph) { return new BoruvkaMinimumSpanningTree<>(graph); } @Override public String toString() { return "Boruvka"; } } @Test public void testBenchmarkDenseGraphs() { System.out.println("Minimum Spanning Tree Benchmark using dense graphs"); System.out.println("-------------------------------"); System.out .println( "Using G(n,p) random graph with n = " + PERF_BENCHMARK_VERTICES_COUNT_DENSE + ", p = " + PERF_BENCHMARK_EDGES_PROP_DENSE); System.out.println("Warmup phase " + WARMUP_REPEAT + " executions"); System.out.println("Averaging results over " + REPEAT + " executions"); List> algFactory = new ArrayList<>(); algFactory.add(PrimBenchmark::new); algFactory.add(KruskalBenchmark::new); algFactory.add(BoruvkaBenchmark::new); for (Supplier alg : algFactory) { System.gc(); StopWatch watch = new StopWatch(); BenchmarkBase benchmark = alg.get(); System.out.printf("%-30s :", benchmark.toString()); for (int i = 0; i < WARMUP_REPEAT; i++) { System.out.print("-"); benchmark.setupDense(); benchmark.runDense(); } double avgGraphCreate = 0d; double avgExecution = 0d; for (int i = 0; i < REPEAT; i++) { System.out.print("+"); watch.start(); benchmark.setupDense(); avgGraphCreate += watch.getElapsed(TimeUnit.MILLISECONDS); watch.start(); benchmark.runDense(); avgExecution += watch.getElapsed(TimeUnit.MILLISECONDS); } avgGraphCreate /= REPEAT; avgExecution /= REPEAT; System.out.print(" -> "); System.out .printf("setup %.3f (ms) | execution %.3f (ms)\n", avgGraphCreate, avgExecution); } } @Test public void testBenchmarkSparseGraphs() { System.out.println("Minimum Spanning Tree Benchmark using sparse graphs"); System.out.println("-------------------------------"); System.out .println( "Using G(n,M) random graph with n = " + PERF_BENCHMARK_VERTICES_COUNT_SPARSE + ", M = " + PERF_BENCHMARK_EDGES_COUNT_SPARSE); System.out.println("Warmup phase " + WARMUP_REPEAT + " executions"); System.out.println("Averaging results over " + REPEAT + " executions"); List> algFactory = new ArrayList<>(); algFactory.add(PrimBenchmark::new); algFactory.add(KruskalBenchmark::new); algFactory.add(BoruvkaBenchmark::new); for (Supplier alg : algFactory) { System.gc(); StopWatch watch = new StopWatch(); BenchmarkBase benchmark = alg.get(); System.out.printf("%-30s :", benchmark.toString()); for (int i = 0; i < WARMUP_REPEAT; i++) { System.out.print("-"); benchmark.setupSparse(); benchmark.runSparse(); } double avgGraphCreate = 0d; double avgExecution = 0d; for (int i = 0; i < REPEAT; i++) { System.out.print("+"); watch.start(); benchmark.setupSparse(); avgGraphCreate += watch.getElapsed(TimeUnit.MILLISECONDS); watch.start(); benchmark.runSparse(); avgExecution += watch.getElapsed(TimeUnit.MILLISECONDS); } avgGraphCreate /= REPEAT; avgExecution /= REPEAT; System.out.print(" -> "); System.out .printf("setup %.3f (ms) | execution %.3f (ms)\n", avgGraphCreate, avgExecution); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/000077500000000000000000000000001402514743400257535ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/AbstractGraphIteratorTest.java000066400000000000000000000140351402514743400337200ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Liviu Rau and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.event.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * A basis for testing {@link org.jgrapht.traverse.BreadthFirstIterator} and * {@link org.jgrapht.traverse.DepthFirstIterator} classes. * * @author Liviu Rau */ public abstract class AbstractGraphIteratorTest { // ~ Instance fields -------------------------------------------------------- StringBuilder result; /** * . */ @Test public void testDirectedGraph() { Graph graph = createDirectedGraph(); AbstractGraphIterator iterator = createIterator(graph, "1"); doDirectedGraphTest(iterator); } protected void collectResult(Iterator iterator, StringBuilder result) { while (iterator.hasNext()) { result.append(iterator.next()); if (iterator.hasNext()) { result.append(','); } } } public void doDirectedGraphTest(AbstractGraphIterator iterator) { result = new StringBuilder(); MyTraversalListener listener = new MyTraversalListener<>(); iterator.addTraversalListener(listener); collectResult(iterator, result); assertEquals(getExpectedStr2(), result.toString()); assertEquals(getExpectedFinishString(), listener.getFinishString()); } abstract String getExpectedStr1(); abstract String getExpectedStr2(); String getExpectedFinishString() { return ""; } Graph createDirectedGraph() { Graph graph = new DefaultDirectedWeightedGraph<>(DefaultWeightedEdge.class); // String v1 = "1"; String v2 = "2"; String v3 = "3"; String v4 = "4"; String v5 = "5"; String v6 = "6"; String v7 = "7"; String v8 = "8"; String v9 = "9"; graph.addVertex(v1); graph.addVertex(v2); graph.addVertex("3"); graph.addVertex("4"); graph.addVertex("5"); graph.addVertex("6"); graph.addVertex("7"); graph.addVertex("8"); graph.addVertex("9"); graph.addVertex("orphan"); // NOTE: set weights on some of the edges to test traversals like // ClosestFirstIterator where it matters. For other traversals, it // will be ignored. Rely on the default edge weight being 1. graph.addEdge(v1, v2); Graphs.addEdge(graph, v1, v3, 100); Graphs.addEdge(graph, v2, v4, 1000); graph.addEdge(v3, v5); Graphs.addEdge(graph, v3, v6, 100); graph.addEdge(v5, v6); Graphs.addEdge(graph, v5, v7, 200); graph.addEdge(v6, v1); Graphs.addEdge(graph, v7, v8, 100); graph.addEdge(v7, v9); graph.addEdge(v8, v2); graph.addEdge(v9, v4); return graph; } abstract AbstractGraphIterator createIterator( Graph g, String startVertex); // ~ Inner Classes ---------------------------------------------------------- /** * Internal traversal listener. * * @author Barak Naveh */ private class MyTraversalListener implements TraversalListener { private int componentNumber = 0; private int numComponentVertices = 0; private String finishString = ""; /** * @see TraversalListener#connectedComponentFinished(ConnectedComponentTraversalEvent) */ @Override public void connectedComponentFinished(ConnectedComponentTraversalEvent e) { switch (componentNumber) { case 1: assertEquals(getExpectedStr1(), result.toString()); assertEquals(9, numComponentVertices); break; case 2: assertEquals(getExpectedStr2(), result.toString()); assertEquals(1, numComponentVertices); break; default: Assert.fail("Should not get here."); break; } numComponentVertices = 0; } /** * @see TraversalListener#connectedComponentStarted(ConnectedComponentTraversalEvent) */ @Override public void connectedComponentStarted(ConnectedComponentTraversalEvent e) { componentNumber++; } /** * @see TraversalListener#edgeTraversed(EdgeTraversalEvent) */ @Override public void edgeTraversed(EdgeTraversalEvent e) { // to be tested... } /** * @see TraversalListener#vertexTraversed(VertexTraversalEvent) */ @Override public void vertexTraversed(VertexTraversalEvent e) { numComponentVertices++; } /** * @see TraversalListener#vertexTraversed(VertexTraversalEvent) */ @Override public void vertexFinished(VertexTraversalEvent e) { finishString += e.getVertex() + ":"; } public String getFinishString() { return finishString; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/BreadthFirstIteratorTest.java000066400000000000000000000116331402514743400335550ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Liviu Rau and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Tests for the {@link BreadthFirstIterator} class. * *

    * NOTE: This test uses hard-coded expected ordering isn't really guaranteed by the specification of * the algorithm. This could cause false failures if the traversal implementation changes. *

    * * @author Liviu Rau * @author Patrick Sharp */ public class BreadthFirstIteratorTest extends CrossComponentIteratorTest { // ~ Methods ---------------------------------------------------------------- @Override String getExpectedStr1() { return "1,2,3,4,5,6,7,8,9"; } @Override String getExpectedStr2() { return "1,2,3,4,5,6,7,8,9,orphan"; } @Override AbstractGraphIterator createIterator( Graph g, String vertex) { AbstractGraphIterator i = new BreadthFirstIterator<>(g, vertex); i.setCrossComponentTraversal(true); return i; } @Override String getExpectedCCStr1() { return "orphan"; } @Override String getExpectedCCStr2() { return "orphan,7,8,9,2,4"; } @Override String getExpectedCCStr3() { return "orphan,7,8,9,2,4,3,5,6,1"; } @Override AbstractGraphIterator createIterator( Graph g, Iterable startVertex) { return new BreadthFirstIterator<>(g, startVertex); } @Test public void searchTreeTest() { Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); g.addVertex("z"); DefaultEdge e1 = g.addEdge("a", "b"); DefaultEdge e2 = g.addEdge("b", "c"); DefaultEdge e3 = g.addEdge("b", "z"); DefaultEdge e4 = g.addEdge("b", "d"); DefaultEdge e5 = g.addEdge("d", "e"); BreadthFirstIterator bfs = new BreadthFirstIterator<>(g, "a"); while (bfs.hasNext()) bfs.next(); assertEquals(0, bfs.getDepth("a")); assertEquals(1, bfs.getDepth("b")); assertEquals(2, bfs.getDepth("c")); assertEquals(2, bfs.getDepth("d")); assertEquals(3, bfs.getDepth("e")); assertEquals(2, bfs.getDepth("z")); assertNull(bfs.getSpanningTreeEdge("a")); assertEquals(e1, bfs.getSpanningTreeEdge("b")); assertEquals(e2, bfs.getSpanningTreeEdge("c")); assertEquals(e4, bfs.getSpanningTreeEdge("d")); assertEquals(e5, bfs.getSpanningTreeEdge("e")); assertEquals(e3, bfs.getSpanningTreeEdge("z")); assertNull(bfs.getParent("a")); assertEquals("a", bfs.getParent("b")); assertEquals("b", bfs.getParent("c")); assertEquals("b", bfs.getParent("d")); assertEquals("d", bfs.getParent("e")); assertEquals("b", bfs.getParent("z")); } @Test public void searchTreeDirectedCycleTest() { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); DefaultEdge e1 = Graphs.addEdgeWithVertices(g, 0, 1); DefaultEdge e2 = Graphs.addEdgeWithVertices(g, 1, 2); DefaultEdge e3 = Graphs.addEdgeWithVertices(g, 2, 3); Graphs.addEdgeWithVertices(g, 3, 0); BreadthFirstIterator bfs = new BreadthFirstIterator<>(g, 0); while (bfs.hasNext()) bfs.next(); assertEquals(0, bfs.getDepth(0)); assertEquals(1, bfs.getDepth(1)); assertEquals(2, bfs.getDepth(2)); assertEquals(3, bfs.getDepth(3)); assertNull(bfs.getSpanningTreeEdge(0)); assertEquals(e1, bfs.getSpanningTreeEdge(1)); assertEquals(e2, bfs.getSpanningTreeEdge(2)); assertEquals(e3, bfs.getSpanningTreeEdge(3)); assertNull(bfs.getParent(0)); assertEquals(new Integer(0), bfs.getParent(1)); assertEquals(new Integer(1), bfs.getParent(2)); assertEquals(new Integer(2), bfs.getParent(3)); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/ClosestFirstIteratorTest.java000066400000000000000000000101301402514743400336070ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Tests for ClosestFirstIterator. * * @author John V. Sichi, Patrick Sharp */ public class ClosestFirstIteratorTest extends CrossComponentIteratorTest { // ~ Methods ---------------------------------------------------------------- /** * . */ @Test public void testRadius() { result = new StringBuilder(); Graph graph = createDirectedGraph(); // NOTE: pick 301 as the radius because it discriminates // the boundary case edge between v7 and v9 AbstractGraphIterator iterator = new ClosestFirstIterator<>(graph, "1", 301); collectResult(iterator, result); assertEquals("1,2,3,5,6,7", result.toString()); } /** * Test simultaneous search from multiple start vertices. */ @Test public void testMultipleStarts() { result = new StringBuilder(); Graph graph = new DirectedPseudograph<>(DefaultEdge.class); graph.addVertex("1"); graph.addVertex("2"); graph.addVertex("3"); graph.addVertex("4"); graph.addVertex("5"); graph.addVertex("6"); graph.addVertex("7"); graph.addVertex("8"); graph.addVertex("9"); graph.addEdge("1", "7"); graph.addEdge("2", "4"); graph.addEdge("2", "5"); graph.addEdge("2", "6"); graph.addEdge("2", "7"); graph.addEdge("2", "9"); graph.addEdge("3", "8"); graph.addEdge("4", "2"); graph.addEdge("4", "6"); graph.addEdge("4", "7"); graph.addEdge("6", "2"); graph.addEdge("6", "4"); graph.addEdge("6", "7"); graph.addEdge("8", "2"); graph.addEdge("8", "4"); graph.addEdge("8", "6"); graph.addEdge("8", "9"); List starts = new ArrayList<>(); starts.add("3"); starts.add("1"); AbstractGraphIterator iterator = new ClosestFirstIterator<>(graph, starts, 2); collectResult(iterator, result); assertEquals("3,1,8,7,4,9,2,6", result.toString()); } // NOTE: the edge weights make the result deterministic @Override String getExpectedStr1() { return "1,2,3,5,6,7,9,4,8"; } @Override String getExpectedStr2() { return getExpectedStr1() + ",orphan"; } @Override AbstractGraphIterator createIterator( Graph g, String vertex) { AbstractGraphIterator i = new ClosestFirstIterator<>(g, vertex); i.setCrossComponentTraversal(true); return i; } @Override String getExpectedCCStr1() { return "orphan,3,7,5,9,6,4,1,2,8"; } @Override String getExpectedCCStr2() { return "orphan,7,9,4,8,2"; } @Override String getExpectedCCStr3() { return "orphan,3,7,5,9,6,4,1,2,8"; } @Override int getExpectedCCVertexCount1() { return 10; } @Override AbstractGraphIterator createIterator( Graph g, Iterable startVertex) { return new ClosestFirstIterator<>(g, startVertex); } } CrossComponentIteratorTest.java000066400000000000000000000152461402514743400340750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/* * (C) Copyright 2003-2021, by Liviu Rau and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.event.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; /** * A basis for testing {@link org.jgrapht.traverse.BreadthFirstIterator} and * {@link org.jgrapht.traverse.DepthFirstIterator} classes. * * @author Patrick Sharp (I pretty much just ripped off Liviu Rau's code from * AbstractGraphIteratorTest) */ public abstract class CrossComponentIteratorTest extends AbstractGraphIteratorTest { // ~ Instance fields -------------------------------------------------------- StringBuilder result; // ~ Methods ---------------------------------------------------------------- /** * . */ @Test public void testDirectedGraphViaCCI() { result = new StringBuilder(); Graph graph = createDirectedGraph(); AbstractGraphIterator iterator = createIterator(graph, Arrays.asList("orphan", "7", "3")); MyTraversalListener listener = new MyTraversalListener<>(); iterator.addTraversalListener(listener); collectResult(iterator, result); assertEquals(getExpectedCCStr3(), result.toString()); assertEquals(getExpectedCCFinishString(), listener.getFinishString()); } @Test public void testDirectedGraphNullConstructors() { Graph graph = createDirectedGraph(); doDirectedGraphTest(createIterator(graph, (String) null)); doDirectedGraphTest(createIterator(graph, (Iterable) null)); } @Test public void testNonCrossComponentTraversal() { final ModifiableInteger iteratorCalls = new ModifiableInteger(); final ModifiableInteger sizeCalls = new ModifiableInteger(); Graph graph = createDirectedGraph(); Graph wrapper = new GraphDelegator(graph) { @Override public Set vertexSet() { return new AbstractSet() { @Override public Iterator iterator() { iteratorCalls.increment(); return getDelegate().vertexSet().iterator(); } @Override public int size() { sizeCalls.increment(); return getDelegate().vertexSet().size(); } }; } }; AbstractGraphIterator iterator = createIterator(wrapper, Arrays.asList("orphan")); assertFalse(iterator.isCrossComponentTraversal()); result = new StringBuilder(); collectResult(iterator, result); assertEquals(0, iteratorCalls.getValue()); assertEquals(0, sizeCalls.getValue()); } abstract String getExpectedCCStr1(); abstract String getExpectedCCStr2(); abstract String getExpectedCCStr3(); int getExpectedCCVertexCount1() { return 1; } String getExpectedCCFinishString() { return ""; } abstract AbstractGraphIterator createIterator( Graph g, Iterable startVertex); // ~ Inner Classes ---------------------------------------------------------- /** * Internal traversal listener. * * @author Barak Naveh */ private class MyTraversalListener implements TraversalListener { private int componentNumber = 0; private int numComponentVertices = 0; private String finishString = ""; /** * @see TraversalListener#connectedComponentFinished(ConnectedComponentTraversalEvent) */ @Override public void connectedComponentFinished(ConnectedComponentTraversalEvent e) { switch (componentNumber) { case 1: assertEquals(getExpectedCCStr1(), result.toString()); assertEquals(getExpectedCCVertexCount1(), numComponentVertices); break; case 2: assertEquals(getExpectedCCStr2(), result.toString()); assertEquals(5, numComponentVertices); break; case 3: assertEquals(getExpectedCCStr3(), result.toString()); assertEquals(4, numComponentVertices); break; default: Assert.fail("Should not get here."); break; } numComponentVertices = 0; } /** * @see TraversalListener#connectedComponentStarted(ConnectedComponentTraversalEvent) */ @Override public void connectedComponentStarted(ConnectedComponentTraversalEvent e) { componentNumber++; } /** * @see TraversalListener#edgeTraversed(EdgeTraversalEvent) */ @Override public void edgeTraversed(EdgeTraversalEvent e) { // to be tested... } /** * @see TraversalListener#vertexTraversed(VertexTraversalEvent) */ @Override public void vertexTraversed(VertexTraversalEvent e) { numComponentVertices++; } /** * @see TraversalListener#vertexTraversed(VertexTraversalEvent) */ @Override public void vertexFinished(VertexTraversalEvent e) { finishString += e.getVertex() + ":"; } public String getFinishString() { return finishString; } } } DegeneracyOrderingIteratorTest.java000066400000000000000000000111101402514743400346430ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.event.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * Tests for {@link DegeneracyOrderingIterator}. * * @author Dimitrios Michail */ public class DegeneracyOrderingIteratorTest { @Test public void testGraph1() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); g.addVertex("v6"); g.addVertex("v7"); g.addVertex("v8"); g.addVertex("v9"); g.addVertex("v10"); // biggest clique: { V1, V2, V3, V4 } g.addEdge("v1", "v2"); g.addEdge("v1", "v3"); g.addEdge("v1", "v4"); g.addEdge("v2", "v3"); g.addEdge("v2", "v4"); g.addEdge("v3", "v4"); // smaller clique: { V5, V6, V7 } g.addEdge("v5", "v6"); g.addEdge("v5", "v7"); g.addEdge("v6", "v7"); // for fun, add an overlapping clique { V3, V4, V5 } g.addEdge("v3", "v5"); g.addEdge("v4", "v5"); // make V8 less lonely g.addEdge("v7", "v8"); // add one more maximal which is also the biggest { V1, V2, V9, V10 } g.addEdge("v1", "v9"); g.addEdge("v1", "v10"); g.addEdge("v2", "v9"); g.addEdge("v2", "v10"); g.addEdge("v9", "v10"); StringBuilder sb = new StringBuilder(); DegeneracyOrderingIterator it = new DegeneracyOrderingIterator<>(g); while (it.hasNext()) { String v = it.next(); sb.append("," + v); } assertEquals(",v8,v6,v7,v5,v9,v10,v1,v2,v3,v4", sb.toString()); } @Test public void testGraphWithListener() { SimpleGraph g = new SimpleGraph<>(DefaultEdge.class); Graphs .addAllVertices( g, Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k")); g.addEdge("b", "c"); g.addEdge("c", "d"); g.addEdge("c", "e"); g.addEdge("e", "f"); g.addEdge("e", "g"); g.addEdge("e", "h"); g.addEdge("f", "g"); g.addEdge("f", "h"); g.addEdge("f", "i"); g.addEdge("g", "h"); g.addEdge("i", "j"); g.addEdge("i", "k"); g.addEdge("j", "k"); DegeneracyOrderingIterator it = new DegeneracyOrderingIterator<>(g); TraversalListener listener = new TestTraversalListener<>(); it.addTraversalListener(listener); while (it.hasNext()) { it.next(); } assertEquals( ",s_a,f_a,s_b,f_b,s_d,f_d,s_c,f_c,s_j,f_j,s_i,f_i,s_k,f_k,s_e,f_e,s_f,f_f,s_g,f_g,s_h,f_h", listener.toString()); } private static class TestTraversalListener implements TraversalListener { private StringBuilder sb = new StringBuilder(); @Override public void connectedComponentFinished(ConnectedComponentTraversalEvent e) { fail("Should not be called"); } @Override public void connectedComponentStarted(ConnectedComponentTraversalEvent e) { fail("Should not be called"); } @Override public void edgeTraversed(EdgeTraversalEvent e) { fail("Should not be called"); } @Override public void vertexTraversed(VertexTraversalEvent e) { sb.append(",s_" + e.getVertex()); } @Override public void vertexFinished(VertexTraversalEvent e) { sb.append(",f_" + e.getVertex()); } public String toString() { return sb.toString(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/DepthFirstIteratorTest.java000066400000000000000000000076751402514743400332630ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Liviu Rau and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; /** * Tests for the {@link DepthFirstIteratorTest} class. * *

    * NOTE: This test uses hard-coded expected ordering isn't really guaranteed by the specification of * the algorithm. This could cause false failures if the traversal implementation changes. *

    * * @author Liviu Rau, Patrick Sharp */ public class DepthFirstIteratorTest extends CrossComponentIteratorTest { // ~ Methods ---------------------------------------------------------------- @Override String getExpectedStr1() { return "1,3,6,5,7,9,4,8,2"; } @Override String getExpectedStr2() { return "1,3,6,5,7,9,4,8,2,orphan"; } @Override String getExpectedFinishString() { return "6:4:9:2:8:7:5:3:1:orphan:"; } @Override String getExpectedCCStr1() { return "orphan"; } @Override String getExpectedCCStr2() { return "orphan,7,9,4,8,2"; } @Override String getExpectedCCStr3() { return "orphan,7,9,4,8,2,3,6,1,5"; } @Override String getExpectedCCFinishString() { return "orphan:4:9:2:8:7:1:6:5:3:"; } @Override AbstractGraphIterator createIterator( Graph g, Iterable startVertex) { return new DepthFirstIterator<>(g, startVertex); } @Override AbstractGraphIterator createIterator( Graph g, String vertex) { AbstractGraphIterator i = new DepthFirstIterator<>(g, vertex); i.setCrossComponentTraversal(true); return i; } /** * See Sourceforge bug 1169182 for details. */ @Test public void testBug1169182() { Graph dg = new DefaultDirectedGraph<>(DefaultEdge.class); String a = "A"; String b = "B"; String c = "C"; String d = "D"; String e = "E"; String f = "F"; String g = "G"; String h = "H"; String i = "I"; String j = "J"; String k = "K"; String l = "L"; dg.addVertex(a); dg.addVertex(b); dg.addVertex(c); dg.addVertex(d); dg.addVertex(e); dg.addVertex(f); dg.addVertex(g); dg.addVertex(h); dg.addVertex(i); dg.addVertex(j); dg.addVertex(k); dg.addVertex(l); dg.addEdge(a, b); dg.addEdge(b, c); dg.addEdge(c, j); dg.addEdge(c, d); dg.addEdge(c, e); dg.addEdge(c, f); dg.addEdge(c, g); dg.addEdge(d, h); dg.addEdge(e, h); dg.addEdge(f, i); dg.addEdge(g, i); dg.addEdge(h, j); dg.addEdge(i, c); dg.addEdge(j, k); dg.addEdge(k, l); Iterator dfs = new DepthFirstIterator<>(dg); String actual = ""; while (dfs.hasNext()) { String v = dfs.next(); actual += v; } String expected = "ABCGIFEHJKLD"; assertEquals(expected, actual); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/EdgeSelectionTest.java000066400000000000000000000113701402514743400321720ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Sean Hudson and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.assertEquals; /** * Tests for overriding the {@link CrossComponentIterator#selectOutgoingEdges selectOutgoingEdges} * method. Overriding this method allows for selecting outgoing edges based on the source vertex and * other traversal state. * * @author Sean Hudson */ public class EdgeSelectionTest { /** * Tests selecting the outgoing edges based on criteria that incorporates the source, edge and * traversal iterator state. */ @Test public void testEdgeSelectionOverride() { Graph graph = createGraph(); DepthFirstIterator iterator = new DepthFirstIterator(graph) { String evenEdgeColor = "BLUE"; String oddEdgeColor = "RED"; @Override protected Set selectOutgoingEdges(StatefulVertex vertex) { return graph .outgoingEdgesOf(vertex).stream().filter(e -> filterEdge(vertex, e)) .collect(Collectors.toSet()); } /** * Checks if the edge color corresponds to the vertex parity. */ private boolean filterEdge(StatefulVertex vertex, StatefulEdge edge) { return vertex.getState() % 2 == 0 ? edge.getColor().equals(evenEdgeColor) : edge.getColor().equals(oddEdgeColor); } }; VertexTrackingTraversalListener listener = new VertexTrackingTraversalListener<>(graph); iterator.addTraversalListener(listener); StringBuilder traversedOrder = new StringBuilder(); while (iterator.hasNext()) { traversedOrder.append(iterator.next().getState()); } listener.checkAllVerticesTraversed(); listener.checkAllVerticesFinished(); assertEquals("1342567", traversedOrder.toString()); } private Graph createGraph() { Graph graph = new DefaultDirectedGraph<>(StatefulEdge.class); StatefulVertex v1 = new StatefulVertex(1); StatefulVertex v2 = new StatefulVertex(2); StatefulVertex v3 = new StatefulVertex(3); StatefulVertex v4 = new StatefulVertex(4); StatefulVertex v5 = new StatefulVertex(5); StatefulVertex v6 = new StatefulVertex(6); StatefulVertex v7 = new StatefulVertex(7); graph.addVertex(v1); graph.addVertex(v2); graph.addVertex(v3); graph.addVertex(v4); graph.addVertex(v5); graph.addVertex(v6); graph.addVertex(v7); graph.addVertex(v7); graph.addEdge(v1, v2, new StatefulEdge("BLUE")); graph.addEdge(v1, v3, new StatefulEdge("RED")); graph.addEdge(v1, v3, new StatefulEdge("BLUE")); graph.addEdge(v2, v4, new StatefulEdge("BLUE")); graph.addEdge(v2, v5, new StatefulEdge("BLUE")); graph.addEdge(v2, v5, new StatefulEdge("RED")); graph.addEdge(v3, v4, new StatefulEdge("RED")); graph.addEdge(v3, v7, new StatefulEdge("BLUE")); graph.addEdge(v4, v6, new StatefulEdge("RED")); graph.addEdge(v6, v2, new StatefulEdge("BLUE")); graph.addEdge(v7, v5, new StatefulEdge("RED")); return graph; } private static class StatefulEdge extends DefaultWeightedEdge { private final String color; StatefulEdge(String color) { this.color = color; } String getColor() { return color; } } private static class StatefulVertex { private final int state; StatefulVertex(int state) { this.state = state; } int getState() { return state; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/IgnoreDirectionTest.java000066400000000000000000000040111402514743400325360ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.graph.*; /** * Tests for the ignoreDirection parameter to XXFirstIterator. * *

    * NOTE: This test uses hard-coded expected ordering which isn't really guaranteed by the * specification of the algorithm. This could cause spurious failures if the traversal * implementation changes. *

    * * @author John V. Sichi */ public class IgnoreDirectionTest extends AbstractGraphIteratorTest { // ~ Methods ---------------------------------------------------------------- @Override String getExpectedStr1() { return "4,9,7,8,2,1,3,6,5"; } @Override String getExpectedStr2() { return "4,9,7,8,2,1,3,6,5,orphan"; } @Override String getExpectedFinishString() { return "5:6:3:1:2:8:7:9:4:orphan:"; } @Override AbstractGraphIterator createIterator( Graph g, String vertex) { // ignore the passed in vertex and always start from v4, since that's // the only vertex without out-edges Graph undirectedView = new AsUndirectedGraph<>(g); AbstractGraphIterator i = new DepthFirstIterator<>(undirectedView, "4"); i.setCrossComponentTraversal(true); return i; } } LexBreadthFirstIteratorTest.java000066400000000000000000000144761402514743400341570ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * Tests for the {@link LexBreadthFirstIterator} * * @author Timofey Chudakov */ public class LexBreadthFirstIteratorTest { /** * Tests basic properties of events fired by {@code LexBreadthFirstIterator} */ @Test public void testEvents() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 1, 3); Graphs.addEdgeWithVertices(graph, 1, 4); Graphs.addEdgeWithVertices(graph, 2, 4); Graphs.addEdgeWithVertices(graph, 3, 4); LexBreadthFirstIterator iterator = new LexBreadthFirstIterator<>(graph); VertexTrackingTraversalListener listener = new VertexTrackingTraversalListener<>(graph); iterator.addTraversalListener(listener); for (int i = 0; i < 4; i++) { iterator.next(); } listener.checkAllVerticesTraversed(); listener.checkAllVerticesFinished(); } /** * Tests iterator on empty graph. */ @Test(expected = NoSuchElementException.class) public void testLexicographicalBfsIterator1() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); LexBreadthFirstIterator iterator = new LexBreadthFirstIterator<>(graph); assertFalse(iterator.hasNext()); iterator.next(); } /** * Tests iterator for basic invariants. */ @Test public void testLexicographicalBfsIterator2() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 2, 3); Graphs.addEdgeWithVertices(graph, 3, 4); LexBreadthFirstIterator iterator = new LexBreadthFirstIterator<>(graph); Set returned = new HashSet<>(); assertTrue(iterator.hasNext()); Integer vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(graph.vertexSet().equals(returned)); assertFalse(iterator.hasNext()); } /** * Tests iterator on disconnected graph. */ @Test public void testLexicographicalBfsIterator3() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); graph.addVertex("a"); graph.addVertex("b"); graph.addVertex("c"); graph.addVertex("d"); LexBreadthFirstIterator iterator = new LexBreadthFirstIterator<>(graph); Set returned = new HashSet<>(); assertTrue(iterator.hasNext()); String vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(graph.vertexSet().equals(returned)); assertFalse(iterator.hasNext()); } /** * Tests iterator on pseudograph. */ @Test public void testLexicographicalBfsIterator4() { Graph graph = new Pseudograph<>(DefaultEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); Graphs.addEdgeWithVertices(graph, 1, 1); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 1, 2); Graphs.addEdgeWithVertices(graph, 1, 3); Graphs.addEdgeWithVertices(graph, 1, 3); Graphs.addEdgeWithVertices(graph, 2, 3); Graphs.addEdgeWithVertices(graph, 2, 3); Graphs.addEdgeWithVertices(graph, 3, 3); Graphs.addEdgeWithVertices(graph, 3, 3); LexBreadthFirstIterator iterator = new LexBreadthFirstIterator<>(graph); Set returned = new HashSet<>(); assertTrue(iterator.hasNext()); Integer vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(graph.vertexSet().equals(returned)); assertFalse(iterator.hasNext()); } } MaximumCardinalityIteratorTest.java000066400000000000000000000143361402514743400347210ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/* * (C) Copyright 2018-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; /** * Tests for the {@link MaximumCardinalityIterator} * * @author Timofey Chudakov */ public class MaximumCardinalityIteratorTest { /** * Tests basic properties of events fired by {@code LexBreadthFirstIterator}. */ @Test public void testEvents() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, "a", "b"); Graphs.addEdgeWithVertices(graph, "b", "c"); Graphs.addEdgeWithVertices(graph, "c", "a"); Graphs.addEdgeWithVertices(graph, "b", "d"); VertexTrackingTraversalListener listener = new VertexTrackingTraversalListener<>(graph); MaximumCardinalityIterator iterator = new MaximumCardinalityIterator<>(graph); iterator.addTraversalListener(listener); for (int i = 0; i < 4; i++) { iterator.next(); } listener.checkAllVerticesTraversed(); listener.checkAllVerticesFinished(); } /** * Tests iterator on empty graph. */ @Test(expected = NoSuchElementException.class) public void testMaximumCardinalityIterator1() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); MaximumCardinalityIterator iterator = new MaximumCardinalityIterator<>(graph); assertFalse(iterator.hasNext()); iterator.next(); } /** * Tests iterator on basic invariants. */ @Test public void testMaximumCardinalityIterator2() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, "a", "b"); Graphs.addEdgeWithVertices(graph, "b", "c"); Graphs.addEdgeWithVertices(graph, "b", "d"); Graphs.addEdgeWithVertices(graph, "c", "d"); MaximumCardinalityIterator iterator = new MaximumCardinalityIterator<>(graph); Set returned = new HashSet<>(); assertTrue(iterator.hasNext()); String vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(graph.vertexSet().equals(returned)); assertFalse(iterator.hasNext()); } /** * Tests iterator on disconnected graph. */ @Test public void testMaximumCardinalityIterator3() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); MaximumCardinalityIterator iterator = new MaximumCardinalityIterator<>(graph); Set returned = new HashSet<>(); assertTrue(iterator.hasNext()); Integer vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(graph.vertexSet().equals(returned)); assertFalse(iterator.hasNext()); } /** * Tests iterator on pseudograph. */ @Test public void testMaximumCardinalityIterator4() { Graph graph = new DefaultUndirectedGraph<>(DefaultEdge.class); Graphs.addEdgeWithVertices(graph, "a", "a"); Graphs.addEdgeWithVertices(graph, "a", "b"); Graphs.addEdgeWithVertices(graph, "a", "b"); Graphs.addEdgeWithVertices(graph, "a", "c"); Graphs.addEdgeWithVertices(graph, "a", "c"); Graphs.addEdgeWithVertices(graph, "b", "c"); Graphs.addEdgeWithVertices(graph, "b", "c"); Graphs.addEdgeWithVertices(graph, "c", "c"); Graphs.addEdgeWithVertices(graph, "c", "c"); MaximumCardinalityIterator iterator = new MaximumCardinalityIterator<>(graph); Set returned = new HashSet<>(); assertTrue(iterator.hasNext()); String vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(iterator.hasNext()); vertex = iterator.next(); returned.add(vertex); assertTrue(graph.containsVertex(vertex)); assertTrue(graph.vertexSet().equals(returned)); assertFalse(iterator.hasNext()); } } RandomWalkVertexIteratorTest.java000066400000000000000000000126741402514743400343600ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/* * (C) Copyright 2016-2021, by Assaf Mizrachi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.jgrapht.Graph; import org.jgrapht.generate.LinearGraphGenerator; import org.jgrapht.generate.RingGraphGenerator; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for the {@link RandomWalkVertexIterator} class. * * @author Assaf Mizrachi * */ public class RandomWalkVertexIteratorTest { /** * Tests invalid vertex */ @Test(expected = IllegalArgumentException.class) public void testInvalidVertex() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); new RandomWalkVertexIterator<>(graph, "unknown", 100); } /** * Tests single node graph */ @Test public void testSingleNode() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("123"); Iterator iter = new RandomWalkVertexIterator<>(graph, "123"); assertTrue(iter.hasNext()); assertEquals("123", iter.next()); assertFalse(iter.hasNext()); } /** * Tests iterator does not have more elements after reaching sink vertex. */ @Test public void testSink() { Graph graph = GraphTypeBuilder .directed().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeClass(DefaultEdge.class).allowingMultipleEdges(false).allowingSelfLoops(true) .buildGraph(); int graphSize = 10; LinearGraphGenerator graphGenerator = new LinearGraphGenerator<>(graphSize); graphGenerator.generateGraph(graph); Iterator iter = new RandomWalkVertexIterator<>(graph, graph.vertexSet().iterator().next()); for (int i = 0; i < graphSize; i++) { assertTrue(iter.hasNext()); assertNotNull(iter.next()); } assertFalse(iter.hasNext()); } /** * Tests iterator is exhausted after maxSteps */ @Test public void testExhausted() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeClass(DefaultEdge.class).allowingMultipleEdges(false).allowingSelfLoops(false) .buildGraph(); RingGraphGenerator graphGenerator = new RingGraphGenerator<>(10); graphGenerator.generateGraph(graph); long maxSteps = 4; Iterator iter = new RandomWalkVertexIterator<>(graph, "1", maxSteps); List walk = new ArrayList<>(); while (iter.hasNext()) { walk.add(iter.next()); } assertEquals(walk.size(), 5); } /** * Test deterministic walk using directed ring graph. */ @Test public void testDeterministic() { Graph graph = GraphTypeBuilder .directed().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeClass(DefaultEdge.class).allowingMultipleEdges(false).allowingSelfLoops(true) .buildGraph(); int ringSize = 5; RingGraphGenerator graphGenerator = new RingGraphGenerator<>(ringSize); graphGenerator.generateGraph(graph); Iterator iter = new RandomWalkVertexIterator<>(graph, "0", 20); int step = 0; while (iter.hasNext()) { assertEquals(String.valueOf(step % ringSize), iter.next()); step++; } } /** * Tests for a long time */ @Test public void testLongTime() { Graph graph = GraphTypeBuilder .undirected().vertexSupplier(SupplierUtil.createStringSupplier()) .edgeClass(DefaultEdge.class).allowingMultipleEdges(false).allowingSelfLoops(false) .buildGraph(); graph.addVertex("0"); graph.addVertex("1"); graph.addVertex("2"); graph.addEdge("0", "1"); graph.addEdge("1", "2"); Iterator iter = new RandomWalkVertexIterator<>(graph, "0"); int count = 0; List walk = new ArrayList<>(); while (iter.hasNext()) { if (count >= 10000) { break; } count++; walk.add(iter.next()); } assert count == 10000; } } TopologicalOrderIteratorTest.java000066400000000000000000000214071402514743400343650ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/* * (C) Copyright 2005-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for TopologicalOrderIterator. * * @author John V. Sichi */ public class TopologicalOrderIteratorTest { @Test public void testRecipe() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); String[] v = new String[9]; v[0] = "preheat oven"; v[1] = "sift dry ingredients"; v[2] = "stir wet ingredients"; v[3] = "mix wet and dry ingredients"; v[4] = "spoon onto pan"; v[5] = "bake"; v[6] = "cool"; v[7] = "frost"; v[8] = "eat"; // add in mixed up order graph.addVertex(v[4]); graph.addVertex(v[8]); graph.addVertex(v[1]); graph.addVertex(v[3]); graph.addVertex(v[7]); graph.addVertex(v[6]); graph.addVertex(v[0]); graph.addVertex(v[2]); graph.addVertex(v[5]); // specify enough edges to guarantee deterministic total order graph.addEdge(v[0], v[1]); graph.addEdge(v[1], v[2]); graph.addEdge(v[0], v[2]); graph.addEdge(v[1], v[3]); graph.addEdge(v[2], v[3]); graph.addEdge(v[3], v[4]); graph.addEdge(v[4], v[5]); graph.addEdge(v[5], v[6]); graph.addEdge(v[6], v[7]); graph.addEdge(v[7], v[8]); graph.addEdge(v[6], v[8]); Iterator iter = new TopologicalOrderIterator<>(graph); int i = 0; while (iter.hasNext()) { assertEquals(v[i], iter.next()); ++i; } // Test with a reversed view Graph reversed = new EdgeReversedGraph<>(graph); iter = new TopologicalOrderIterator<>(reversed); i = v.length - 1; while (iter.hasNext()) { assertEquals(v[i], iter.next()); --i; } } @Test public void testEmptyGraph() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); Iterator iter = new TopologicalOrderIterator<>(graph); assertFalse(iter.hasNext()); } @Test public void testGraph1() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList("v0", "v1", "v2", "v3", "v4", "v5")); graph.addEdge("v0", "v1"); graph.addEdge("v0", "v2"); graph.addEdge("v1", "v4"); graph.addEdge("v2", "v4"); graph.addEdge("v3", "v2"); graph.addEdge("v3", "v4"); graph.addEdge("v4", "v5"); Iterator it = new TopologicalOrderIterator<>(graph); assertTrue(it.hasNext()); assertEquals("v0", it.next()); assertTrue(it.hasNext()); assertEquals("v3", it.next()); assertTrue(it.hasNext()); assertEquals("v1", it.next()); assertTrue(it.hasNext()); assertEquals("v2", it.next()); assertTrue(it.hasNext()); assertEquals("v4", it.next()); assertTrue(it.hasNext()); assertEquals("v5", it.next()); assertFalse(it.hasNext()); } @Test public void testGraphWithPartialOrder() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList("v0", "v1", "v2", "v3", "v4", "v5")); graph.addEdge("v0", "v1"); graph.addEdge("v0", "v2"); graph.addEdge("v1", "v4"); graph.addEdge("v2", "v4"); graph.addEdge("v3", "v2"); graph.addEdge("v3", "v4"); graph.addEdge("v4", "v5"); Comparator cf = (a, b) -> { if (a.equals("v0") && b.equals("v3")) { return 1; } else if (a.equals("v3") && b.equals("v0")) { return -1; } if (a.equals("v1") && b.equals("v2")) { return 1; } else if (a.equals("v2") && b.equals("v1")) { return -1; } return -1; }; Iterator it = new TopologicalOrderIterator<>(graph, cf); assertTrue(it.hasNext()); assertEquals("v3", it.next()); assertTrue(it.hasNext()); assertEquals("v0", it.next()); assertTrue(it.hasNext()); assertEquals("v2", it.next()); assertTrue(it.hasNext()); assertEquals("v1", it.next()); assertTrue(it.hasNext()); assertEquals("v4", it.next()); assertTrue(it.hasNext()); assertEquals("v5", it.next()); assertFalse(it.hasNext()); } @Test public void testGraphWithParallelEdges() { Graph graph = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList("v0", "v1", "v2", "v3", "v4", "v5")); graph.addEdge("v0", "v1"); graph.addEdge("v0", "v1"); graph.addEdge("v0", "v2"); graph.addEdge("v1", "v4"); graph.addEdge("v2", "v4"); graph.addEdge("v2", "v4"); graph.addEdge("v3", "v2"); graph.addEdge("v3", "v4"); graph.addEdge("v4", "v5"); Iterator it = new TopologicalOrderIterator<>(graph); assertTrue(it.hasNext()); assertEquals("v0", it.next()); assertTrue(it.hasNext()); assertEquals("v3", it.next()); assertTrue(it.hasNext()); assertEquals("v1", it.next()); assertTrue(it.hasNext()); assertEquals("v2", it.next()); assertTrue(it.hasNext()); assertEquals("v4", it.next()); assertTrue(it.hasNext()); assertEquals("v5", it.next()); assertFalse(it.hasNext()); } @Test(expected = IllegalArgumentException.class) public void testWithSelfLoops() { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList("v0", "v1", "v2")); graph.addEdge("v0", "v1"); graph.addEdge("v0", "v2"); graph.addEdge("v1", "v2"); graph.addEdge("v2", "v2"); new TopologicalOrderIterator<>(graph); } @Test(expected = IllegalArgumentException.class) public void testGraphWithCycle() { Graph graph = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList("v0", "v1", "v2", "v3", "v4", "v5")); graph.addEdge("v0", "v1"); graph.addEdge("v0", "v1"); graph.addEdge("v0", "v2"); graph.addEdge("v1", "v4"); graph.addEdge("v2", "v4"); graph.addEdge("v2", "v4"); graph.addEdge("v3", "v2"); graph.addEdge("v3", "v4"); graph.addEdge("v4", "v5"); graph.addEdge("v5", "v2"); Iterator it = new TopologicalOrderIterator<>(graph); while (it.hasNext()) { it.next(); } } @Test public void testDisconnected() { Graph graph = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(graph, Arrays.asList(0, 1, 2, 3)); graph.addEdge(0, 1); graph.addEdge(2, 3); Comparator cf = (a, b) -> { if (a < b) { return 1; } else if (a > b) { return -1; } else { return 0; } }; Iterator it = new TopologicalOrderIterator<>(graph, cf); assertTrue(it.hasNext()); assertEquals(Integer.valueOf(2), it.next()); assertTrue(it.hasNext()); assertEquals(Integer.valueOf(3), it.next()); assertTrue(it.hasNext()); assertEquals(Integer.valueOf(0), it.next()); assertTrue(it.hasNext()); assertEquals(Integer.valueOf(1), it.next()); assertFalse(it.hasNext()); } @Test(expected = IllegalArgumentException.class) public void testTryToDisableCrossComponent() { Graph graph = new DirectedPseudograph<>(DefaultEdge.class); new TopologicalOrderIterator<>(graph).setCrossComponentTraversal(false); } } VertexTrackingTraversalListener.java000066400000000000000000000037571402514743400351050ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/traverse/* * (C) Copyright 2016-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.traverse; import org.jgrapht.*; import org.jgrapht.event.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * TraversalListener for testing basic graph traversal invariants */ public class VertexTrackingTraversalListener extends TraversalListenerAdapter { private Set verticesTraversed = new HashSet<>(); private Set verticesFinished = new HashSet<>(); private Graph graph; VertexTrackingTraversalListener(Graph graph) { this.graph = graph; } @Override public void vertexTraversed(VertexTraversalEvent e) { assertTrue(graph.containsVertex(e.getVertex())); verticesTraversed.add(e.getVertex()); } @Override public void vertexFinished(VertexTraversalEvent e) { assertTrue(graph.containsVertex(e.getVertex())); verticesFinished.add(e.getVertex()); } public Set getVerticesTraversed() { return verticesTraversed; } public Set getVerticesFinished() { return verticesFinished; } public void checkAllVerticesTraversed() { assertEquals(graph.vertexSet(), verticesTraversed); } public void checkAllVerticesFinished() { assertEquals(graph.vertexSet(), verticesFinished); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/util/000077500000000000000000000000001402514743400250755ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/util/AVLTreeTest.java000066400000000000000000000221621402514743400300450ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Timofey Chudakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.jgrapht.util.AVLTree.TreeNode; import static org.junit.Assert.*; /** * Tests for {@link AVLTree} * * @author Timofey Chudakov */ public class AVLTreeTest { private static final Random RANDOM = new Random(17L); @Test public void testEmpty() { AVLTree tree = new AVLTree<>(); assertEquals(0, tree.getSize()); assertNull(tree.getRoot()); } @Test public void testOneNode() { AVLTree tree = new AVLTree<>(); TreeNode node = tree.addMax(1); assertEquals(1, (int) node.getValue()); assertEquals(node, tree.getRoot()); assertEquals(node, node.getRoot()); assertEquals(node, node.getTreeMax()); assertEquals(node, node.getTreeMin()); assertEquals(node, tree.getMin()); assertEquals(node, tree.getMax()); assertEquals(1, node.getHeight()); assertEquals(1, tree.getSize()); } @Test public void testAddMax() { final int testNum = 50; for (int nodeNum = 0; nodeNum < testNum; nodeNum++) { AVLTree tree = new AVLTree<>(); for (int i = 0; i < nodeNum; i++) { tree.addMax(i); diagnostic(tree); } assertEquals(nodeNum, tree.getSize()); } } @Test public void testAddMin() { final int testNum = 50; for (int nodeNum = 0; nodeNum < testNum; nodeNum++) { AVLTree tree = new AVLTree<>(); for (int i = 0; i < nodeNum; i++) { tree.addMin(i); diagnostic(tree); } assertEquals(nodeNum, tree.getSize()); } } @Test public void testMergeAfter() { for (int leftSize = 0; leftSize < 50; leftSize++) { for (int rightSize = 0; rightSize < 50; rightSize++) { AVLTree left = new AVLTree<>(); AVLTree right = new AVLTree<>(); fillNodes(left, 0, leftSize); fillNodes(right, leftSize, leftSize + rightSize); left.mergeAfter(right); assertEquals(leftSize + rightSize, left.getSize()); assertEquals(0, right.getSize()); testTreeValueRange(left, 0, leftSize + rightSize); diagnostic(left); } } } @Test public void testMergeBefore() { for (int leftSize = 0; leftSize < 50; leftSize++) { for (int rightSize = 0; rightSize < 50; rightSize++) { AVLTree left = new AVLTree<>(); AVLTree right = new AVLTree<>(); fillNodes(left, 0, leftSize); fillNodes(right, leftSize, leftSize + rightSize); right.mergeBefore(left); testTreeValueRange(right, 0, leftSize + rightSize); diagnostic(right); } } } @Test public void testSplitAfter() { for (int treeSize = 1; treeSize < 50; treeSize++) { for (int split = 0; split < treeSize; split++) { AVLTree tree = new AVLTree<>(); List> nodes = fillNodes(tree, 0, treeSize); TreeNode splitNode = nodes.get(split); AVLTree right = tree.splitAfter(splitNode); testTreeValueRange(tree, 0, split + 1); testTreeValueRange(right, split + 1, treeSize); diagnostic(tree); diagnostic(right); } } } @Test public void testSplitBefore() { for (int treeSize = 1; treeSize < 50; treeSize++) { for (int split = 0; split < treeSize; split++) { AVLTree tree = new AVLTree<>(); List> nodes = fillNodes(tree, 0, treeSize); TreeNode splitNode = nodes.get(split); AVLTree right = tree.splitBefore(splitNode); testTreeValueRange(tree, 0, split); testTreeValueRange(right, split, treeSize); diagnostic(tree); diagnostic(right); } } } @Test public void testIterator() { for (int treeSize = 1; treeSize < 50; treeSize++) { AVLTree tree = new AVLTree<>(); List> nodes = fillNodes(tree, 0, treeSize); Iterator> iterator = tree.nodeIterator(); for (TreeNode expected : nodes) { assertTrue(iterator.hasNext()); TreeNode actual = iterator.next(); assertEquals(expected, actual); } assertFalse(iterator.hasNext()); } } private void testTreeValueRange(AVLTree tree, int from, int to) { assertEquals(to - from, tree.getSize()); Iterator> it = tree.nodeIterator(); for (int i = from; i < to; i++) { assertTrue(it.hasNext()); TreeNode node = it.next(); assertEquals(i, node.getValue().intValue()); } } private List> fillNodes(AVLTree tree) { final int nodeNum = 100; return fillNodes(tree, 0, nodeNum); } private List> fillNodes(AVLTree tree, int from, int to) { Deque> nodes = new ArrayDeque<>(); int middle = (from + to) / 2; Deque minValues = IntStream.range(from, middle).boxed().collect(Collectors.toCollection(ArrayDeque::new)); Deque maxValues = IntStream.range(middle, to).boxed().collect(Collectors.toCollection(ArrayDeque::new)); for (int i = from; i < to; i++) { int rand = RANDOM.nextInt(2); if ((rand == 0 && !minValues.isEmpty()) || maxValues.isEmpty()) { nodes.addFirst(tree.addMin(minValues.removeLast())); } else { nodes.addLast(tree.addMax(maxValues.removeFirst())); } diagnostic(tree); } return new ArrayList<>(nodes); } void diagnostic(AVLTree tree) { TreeNode root = tree.getRoot(); if (root != null) { TreeNode virtualRoot = root.getParent(); assertEquals(virtualRoot.getLeft(), root); diagnostic(virtualRoot.left); } } DiagnosticInfo diagnostic(TreeNode node) { if (node == null) { return new DiagnosticInfo(null, null, 0, 0); } DiagnosticInfo leftInfo = diagnostic(node.getLeft()); DiagnosticInfo rightInfo = diagnostic(node.getRight()); assertEquals(node.getHeight(), Math.max(leftInfo.height, rightInfo.height) + 1); assertEquals(node.getSubtreeSize(), leftInfo.size + rightInfo.size + 1); assertTrue(Math.abs(node.getLeftHeight() - node.getRightHeight()) < 2); if (node.getLeft() == null) { assertEquals(node.getSubtreeMin(), node); } else { assertEquals(node.getLeft().getParent(), node); assertEquals(node.getSubtreeMin(), leftInfo.subtreeMin); assertEquals(node.getPredecessor(), leftInfo.subtreeMax); assertEquals(leftInfo.subtreeMax.getSuccessor(), node); } if (node.getRight() == null) { assert node.getSubtreeMax() == node; } else { assertEquals(node.getRight().getParent(), node); assertEquals(node.getSubtreeMax(), rightInfo.subtreeMax); assertEquals(node.getSuccessor(), rightInfo.subtreeMin); assertEquals(rightInfo.subtreeMin.predecessor, node); } return new DiagnosticInfo( node.getSubtreeMin(), node.getSubtreeMax(), node.getHeight(), node.getSubtreeSize()); } private static class DiagnosticInfo { TreeNode subtreeMin; TreeNode subtreeMax; int height; int size; public DiagnosticInfo( TreeNode subtreeMin, TreeNode subtreeMax, int height, int size) { this.subtreeMin = subtreeMin; this.subtreeMax = subtreeMax; this.height = height; this.size = size; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/util/DoublyLinkedListTest.java000066400000000000000000001521261402514743400320300ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Hannes Wellmann and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import org.jgrapht.util.DoublyLinkedList.*; import org.junit.*; import org.junit.rules.*; import org.junit.runner.*; import org.junit.runners.*; import org.junit.runners.Parameterized.*; import java.util.*; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; /** * Tests for {@link DoublyLinkedList}. * * @author Hannes Wellmann * */ @RunWith(Parameterized.class) public class DoublyLinkedListTest { private static final int MAX_LIST_SIZE = 8; @Parameters(name = "List with size {0}") public static Object[] getListSizes() { Object[][] parameterSets = new Object[MAX_LIST_SIZE + 1][]; for (int size = 0; size < MAX_LIST_SIZE + 1; size++) { List elements = new ArrayList<>(size); for (int i = 0; i < size; i++) { elements.add("obj" + i); } if (size >= 6) { // make two elements equal elements.set(3, new String("obj2")); // create equal new String-object } parameterSets[size] = new Object[] { size, Collections.unmodifiableList(elements) }; } return parameterSets; } @Rule public ExpectedException thrown = ExpectedException.none(); @Parameterized.Parameter(0) public int size; @Parameterized.Parameter(1) public List expectedElements; private DoublyLinkedList list; @Before public void setUp() { list = createDoublyLinkedList(expectedElements); } // ------------------------------------------------------------------------ // test cases /** Test for {@link DoublyLinkedList#isEmpty()}. */ @Test public void testIsEmpty() { assertThat(list.isEmpty(), is(equalTo(size == 0))); } /** Test for {@link DoublyLinkedList#size()}. */ @Test public void testSize() { assertThat(list.size(), is(equalTo(size))); } /** Test for {@link DoublyLinkedList#clear()}. */ @Test public void testClear() { List> allNodes = getListNodesOfList(list); list.clear(); assertTrue(list.isEmpty()); assertThat(list.size(), is(equalTo(0))); for (ListNode listNode : allNodes) { assertFalse(list.containsNode(listNode)); } } // test ListNode methods /** Test for {@link DoublyLinkedList#addNodeFirst(DoublyLinkedList.ListNode)}. */ @Test public void testAddNodeFirst_freeNode_nodeAddedToList() { String element = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(0, element); ListNode node = createFreeListNode(element); list.addNodeFirst(node); assertThat(list.getFirstNode(), is(equalTo(node))); assertTrue(list.containsNode(node)); assertSameContent(list, expectedList); } @Test(expected = IllegalArgumentException.class) public void testAddNodeFirst_nodeInOtherList_IllegalArgumentException() { ListNode node = createListNodeInOtherList(); list.addNodeFirst(node); } @Test(expected = IllegalArgumentException.class) public void testAddNodeFirst_nodeOfThisList_IllegalArgumentException() { if (size == 0) { throw new IllegalArgumentException(); // throw expected exception to skip for empty list } ListNode node = list.getNode(size / 2); list.addNodeFirst(node); } /** Test for {@link DoublyLinkedList#addNodeLast(DoublyLinkedList.ListNode)}. */ @Test public void testAddNodeLast_freeNode_nodeAddedToList() { String element = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(size, element); ListNode node = createFreeListNode(element); list.addNodeLast(node); assertThat(list.getLastNode(), is(equalTo(node))); assertTrue(list.containsNode(node)); assertSameContent(list, expectedList); } @Test(expected = IllegalArgumentException.class) public void testAddNodeLast_nodeInOtherList_IllegalArgumentException() { ListNode node = createListNodeInOtherList(); list.addNodeLast(node); } @Test(expected = IllegalArgumentException.class) public void testAddNodeLast_nodeOfThisList_IllegalArgumentException() { if (size == 0) { throw new IllegalArgumentException(); // throw expected exception to skip for empty list } ListNode node = list.getNode(size / 2); list.addNodeLast(node); } /** Test for {@link DoublyLinkedList#addNode(int, DoublyLinkedList.ListNode)}. */ @Test public void testAddNode_freeNode_nodeAddedToList() { String element = "another"; int index = size / 2; List expectedList = new ArrayList<>(expectedElements); expectedList.add(index, element); ListNode node = createFreeListNode(element); list.addNode(index, node); assertTrue(list.containsNode(node)); assertSameContent(list, expectedList); } @Test(expected = IllegalArgumentException.class) public void testAddNode_nodeInOtherList_IllegalArgumentException() { ListNode node = createListNodeInOtherList(); list.addNode(size / 2, node); } @Test(expected = IllegalArgumentException.class) public void testAddNode_nodeOfThisList_IllegalArgumentException() { if (size == 0) { throw new IllegalArgumentException(); // throw expected exception to skip for empty list } ListNode node = list.getLastNode(); list.addNode(size / 2, node); } /** * Test for * {@link DoublyLinkedList#addNodeBefore(DoublyLinkedList.ListNode, DoublyLinkedList.ListNode)}. */ @Test public void testAddNodeBefore_freeNodeBeforeNodeInList_nodeAddedToList() { if (size == 0) { return; } String element = "another"; int index = size / 2; List expectedList = new ArrayList<>(expectedElements); expectedList.add(index, element); ListNode node = createFreeListNode(element); ListNode beforeNode = list.getNode(index); list.addNodeBefore(node, beforeNode); assertTrue(list.containsNode(node)); assertSameContent(list, expectedList); } @Test(expected = IllegalArgumentException.class) public void testAddNodeBefore_freeNodeBeforeNodeInOtherList_IllegalArgumentException() { ListNode node = createFreeListNode("another"); ListNode beforeNode = createListNodeInOtherList(); list.addNodeBefore(node, beforeNode); } @Test(expected = IllegalArgumentException.class) public void testAddNodeBefore_freeNodeBeforeFreeNode_IllegalArgumentException() { ListNode node = createFreeListNode("another"); ListNode beforeNode = createFreeListNode("another"); list.addNodeBefore(node, beforeNode); } @Test(expected = IllegalArgumentException.class) public void testAddNodeBefore_nodeInOtherListBeforeNodeOfList_IllegalArgumentException() { if (size == 0) { throw new IllegalArgumentException(); // throw expected exception to skip for empty list } ListNode node = createListNodeInOtherList(); ListNode beforeNode = list.getNode(size / 2); list.addNodeBefore(node, beforeNode); } @Test(expected = IllegalArgumentException.class) public void testAddNodeBefore_nodeInThisListBeforeNodeOfList_IllegalArgumentException() { if (size == 0) { throw new IllegalArgumentException(); // throw expected exception to skip for empty list } ListNode node = list.getFirstNode(); ListNode beforeNode = list.getNode(size / 2); list.addNodeBefore(node, beforeNode); } /** Test for {@link DoublyLinkedList#getFirstNode()}. */ @Test public void testGetFirstNode() { if (size == 0) { thrown.expect(NoSuchElementException.class); } ListNode firstNode = list.getFirstNode(); assertThat(firstNode, is(sameInstance(list.getNode(0)))); assertThat(firstNode.getValue(), is(sameInstance(expectedElements.get(0)))); } /** Test for {@link DoublyLinkedList#getLastNode()}. */ @Test public void testGetLastNode() { if (size == 0) { thrown.expect(NoSuchElementException.class); } ListNode firstNode = list.getLastNode(); assertThat(firstNode, is(sameInstance(list.getNode(size - 1)))); assertThat(firstNode.getValue(), is(sameInstance(expectedElements.get(size - 1)))); } /** Test for {@link DoublyLinkedList#getNode(int)}. */ @Test public void testGetNode() { for (int i = 0; i < size; i++) { String expectedElement = expectedElements.get(i); ListNode node = list.getNode(i); assertThat(node.getValue(), is(sameInstance(expectedElement))); } } @Test(expected = IndexOutOfBoundsException.class) public void testGetNode_indexSize_IndexOutOfBoundsException() { list.getNode(size); } @Test(expected = IndexOutOfBoundsException.class) public void testGetNode_indexNegative_IndexOutOfBoundsException() { list.getNode(-1); } /** Test for {@link DoublyLinkedList#indexOfNode(DoublyLinkedList.ListNode)}. */ @Test public void testIndexOfNode_nodeInList_indexOfNode() { if (size == 0) { return; } int index = size / 3; NodeIterator iterator = list.iterator(); for (int i = 0; i < index; i++) { iterator.nextNode(); // do not use getNode(int). Test another program path. } ListNode node = iterator.nextNode(); int indexOfNode = list.indexOfNode(node); assertThat(indexOfNode, is(equalTo(index))); } @Test public void testIndexOfNode_nodeInOtherList_minusOne() { ListNode node = createListNodeInOtherList(); int indexOfNode = list.indexOfNode(node); assertThat(indexOfNode, is(equalTo(-1))); } @Test public void testIndexOfNode_nodeInNoList_minusOne() { ListNode node = createFreeListNode("another"); int indexOfNode = list.indexOfNode(node); assertThat(indexOfNode, is(equalTo(-1))); } /** Test for {@link DoublyLinkedList#containsNode(DoublyLinkedList.ListNode)}. */ @Test public void testContainsNode_nodeInList_true() { if (size == 0) { return; } ListNode node = list.getNode(size / 3); boolean contains = list.containsNode(node); assertTrue(contains); } @Test public void testContainsNode_nodeInOtherList_false() { ListNode node = createListNodeInOtherList(); boolean contains = list.containsNode(node); assertFalse(contains); } @Test public void testContainsNode_nodeInNoList_false() { ListNode node = createFreeListNode("another"); boolean contains = list.containsNode(node); assertFalse(contains); } /** * Test for {@link DoublyLinkedList#removeNode(DoublyLinkedList.ListNode)}. */ @Test public void testRemoveNode_nodeInList_nodeRemoved() { if (size == 0) { return; } int index = size / 2; List expectedList = new ArrayList<>(expectedElements); expectedList.remove(index); ListNode node = list.getNode(index); boolean removed = list.removeNode(node); assertTrue(removed); assertSameContent(list, expectedList); assertFalse(list.containsNode(node)); if (size == 1) { assertTrue(list.isEmpty()); } } @Test public void testRemoveNode_nodeNotInList_listUnchanged() { ListNode nodeNotInList = createListNodeInOtherList(); boolean removed = list.removeNode(nodeNotInList); assertFalse(removed); assertSameContent(list, expectedElements); // ensure list did not change } @Test public void testRemoveNode_removeAllNodesInListFromFront_emptyList() { List> allNodes = getListNodesOfList(list); for (int i = 0; i < size; i++) { // remove first node in each iteration ListNode nodeToRemove = allNodes.remove(0); // test if head was updated correctly in previous remove assertThat(list.getFirstNode(), is(sameInstance(nodeToRemove))); boolean removed = list.removeNode(nodeToRemove); assertTrue(removed); assertSameNodes(list, allNodes); } assertThat(list.size(), is(equalTo(0))); assertTrue(list.isEmpty()); } @Test public void testRemoveNode_removeAllNodesInListFromEnd_emptyList() { List> allNodes = getListNodesOfList(list); for (int i = 0; i < size; i++) { // remove last node in each iteration ListNode nodeToRemove = allNodes.remove(allNodes.size() - 1); // test if tail was updated correctly in previous remove assertThat(list.getLastNode(), is(sameInstance(nodeToRemove))); boolean removed = list.removeNode(nodeToRemove); assertTrue(removed); assertSameNodes(list, allNodes); } assertThat(list.size(), is(equalTo(0))); assertTrue(list.isEmpty()); } @Test public void testRemoveNode_removeAllNodesInListFromMiddle_emptyList() { if (size == 0) { return; } List> allNodes = getListNodesOfList(list); ListNode head = allNodes.get(0); ListNode tail = allNodes.get(size - 1); for (int i = 0; i < size; i++) { // remove last node in each iteration int index = allNodes.size() / 2; ListNode nodeToRemove = allNodes.remove(index); // test if head and tail were updated correctly in previous remove assertThat(list.getFirstNode(), is(sameInstance(head))); assertThat(list.getLastNode(), is(sameInstance(tail))); if (!allNodes.isEmpty()) { // if empty this is the last loop if (index == 0) { head = allNodes.get(0); } if (index == allNodes.size()) { tail = allNodes.get(allNodes.size() - 1); } } boolean removed = list.removeNode(nodeToRemove); assertTrue(removed); assertSameNodes(list, allNodes); } assertThat(list.size(), is(equalTo(0))); assertTrue(list.isEmpty()); } /** Test for {@link DoublyLinkedList#nodeOf(Object)}. */ @Test public void testNodeOf_elementInList_nodeOfElement() { String obj2 = "obj2"; // equal String occurs twice in larger lists ListNode expectedNode = size <= 2 ? null : list.getNode(2); ListNode nodeOfElement = list.nodeOf(obj2); assertThat(nodeOfElement, is(sameInstance(expectedNode))); if (size > 2) { assertThat(nodeOfElement.getValue(), is(equalTo(obj2))); } } @Test public void testNodeOf_elementNotInList_null() { String otherElement = "another"; ListNode nodeOfElement = list.nodeOf(otherElement); assertThat(nodeOfElement, is(sameInstance(null))); } /** Test for {@link DoublyLinkedList#lastNodeOf(Object)}. */ @Test public void testLastNodeOf() { String obj2 = "obj2"; // equal String occurs twice in larger lists ListNode expectedNode; if (size <= 2) { expectedNode = null; } else if (size < 6) { expectedNode = list.getNode(2); } else { expectedNode = list.getNode(3); } ListNode nodeOfElement = list.lastNodeOf(obj2); assertThat(nodeOfElement, is(sameInstance(expectedNode))); if (size > 2) { assertThat(nodeOfElement.getValue(), is(equalTo(obj2))); } } /** Test for {@link DoublyLinkedList#addElementFirst(Object)}. */ @Test public void testAddElementFirst_nonNullValue_valueAdded() { String another = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(0, another); ListNode addedNode = list.addElementFirst(another); assertThat(addedNode, is(sameInstance(list.getFirstNode()))); assertTrue(list.containsNode(addedNode)); assertThat(addedNode.getValue(), is(sameInstance(another))); assertSameContent(list, expectedList); } @Test public void testAddElementFirst_nullValue_valueAdded() { // checks actually only ListNode-constructor, no need to test other addElementX()-methods String another = null; List expectedList = new ArrayList<>(expectedElements); expectedList.add(0, another); ListNode addedNode = list.addElementFirst(another); assertThat(addedNode, is(sameInstance(list.getFirstNode()))); assertTrue(list.containsNode(addedNode)); assertThat(addedNode.getValue(), is(sameInstance(another))); assertSameContent(list, expectedList); } /** Test for {@link DoublyLinkedList#addElementLast(Object)}. */ @Test public void testAddElementLast() { String another = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(size, another); ListNode addedNode = list.addElementLast(another); assertThat(addedNode, is(sameInstance(list.getLastNode()))); assertTrue(list.containsNode(addedNode)); assertThat(addedNode.getValue(), is(sameInstance(another))); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#addElementBeforeNode(DoublyLinkedList.ListNode, Object)}. */ @Test public void testAddElementBeforeNode_sucessorInList_ElementAdded() { if (size == 0) { thrown.expect(NullPointerException.class); } String another = "another"; int i = (int) (size / 2.5); List expectedList = new ArrayList<>(expectedElements); expectedList.add(i, another); ListNode nodeBefore = size > 0 ? list.getNode(i) : null; ListNode addedNode = list.addElementBeforeNode(nodeBefore, another); assertThat(addedNode, is(sameInstance(list.getNode(i)))); assertTrue(list.containsNode(addedNode)); assertThat(addedNode.getValue(), is(sameInstance(another))); assertSameContent(list, expectedList); } @Test(expected = IllegalArgumentException.class) public void testAddElementBeforeNode_sucessorInOtherList_IllegalStateException() { String another = "another"; ListNode nodeBefore = createListNodeInOtherList(); list.addElementBeforeNode(nodeBefore, another); } @Test(expected = IllegalArgumentException.class) public void testAddElementBeforeNode_sucessorInNoList_IllegalStateException() { String another = "another"; ListNode nodeBefore = createFreeListNode("another"); list.addElementBeforeNode(nodeBefore, another); } // test List methods /** Test for {@link DoublyLinkedList#add(int, Object)}. */ @Test public void testAddInt_atIndex0() { String anotherString = "another"; List expected = new ArrayList<>(expectedElements); expected.add(0, anotherString); list.add(0, anotherString); assertSameContent(list, expected); } /** Test for {@link DoublyLinkedList#add(int, Object)}. */ @Test public void testAddInt_inTheMiddle() { String anotherString = "another"; List expected = new ArrayList<>(expectedElements); expected.add(size / 2, anotherString); list.add(size / 2, anotherString); assertSameContent(list, expected); } /** Test for {@link DoublyLinkedList#add(int, Object)}. */ @Test public void testAddInt_atIndexSize() { String anotherString = "another"; List expected = new ArrayList<>(expectedElements); expected.add(size, anotherString); list.add(size, anotherString); assertSameContent(list, expected); } /** Test for {@link DoublyLinkedList#get(int)}. */ @Test public void testGetInt() { for (int i = 0; i < size; i++) { String expectedElement = expectedElements.get(i); String element = list.get(i); assertThat(element, is(sameInstance(expectedElement))); } } /** Test for {@link DoublyLinkedList#remove(int)}. */ @Test public void testRemoveInt_atIndex0() { if (size == 0) { thrown.expect(IndexOutOfBoundsException.class); } List expectedList = new ArrayList<>(expectedElements); String expectedRemoved = expectedList.remove(0); String removed = list.remove(0); assertThat(removed, is(sameInstance(expectedRemoved))); assertSameContent(list, expectedList); } /** Test for {@link DoublyLinkedList#remove(int)}. */ @Test public void testRemoveInt_inTheMiddle() { if (size == 0) { thrown.expect(IndexOutOfBoundsException.class); } List expectedList = new ArrayList<>(expectedElements); String expectedRemoved = expectedList.remove(size / 2); String removed = list.remove(size / 2); assertThat(removed, is(sameInstance(expectedRemoved))); assertSameContent(list, expectedList); } /** Test for {@link DoublyLinkedList#remove(int)}. */ @Test public void testRemoveInt_atIndexSizeMinusOne() { if (size == 0) { thrown.expect(IndexOutOfBoundsException.class); } List expectedList = new ArrayList<>(expectedElements); String expectedRemoved = expectedList.remove(size - 1); String removed = list.remove(size - 1); assertThat(removed, is(sameInstance(expectedRemoved))); assertSameContent(list, expectedList); } // test Deque methods /** * Test for {@link DoublyLinkedList#addFirst(Object)}. */ @Test public void testAddFirst() { String anotherString = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(0, anotherString); list.addFirst(anotherString); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#addLast(Object)}. */ @Test public void testAddLast() { String anotherString = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(size, anotherString); list.addLast(anotherString); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#offerFirst(Object)}. */ @Test public void testOfferFirst() { String anotherString = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(0, anotherString); list.offerFirst(anotherString); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#offerLast(Object)}. */ @Test public void testOfferLast() { String anotherString = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(size, anotherString); list.offerLast(anotherString); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#removeFirst()}. */ @Test public void testRemoveFirst() { if (size == 0) { thrown.expect(NoSuchElementException.class); } List expectedList = new ArrayList<>(expectedElements); String expectedFirst = size > 0 ? expectedList.remove(0) : null; String first = list.removeFirst(); assertThat(first, is(sameInstance(expectedFirst))); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#removeLast()}. */ @Test public void testRemoveLast() { if (size == 0) { thrown.expect(NoSuchElementException.class); } List expectedList = new ArrayList<>(expectedElements); String expectedLast = size > 0 ? expectedList.remove(size - 1) : null; String last = list.removeLast(); assertThat(last, is(sameInstance(expectedLast))); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#pollFirst()}. */ @Test public void testPollFirst() { List expectedList = new ArrayList<>(expectedElements); String expectedFirst = size > 0 ? expectedList.remove(0) : null; String first = list.pollFirst(); assertThat(first, is(sameInstance(expectedFirst))); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#pollLast()}. */ @Test public void testPollLast() { List expectedList = new ArrayList<>(expectedElements); String expectedLast = size > 0 ? expectedList.remove(size - 1) : null; String last = list.pollLast(); assertThat(last, is(sameInstance(expectedLast))); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#getFirst()}. */ @Test public void testGetFirst() { if (size == 0) { thrown.expect(NoSuchElementException.class); } String first = list.getFirst(); assertThat(first, is(sameInstance(expectedElements.get(0)))); assertSameContent(list, expectedElements); // ensure content did not change } /** * Test for {@link DoublyLinkedList#getLast()}. */ @Test public void testGetLast() { if (size == 0) { thrown.expect(NoSuchElementException.class); } String last = list.getLast(); assertThat(last, is(sameInstance(expectedElements.get(size - 1)))); assertSameContent(list, expectedElements); // ensure content did not change } /** Test for {@link DoublyLinkedList#peekFirst()}. */ @Test public void testPeekFirst() { String expectedFirst = size > 0 ? expectedElements.get(0) : null; String first = list.peekFirst(); assertThat(first, is(sameInstance(expectedFirst))); assertSameContent(list, expectedElements); // ensure content did not change } /** Test for {@link DoublyLinkedList#peekLast()}. */ @Test public void testPeekLast() { String expectedLast = size > 0 ? expectedElements.get(size - 1) : null; String last = list.peekLast(); assertThat(last, is(sameInstance(expectedLast))); assertSameContent(list, expectedElements); // ensure content did not change } /** Test for {@link DoublyLinkedList#removeFirstOccurrence(Object)}. */ @Test public void testRemoveFirstOccurrence() { boolean expectedRemoved = size >= 3; List expectedList = new ArrayList<>(expectedElements); if (size >= 3) { // if size < 2 no such element, list remains unchanged expectedList.remove(2); } boolean removed = list.removeFirstOccurrence("obj2"); assertThat(removed, is(equalTo(expectedRemoved))); assertSameContent(list, expectedList); } /** Test for {@link DoublyLinkedList#removeLastOccurrence(Object)}. */ @Test public void testRemoveLastOccurrence() { boolean expectedRemoved = size >= 3; List expectedList = new ArrayList<>(expectedElements); if (size >= 6) { // if size < 2 no such element, list remains unchanged expectedList.remove(3); } else if (size >= 3) { expectedList.remove(2); } boolean removed = list.removeLastOccurrence("obj2"); assertThat(removed, is(equalTo(expectedRemoved))); assertSameContent(list, expectedList); } // test Queue methods /** * Test for {@link DoublyLinkedList#offer(Object)}. */ @Test public void testOffer() { String anotherString = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(size, anotherString); list.offer(anotherString); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#remove()}. */ @Test public void testRemove() { if (size == 0) { thrown.expect(NoSuchElementException.class); } List expectedList = new ArrayList<>(expectedElements); String expectedFirst = size > 0 ? expectedList.remove(0) : null; String first = list.remove(); assertThat(first, is(sameInstance(expectedFirst))); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#poll()}. */ @Test public void testPoll() { List expectedList = new ArrayList<>(expectedElements); String expectedFirst = size > 0 ? expectedList.remove(0) : null; String first = list.poll(); assertThat(first, is(sameInstance(expectedFirst))); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#element()}. */ @Test public void testElement() { if (size == 0) { thrown.expect(NoSuchElementException.class); } String first = list.element(); assertThat(first, is(sameInstance(expectedElements.get(0)))); assertSameContent(list, expectedElements); // ensure content did not change } /** * Test for {@link DoublyLinkedList#peek()}. */ @Test public void testPeek() { String expectedFirst = size > 0 ? expectedElements.get(0) : null; String first = list.peek(); assertThat(first, is(sameInstance(expectedFirst))); assertSameContent(list, expectedElements); // ensure content did not change } // test Stack methods /** Test for {@link DoublyLinkedList#push(Object)}. */ @Test public void testPush() { String anotherString = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(0, anotherString); list.push(anotherString); assertSameContent(list, expectedList); } /** * Test for {@link DoublyLinkedList#pop()}. */ @Test public void testPop() { if (size == 0) { thrown.expect(NoSuchElementException.class); } List expectedList = new ArrayList<>(expectedElements); String expectedFirst = size > 0 ? expectedList.remove(0) : null; String first = list.pop(); assertThat(first, is(sameInstance(expectedFirst))); assertSameContent(list, expectedList); } // test special bulk methods /** * Test for {@link DoublyLinkedList#invert()}. */ @Test public void testInvert() { list.invert(); List expected = new ArrayList<>(expectedElements); Collections.reverse(expected); assertSameContent(list, expected); } /** * Test for {@link DoublyLinkedList#moveFrom(int, DoublyLinkedList)}. */ @Test public void testMoveFrom() { int index = size / 3; List other = size < 4 ? Collections.singletonList("another1") : Arrays.asList("another1", "another2"); List expectedList = new ArrayList<>(expectedElements); expectedList.addAll(index, other); DoublyLinkedList sourceList = createDoublyLinkedList(other); list.moveFrom(index, sourceList); assertSameContent(list, expectedList); assertThat(sourceList.size(), is(equalTo(0))); assertTrue(sourceList.isEmpty()); } /** * Test for {@link DoublyLinkedList#append(DoublyLinkedList)}. */ @Test public void testAppend() { List other = size < 4 ? Collections.singletonList("another1") : Arrays.asList("another1", "another2"); List expectedList = new ArrayList<>(expectedElements); expectedList.addAll(size, other); DoublyLinkedList sourceList = createDoublyLinkedList(other); list.append(sourceList); assertSameContent(list, expectedList); assertThat(sourceList.size(), is(equalTo(0))); assertTrue(sourceList.isEmpty()); } /** Test for {@link DoublyLinkedList#prepend(DoublyLinkedList)}. */ @Test public void testPrepend() { List other = size < 4 ? Collections.singletonList("another1") : Arrays.asList("another1", "another2"); List expectedList = new ArrayList<>(expectedElements); expectedList.addAll(0, other); DoublyLinkedList sourceList = createDoublyLinkedList(other); list.prepend(sourceList); assertSameContent(list, expectedList); assertThat(sourceList.size(), is(equalTo(0))); assertTrue(sourceList.isEmpty()); } // test iterator /** * Test for {@link DoublyLinkedList#circularIterator(Object)}. */ @Test public void testCircularIterator() { if (size == 0) { thrown.expect(NoSuchElementException.class); list.circularIterator("anything"); return; } int startIndex = size / 3; String firstElement = expectedElements.get(startIndex); List expectedList = new ArrayList<>(); expectedList.addAll(expectedElements.subList(startIndex, expectedElements.size())); expectedList.addAll(expectedElements.subList(0, startIndex)); NodeIterator wrappingIterator = list.circularIterator(firstElement); for (String expectedElement : expectedList) { assertTrue(wrappingIterator.hasNext()); assertThat(wrappingIterator.next(), is(sameInstance(expectedElement))); } assertFalse(wrappingIterator.hasNext()); } /** * Test for {@link DoublyLinkedList#reverseCircularIterator(Object)}. */ @Test public void testReverseCircularIterator() { if (size == 0) { thrown.expect(NoSuchElementException.class); list.reverseCircularIterator("anything"); return; } int startIndex = size / 3; List expectedList = new ArrayList<>(); String firstElement = expectedElements.get(startIndex); expectedList.addAll(expectedElements.subList(startIndex + 1, size)); expectedList.addAll(expectedElements.subList(0, startIndex + 1)); Collections.reverse(expectedList); NodeIterator wrappingIterator = list.reverseCircularIterator(firstElement); for (String expectedElement : expectedList) { assertTrue(wrappingIterator.hasNext()); assertThat(wrappingIterator.next(), is(sameInstance(expectedElement))); } assertFalse(wrappingIterator.hasNext()); } /** * Test for {@link DoublyLinkedList#descendingIterator()}. */ @Test public void testDescendingIterator() { NodeIterator iterator = list.descendingIterator(); for (int i = size - 1; i >= 0; i--) { assertThat(iterator.next(), is(sameInstance(expectedElements.get(i)))); } assertFalse(iterator.hasNext()); } /** * Test for {@link DoublyLinkedList#iterator()}. */ @Test public void testIterator() { NodeIterator iterator = list.iterator(); for (int i = 0; i < size; i++) { assertThat(iterator.next(), is(sameInstance(expectedElements.get(i)))); } assertFalse(iterator.hasNext()); } /** Test for {@link DoublyLinkedList#listIterator(Object)}. */ @Test public void testListIteratorE() { // test only if returned ListIterator starts expected position beginning. String element; if (size == 0) { thrown.expect(NoSuchElementException.class); element = null; } else { element = expectedElements.get(size / 3); } ListNodeIterator listIterator = list.listIterator(element); assertTrue(listIterator.hasNext()); ListNode firstNode = listIterator.nextNode(); assertThat(firstNode, is(sameInstance(list.getNode(size / 3)))); assertThat(firstNode.getValue(), is(sameInstance(element))); } /** Test for {@link DoublyLinkedList#listIterator(int)}. */ @Test public void testListIteratorInt_indexInTheMiddle_iteratorAtCorrectIndex() { // test only if returned ListIterator starts at the correct position. int index = size / 2; ListNodeIterator listIterator = list.listIterator(index); if (size == 0) { assertFalse(listIterator.hasNext()); assertFalse(listIterator.hasPrevious()); } else { assertThat(listIterator.nextIndex(), is(equalTo(index))); assertThat(listIterator.next(), is(sameInstance(expectedElements.get(index)))); } } /** Test for {@link DoublyLinkedList.ListNodeIterator#nextNode()}. */ @Test public void testListIteratorNext_iterateForwardTroughCompleteList_ListNodesInOrder() { // test only if returned ListIterator starts at the beginning. ListNodeIterator listIterator = list.listIterator(); List> listNodes = getListNodesOfList(list); assertFalse(listIterator.hasPrevious()); assertThat(listIterator.previousIndex(), is(equalTo(-1))); for (int i = 0; i < size; i++) { assertTrue(listIterator.hasNext()); assertThat(listIterator.nextIndex(), is(equalTo(i))); ListNode nextNode = listIterator.nextNode(); assertThat(nextNode, is(sameInstance(listNodes.get(i)))); assertThat(nextNode.getValue(), is(sameInstance(expectedElements.get(i)))); } assertFalse(listIterator.hasNext()); assertThat(listIterator.nextIndex(), is(equalTo(size))); } /** Test for {@link DoublyLinkedList.ListNodeIterator#previousNode()}. */ @Test public void testListIteratorPrevious_iterateBackwardTroughCompleteList_ListNodesInOrder() { // test only if returned ListIterator starts at the beginning. ListNodeIterator listIterator = list.listIterator(size); List> listNodes = getListNodesOfList(list); assertFalse(listIterator.hasNext()); assertThat(listIterator.nextIndex(), is(equalTo(size))); for (int i = size - 1; i >= 0; i--) { assertTrue(listIterator.hasPrevious()); assertThat(listIterator.previousIndex(), is(equalTo(i))); ListNode nextNode = listIterator.previousNode(); assertThat(nextNode, is(sameInstance(listNodes.get(i)))); assertThat(nextNode.getValue(), is(sameInstance(expectedElements.get(i)))); } assertFalse(listIterator.hasPrevious()); assertThat(listIterator.previousIndex(), is(equalTo(-1))); } @Test public void testListIteratorNextPrevious_forwardBackwardPattern_correctElements() { int index = size / 3; ListNodeIterator listIterator = list.listIterator(index); for (; index < size; index++) { assertTrue(listIterator.hasNext()); assertThat(listIterator.nextIndex(), is(equalTo(index))); assertThat(listIterator.next(), is(equalTo(expectedElements.get(index)))); // index++; assertTrue(listIterator.hasPrevious()); assertThat(listIterator.previousIndex(), is(equalTo(index))); assertThat(listIterator.previous(), is(equalTo(expectedElements.get(index)))); // index--; assertTrue(listIterator.hasNext()); assertThat(listIterator.nextIndex(), is(equalTo(index))); assertThat(listIterator.next(), is(equalTo(expectedElements.get(index)))); } assertFalse(listIterator.hasNext()); assertThat(listIterator.nextIndex(), is(equalTo(size))); } @Test(expected = NoSuchElementException.class) public void testListIterator_iterateBehindTail() { ListNodeIterator iterator = list.listIterator(); for (int i = 0; i < size; i++) { iterator.next(); } assertFalse(iterator.hasNext()); iterator.next(); } @Test(expected = NoSuchElementException.class) public void testListIterator_iterateBeforeHead() { ListNodeIterator iterator = list.listIterator(size); for (int i = 0; i < size; i++) { iterator.previous(); } assertFalse(iterator.hasPrevious()); iterator.previous(); } @Test(expected = ConcurrentModificationException.class) public void testListIterator_concurrentAdd_ConcurrentModificationException() { ListNodeIterator listIterator = list.listIterator(); list.add("another"); listIterator.next(); } @Test public void testListIterator_concurrentRemove_ConcurrentModificationException() { if (size == 0) { return; } thrown.expect(ConcurrentModificationException.class); ListNodeIterator listIterator = list.listIterator(); list.removeLast(); listIterator.next(); } /** Test for {@link DoublyLinkedList.ListNodeIterator#remove()}. */ @Test public void testListIteratorRemove_clearListFromTheFront_emptyList() { List> listNodes = getListNodesOfList(list); ListNodeIterator listIterator = list.listIterator(); for (int i = 0; i < size; i++) { ListNode next = listIterator.nextNode(); assertThat(next, is(sameInstance(listNodes.get(i)))); listIterator.remove(); } assertFalse(listIterator.hasNext()); assertThat(list.size(), is(equalTo(0))); assertTrue(list.isEmpty()); } @Test public void testListIteratorRemove_clearListFromTheMiddle_emptyList() { List> listNodes = getListNodesOfList(list); ListNodeIterator listIterator = list.listIterator(); for (int i = 0; i < size; i++) { ListNode next = listIterator.nextNode(); assertThat(next, is(sameInstance(listNodes.get(i)))); listIterator.remove(); } assertFalse(listIterator.hasNext()); assertThat(list.size(), is(equalTo(0))); assertTrue(list.isEmpty()); } @Test public void testListIteratorRemove_clearListFromTheEnd_emptyList() { List> listNodes = getListNodesOfList(list); ListNodeIterator listIterator = list.listIterator(size); for (int i = size - 1; i >= 0; i--) { ListNode next = listIterator.previousNode(); assertThat(next, is(sameInstance(listNodes.get(i)))); listIterator.remove(); } assertFalse(listIterator.hasPrevious()); assertThat(list.size(), is(equalTo(0))); assertTrue(list.isEmpty()); } @Test(expected = IllegalStateException.class) public void testListIteratorRemove_notMovedListIterator_IllegalStateException() { list.listIterator().remove(); } @Test public void testListIteratorRemove_removeTwiceAfterNext_IllegalStateException() { if (size == 0) { return; } ListNodeIterator listIterator = list.listIterator(); listIterator.next(); listIterator.remove(); // check if the last-node of the iterator is cleared correctly thrown.expect(IllegalStateException.class); listIterator.remove(); } @Test public void testListIteratorRemove_removeAfterAdd_IllegalStateException() { if (size == 0) { return; } ListNodeIterator listIterator = list.listIterator(); listIterator.next(); listIterator.add("Another"); // check if the last-node of the iterator is cleared correctly in add() thrown.expect(IllegalStateException.class); listIterator.remove(); } /** Test for {@link DoublyLinkedList.ListNodeIterator#add(Object)}. */ @Test public void testListIteratorAdd_addElementsAtFront_listWithAdditionalElements() { List toAdd = Arrays.asList("another1", "two", "three", "four"); List expectedList = new ArrayList<>(expectedElements); ListNodeIterator listIterator = list.listIterator(0); for (int i = 0; i < toAdd.size(); i++) { String add = toAdd.get(i); expectedList.add(i, add); listIterator.add(add); assertSameContent(list, expectedList); } } @Test public void testListIteratorAdd_addElementsInTheMiddle_listWithAdditionalElements() { List toAdd = Arrays.asList("another1", "two", "three", "four"); List expectedList = new ArrayList<>(expectedElements); int index = size / 2; ListNodeIterator listIterator = list.listIterator(index); for (int i = 0; i < toAdd.size(); i++) { String add = toAdd.get(i); expectedList.add(index + i, add); listIterator.add(add); assertSameContent(list, expectedList); } } @Test public void testListIteratorAdd_addElementsAtEnd_listWithAdditionalElements() { List toAdd = Arrays.asList("another1", "two", "three", "four"); List expectedList = new ArrayList<>(expectedElements); ListNodeIterator listIterator = list.listIterator(size); for (int i = 0; i < toAdd.size(); i++) { String add = toAdd.get(i); expectedList.add(add); listIterator.add(add); assertSameContent(list, expectedList); } } @Test public void testListIteratorAdd_addElementBeforeEnd_listWithAdditionalElements() { if (size == 0) { return; } String element = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.add(size - 1, element); ListNodeIterator listIterator = list.listIterator(size); listIterator.previous(); listIterator.add(element); assertSameContent(list, expectedList); } /** Test for {@link DoublyLinkedList.ListNodeIterator#set(Object)}. */ @Test public void testListIteratorSet_replaceElementInTheMiddle_listWithReplacedElement() { if (size == 0) { return; } int index = size / 2; String element = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.set(index, element); ListNodeIterator listIterator = list.listIterator(index); listIterator.next(); listIterator.set(element); assertSameContent(list, expectedList); } @Test public void testListIteratorSet_replaceElementAtFront_listWithReplacedElement() { if (size == 0) { return; } String element = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.set(0, element); ListNodeIterator listIterator = list.listIterator(0); listIterator.next(); listIterator.set(element); assertSameContent(list, expectedList); } @Test public void testListIteratorSet_replaceElementInAtEnd_listWithReplacedElement() { if (size == 0) { return; } String element = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.set(size - 1, element); ListNodeIterator listIterator = list.listIterator(size); listIterator.previous(); listIterator.set(element); assertSameContent(list, expectedList); } @Test public void testListIteratorSet_setElementWithSubsequentRemove_listWithReplacedElement() { // check if the last node of the iterator is configured right if (size == 0) { return; } int index = size / 2; String element = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.remove(index); ListNodeIterator listIterator = list.listIterator(index); listIterator.next(); listIterator.set(element); listIterator.remove(); assertSameContent(list, expectedList); } @Test public void testListIteratorSet_setTwice_listWithReplacedElement() { if (size == 0) { return; } int index = size / 2; String element = "another"; List expectedList = new ArrayList<>(expectedElements); expectedList.set(index, element); ListNodeIterator listIterator = list.listIterator(index); listIterator.next(); listIterator.set("anotherOne"); listIterator.set(element); assertSameContent(list, expectedList); } @Test(expected = IllegalStateException.class) public void testListIteratorSet_NotMovedListIterator_IllegalstateException() { ListNodeIterator listIterator = list.listIterator(); listIterator.set("another"); } @Test(expected = IllegalStateException.class) public void testListIteratorSet_setAfterAdd_IllegalstateException() { ListNodeIterator listIterator = list.listIterator(); listIterator.add("another"); listIterator.set("another"); } @Test public void testListIteratorSet_setAfterRemove_IllegalstateException() { if (size == 0) { return; } else { thrown.expect(IllegalStateException.class); } ListNodeIterator listIterator = list.listIterator(); listIterator.next(); listIterator.remove(); listIterator.set("another"); } // utility methods private static DoublyLinkedList createDoublyLinkedList(Collection content) { DoublyLinkedList list = new DoublyLinkedList<>(); for (E element : content) { list.addLast(element); // use simplest method as possible } return list; } private static List> getListNodesOfList(DoublyLinkedList list) { List> allNodes = new ArrayList<>(); for (NodeIterator iterator = list.iterator(); iterator.hasNext();) { allNodes.add(iterator.nextNode()); } return allNodes; } /** Returns a {@link ListNode} contained in another {@link DoublyLinkedList}. */ private static ListNode createListNodeInOtherList() { return createDoublyLinkedList(Collections.singletonList("another")).getNode(0); } /** Returns a {@link ListNode} contained in no {@link DoublyLinkedList}. */ private static ListNode createFreeListNode(String element) { DoublyLinkedList list = createDoublyLinkedList(Collections.singletonList(element)); ListNode node = list.getNode(0); list.removeNode(node); return node; } private static void assertSameContent(DoublyLinkedList list, List expected) { assertThat(list.size(), is(equalTo(expected.size()))); for (int i = 0; i < list.size(); i++) { assertThat(list.get(i), is(sameInstance(expected.get(i)))); } } private static void assertSameNodes(DoublyLinkedList list, List> expected) { assertThat(list.size(), is(equalTo(expected.size()))); for (int i = 0; i < list.size(); i++) { assertThat(list.getNode(i), is(sameInstance(expected.get(i)))); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/util/PrefetchIteratorTest.java000066400000000000000000000067111402514743400320570ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by Assaf Lehr and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class PrefetchIteratorTest { // ~ Methods ---------------------------------------------------------------- @Test public void testIteratorInterface() { Iterator iterator = new IterateFrom1To99(); for (int i = 1; i < 100; i++) { assertEquals(true, iterator.hasNext()); assertEquals(i, iterator.next().intValue()); } assertEquals(false, iterator.hasNext()); Exception exceptionThrown = null; try { iterator.next(); } catch (Exception e) { exceptionThrown = e; } assertTrue(exceptionThrown instanceof NoSuchElementException); } @Test public void testEnumInterface() { Enumeration enumuration = new IterateFrom1To99(); for (int i = 1; i < 100; i++) { assertEquals(true, enumuration.hasMoreElements()); assertEquals(i, enumuration.nextElement().intValue()); } assertEquals(false, enumuration.hasMoreElements()); Exception exceptionThrown = null; try { enumuration.nextElement(); } catch (Exception e) { exceptionThrown = e; } assertTrue(exceptionThrown instanceof NoSuchElementException); } // ~ Inner Classes ---------------------------------------------------------- // This test class supplies enumeration of integer from 1 till 100. public static class IterateFrom1To99 implements Enumeration, Iterator { private int counter = 0; private PrefetchIterator nextSupplier; public IterateFrom1To99() { nextSupplier = new PrefetchIterator<>(() -> { counter++; if (counter >= 100) { throw new NoSuchElementException(); } else { return counter; } }); } // forwarding to nextSupplier and return its returned value @Override public boolean hasMoreElements() { return this.nextSupplier.hasMoreElements(); } // forwarding to nextSupplier and return its returned value @Override public Integer nextElement() { return this.nextSupplier.nextElement(); } @Override public Integer next() { return this.nextSupplier.next(); } @Override public boolean hasNext() { return this.nextSupplier.hasNext(); } @Override public void remove() { this.nextSupplier.remove(); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/util/RadixSortTest.java000066400000000000000000000057151402514743400305270ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import org.jgrapht.*; import org.junit.*; import org.junit.experimental.categories.*; import java.util.*; import static org.junit.Assert.assertTrue; /** * Tests for the {@link RadixSort} class. * * @author Alexandru Valeanu */ public class RadixSortTest { /** * Check if the input list is sorted in ascending order. * * @param list the input list * @return true if the list is sorted in ascending order, false otherwise */ public static boolean isSorted(List list) { for (int i = 0; i < list.size() - 1; i++) { if (!(list.get(i) <= list.get(i + 1))) return false; } return true; } @Test public void testNullArray() { RadixSort.sort(null); } @Test public void testEmptyArray() { List list = new ArrayList<>(); RadixSort.sort(list); assertTrue(list.isEmpty()); } @Test public void testSmallArray() { List list = new ArrayList<>(); list.add(3); list.add(1); list.add(10); list.add(2); list.add(5); list.add(3); RadixSort.sort(list); assertTrue(isSorted(list)); } @Test public void testRandomHugeArray() { Random random = new Random(0x881); final int N = 1_000_000; List list = new ArrayList<>(N); for (int i = 0; i < N; i++) { list.add(random.nextInt(Integer.MAX_VALUE)); } RadixSort.sort(list); assertTrue(isSorted(list)); } @Test @Category(SlowTests.class) public void testRandomArrays() { testRandomArrays(new Random(0x88)); } @Test @Category(SlowTests.class) public void testRandomArraysWithNoFixedSeed() { testRandomArrays(new Random()); } private void testRandomArrays(Random random) { final int NUM_TESTS = 500_000; for (int test = 0; test < NUM_TESTS; test++) { final int N = 1 + random.nextInt(100); List list = new ArrayList<>(N); for (int i = 0; i < N; i++) { list.add(random.nextInt(Integer.MAX_VALUE)); } RadixSort.sort(list); assertTrue(isSorted(list)); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/util/StopWatch.java000066400000000000000000000025151402514743400276570ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by Assaf Lehr and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import java.util.concurrent.*; /** * A very simple stop watch. * * @author Assaf Lehr */ public class StopWatch { private long startTime; /** * Construct a new stop watch and start it. */ public StopWatch() { start(); } /** * Restart. */ public void start() { this.startTime = System.nanoTime(); } /** * Get the elapsed time from the last restart. * * @param timeUnit the time unit * @return the elapsed time in the given time unit */ public long getElapsed(TimeUnit timeUnit) { return timeUnit.convert(System.nanoTime() - startTime, TimeUnit.NANOSECONDS); } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/util/SupplierUtilTest.java000066400000000000000000000133631402514743400312470ustar00rootroot00000000000000/* * (C) Copyright 2021-2021, by Hannes Wellmann and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import org.jgrapht.graph.*; import org.jgrapht.util.SupplierUtil.*; import org.junit.*; import java.lang.reflect.*; import java.util.*; import java.util.function.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; import static org.jgrapht.graph.SerializationTestUtils.*; import static org.junit.Assert.*; public class SupplierUtilTest { @Test public void testAllPredefinedPublicSupplieres() throws Exception { for (Field publicField : SupplierUtil.class.getFields()) { if (publicField.getType() == Supplier.class) { Supplier supplier = (Supplier) publicField.get(null); try { testSupplier(supplier); } catch (Throwable e) { // enhance error message with supplier-field name throw new AssertionError("Test failed for " + publicField.getName(), e); } } } } @Test public void testCreateSupplier() throws Exception { @SuppressWarnings("rawtypes") Supplier supplier = SupplierUtil.createSupplier(ArrayList.class); testSupplier(supplier, new ArrayList<>()); } @Test public void testCreateSupplier_classWithoutNoArgumentConstructor() { // SimpleGraph has no no-argument constructor @SuppressWarnings("rawtypes") Supplier supplier = SupplierUtil.createSupplier(SimpleGraph.class); org.junit.Assert.assertThrows(SupplierException.class, () -> supplier.get()); } @Test public void testCreateDefaultEdgeSupplier() throws Exception { Supplier supplier = SupplierUtil.createDefaultEdgeSupplier(); testSupplier(supplier); } @Test public void testCreateDefaultWeightedEdgeSupplier() throws Exception { Supplier supplier = SupplierUtil.createDefaultWeightedEdgeSupplier(); testSupplier(supplier); } @Test public void testCreateIntegerSupplier() throws Exception { Supplier supplier = SupplierUtil.createIntegerSupplier(); testSupplier(supplier, 0, 1, 2, 3, 4); } @Test public void testCreateIntegerSupplierInt() throws Exception { Supplier supplier = SupplierUtil.createIntegerSupplier(4); testSupplier(supplier, 4, 5, 6, 7); } @Test public void testCreateLongSupplier() throws Exception { Supplier supplier = SupplierUtil.createLongSupplier(); testSupplier(supplier, 0L, 1L, 2L, 3L, 4L); } @Test public void testCreateLongSupplierLong() throws Exception { Supplier supplier = SupplierUtil.createLongSupplier(44); testSupplier(supplier, 44L, 45L, 46L, 47L); } @Test public void testCreateStringSupplier() throws Exception { Supplier supplier = SupplierUtil.createStringSupplier(); testSupplier(supplier, "0", "1", "2", "3", "4"); } @Test public void testCreateStringSupplierInt() throws Exception { Supplier supplier = SupplierUtil.createStringSupplier(44); testSupplier(supplier, "44", "45", "46", "47"); } @Test public void testCreateRandomUUIDStringSupplier() throws Exception { Supplier supplier = SupplierUtil.createRandomUUIDStringSupplier(); testSupplier(supplier); } @SafeVarargs private static void testSupplier(Supplier supplier, T... expectedValues) throws Exception { // Test that the supplier supplies the given sequence of expected values Set suppliedValues = new LinkedHashSet<>(); for (T expectedValue : expectedValues) { T value = supplier.get(); assertThat(value, is(equalTo(expectedValue))); assertTrue("Equal value supplied multiple times", suppliedValues.add(value)); suppliedValues.add(value); } Supplier deserializeSupplier = serializeAndDeserialize(supplier); for (int i = 0; i < TESTED_SUPPLIED_VALUES; i++) { T value1 = supplier.get(); T value2 = deserializeSupplier.get(); assertThat(value1, is(equalTo(value2))); } } private static final int TESTED_SUPPLIED_VALUES = 5; private static void testSupplier(Supplier supplier) throws Exception { // Test that the supplier supplies a sequence of distinct values Set suppliedValues = new LinkedHashSet<>(); while (suppliedValues.size() < TESTED_SUPPLIED_VALUES) { T value = supplier.get(); assertTrue("Equal value supplied multiple times", suppliedValues.add(value)); } Supplier deserializeSupplier = serializeAndDeserialize(supplier); for (int i = 0; i < TESTED_SUPPLIED_VALUES; i++) { T value1 = supplier.get(); T value2 = deserializeSupplier.get(); assertThat(value1, is(not(equalTo(value2)))); } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/util/UnmodifiableUnionSetTest.java000066400000000000000000000241611402514743400326670ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import org.junit.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.*; /** * Tests for {@link UnmodifiableUnionSet}. * * @author Dimitrios Michail */ public class UnmodifiableUnionSetTest { @Test public void test1() { UnmodifiableUnionSet union = new UnmodifiableUnionSet<>(Set.of(1, 2, 3, 4, 5), Set.of(1, 2, 3, 4, 5)); assertEquals(5, union.size()); IntStream.rangeClosed(1, 5).forEach(x -> assertTrue(union.contains(x))); IntStream.rangeClosed(6, 15).forEach(x -> assertFalse(union.contains(x))); } @Test public void test2() { UnmodifiableUnionSet union = new UnmodifiableUnionSet<>( Set.of(1, 2, 3, 4, 5), Set.of(6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); assertEquals(15, union.size()); IntStream.rangeClosed(1, 15).forEach(x -> assertTrue(union.contains(x))); IntStream.rangeClosed(16, 20).forEach(x -> assertFalse(union.contains(x))); } @Test public void test3() { UnmodifiableUnionSet union = new UnmodifiableUnionSet<>(Set.of(1, 2, 3, 4, 5), Set.of(3, 4, 5, 6, 7, 8, 9, 10, 20)); assertEquals(11, union.size()); IntStream.rangeClosed(1, 10).forEach(x -> assertTrue(union.contains(x))); IntStream.rangeClosed(11, 19).forEach(x -> assertFalse(union.contains(x))); IntStream.of(20).forEach(x -> assertTrue(union.contains(x))); } @Test public void test4() { UnmodifiableUnionSet union = new UnmodifiableUnionSet<>(new HashSet<>(), Set.of(1, 2, 3, 4, 5)); assertEquals(5, union.size()); IntStream.rangeClosed(1, 5).forEach(x -> assertTrue(union.contains(x))); IntStream.of(6).forEach(x -> assertFalse(union.contains(x))); } @Test public void test5() { UnmodifiableUnionSet union = new UnmodifiableUnionSet<>(new HashSet<>(), new HashSet<>()); assertEquals(0, union.size()); IntStream.rangeClosed(1, 5).forEach(x -> assertFalse(union.contains(x))); } @Test public void testIteratorDisjoint() { UnmodifiableUnionSet union = new UnmodifiableUnionSet<>( Set.of(1, 2, 3, 4, 5), Set.of(6, 7, 8, 9, 10, 11, 12, 13, 14, 15)); assertEquals(15, union.size()); List collectedElementsAsList = StreamSupport .stream(union.spliterator(), false).collect(Collectors.toCollection(ArrayList::new)); assertEquals(15, collectedElementsAsList.size()); Set collectedElementsAsSet = StreamSupport .stream(union.spliterator(), false).collect(Collectors.toCollection(HashSet::new)); assertEquals(15, collectedElementsAsSet.size()); IntStream.rangeClosed(1, 15).forEach(x -> assertTrue(collectedElementsAsList.contains(x))); IntStream.rangeClosed(1, 15).forEach(x -> assertTrue(collectedElementsAsSet.contains(x))); } @Test public void testIteratorCommonElements() { UnmodifiableUnionSet union = new UnmodifiableUnionSet<>(Set.of(1, 2, 3, 4, 5), Set.of(3, 4, 5, 6, 7, 8, 9, 10)); assertEquals(10, union.size()); List collectedElementsAsList = StreamSupport .stream(union.spliterator(), false).collect(Collectors.toCollection(ArrayList::new)); assertEquals(10, collectedElementsAsList.size()); Set collectedElementsAsSet = StreamSupport .stream(union.spliterator(), false).collect(Collectors.toCollection(HashSet::new)); assertEquals(10, collectedElementsAsSet.size()); IntStream.rangeClosed(1, 10).forEach(x -> assertTrue(collectedElementsAsList.contains(x))); IntStream.rangeClosed(1, 10).forEach(x -> assertTrue(collectedElementsAsSet.contains(x))); } @Test public void testOptimizations() { // per https://github.com/jgrapht/jgrapht/issues/757, verify // that constructor and contains do not require call to size on // underlying sets, and that size/iterator calls are optimized // based on the relative sizes of the underlying sets Set smallerHash = Set.of(1, 2, 3, 4, 5); ProfilingSet smaller = new ProfilingSet<>(smallerHash); Set biggerHash = Set.of(3, 4, 5, 6, 7, 8, 9, 10); ProfilingSet bigger = new ProfilingSet<>(biggerHash); UnmodifiableUnionSet union = new UnmodifiableUnionSet<>(smaller, bigger); verifyOptimizations(smaller, bigger, union); // repeat with smaller/bigger constructor parameters swapped UnmodifiableUnionSet swapped = new UnmodifiableUnionSet<>(bigger, smaller); verifyOptimizations(smaller, bigger, swapped); // now verify that if we dynamically resize the underlying data on // existing unions, the optimizations are still performed correctly ProfilingSet shrunk = bigger; ProfilingSet grown = smaller; shrunk.setDelegate(smallerHash); grown.setDelegate(biggerHash); verifyOptimizations(shrunk, grown, union); verifyOptimizations(shrunk, grown, swapped); } private void verifyOptimizations( ProfilingSet smaller, ProfilingSet bigger, UnmodifiableUnionSet union) { // verify that constructor did not make calls on // underlying sets (or that reused input sets were properly // precleared) verifyNoCalls(smaller, bigger); // likewise for contains assertTrue(union.contains(3)); assertFalse(union.contains(11)); verifyNoCalls(smaller, bigger); // verify optimizations for calls to size() verifySizeOptimizations(smaller, bigger, union); // repeat to verify that size checks are performed each time verifySizeOptimizations(smaller, bigger, union); // verify optimizations for iteration verifyIterationOptimizations(smaller, bigger, union); // and repeat verifyIterationOptimizations(smaller, bigger, union); smaller.clearCallCounts(); bigger.clearCallCounts(); } private void verifyNoCalls(ProfilingSet smaller, ProfilingSet bigger) { assertEquals(0, smaller.getSizeCallCount()); assertEquals(0, bigger.getSizeCallCount()); assertEquals(0, smaller.getIteratorCallCount()); assertEquals(0, bigger.getIteratorCallCount()); } private void verifySizeOptimizations( ProfilingSet smaller, ProfilingSet bigger, UnmodifiableUnionSet union) { smaller.clearCallCounts(); bigger.clearCallCounts(); assertEquals(10, union.size()); assertEquals(1, smaller.getSizeCallCount()); assertEquals(1, bigger.getSizeCallCount()); assertEquals(1, smaller.getIteratorCallCount()); assertEquals(0, bigger.getIteratorCallCount()); assertEquals(5, smaller.getIteratorNextCallCount()); assertEquals(0, bigger.getIteratorNextCallCount()); } private void verifyIterationOptimizations( ProfilingSet smaller, ProfilingSet bigger, UnmodifiableUnionSet union) { smaller.clearCallCounts(); bigger.clearCallCounts(); int count = 0; for (Integer i : union) { count++; } assertEquals(10, count); assertEquals(1, smaller.getSizeCallCount()); assertEquals(1, bigger.getSizeCallCount()); assertEquals(1, smaller.getIteratorCallCount()); assertEquals(1, bigger.getIteratorCallCount()); assertEquals(5, smaller.getIteratorNextCallCount()); assertEquals(8, bigger.getIteratorNextCallCount()); } /** * Set wrapper for counting calls to individual methods. */ private static class ProfilingSet extends AbstractSet { private Set delegate; private int iteratorCalls = 0; private int iteratorNextCalls = 0; private int sizeCalls = 0; ProfilingSet(Set delegate) { setDelegate(delegate); } void setDelegate(Set delegate) { this.delegate = delegate; } @Override public boolean contains(Object o) { return delegate.contains(o); } @Override public Iterator iterator() { iteratorCalls++; return new Iterator() { private Iterator delegateIterator = delegate.iterator(); public boolean hasNext() { return delegateIterator.hasNext(); } public E next() { iteratorNextCalls++; return delegateIterator.next(); } }; } @Override public int size() { sizeCalls++; return delegate.size(); } int getSizeCallCount() { return sizeCalls; } int getIteratorCallCount() { return iteratorCalls; } int getIteratorNextCallCount() { return iteratorNextCalls; } void clearCallCounts() { sizeCalls = 0; iteratorCalls = 0; iteratorNextCalls = 0; } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/java/org/jgrapht/util/VertexToIntegerMappingTest.java000066400000000000000000000052571402514743400332230ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Alexandru Valeanu and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.util; import org.junit.*; import java.util.*; import java.util.function.*; import java.util.stream.*; /** * Tests for {@link VertexToIntegerMapping} * * @author Alexandru Valeanu */ public class VertexToIntegerMappingTest { @Test(expected = NullPointerException.class) public void testNullSet() { VertexToIntegerMapping mapping = new VertexToIntegerMapping<>((Set) null); } @Test public void testEmptySet() { VertexToIntegerMapping mapping = new VertexToIntegerMapping<>(new HashSet<>()); Assert.assertTrue(mapping.getIndexList().isEmpty()); Assert.assertTrue(mapping.getVertexMap().isEmpty()); } @Test(expected = IllegalArgumentException.class) public void testNotUniqueElements() { VertexToIntegerMapping mapping = new VertexToIntegerMapping<>(Arrays.asList(1, 2, 1)); } @Test public void testRandomInstances() { Random random = new Random(0x88); final int NUM_TESTS = 1024; Supplier supplier = SupplierUtil.createStringSupplier(random.nextInt(100)); for (int test = 0; test < NUM_TESTS; test++) { final int N = 10 + random.nextInt(1024); Set vertices = IntStream.range(0, N).mapToObj(x -> supplier.get()).collect(Collectors.toSet()); VertexToIntegerMapping mapping = new VertexToIntegerMapping<>(vertices); Map vertexMap = mapping.getVertexMap(); List indexList = mapping.getIndexList(); Assert.assertEquals(N, vertexMap.size()); Assert.assertEquals(N, indexList.size()); for (int i = 0; i < indexList.size(); i++) { Assert.assertEquals(i, vertexMap.get(indexList.get(i)).intValue()); } for (Map.Entry entry : vertexMap.entrySet()) { Assert.assertEquals(indexList.get(entry.getValue()), entry.getKey()); } } } } jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/resources/000077500000000000000000000000001402514743400227635ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-core/src/test/resources/edges.txt000066400000000000000000000643141402514743400246230ustar00rootroot00000000000000M050 M044 7348.0 M044 M046 587.0 M050 M046 2382.0 M046 M047 25452.0 M046 M048 5403.0 M048 M047 8339.0 M046 M045 7623.0 M045 M034 630.0 M045 M050 2910.0 M045 M044 869.0 M050 M049 2772.0 M049 M048 16524.0 M049 M047 4052.0 M047 M045 3458.0 M045 M048 821.0 M049 M046 3479.0 M046 M034 2399.0 M034 M047 1249.0 M044 M043 3994.0 M043 M045 131.0 M049 M045 845.0 M043 M029 2417.0 M029 M037 4094.0 M037 M038 6474.0 M038 M029 446.0 M029 M028 5473.0 M028 M043 3627.0 M050 D004 326.0 M050 M048 1354.0 M034 M035 8966.0 M035 M045 270.0 M035 M046 1065.0 M035 M033 3490.0 M033 M034 3907.0 M047 M035 619.0 M047 M050 1246.0 M044 M049 398.0 D004 M043 548.0 M038 D005 1111.0 M038 M039 4273.0 M039 M040 3709.0 M040 M041 2813.0 M038 M034 280.0 M034 M039 60.0 M040 M034 48.0 M040 M038 420.0 M043 M042 3083.0 M042 M044 406.0 M042 M029 923.0 M028 M027 5461.0 M027 M026 4675.0 M026 M025 6314.0 M025 M024 7447.0 M024 M021 7366.0 M021 M019 4943.0 M019 M017 2793.0 M017 M016 18196.0 M017 M034 460.0 M017 M035 274.0 M033 M032 6008.0 M032 M017 524.0 M017 M036 112.0 M036 M030 5314.0 M030 D003 1119.0 D003 M029 877.0 M038 M017 282.0 M017 M039 102.0 M041 D006 882.0 D006 M017 32.0 M019 M018 6067.0 M018 M021 921.0 M024 D001 2778.0 D001 M025 2062.0 M025 M041 33.0 M041 D001 11.0 D006 M040 366.0 M039 M037 848.0 M029 M030 4588.0 M036 D003 551.0 D003 M031 188.0 M031 M032 5547.0 M032 M035 6168.0 M032 M036 3110.0 M032 M034 1225.0 M036 M031 4119.0 M018 M017 15410.0 M018 M051 6105.0 M051 M017 2309.0 M016 M015 14697.0 M015 M014 5045.0 M014 M013 6512.0 M013 M008 490.0 M008 M009 8147.0 M009 M007 5905.0 M009 M014 2699.0 M014 M008 1635.0 M009 M001 935.0 M001 M007 3726.0 M007 M023 1562.0 M023 M022 3727.0 M022 M021 5362.0 M024 M026 456.0 M037 D005 1038.0 M035 M036 2929.0 D005 M029 788.0 M036 M037 72.0 M041 M039 144.0 D005 M039 428.0 M036 M029 353.0 M033 M036 310.0 M028 M037 118.0 M034 M031 118.0 D003 M028 252.0 M018 D001 69.0 D001 M021 565.0 M023 M001 6888.0 M023 D012 560.0 D012 M022 273.0 M007 M002 2842.0 M001 M008 2679.0 M009 M015 2357.0 M042 D001 44.0 D001 M026 205.0 M034 M036 114.0 M043 M050 1211.0 M044 M028 355.0 M028 M042 550.0 M042 M030 49.0 M031 M035 1008.0 M031 M033 497.0 M031 M042 20.0 M029 M027 704.0 M029 M026 79.0 M018 D007 197.0 D007 M016 676.0 M014 M017 745.0 M017 M015 2659.0 M017 M008 462.0 M009 M017 872.0 M017 D007 2665.0 D007 M008 44.0 M008 M016 686.0 M013 M031 646.0 M013 M036 29.0 M033 M013 65.0 M013 M032 71.0 D001 M013 63.0 M031 M014 67.0 M018 M031 178.0 M031 M017 711.0 M022 M031 34.0 M031 M023 29.0 M001 M031 24.0 M031 M007 231.0 M007 M008 8224.0 M031 D007 2.0 M015 M008 4041.0 M015 M007 425.0 M002 M001 1105.0 D012 M001 217.0 M031 M008 99.0 M016 M031 195.0 M016 M018 1636.0 M051 M019 741.0 M015 M031 61.0 M023 M008 746.0 M051 M016 506.0 M013 M009 1795.0 M028 M031 83.0 M034 M013 186.0 M014 M032 55.0 M023 M009 287.0 M007 M022 221.0 M026 M031 62.0 M031 M027 68.0 M016 M014 999.0 M015 M013 687.0 D001 M031 94.0 M025 M021 854.0 M019 M031 82.0 M051 M031 50.0 M032 M019 54.0 M026 M036 23.0 M050 M029 157.0 M029 M044 260.0 M050 M028 155.0 M028 M030 199.0 M036 M049 122.0 M049 M030 73.0 M031 M044 173.0 M043 M046 233.0 M046 M032 508.0 M032 M047 544.0 M043 M032 52.0 M046 M031 1256.0 M036 M047 192.0 M049 M033 258.0 M036 M048 261.0 M048 M031 1427.0 M047 M031 1286.0 M030 M031 743.0 M031 M029 226.0 M037 M031 137.0 M031 M038 296.0 M041 M031 129.0 M039 M031 100.0 D006 M031 29.0 M036 M041 47.0 M031 M025 88.0 M031 M050 300.0 M016 M019 488.0 M038 M046 317.0 M036 M046 202.0 M038 D003 26.0 D003 M039 6.0 M030 M027 48.0 M035 M016 100.0 M009 M035 143.0 M035 M015 54.0 M014 M035 52.0 M016 M034 159.0 M034 M015 92.0 M018 M035 87.0 M007 M006 9992.0 M006 M008 1213.0 M009 M006 3177.0 M006 M035 468.0 M035 M007 107.0 M032 M006 735.0 M006 M033 329.0 M029 M006 106.0 M006 M028 77.0 M028 M007 70.0 M007 M027 115.0 M025 M006 234.0 M006 M024 169.0 M017 M002 193.0 M002 M003 3084.0 M003 M017 224.0 M017 M007 636.0 M017 M006 1455.0 M007 M014 492.0 M014 M006 352.0 M006 M002 1054.0 M002 M015 90.0 M016 M007 416.0 M017 D015 447.0 D015 M018 201.0 M013 M017 1420.0 M018 M013 473.0 M013 M016 683.0 M013 M007 403.0 M008 M019 105.0 M013 M001 274.0 M023 M013 302.0 M013 M022 148.0 M043 M013 106.0 M013 M050 41.0 M048 M013 227.0 M047 M013 239.0 M013 M046 229.0 M013 M045 56.0 M049 M013 75.0 M025 M013 293.0 M013 M024 183.0 M021 M013 242.0 M019 M020 1544.0 M020 M013 41.0 M013 M019 209.0 M007 M010 1046.0 M010 M006 4461.0 M018 M020 597.0 M017 D001 145.0 M026 M017 343.0 M017 M024 247.0 M022 M018 103.0 M051 M021 62.0 M026 M018 125.0 M017 M027 154.0 M046 M017 428.0 M017 M047 467.0 M047 M018 132.0 M051 M047 40.0 M017 M045 177.0 M048 M016 217.0 M017 M050 177.0 M050 M018 53.0 M025 M050 54.0 M048 D001 73.0 D001 M047 81.0 M047 M024 71.0 M003 M006 3200.0 M003 M004 595.0 M004 M005 714.0 M013 M012 1202.0 M012 M011 686.0 M011 D002 130.0 D002 M012 59.0 M006 M005 4192.0 M005 M003 718.0 M017 M021 407.0 M025 M017 796.0 M005 M008 273.0 M007 M005 907.0 M005 M009 1106.0 M009 M004 179.0 M007 M004 136.0 M004 M006 484.0 M004 M008 59.0 M004 M010 144.0 M010 M005 1171.0 M017 M005 654.0 M005 M018 168.0 M026 M028 96.0 M027 M037 44.0 M038 M005 138.0 M005 M039 42.0 M031 M005 247.0 M036 M005 47.0 M033 M005 73.0 M034 M005 270.0 M005 M032 191.0 M031 M004 79.0 M006 M031 985.0 M006 M036 161.0 M030 M037 108.0 M030 M032 320.0 M025 M004 16.0 M004 M024 14.0 M017 M004 111.0 M015 M005 174.0 M005 M016 335.0 M001 M022 996.0 D001 M005 55.0 M024 M019 361.0 M019 M025 245.0 M018 M024 136.0 M021 M016 180.0 M010 M009 2333.0 M008 M010 443.0 D006 M010 20.0 M006 M040 58.0 M015 M018 368.0 M019 M014 119.0 M020 M017 224.0 M011 M013 819.0 M015 M019 112.0 M024 M022 335.0 M017 D014 325.0 D014 M018 67.0 M016 D014 28.0 D014 D015 177.0 M022 M016 105.0 M016 M023 134.0 M001 M018 115.0 M003 M008 273.0 M008 M021 123.0 M021 M006 149.0 M009 M022 141.0 M022 M005 48.0 M005 M001 76.0 M023 M006 232.0 M005 M023 61.0 M004 M023 16.0 M001 M015 148.0 M024 M015 58.0 M009 M024 135.0 M024 M007 144.0 M007 M025 268.0 M024 M008 89.0 M025 M005 62.0 M005 M026 69.0 M026 M006 150.0 M006 M027 117.0 M029 M005 27.0 M005 M030 14.0 M032 M003 50.0 M017 M033 218.0 M033 M019 36.0 M020 M032 16.0 M032 M021 48.0 M015 M033 35.0 D005 M018 6.0 M018 M039 41.0 M040 M018 33.0 M018 M041 81.0 D006 M051 4.0 D006 M018 8.0 M041 M051 21.0 M017 M040 100.0 M038 M018 102.0 M030 M018 29.0 M036 M019 22.0 M025 M030 23.0 M028 M021 66.0 M021 M027 112.0 M027 M025 264.0 M028 M025 110.0 M024 M029 39.0 M029 M019 36.0 M019 M030 15.0 M018 M036 44.0 D003 M017 60.0 M025 M035 45.0 M021 M033 30.0 M033 M018 94.0 M021 M015 114.0 M022 M033 16.0 M033 M001 18.0 M001 M032 27.0 M035 M023 20.0 M003 M036 17.0 M036 M004 11.0 M028 M004 11.0 M004 M027 9.0 M027 M003 24.0 M002 M026 83.0 M026 M001 115.0 M008 M025 229.0 M025 M023 366.0 M023 M024 115.0 M009 D003 18.0 D003 M021 6.0 M051 M022 24.0 M019 D007 54.0 M020 M016 69.0 M025 M016 381.0 M017 M022 182.0 M023 M002 315.0 M005 M019 53.0 M021 M005 51.0 M021 M026 238.0 D001 D003 19.0 D003 M024 12.0 D003 M027 71.0 D003 M026 9.0 M035 M030 340.0 M029 M032 109.0 M050 M036 93.0 M047 M030 130.0 M047 D003 33.0 D003 M046 44.0 M050 M035 135.0 M032 M044 65.0 M043 M034 36.0 M033 M028 33.0 M049 M034 357.0 M047 M038 257.0 M038 M049 175.0 M038 M045 141.0 M046 M037 109.0 M039 M046 122.0 M045 M039 48.0 M048 M038 337.0 M040 M045 37.0 M047 M039 113.0 M050 M038 97.0 M038 M044 47.0 M044 M037 38.0 M037 M050 49.0 M039 M050 44.0 M045 M037 56.0 M050 D005 6.0 M035 M048 439.0 M034 M050 163.0 M049 M035 167.0 M039 M049 71.0 M049 M037 58.0 M030 M038 41.0 M041 M050 45.0 M046 M041 157.0 M046 M030 108.0 M046 M033 746.0 M031 M045 400.0 M033 M044 54.0 M044 D004 517.0 M038 M035 166.0 M032 M039 38.0 M041 M033 62.0 M033 D006 15.0 M032 M041 73.0 M041 M034 102.0 M030 M041 11.0 M035 D006 11.0 M038 M033 141.0 M036 M038 78.0 M045 M032 165.0 M031 M049 654.0 M036 M044 58.0 M044 M030 50.0 M030 M043 72.0 D003 M049 23.0 M030 M050 77.0 M048 M030 120.0 M047 M028 135.0 M047 M029 156.0 M029 M048 148.0 M047 M033 674.0 M046 M029 168.0 M043 M031 89.0 M031 D004 13.0 D004 M028 140.0 M025 M034 72.0 M025 M032 53.0 M026 M032 46.0 M033 M026 30.0 M029 M025 77.0 M025 D003 8.0 D004 M029 110.0 M009 D001 86.0 M008 M002 587.0 M003 M001 203.0 D006 M007 25.0 M041 M007 94.0 M018 M006 450.0 M017 M010 897.0 M010 M016 401.0 M006 M016 661.0 M010 M015 282.0 M051 M006 134.0 M013 M006 699.0 M010 M013 3450.0 M013 M026 274.0 M027 M013 247.0 M013 M028 102.0 D004 M013 4.0 M013 M042 568.0 M013 M044 17.0 M017 M048 592.0 M049 M018 70.0 M051 M049 11.0 M049 M017 249.0 M018 M048 131.0 M014 M049 20.0 M049 M015 22.0 M006 M048 1046.0 M048 M002 30.0 M025 M048 102.0 M048 M026 88.0 M029 M049 86.0 M041 M049 52.0 M049 M040 38.0 M040 M036 19.0 M048 M037 117.0 M032 M048 672.0 M048 M033 527.0 M049 M028 102.0 M028 M048 156.0 M049 M027 47.0 M026 M044 26.0 M044 M025 26.0 M019 M050 26.0 M046 M021 61.0 D003 M048 33.0 M043 D003 17.0 D003 M044 7.0 D003 M045 15.0 D003 M050 17.0 D001 M046 76.0 M046 M024 71.0 M049 M026 55.0 M032 M049 296.0 M045 M033 160.0 M045 M030 38.0 M050 M042 223.0 M038 M028 63.0 M046 M028 120.0 M048 M043 230.0 M045 M036 70.0 M034 M048 1021.0 M027 M047 95.0 M047 M026 78.0 M025 M046 89.0 M046 M026 63.0 M047 M025 93.0 M025 M045 38.0 M026 M045 46.0 M045 M024 26.0 M027 D004 87.0 D004 M026 11.0 M033 D004 9.0 M019 D001 153.0 M021 M020 229.0 M020 M047 20.0 M048 M024 52.0 M048 M044 414.0 M049 M043 161.0 M032 M028 51.0 M031 M024 130.0 M016 M009 998.0 M032 D001 49.0 M019 M022 197.0 M022 M015 60.0 M021 M001 134.0 M023 M017 251.0 M021 M031 82.0 M009 M031 391.0 M032 M008 64.0 M008 M036 42.0 M039 M001 14.0 M001 M040 16.0 M040 M009 28.0 M008 M040 21.0 M027 M040 14.0 M040 M028 9.0 M029 M039 46.0 M039 M027 18.0 M027 M038 59.0 M028 M039 23.0 M042 M027 80.0 D001 M027 54.0 M044 M024 31.0 M024 M043 51.0 M043 D001 20.0 D001 M044 12.0 D003 M032 131.0 M016 M036 52.0 M050 M032 138.0 M043 M047 185.0 D004 M046 64.0 M034 M037 83.0 D001 M034 142.0 M036 M028 92.0 M013 M030 16.0 M038 M041 110.0 D006 M038 20.0 D006 M039 15.0 M006 M015 352.0 M001 M006 424.0 M010 D001 56.0 M024 M010 107.0 M010 M025 168.0 M003 M010 299.0 M037 M010 49.0 M010 M038 94.0 M034 M010 235.0 M008 M034 70.0 M017 M029 109.0 M028 M017 115.0 M027 M019 57.0 M051 M028 12.0 M037 M019 31.0 M019 M038 35.0 D005 M017 27.0 M040 M016 38.0 M016 M041 84.0 M041 M015 33.0 M018 M008 178.0 M009 M019 120.0 M019 M007 123.0 D006 M021 4.0 M023 M041 30.0 M041 M001 34.0 M003 M007 899.0 M006 M041 192.0 M006 D006 31.0 M007 M036 46.0 M006 D001 134.0 M010 M031 326.0 M032 M007 135.0 M006 D003 36.0 M023 M021 271.0 M041 M010 81.0 M018 M010 258.0 M040 M007 31.0 M009 D006 14.0 M010 M040 25.0 M038 M009 81.0 M019 M006 136.0 M006 M020 42.0 M021 M007 181.0 M020 M007 41.0 M022 M006 103.0 M002 M005 162.0 M002 M018 67.0 M005 M027 55.0 M046 M003 32.0 M003 M045 12.0 M045 M002 12.0 M008 M047 53.0 M046 M005 55.0 M043 M005 19.0 M007 D004 5.0 M045 M005 33.0 M045 M008 17.0 M022 M045 7.0 M045 M021 20.0 M027 M045 25.0 M045 M028 37.0 M029 M007 64.0 M007 M037 59.0 M037 M008 38.0 M008 M038 51.0 M038 M015 51.0 M041 M017 178.0 M017 M044 92.0 M044 M008 16.0 M041 M005 51.0 M005 M047 51.0 M047 M041 186.0 M005 D006 6.0 M005 M049 38.0 M050 M005 29.0 M005 M037 49.0 M037 M006 127.0 M006 M038 388.0 M007 D005 18.0 D005 M008 10.0 M009 M029 50.0 M028 M015 43.0 M016 M027 84.0 M026 M016 181.0 M015 M025 207.0 M007 D001 74.0 M030 M016 20.0 M016 M029 55.0 M017 M030 58.0 M018 M032 177.0 M018 M025 189.0 M029 M035 88.0 M038 M032 141.0 M040 M033 25.0 M031 M040 53.0 D001 M035 50.0 M033 D001 18.0 M024 M033 48.0 M032 M024 51.0 M024 M035 45.0 M035 M021 39.0 M022 M036 22.0 M036 M023 14.0 M035 M022 16.0 M026 M035 26.0 M036 M027 27.0 M043 M036 53.0 M027 M035 40.0 M036 M025 40.0 M030 M024 23.0 M021 M029 40.0 M037 M018 54.0 M037 M020 13.0 M020 M039 5.0 M041 M019 40.0 M039 M016 39.0 M037 M017 134.0 M013 M038 155.0 M013 M037 64.0 M038 M021 33.0 M025 M037 40.0 M037 M026 24.0 M047 M037 110.0 M047 D005 24.0 M039 M033 35.0 M035 M039 34.0 M037 M035 61.0 M033 M037 50.0 M037 M032 61.0 M025 M022 203.0 M025 M009 303.0 M009 M027 113.0 D015 M016 45.0 M027 M018 65.0 M043 D015 2.0 D015 M044 4.0 M050 D015 6.0 M046 M051 30.0 M019 M046 45.0 M038 M016 156.0 M037 M016 54.0 M045 M016 77.0 M046 M016 175.0 M014 M047 45.0 M046 M014 67.0 M014 M045 21.0 M014 M050 14.0 M043 M015 21.0 M028 M016 62.0 M051 M024 36.0 M014 M018 221.0 M013 M051 115.0 M046 M009 326.0 M009 M047 249.0 M033 M008 67.0 M029 M033 67.0 M033 M030 62.0 D004 M048 34.0 M042 M047 43.0 M047 M016 177.0 D004 M042 84.0 M034 M044 86.0 M042 M037 102.0 M041 D005 8.0 M035 M010 97.0 M039 M006 91.0 M009 M041 64.0 M010 M002 155.0 M047 M007 250.0 M006 M046 854.0 M007 M046 253.0 M050 M007 64.0 M006 M045 192.0 M022 M008 148.0 M051 D003 4.0 D003 M018 18.0 M016 D003 19.0 M007 D003 22.0 M008 D003 8.0 M009 M002 356.0 M005 D003 20.0 M026 M014 123.0 M043 M009 34.0 M009 M044 34.0 M050 M008 18.0 M047 M006 876.0 M009 M048 142.0 D003 M015 3.0 M018 M009 283.0 M014 M005 239.0 M013 M005 359.0 M051 M005 57.0 M028 M005 28.0 D002 M005 11.0 M005 M012 50.0 M006 M011 239.0 M011 M003 95.0 M003 M012 43.0 M012 M002 41.0 M017 M012 80.0 M017 M011 173.0 M015 M012 51.0 M011 M016 121.0 D001 M016 79.0 M018 M012 23.0 M012 M014 273.0 M014 M011 267.0 M011 M008 91.0 M004 M012 6.0 M011 M005 310.0 M009 D002 11.0 M012 M009 82.0 M009 M011 181.0 M008 D002 7.0 M014 D015 11.0 D015 M008 5.0 M007 D015 3.0 D002 M013 49.0 M008 M051 67.0 M051 M007 66.0 M010 M001 146.0 M001 M016 153.0 M016 M002 145.0 M015 M003 92.0 M027 M002 35.0 M009 M028 72.0 M028 M010 57.0 M010 M029 35.0 M030 M006 58.0 M032 M009 146.0 M009 M033 125.0 M033 M007 87.0 M010 M032 101.0 M005 M035 132.0 M009 M036 44.0 M002 M004 122.0 M002 M032 31.0 M022 M032 25.0 M002 D013 24.0 M003 M019 29.0 M047 M044 407.0 M027 M034 37.0 M034 M026 45.0 M006 M034 810.0 M034 M002 58.0 M008 M035 43.0 M031 M002 54.0 M003 M009 379.0 M009 M026 145.0 M022 M010 103.0 M003 M025 67.0 M003 D001 22.0 M051 M003 24.0 M003 M016 128.0 M010 M026 141.0 M051 M009 71.0 M010 M012 132.0 M014 M010 623.0 M039 M010 26.0 D003 M035 190.0 M010 M027 126.0 M034 M009 221.0 M034 M007 148.0 M023 M010 145.0 M034 D005 10.0 D004 M047 32.0 M046 M040 90.0 D006 M046 30.0 M047 D006 28.0 M038 M043 54.0 M040 M037 49.0 M027 M032 46.0 M015 M032 62.0 M032 M016 183.0 M033 M020 7.0 M045 M029 57.0 M024 M034 56.0 M022 M014 90.0 M041 M045 56.0 M051 M035 24.0 M037 M041 23.0 M024 M036 33.0 M019 M035 35.0 M021 M034 47.0 M017 D010 20.0 M018 M023 107.0 M034 M028 27.0 M044 M027 45.0 M001 M024 84.0 M017 D009 5.0 M036 M021 30.0 M033 M002 13.0 M004 M033 20.0 M033 M023 10.0 D006 M034 19.0 D005 M028 21.0 M008 D001 49.0 D001 M001 55.0 M015 D001 26.0 M019 M001 111.0 M009 M021 148.0 M027 M014 75.0 M014 M028 66.0 M047 M012 9.0 M044 M014 4.0 M014 M043 45.0 M011 M026 30.0 M025 M012 21.0 M024 M011 29.0 M011 M021 31.0 M014 M020 36.0 M017 M001 224.0 M019 M028 39.0 M029 M018 50.0 M030 D005 26.0 M033 M027 32.0 M035 M043 45.0 M040 M047 67.0 M041 M026 22.0 M024 M039 13.0 M026 D005 9.0 M036 M042 28.0 M044 M035 82.0 M049 M025 57.0 M049 M024 39.0 M019 M049 33.0 M049 M021 34.0 M016 M024 132.0 M051 M015 75.0 M021 M002 68.0 M005 M024 43.0 M051 M010 77.0 M003 M033 30.0 M029 M003 18.0 M003 M026 53.0 M002 M013 165.0 M029 M013 69.0 M018 M007 232.0 M022 M002 52.0 M010 M021 123.0 M023 M019 110.0 M001 M025 228.0 M001 M027 45.0 D005 M001 5.0 M001 M038 40.0 M041 M008 40.0 M023 M015 103.0 M003 M014 123.0 M011 M007 154.0 M019 M010 94.0 M051 M025 57.0 M022 M003 50.0 M010 M011 778.0 M003 D002 2.0 D002 M006 9.0 M026 M007 161.0 M027 M043 234.0 M051 M001 32.0 M027 M050 59.0 M048 M007 209.0 M021 M003 45.0 M041 M002 32.0 M003 D006 8.0 M038 M002 34.0 M009 M037 34.0 D005 M009 19.0 M003 M018 63.0 M018 M028 48.0 M015 M020 39.0 M051 M020 38.0 M051 M002 18.0 M018 M004 45.0 M016 M004 78.0 M015 M004 53.0 M009 M020 33.0 M019 M004 13.0 M025 M014 258.0 M017 M043 95.0 M049 M016 103.0 M030 M007 16.0 M010 M036 46.0 M033 M010 109.0 M014 M034 101.0 M002 M035 23.0 M003 M034 115.0 M023 M034 20.0 M001 M034 25.0 M034 M004 52.0 M024 M003 51.0 M015 M026 101.0 M019 M026 80.0 M034 M018 180.0 M049 D004 44.0 D003 M037 90.0 D002 M010 5.0 M045 M007 69.0 M005 D005 6.0 M007 M039 36.0 M009 M049 75.0 D005 D003 12.0 M023 M003 101.0 M035 M041 64.0 M018 M046 123.0 D001 M039 13.0 M049 M006 294.0 M046 M027 82.0 D006 M048 23.0 M048 M041 144.0 M045 M018 45.0 M051 M048 41.0 M051 M045 17.0 M007 M049 103.0 M008 M048 56.0 M019 M048 61.0 D014 M048 5.0 M015 M046 54.0 M046 M008 59.0 D014 M051 9.0 M051 M032 54.0 M003 M031 54.0 D004 M037 14.0 M029 M034 66.0 M023 D001 53.0 M021 M014 124.0 M014 M024 90.0 M020 M025 17.0 M008 M012 55.0 M001 M014 176.0 M014 M023 159.0 D001 M020 10.0 M020 M024 21.0 M011 M015 115.0 M020 M026 21.0 M024 M028 92.0 M042 M018 36.0 M002 M043 17.0 D007 M015 98.0 D012 M007 47.0 M009 D007 33.0 M014 D007 49.0 M042 M014 76.0 M019 M011 41.0 M011 M020 9.0 M020 M010 18.0 M013 D007 211.0 M051 M014 67.0 M012 M019 17.0 M014 D014 6.0 D014 M013 4.0 M024 M027 134.0 M026 M043 62.0 M032 M042 23.0 M042 M033 19.0 M043 M033 33.0 M010 M047 198.0 M046 M010 154.0 M045 M010 23.0 M048 M010 87.0 M040 M048 48.0 M042 M048 117.0 M035 M001 20.0 M042 M034 16.0 D003 M040 6.0 M041 D003 10.0 D005 M049 9.0 D004 M039 2.0 D006 M036 8.0 M032 M040 24.0 M014 M039 14.0 M029 M014 31.0 M050 M015 29.0 M021 M048 43.0 M049 D001 35.0 M048 M014 38.0 M010 D003 7.0 M022 M026 125.0 M022 M027 55.0 M029 M022 24.0 M022 M043 19.0 M050 M001 6.0 M023 M046 18.0 M047 M002 33.0 M003 M037 11.0 M037 M002 21.0 D006 M002 4.0 M050 M026 45.0 M034 M019 41.0 D004 M017 10.0 M045 M019 25.0 M037 D001 12.0 D001 M038 27.0 D002 M014 14.0 D014 M021 3.0 D014 M024 4.0 M025 M043 92.0 M024 M050 30.0 M021 M030 12.0 M030 M022 3.0 M022 D001 69.0 M024 M002 59.0 M013 M003 139.0 M026 M008 129.0 M027 M015 45.0 M003 M047 35.0 M051 M050 16.0 M010 M030 18.0 M028 M035 44.0 M033 M011 14.0 M011 M035 12.0 M033 D002 3.0 D002 M007 6.0 M032 M011 13.0 M036 D001 12.0 M032 M023 20.0 M006 M012 52.0 M051 M033 40.0 M009 M030 17.0 M050 M006 159.0 M030 M015 10.0 M015 M029 38.0 M009 M050 47.0 M043 M007 35.0 M007 M044 36.0 M001 M004 16.0 M008 M027 81.0 M008 M030 27.0 M022 M037 16.0 M037 M023 22.0 M039 M008 20.0 M037 M004 8.0 M028 M002 21.0 D005 M015 12.0 M014 M038 38.0 M038 M007 135.0 M037 M014 19.0 M011 M038 26.0 M015 D006 11.0 D006 M008 10.0 M041 M013 97.0 M002 M025 159.0 M016 D006 13.0 M040 M014 12.0 M051 M038 14.0 M016 M012 51.0 M002 M014 140.0 D005 M003 8.0 M003 M039 9.0 M041 M003 25.0 M003 M040 9.0 M038 M003 28.0 M026 M051 23.0 M029 M008 43.0 M028 M022 45.0 M029 D001 18.0 M002 M040 10.0 M014 M041 34.0 M009 M039 37.0 M028 M008 63.0 M008 M043 49.0 M014 D006 5.0 M015 M037 27.0 M040 M015 10.0 M012 M038 10.0 M026 D002 1.0 D002 M025 5.0 M012 M007 53.0 M012 M051 8.0 M018 M011 82.0 M023 M027 61.0 M011 M043 16.0 M043 M019 26.0 M015 M044 9.0 M046 M001 22.0 M001 M048 19.0 M015 M047 60.0 M019 M047 53.0 M048 M015 69.0 M044 M016 35.0 M016 M050 73.0 M043 M021 38.0 M019 D005 13.0 M038 M025 44.0 M043 M018 36.0 M051 M027 17.0 M029 M012 3.0 M011 M028 20.0 M024 M037 33.0 M029 M023 26.0 M026 M038 57.0 M041 M027 33.0 M004 D006 3.0 D006 M026 12.0 M024 M041 27.0 M024 D006 3.0 M024 M040 9.0 M024 D015 2.0 D015 M038 3.0 M021 M047 39.0 M021 M004 16.0 M045 M009 82.0 M049 M008 30.0 M049 M010 47.0 M004 M022 18.0 M004 M032 37.0 M010 M050 18.0 M030 M034 48.0 M023 M026 146.0 M033 M050 96.0 M042 M046 38.0 M032 D004 10.0 M039 M030 7.0 M039 M048 85.0 M002 M039 11.0 M040 M005 25.0 D005 M006 18.0 M042 M007 63.0 M043 M016 31.0 M040 M029 16.0 M029 M041 24.0 M041 M028 25.0 M049 D006 12.0 D014 M036 1.0 M036 D015 4.0 M028 M001 44.0 M022 M044 7.0 M044 M021 17.0 M021 M050 27.0 M002 M050 9.0 M003 M044 10.0 M043 M004 8.0 M023 D002 2.0 M049 M001 7.0 M046 M022 21.0 M049 M023 8.0 M022 M049 13.0 M048 M023 28.0 M001 M045 7.0 M045 M023 8.0 M023 M044 10.0 M044 M001 9.0 M050 M023 7.0 M001 M047 20.0 M037 M001 21.0 M023 M038 36.0 M023 M039 12.0 M023 D006 3.0 M028 M023 38.0 M006 M043 72.0 M043 M023 41.0 M022 M050 10.0 M001 M029 34.0 D001 M002 16.0 D003 M033 7.0 M049 M042 32.0 M037 M043 144.0 M038 M042 36.0 M041 M043 22.0 M027 M048 77.0 D006 M019 8.0 M041 M021 29.0 M043 M040 11.0 M030 M023 10.0 D001 D004 8.0 D004 M045 36.0 M035 M020 6.0 M035 M040 28.0 M040 D001 5.0 M025 M039 25.0 M039 M026 14.0 M026 M040 15.0 M025 D004 9.0 M012 D001 7.0 M028 D001 31.0 M020 M012 6.0 M014 M004 60.0 D015 M013 9.0 M030 M011 6.0 M011 M029 15.0 M011 M045 2.0 M046 M012 11.0 M011 M048 10.0 M011 M047 6.0 M041 M012 2.0 M039 M013 40.0 D005 M016 13.0 M022 M011 17.0 M036 M001 11.0 M035 M013 151.0 M036 M014 27.0 M012 M035 12.0 M027 M012 21.0 M012 M026 16.0 M014 D005 5.0 M014 D001 28.0 M019 D002 3.0 M002 M046 42.0 M049 M002 11.0 M023 M047 23.0 M047 M022 17.0 D004 M009 7.0 M045 D005 10.0 D006 M025 6.0 M046 D005 23.0 M042 M045 24.0 M020 M046 16.0 M015 M045 20.0 M012 M045 1.0 M045 D002 2.0 M046 M011 17.0 M035 D004 11.0 M042 M026 61.0 M020 M031 19.0 M016 D002 10.0 M011 M051 22.0 M026 M030 17.0 M044 M005 22.0 D005 M040 3.0 M030 M051 4.0 M033 M016 92.0 M044 M039 21.0 M040 M050 23.0 M035 M042 17.0 M033 D005 6.0 D005 M032 3.0 M042 M006 146.0 M006 M044 72.0 M043 M039 19.0 M051 M042 5.0 M051 M043 8.0 M041 M042 25.0 M040 M042 4.0 M030 M040 5.0 M025 M040 12.0 M040 M022 6.0 M019 M042 20.0 M021 M037 20.0 M024 M038 43.0 M042 M039 14.0 M015 M039 10.0 D005 M023 6.0 M036 M051 12.0 M020 M022 25.0 M022 D007 16.0 M023 D007 57.0 D007 M001 29.0 M011 M001 38.0 M042 M016 51.0 M013 D012 53.0 M025 M042 189.0 M026 D007 58.0 D007 M025 145.0 M042 M009 66.0 M051 M023 38.0 M006 D012 5.0 M042 M023 49.0 D014 M023 3.0 D014 M019 11.0 D004 M001 1.0 M020 M048 13.0 M051 D007 19.0 M020 M023 43.0 M024 M012 18.0 M022 M042 35.0 M017 M042 87.0 M024 M042 54.0 M046 D007 3.0 M042 M015 32.0 M011 M042 17.0 M028 M012 8.0 M011 M025 40.0 M042 M008 50.0 M020 M008 30.0 M020 M001 19.0 D007 M027 12.0 M042 D007 13.0 M032 D006 15.0 M009 D014 10.0 M011 M031 21.0 M033 M014 45.0 M020 M029 8.0 M020 M036 3.0 D003 M042 7.0 M030 M020 5.0 M036 M015 18.0 M039 M036 15.0 D005 M027 16.0 M043 D006 6.0 D006 M044 6.0 M044 M040 8.0 M024 D005 9.0 D005 M021 7.0 M022 M034 19.0 M004 M026 11.0 M002 M029 28.0 M043 M003 11.0 M003 M042 9.0 M004 M046 11.0 M044 M002 3.0 M004 M044 9.0 M048 M005 56.0 M003 M050 10.0 M042 M001 47.0 M001 M043 27.0 M002 M019 45.0 M043 M012 5.0 M012 M044 3.0 M019 M039 23.0 M022 M041 13.0 D006 M050 18.0 M044 M018 15.0 M038 D004 9.0 M033 M025 32.0 M019 M040 12.0 D004 M030 12.0 M019 D015 18.0 D004 M036 4.0 M044 D005 3.0 M010 D005 14.0 M044 M010 5.0 M035 M003 52.0 M036 M012 4.0 M003 M030 7.0 M030 M004 4.0 M014 M030 11.0 M034 D003 4.0 M019 M044 15.0 M027 D006 5.0 M045 D006 18.0 M020 M002 15.0 D007 M007 35.0 M042 M002 34.0 M012 M042 17.0 M011 D007 13.0 D012 M008 50.0 M024 D007 11.0 M043 D007 6.0 M042 M021 48.0 M017 D012 12.0 D012 M016 6.0 M030 D007 1.0 M028 M003 13.0 M018 D012 9.0 M021 D007 31.0 D007 M028 10.0 M003 M020 11.0 M048 D015 8.0 D015 M047 8.0 D015 M045 5.0 M034 M051 38.0 M051 D005 6.0 D005 M036 9.0 D001 D002 3.0 M015 D014 4.0 M007 D014 3.0 D014 M006 5.0 M009 D015 6.0 D014 M028 2.0 D015 M010 1.0 M025 D014 3.0 M013 M004 31.0 M022 M048 24.0 M022 M012 10.0 M012 M023 29.0 M021 D012 33.0 D012 M024 9.0 D012 M026 19.0 D007 M047 11.0 M051 D012 6.0 M012 M031 20.0 M019 D003 4.0 M012 M032 6.0 D002 M031 6.0 M021 M012 16.0 M001 M030 12.0 D002 M017 16.0 D002 M034 5.0 M034 M011 9.0 D003 M013 7.0 M026 D014 1.0 D004 M024 6.0 M004 M035 16.0 D003 M002 8.0 M048 D005 15.0 M029 M051 10.0 D001 D011 2.0 D011 D007 6.0 D007 D001 11.0 D001 D014 8.0 D014 D013 6.0 D013 D012 6.0 D012 D003 6.0 D003 D009 5.0 D009 D004 6.0 D004 D006 8.0 D006 D010 6.0 D010 D008 6.0 D008 D015 5.0 D015 D005 6.0 D005 D002 6.0 D002 D011 1.0 D004 D005 2.0 M036 M002 17.0 M020 M038 11.0 M011 M044 2.0 M020 M028 8.0 D004 M034 17.0 M002 M011 49.0 M020 D007 8.0 D007 M006 16.0 M027 M020 9.0 D012 M002 5.0 D007 D015 6.0 D005 M043 13.0 D006 M028 5.0 M001 D006 7.0 M044 M041 26.0 M020 M050 6.0 M045 D001 22.0 D012 M019 10.0 M045 D007 2.0 M047 D012 3.0 D006 M013 14.0 M029 D006 4.0 M031 D005 9.0 M005 D012 1.0 M051 M044 6.0 D015 M011 1.0 M004 M051 17.0 M004 M029 9.0 M030 M002 9.0 M051 M037 12.0 M030 D001 8.0 D007 M010 101.0 M005 M020 12.0 M016 D004 5.0 M003 M048 23.0 D014 M005 5.0 M025 D005 9.0 M005 M042 17.0 M049 M003 5.0 M047 M004 12.0 M010 M043 44.0 M051 M039 4.0 M041 M004 16.0 M049 M004 2.0 M004 M045 5.0 D015 M006 10.0 M050 D001 18.0 M030 D006 7.0 M014 D003 4.0 M012 D003 3.0 M051 D015 19.0 M021 M040 10.0 M011 M023 45.0 D015 M015 12.0 M002 D007 14.0 D007 M003 8.0 D012 M014 10.0 M011 M004 70.0 D012 M025 37.0 D003 M003 2.0 M021 M039 13.0 D010 M016 8.0 D012 M027 6.0 M042 D005 8.0 M020 M042 4.0 M022 M039 4.0 D004 M008 2.0 D015 M021 4.0 M010 M042 125.0 M020 D012 3.0 D015 M046 8.0 M046 D014 2.0 M040 M013 27.0 M029 D012 2.0 M051 D001 18.0 M048 M012 1.0 M011 D001 11.0 M010 D014 5.0 M038 M004 18.0 M004 M039 3.0 M051 M040 3.0 M022 M038 14.0 D003 M001 5.0 D003 M023 3.0 M001 M012 33.0 M011 M039 9.0 M032 D007 5.0 M036 D007 2.0 M029 D007 6.0 D005 M013 9.0 D006 D007 2.0 D007 M040 2.0 D007 M039 1.0 M028 D012 2.0 M034 M012 8.0 M033 M012 9.0 M021 D002 3.0 M020 M045 10.0 M021 D004 6.0 M011 M041 21.0 M002 D005 4.0 D012 M010 21.0 M049 D014 2.0 M037 M011 18.0 M020 D015 2.0 D012 M015 4.0 M023 M040 9.0 M038 D012 1.0 D007 D005 2.0 D013 M001 3.0 D007 M041 7.0 M020 M034 5.0 M034 D011 1.0 M041 M020 5.0 D006 D003 1.0 M036 M011 6.0 M012 M030 1.0 D003 M011 2.0 M040 M011 10.0 M011 M027 19.0 M042 M004 6.0 M004 M050 3.0 M004 M048 12.0 M006 D004 9.0 M003 D012 1.0 M022 D005 4.0 M011 D006 4.0 M037 M012 4.0 D005 M011 2.0 M002 D009 1.0 D012 D007 3.0 D012 M009 10.0 D010 M015 1.0 D015 M034 3.0 M035 D015 2.0 M007 D013 3.0 D013 M008 2.0 D013 M032 1.0 M044 M020 3.0 M037 D007 5.0 M043 M020 3.0 D012 M048 1.0 D007 D014 8.0 D014 M047 3.0 M001 D015 1.0 M003 D015 3.0 D001 M004 5.0 M037 D015 3.0 D015 M005 3.0 D014 M038 4.0 D014 M039 1.0 M035 D002 1.0 D006 M037 3.0 D004 M005 1.0 D004 M010 1.0 M041 D012 1.0 D005 D006 3.0 M020 M049 5.0 D015 M039 2.0 D015 M040 1.0 M040 D014 2.0 D014 M041 1.0 D007 D010 1.0 D007 M038 6.0 M002 D014 1.0 D015 M002 1.0 D002 M015 8.0 M051 D002 1.0 M037 D002 1.0 M004 D002 4.0 M022 D002 3.0 D002 M001 2.0 M045 D014 1.0 M020 M040 1.0 D003 M022 3.0 D004 M018 6.0 M041 D004 4.0 M005 D007 5.0 D002 M018 2.0 M002 D002 2.0 M034 D012 1.0 D001 D012 5.0 M026 D015 1.0 D004 M019 2.0 M040 M004 3.0 M015 D004 2.0 M033 D015 4.0 D015 M032 5.0 M035 D010 1.0 D010 M034 1.0 M022 D004 1.0 M012 D012 2.0 M024 D011 1.0 D002 M042 1.0 M035 D005 1.0 M035 D011 1.0 D008 M036 1.0 D005 M020 1.0 D015 M029 2.0 D007 M035 3.0 D007 M048 2.0 D014 M033 1.0 M032 D014 2.0 M031 D015 2.0 D015 M030 2.0 M011 M049 1.0 D002 M024 3.0 M023 D015 1.0 D001 D015 1.0 D015 M004 2.0 D004 M020 1.0 D015 D004 1.0 D001 D006 1.0 D006 M022 4.0 M042 D006 6.0 M049 D015 2.0 D012 M011 1.0 D012 M004 1.0 D007 M012 1.0 M033 D012 1.0 D004 D003 2.0 D007 M034 1.0 M033 D007 2.0 M049 M012 1.0 M028 D015 1.0 M037 D012 3.0 M023 D004 2.0 M020 D006 1.0 M003 D013 1.0 D007 M004 1.0 M020 M004 1.0 D005 M004 1.0 D002 M036 1.0 M031 D014 3.0 D014 M035 1.0 D004 M002 1.0 M022 D015 1.0 M008 D014 2.0 D014 M043 1.0 M042 D012 1.0 D009 M015 1.0 D010 M014 2.0 M030 D012 1.0 jgrapht-jgrapht-1.5.1/jgrapht-demo/000077500000000000000000000000001402514743400171775ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/pom.xml000066400000000000000000000040711402514743400205160ustar00rootroot00000000000000 4.0.0 org.jgrapht jgrapht 1.5.1 jgrapht-demo JGraphT - Demo ${project.parent.basedir} GNU Lesser General Public License Version 2.1, February 1999 http://jgrapht.sourceforge.net/LGPL.html repo Eclipse Public License (EPL) 2.0 http://www.eclipse.org/legal/epl-v20.html repo org.apache.maven.plugins maven-jar-plugin org.jgrapht.demo.JGraphXAdapterDemo true ${project.groupId} jgrapht-io ${project.groupId} jgrapht-ext junit junit test jgrapht-jgrapht-1.5.1/jgrapht-demo/src/000077500000000000000000000000001402514743400177665ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/000077500000000000000000000000001402514743400207125ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/000077500000000000000000000000001402514743400216335ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/module-info.java000066400000000000000000000003221402514743400247110ustar00rootroot00000000000000module org.jgrapht.demo { exports org.jgrapht.demo; requires transitive org.jgrapht.core; requires transitive org.jgrapht.io; requires transitive org.jgrapht.ext; requires java.desktop; } jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/000077500000000000000000000000001402514743400224225ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/000077500000000000000000000000001402514743400240615ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/000077500000000000000000000000001402514743400250055ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/CompleteGraphDemo.java000066400000000000000000000050711402514743400312120ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Tim Shearouse and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; //@example:class:begin import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import org.jgrapht.util.*; import java.util.*; import java.util.function.*; //@example:class:end /** * Demonstrates how to create a complete graph and perform a depth first search on it. * */ // @example:class:begin public final class CompleteGraphDemo { // number of vertices private static final int SIZE = 10; /** * Main demo entry point. * * @param args command line arguments */ public static void main(String[] args) { // Create the VertexFactory so the generator can create vertices Supplier vSupplier = new Supplier() { private int id = 0; @Override public String get() { return "v" + id++; } }; // @example:generate:begin // Create the graph object Graph completeGraph = new SimpleGraph<>(vSupplier, SupplierUtil.createDefaultEdgeSupplier(), false); // Create the CompleteGraphGenerator object CompleteGraphGenerator completeGenerator = new CompleteGraphGenerator<>(SIZE); // Use the CompleteGraphGenerator object to make completeGraph a // complete graph with [size] number of vertices completeGenerator.generateGraph(completeGraph); // @example:generate:end // Print out the graph to be sure it's really complete Iterator iter = new DepthFirstIterator<>(completeGraph); while (iter.hasNext()) { String vertex = iter.next(); System.out .println( "Vertex " + vertex + " is connected to: " + completeGraph.edgesOf(vertex).toString()); } } } // @example:class:end jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/DependencyDemo.java000066400000000000000000000101051402514743400305300ustar00rootroot00000000000000/* * (C) Copyright 2012-2021, by Rob Janes and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; import org.jgrapht.*; import org.jgrapht.alg.cycle.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import java.util.*; /*** * This class is a demonstration program for creating a dependency chart, directed graph, then * locating and outputting any implicit loops, cycles. **/ public class DependencyDemo { /** * Test creating a directed graph, checking it for cycles and either outputting cycles detected * or topological ordering if not. * * @param createCycles true - create a directed graph which contains cycles. false - create a * directed graph which does not contain any cycles. */ public static void test(boolean createCycles) { CycleDetector cycleDetector; Graph g; g = new DefaultDirectedGraph(DefaultEdge.class); // Add vertices g.addVertex("a"); g.addVertex("b"); g.addVertex("c"); g.addVertex("d"); g.addVertex("e"); // Add edges g.addEdge("b", "a"); g.addEdge("c", "b"); if (createCycles) { g.addEdge("a", "c"); } g.addEdge("e", "d"); if (createCycles) { g.addEdge("d", "e"); } // Printing the vetrices and the edges System.out.println(g.toString()); // Checking for cycles in the dependencies cycleDetector = new CycleDetector(g); // Cycle(s) detected. if (cycleDetector.detectCycles()) { Iterator iterator; Set cycleVertices; Set subCycle; String cycle; System.out.println("Cycles detected."); // Get all vertices involved in cycles. cycleVertices = cycleDetector.findCycles(); // Loop through vertices trying to find disjoint cycles. while (!cycleVertices.isEmpty()) { System.out.println("Cycle:"); // Get a vertex involved in a cycle. iterator = cycleVertices.iterator(); cycle = iterator.next(); // Get all vertices involved with this vertex. subCycle = cycleDetector.findCyclesContainingVertex(cycle); for (String sub : subCycle) { System.out.println(" " + sub); // Remove vertex so that this cycle is not encountered again cycleVertices.remove(sub); } } } // If no cycles are detected, output vertices topologically ordered else { String v; TopologicalOrderIterator orderIterator; orderIterator = new TopologicalOrderIterator(g); System.out.println("\nTopological Ordering:"); while (orderIterator.hasNext()) { v = orderIterator.next(); System.out.println(v); } } } /** * Generate two cases, one with cycles, this is dependencies and one without. * * @param args Ignored. */ public static void main(String[] args) { System.out.println("\nCase 1: There are cycles."); test(true); System.out.println("\nCase 2: There are no cycles."); test(false); System.out.println("\nAll done"); System.exit(0); } } jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/DirectedGraphDemo.java000066400000000000000000000101031402514743400311550ustar00rootroot00000000000000/* * (C) Copyright 2008-2021, by Minh Van Nguyen and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; //@example:main:begin import org.jgrapht.*; import org.jgrapht.alg.connectivity.*; import org.jgrapht.alg.interfaces.ShortestPathAlgorithm.*; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.shortestpath.*; import org.jgrapht.graph.*; import java.util.*; //@example:main:end /** * This class demonstrates some of the operations that can be performed on directed graphs. After * constructing a basic directed graph, it computes all the strongly connected components of this * graph. It then finds the shortest path from one vertex to another using Dijkstra's shortest path * algorithm. The sample code should help to clarify to users of JGraphT that the class * org.jgrapht.alg.shortestpath.DijkstraShortestPath can be used to find shortest paths within * directed graphs. * * @author Minh Van Nguyen */ public class DirectedGraphDemo { /** * The starting point for the demo. * * @param args ignored. */ public static void main(String args[]) { // @example:main:begin // constructs a directed graph with the specified vertices and edges Graph directedGraph = new DefaultDirectedGraph(DefaultEdge.class); directedGraph.addVertex("a"); directedGraph.addVertex("b"); directedGraph.addVertex("c"); directedGraph.addVertex("d"); directedGraph.addVertex("e"); directedGraph.addVertex("f"); directedGraph.addVertex("g"); directedGraph.addVertex("h"); directedGraph.addVertex("i"); directedGraph.addEdge("a", "b"); directedGraph.addEdge("b", "d"); directedGraph.addEdge("d", "c"); directedGraph.addEdge("c", "a"); directedGraph.addEdge("e", "d"); directedGraph.addEdge("e", "f"); directedGraph.addEdge("f", "g"); directedGraph.addEdge("g", "e"); directedGraph.addEdge("h", "e"); directedGraph.addEdge("i", "h"); // computes all the strongly connected components of the directed graph StrongConnectivityAlgorithm scAlg = new KosarajuStrongConnectivityInspector<>(directedGraph); List> stronglyConnectedSubgraphs = scAlg.getStronglyConnectedComponents(); // prints the strongly connected components System.out.println("Strongly connected components:"); for (int i = 0; i < stronglyConnectedSubgraphs.size(); i++) { System.out.println(stronglyConnectedSubgraphs.get(i)); } System.out.println(); // Prints the shortest path from vertex i to vertex c. This certainly // exists for our particular directed graph. System.out.println("Shortest path from i to c:"); DijkstraShortestPath dijkstraAlg = new DijkstraShortestPath<>(directedGraph); SingleSourcePaths iPaths = dijkstraAlg.getPaths("i"); System.out.println(iPaths.getPath("c") + "\n"); // Prints the shortest path from vertex c to vertex i. This path does // NOT exist for our particular directed graph. Hence the path is // empty and the result must be null. System.out.println("Shortest path from c to i:"); SingleSourcePaths cPaths = dijkstraAlg.getPaths("c"); System.out.println(cPaths.getPath("i")); // @example:main:end } } jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphBuilderDemo.java000066400000000000000000000040001402514743400310170ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by John Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; /** * Demonstrates how to use {@link GraphTypeBuilder} and {@link GraphBuilder}. * * @author John Sichi */ public final class GraphBuilderDemo { /** * Main demo entry point. * * @param args command line arguments */ public static void main(String[] args) { Graph kite = buildKiteGraph(); } /** * Builds a simple graph with no vertices. * * @return a modifiable empty graph instance */ // @example:buildType:begin private static Graph buildEmptySimpleGraph() { return GraphTypeBuilder . undirected().allowingMultipleEdges(false) .allowingSelfLoops(false).edgeClass(DefaultEdge.class).weighted(false).buildGraph(); } // @example:buildType:end /** * Builds the kite graph. * * @return an unmodifiable instance of the kite graph */ // @example:buildEdges:begin private static Graph buildKiteGraph() { return new GraphBuilder<>(buildEmptySimpleGraph()) .addEdgeChain(1, 2, 3, 4, 1).addEdge(2, 4).addEdge(3, 5).buildAsUnmodifiable(); } // @example:buildEdges:end } jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/GraphMLDemo.java000066400000000000000000000213121402514743400277460ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.nio.graphml.*; import org.jgrapht.nio.graphml.GraphMLExporter.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * This class demonstrates exporting and importing a graph with custom vertex and edge attributes in * GraphML. Vertices of the graph have an attribute called "color" and a "name" attribute. Edges * have a "weight" attribute as well as a "name" attribute. * * The demo constructs a complete graph with random edge weights and exports it as GraphML. The * output is then re-imported into a second graph. */ public final class GraphMLDemo { // number of vertices private static final int SIZE = 6; // random number generator private static final Random GENERATOR = new Random(17); /** * Color */ enum Color { BLACK("black"), WHITE("white"); private final String value; private Color(String value) { this.value = value; } public String toString() { return value; } } /** * A custom graph vertex. */ static class CustomVertex { private String id; private Color color; public CustomVertex(String id) { this(id, null); } public CustomVertex(String id, Color color) { this.id = id; this.color = color; } @Override public int hashCode() { return (id == null) ? 0 : id.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; CustomVertex other = (CustomVertex) obj; if (id == null) { return other.id == null; } else { return id.equals(other.id); } } public Color getColor() { return color; } public void setColor(Color color) { this.color = color; } public String getId() { return id; } public void setId(String id) { this.id = id; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("(").append(id); if (color != null) { sb.append(",").append(color); } sb.append(")"); return sb.toString(); } } /** * A custom vertex supplier which gives each vertex a random color. */ static class CustomVertexSupplier implements Supplier { private int id = 0; @Override public CustomVertex get() { return new CustomVertex( String.valueOf(id++), GENERATOR.nextBoolean() ? Color.BLACK : Color.WHITE); } } /** * Create exporter */ private static GraphExporter createExporter() { /* * Create the exporter. The constructor parameter is a function which generates for each * vertex a unique identifier. */ GraphMLExporter exporter = new GraphMLExporter<>(v -> v.id); /* * Set to export the internal edge weights */ exporter.setExportEdgeWeights(true); /* * The exporter may need to generate for each vertex a set of attributes. */ exporter.setVertexAttributeProvider(v -> { Map m = new HashMap<>(); if (v.getColor() != null) { m.put("color", DefaultAttribute.createAttribute(v.getColor().toString())); } m.put("name", DefaultAttribute.createAttribute("node-" + v.id)); return m; }); /* * Set the edge id provider. * * The exporter needs to generate for each edge a unique identifier. */ exporter.setEdgeIdProvider(new IntegerIdProvider(0)); /* * The exporter may need to generate for each edge a set of attributes. */ exporter.setEdgeAttributeProvider(e -> { Map m = new HashMap<>(); m.put("name", DefaultAttribute.createAttribute(e.toString())); return m; }); /* * Register additional color attribute for vertices */ exporter.registerAttribute("color", AttributeCategory.NODE, AttributeType.STRING); /* * Register additional name attribute for vertices and edges */ exporter.registerAttribute("name", AttributeCategory.ALL, AttributeType.STRING); return exporter; } /** * Create the importer */ private static GraphImporter createImporter() { /* * Create the graph importer. */ GraphMLImporter importer = new GraphMLImporter<>(); /* * Add vertex attribute consumer to read back vertex color from file. */ importer.addVertexAttributeConsumer((p, attrValue) -> { CustomVertex v = p.getFirst(); String attrName = p.getSecond(); if (attrName.equals("color")) { String color = attrValue.getValue(); switch (color) { case "black": v.setColor(Color.BLACK); break; case "white": v.setColor(Color.WHITE); break; } } }); return importer; } /** * Main demo method * * @param args command line arguments */ public static void main(String[] args) { /* * Generate the complete graph. Vertices have random colors and edges have random edge * weights. */ Graph graph1 = GraphTypeBuilder .directed().weighted(true).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(new CustomVertexSupplier()) .edgeSupplier(SupplierUtil.createDefaultWeightedEdgeSupplier()).buildGraph(); CompleteGraphGenerator completeGenerator = new CompleteGraphGenerator<>(SIZE); System.out.println("-- Generating complete graph"); completeGenerator.generateGraph(graph1); /* * Assign random weights to the graph */ for (var e : graph1.edgeSet()) { graph1.setEdgeWeight(e, GENERATOR.nextInt(100)); } // now export and import back again System.out.println("-- Exporting graph as GraphML"); GraphExporter exporter = createExporter(); // export as string Writer writer = new StringWriter(); exporter.exportGraph(graph1, writer); String graph1AsGraphML = writer.toString(); // display System.out.println(graph1AsGraphML); // import it back System.out.println("-- Importing graph back from GraphML"); Graph graph2 = GraphTypeBuilder .directed().weighted(true).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(new CustomVertexSupplier()) .edgeSupplier(SupplierUtil.createDefaultWeightedEdgeSupplier()).buildGraph(); GraphImporter importer = createImporter(); importer.importGraph(graph2, new StringReader(graph1AsGraphML)); } } jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/HelloJGraphT.java000066400000000000000000000130031402514743400301300ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; //@example:uriCreate:begin import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.nio.*; import org.jgrapht.nio.dot.*; import org.jgrapht.traverse.*; import java.io.*; import java.net.*; import java.util.*; //@example:uriCreate:end //@example:render:begin //@example:render:end //@example:uriCreate:begin //@example:uriCreate:end /** * A simple introduction to using JGraphT. * * @author Barak Naveh */ public final class HelloJGraphT { private HelloJGraphT() { } // ensure non-instantiability. /** * The starting point for the demo. * * @param args ignored. * * @throws URISyntaxException if invalid URI is constructed. * @throws ExportException if graph cannot be exported. */ public static void main(String[] args) throws URISyntaxException, ExportException { Graph stringGraph = createStringGraph(); // note undirected edges are printed as: {,} System.out.println("-- toString output"); // @example:toString:begin System.out.println(stringGraph.toString()); // @example:toString:end System.out.println(); // @example:traverse:begin // create a graph based on URI objects Graph hrefGraph = createHrefGraph(); // find the vertex corresponding to www.jgrapht.org // @example:findVertex:begin URI start = hrefGraph .vertexSet().stream().filter(uri -> uri.getHost().equals("www.jgrapht.org")).findAny() .get(); // @example:findVertex:end // @example:traverse:end // perform a graph traversal starting from that vertex System.out.println("-- traverseHrefGraph output"); traverseHrefGraph(hrefGraph, start); System.out.println(); System.out.println("-- renderHrefGraph output"); renderHrefGraph(hrefGraph); System.out.println(); } /** * Creates a toy directed graph based on URI objects that represents link structure. * * @return a graph based on URI objects. */ private static Graph createHrefGraph() throws URISyntaxException { // @example:uriCreate:begin Graph g = new DefaultDirectedGraph<>(DefaultEdge.class); URI google = new URI("http://www.google.com"); URI wikipedia = new URI("http://www.wikipedia.org"); URI jgrapht = new URI("http://www.jgrapht.org"); // add the vertices g.addVertex(google); g.addVertex(wikipedia); g.addVertex(jgrapht); // add edges to create linking structure g.addEdge(jgrapht, wikipedia); g.addEdge(google, jgrapht); g.addEdge(google, wikipedia); g.addEdge(wikipedia, google); // @example:uriCreate:end return g; } /** * Traverse a graph in depth-first order and print the vertices. * * @param hrefGraph a graph based on URI objects * * @param start the vertex where the traversal should start */ private static void traverseHrefGraph(Graph hrefGraph, URI start) { // @example:traverse:begin Iterator iterator = new DepthFirstIterator<>(hrefGraph, start); while (iterator.hasNext()) { URI uri = iterator.next(); System.out.println(uri); } // @example:traverse:end } /** * Render a graph in DOT format. * * @param hrefGraph a graph based on URI objects */ private static void renderHrefGraph(Graph hrefGraph) throws ExportException { // @example:render:begin DOTExporter exporter = new DOTExporter<>(v -> v.getHost().replace('.', '_')); exporter.setVertexAttributeProvider((v) -> { Map map = new LinkedHashMap<>(); map.put("label", DefaultAttribute.createAttribute(v.toString())); return map; }); Writer writer = new StringWriter(); exporter.exportGraph(hrefGraph, writer); System.out.println(writer.toString()); // @example:render:end } /** * Create a toy graph based on String objects. * * @return a graph based on String objects. */ private static Graph createStringGraph() { Graph g = new SimpleGraph<>(DefaultEdge.class); String v1 = "v1"; String v2 = "v2"; String v3 = "v3"; String v4 = "v4"; // add the vertices g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); g.addVertex(v4); // add edges to create a circuit g.addEdge(v1, v2); g.addEdge(v2, v3); g.addEdge(v3, v4); g.addEdge(v4, v1); return g; } } jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/JGraphXAdapterDemo.java000066400000000000000000000063411402514743400312650ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; //@example:full:begin import com.mxgraph.layout.*; import com.mxgraph.swing.*; import org.jgrapht.*; import org.jgrapht.ext.*; import org.jgrapht.graph.*; import javax.swing.*; import java.awt.*; /** * A demo applet that shows how to use JGraphX to visualize JGraphT graphs. Applet based on * JGraphAdapterDemo. * */ public class JGraphXAdapterDemo extends JApplet { private static final long serialVersionUID = 2202072534703043194L; private static final Dimension DEFAULT_SIZE = new Dimension(530, 320); private JGraphXAdapter jgxAdapter; /** * An alternative starting point for this demo, to also allow running this applet as an * application. * * @param args command line arguments */ public static void main(String[] args) { JGraphXAdapterDemo applet = new JGraphXAdapterDemo(); applet.init(); JFrame frame = new JFrame(); frame.getContentPane().add(applet); frame.setTitle("JGraphT Adapter to JGraphX Demo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } @Override public void init() { // create a JGraphT graph ListenableGraph g = new DefaultListenableGraph<>(new DefaultDirectedGraph<>(DefaultEdge.class)); // create a visualization using JGraph, via an adapter jgxAdapter = new JGraphXAdapter<>(g); setPreferredSize(DEFAULT_SIZE); mxGraphComponent component = new mxGraphComponent(jgxAdapter); component.setConnectable(false); component.getGraph().setAllowDanglingEdges(false); getContentPane().add(component); resize(DEFAULT_SIZE); String v1 = "v1"; String v2 = "v2"; String v3 = "v3"; String v4 = "v4"; // add some sample data (graph manipulated via JGraphX) g.addVertex(v1); g.addVertex(v2); g.addVertex(v3); g.addVertex(v4); g.addEdge(v1, v2); g.addEdge(v2, v3); g.addEdge(v3, v1); g.addEdge(v4, v3); // positioning via jgraphx layouts mxCircleLayout layout = new mxCircleLayout(jgxAdapter); // center the circle int radius = 100; layout.setX0((DEFAULT_SIZE.width / 2.0) - radius); layout.setY0((DEFAULT_SIZE.height / 2.0) - radius); layout.setRadius(radius); layout.setMoveCircle(true); layout.execute(jgxAdapter.getDefaultParent()); // that's all there is to it!... } } // @example:full:end jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/LabeledEdges.java000066400000000000000000000071151402514743400301540ustar00rootroot00000000000000/* * (C) Copyright 2012-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.*; /** * An example of how to apply edge labels using a custom edge class. * * @author Barak Naveh */ public class LabeledEdges { private static final String FRIEND = "friend"; private static final String ENEMY = "enemy"; /** * The starting point for the demo. * * @param args ignored. */ public static void main(String[] args) { // @example:create:begin Graph graph = new DefaultDirectedGraph<>(RelationshipEdge.class); ArrayList people = new ArrayList(); people.add("John"); people.add("James"); people.add("Sarah"); people.add("Jessica"); // John is everyone's friend for (String person : people) { graph.addVertex(person); if (!person.equals("John")) { graph.addEdge("John", person, new RelationshipEdge(FRIEND)); } } // Apparently James doesn't really like John graph.addEdge("James", "John", new RelationshipEdge(ENEMY)); // Jessica is Sarah and James's friend graph.addEdge("Jessica", "Sarah", new RelationshipEdge(FRIEND)); graph.addEdge("Jessica", "James", new RelationshipEdge(FRIEND)); // But Sarah doesn't really like James graph.addEdge("Sarah", "James", new RelationshipEdge(ENEMY)); // @example:create:end // @example:print:begin for (RelationshipEdge edge : graph.edgeSet()) { String v1 = graph.getEdgeSource(edge); String v2 = graph.getEdgeTarget(edge); if (edge.getLabel().equals("enemy")) { System.out.printf(v1 + " is an enemy of " + v2 + "\n"); } else if (edge.getLabel().equals("friend")) { System.out.printf(v1 + " is a friend of " + v2 + "\n"); } } // @example:print:end assert (isEnemyOf(graph, "James", "John")); } // @example:isEnemyOf:begin private static boolean isEnemyOf( Graph graph, String person1, String person2) { return graph.getEdge(person1, person2).getLabel().equals(ENEMY); } // @example:isEnemyOf:end } /** * Custom edge class labeled with relationship type. */ // @example:edgeclass:begin class RelationshipEdge extends DefaultEdge { private String label; /** * Constructs a relationship edge * * @param label the label of the new edge. * */ public RelationshipEdge(String label) { this.label = label; } /** * Gets the label associated with this edge. * * @return edge label */ public String getLabel() { return label; } @Override public String toString() { return "(" + getSource() + " : " + getTarget() + " : " + label + ")"; } } // @example:edgeclass:end jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/ParberryKnightTour.java000066400000000000000000000372521402514743400314660ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Kirill Vishnyakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; import org.jgrapht.alg.util.*; /** * Implementation of {@literal } * Parberry's algorithm{@literal } for * {@literal }closed knight's tour * problem{@literal }. * * This algorithm was firstly introduced in "Discrete Applied Mathematics" Volume 73, Issue 3, 21 * March 1997, Pages 251-260. * * A knight's tour is a sequence of moves of a knight on a chessboard such that the knight visits * every square only once. If the knight ends on a square that is one knight's move from the * beginning square (so that it could tour the board again immediately, following the same path), * the tour is closed, otherwise it is open. * * The knight's tour problem is the mathematical problem of finding a knight's tour. * * The time complexity of the algorithm is linear in the size of the board, i.e. it is equal to * $O(n^2)$, where $n$ is one dimension of the board. * * The Parberry's algorithm finds CLOSED knight's tour for all boards with size $n \times n$ and $n * \times n + 2$, where $n$ is even and $n \geq 6$. * * The knight's tour is said to be structured if it contains the following $8$ UNDIRECTED moves: * * Knight's tour on board of size $n \times m$ is called structured if it contains the following $8$ * UNDIRECTED moves: 1). $(1, 0) \to (0, 2)$ - denoted as $1$ on the picture below. 2). $(2, 0) \to * (0, 1)$ - denoted as $2$ on the picture below. 3). $(n - 3, 0) \to (n - 1, 1)$ - denoted as $3$ * on the picture below. 4). $(n - 2, 0) \to (n - 1, 2)$ - denoted as $4$ on the picture below. 5). * $(0, m - 3) \to (1, m - 1)$ - denoted as $5$ on the picture below. 6). $(0, m - 2) \to (2, m - * 1)$ - denoted as $6$ on the picture below. 7). $(n - 3, m - 1) \to (n - 1, m - 2)$ - denoted as * $7$ on the picture below. 8). $(n - 2, m - 1) \to (n - 1, m - 3)$ - denoted as $8$ on the picture * below. * * ######################################### #*12*********************************34*# * #2*************************************3# #1*************************************4# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #5*************************************7# * #4*************************************6# #*54*********************************67*# * ######################################### * * If you are confused with the formal definition of the structured knight's tour please refer to * the illustration on the page 3 of the paper * {@literal } "An efficient algorithm for * the Knight’s tour problem" {@literal } by Ian Parberry. * * Algorithm description: Split the initial board on $4$ boards as evenly as possible. Solve the * problem for these $4$ boards recursively. Delete the edges which contract the start and the * finish cell of the tour on each board, so that on each on $4$ boards closed knight's tour became * open knight's tour. Contract these $4$ boards by adding $4$ additional edges between the * quadrants. */ public class ParberryKnightTour { /** * Width of the board. */ private int n; /** * Height of the board. */ private int m; /** * Constructor. * * @param n width of the board. * @param m height of the board. */ public ParberryKnightTour(int n, int m) { /* * Theorem 2.1 (page 3 Parberry's paper) For all even n >= 6 there exist a structured * knight's tour on n x n board and n x (n + 2) board. Such a tour can be constructed in * time O(n^2). */ if (n < 6 || n % 2 != 0) { throw new IllegalArgumentException("n has to be greater than 5 and even!"); } if (m != n + 2 && m != n) { throw new IllegalArgumentException( "n x n and n x (n + 2) are the only possible board configurations!"); } this.n = n; this.m = m; } /** * Generates a closed knight's tour for a piece of the board which is being set by left-upper * and right-bottom cells. * * @param start left-upper cell of the piece of the original chessboard. * @param end right-bottom cell of the piece of the original chessboard. * @return closed knight's tour on this piece of the board. */ private KnightTour generateTour(Pair start, Pair end) { /* * Width and height of the board. */ int nDim = end.getFirst() - start.getFirst() + 1; int mDim = end.getSecond() - start.getSecond() + 1; /* * Base case. */ if (Math.max(nDim, mDim) <= 12) { return new WarnsdorffRuleKnightTourHeuristic(nDim, mDim) .getTour(TourType.CLOSED, true, start.getFirst(), start.getSecond()); } /* * Start and end points of each quadrant. The following variables denoted as s1, e1, s2, e2, * s3, e3, s4, e4 in the picture below. */ Pair start1, end1, start2, end2, start3, end3, start4, end4; int k = nDim / 4; /* * n can be either of form 4k or 4k + 2. The split is being performed depending on the form * of n and board configuration. We want to split the board as evenly as possible. You can * read more about split procedure on page 3 of Parberry's paper. */ int rem = nDim % 4; /* * Need to handle this case separately to achieve the most possible even split. */ if (nDim + 2 == mDim && rem == 2) { start1 = new Pair<>(start.getFirst(), start.getSecond()); end1 = new Pair<>(start.getFirst() + 2 * k - 1, start.getSecond() + mDim / 2 - 1); } else { start1 = new Pair<>(start.getFirst(), start.getSecond()); end1 = new Pair<>(start.getFirst() + 2 * k - 1, start.getSecond() + 2 * k - 1); } start2 = new Pair<>(end1.getFirst() + 1, start1.getSecond()); end2 = new Pair<>(end.getFirst(), end1.getSecond()); start3 = new Pair<>(start.getFirst(), end1.getSecond() + 1); end3 = new Pair<>(end1.getFirst(), end.getSecond()); start4 = new Pair<>(end1.getFirst() + 1, end1.getSecond() + 1); end4 = new Pair<>(end.getFirst(), end.getSecond()); /* * ######################################### #s1*****************|s2*****************# * #*******************|*******************# #*******************|*******************# * #******TOUR 1*******|******TOUR 2*******# #*******************|*******************# * #*******************|*******************# #*******************|*******************# * #*****************e1|*****************e2# #-------------------|-------------------# * #s3*****************|s4*****************# #*******************|*******************# * #*******************|*******************# #******TOUR 3*******|******TOUR 4 ******# * #*******************|*******************# #*******************|*******************# * #*******************|*******************# #*****************e3|*****************e4# * ######################################### */ /* * Recursively solving problem for small quadrants. */ KnightTour tour1 = generateTour(start1, end1); KnightTour tour2 = generateTour(start2, end2); KnightTour tour3 = generateTour(start3, end3); KnightTour tour4 = generateTour(start4, end4); /* * Removing edges A, B, C and D. * * ######################################### #*******************|*******************# * #*******************|*******************# #*******************|*******************# * #******TOUR 1*******|******TOUR 2*******# #*******************|*******************# * #*******************|*B*****************# #******************A|*******************# * #****************A**|B******************# #-------------------|-------------------# * #******************D|**C****************# #*******************|C******************# * #*****************D*|*******************# #******TOUR 3*******|******TOUR 4*******# * #*******************|*******************# #*******************|*******************# * #*******************|*******************# #*******************|*******************# * ######################################### */ /* * Adding edges E, F, G, H to contract the quadrants. * * ######################################### #*******************|*******************# * #*******************|*******************# #*******************|*******************# * #******TOUR 1*******|******TOUR 2*******# #*******************|*******************# * #*******************|*F*****************# #******************F|*******************# * #****************E**|G******************# #-------------------|-------------------# * #******************E|**G****************# #*******************|H******************# * #*****************H*|*******************# #******TOUR 3*******|******TOUR 4*******# * #*******************|*******************# #*******************|*******************# * #*******************|*******************# #*******************|*******************# * ######################################### */ /* * Relation between nodes in structured array and endpoints of the edges to be * deleted/added. Note that you don't know the direction of the edges A, B, C, D, so you * have to check both options. * * ######################################### #**0***************2|**0***************2# * #1******************|1******************# #*****************3*|*****************3*# * #******TOUR 1*******|******TOUR 2*******# #*******************|*******************# * #*4*****************|*4*****************# #******************6|******************6# * #5***************7**|5***************7**# #-------------------|-------------------# * #**0***************2|**0***************2# #1***************3**|1******************# * #*******************|*****************3*# #******TOUR 3*******|******TOUR 4*******# * #*******************|*******************# #*4*****************|*4*****************# * #******************6|******************6# #5***************7**|5***************7**# * ######################################### _________________________________ * * A.start = tour1.forStructured[6]; A.end = tour1.forStructured[7]; * * or * * A.end = tour1.forStructured[6]; A.start = tour1.forStructured[7]; * __________________________________ * * B.start = tour2.forStructured[4]; B.end = tour2.forStructured[5]; * * or * * B.end = tour2.forStructured[4]; B.start = tour2.forStructured[5]; * __________________________________ * * C.start = tour4.forStructured[0]; C.end = tour4.forStructured[1]; * * or * * C.end = tour4.forStructured[0]; C.start = tour4.forStructured[1]; * __________________________________ * * D.start = tour2.forStructured[2]; D.end = tour2.forStructured[3]; * * or * * D.end = tour2.forStructured[2]; D.start = tour2.forStructured[3]; * __________________________________ */ /* * Deleting and simultaneously contracting. */ if (tour1.getStructured().get(7).getNext() == tour1.getStructured().get(6)) { tour1.getStructured().get(7).setNext(tour3.getStructured().get(2)); tour1.getStructured().get(6).setPrev(tour2.getStructured().get(4)); } else { tour1.getStructured().get(7).setPrev(tour3.getStructured().get(2)); tour1.getStructured().get(6).setNext(tour2.getStructured().get(4)); } if (tour3.getStructured().get(2).getPrev() == tour3.getStructured().get(3)) { tour3.getStructured().get(2).setPrev(tour1.getStructured().get(7)); tour3.getStructured().get(3).setNext(tour4.getStructured().get(1)); } else { tour3.getStructured().get(2).setNext(tour1.getStructured().get(7)); tour3.getStructured().get(3).setPrev(tour4.getStructured().get(1)); } if (tour4.getStructured().get(1).getPrev() == tour4.getStructured().get(0)) { tour4.getStructured().get(1).setPrev(tour3.getStructured().get(3)); tour4.getStructured().get(0).setNext(tour2.getStructured().get(5)); } else { tour4.getStructured().get(1).setNext(tour3.getStructured().get(3)); tour4.getStructured().get(0).setPrev(tour2.getStructured().get(5)); } if (tour2.getStructured().get(5).getPrev() == tour2.getStructured().get(4)) { tour2.getStructured().get(5).setPrev(tour4.getStructured().get(0)); tour2.getStructured().get(4).setNext(tour1.getStructured().get(6)); } else { tour2.getStructured().get(5).setNext(tour4.getStructured().get(0)); tour2.getStructured().get(4).setPrev(tour1.getStructured().get(6)); } /* * Update the start node after you've contracted all quadrants. */ tour1.getList().setStartNode(tour3.getStructured().get(2)); /* * Update structured pointers. Note that we do not need to update the first two nodes, since * they are already set correctly. */ tour1.getStructured().set(2, tour2.getStructured().get(2)); tour1.getStructured().set(3, tour2.getStructured().get(3)); tour1.getStructured().set(4, tour3.getStructured().get(4)); tour1.getStructured().set(5, tour3.getStructured().get(5)); tour1.getStructured().set(6, tour4.getStructured().get(6)); tour1.getStructured().set(7, tour4.getStructured().get(7)); /* * Update size of the list. */ tour1 .getList().setSize( tour1.getList().getSize() + tour2.getList().getSize() + tour3.getList().getSize() + tour4.getList().getSize()); return tour1; } /** * Returns a closed knight's tour. * * @return closed knight's tour. */ public KnightTour getTour() { return generateTour(new Pair<>(0, 0), new Pair<>(n - 1, m - 1)); } } jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/PerformanceDemo.java000066400000000000000000000071361402514743400307250ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.traverse.*; import java.io.*; import java.util.*; /** * A simple demo to test memory and CPU consumption on a graph with 3 million elements. * *

    * NOTE: To run this demo you may need to increase the JVM max mem size. In Sun's JVM it is done * using the "-Xmx" switch. Specify "-Xmx300M" to set it to 300MB. *

    * *

    * WARNING: Don't run this demo as-is on machines with less than 512MB memory. Your machine will * start paging severely. You need to first modify it to have fewer graph elements. This is easily * done by changing the loop counters below. *

    * * @author Barak Naveh */ public final class PerformanceDemo { /** * The starting point for the demo. * * @param args ignored. */ public static void main(String[] args) { long time = System.currentTimeMillis(); reportPerformanceFor("starting at", time); Graph g = new Pseudograph<>(DefaultEdge.class); Object prev; Object curr; curr = prev = new Object(); g.addVertex(prev); int numVertices = 10000; int numEdgesPerVertex = 200; int numElements = numVertices * (1 + numEdgesPerVertex); System.out .println( "\n" + "allocating graph with " + numElements + " elements (may take a few tens of seconds)..."); for (int i = 0; i < numVertices; i++) { curr = new Object(); g.addVertex(curr); for (int j = 0; j < numEdgesPerVertex; j++) { g.addEdge(prev, curr); } prev = curr; } reportPerformanceFor("graph allocation", time); time = System.currentTimeMillis(); for (Iterator i = new BreadthFirstIterator<>(g); i.hasNext();) { i.next(); } reportPerformanceFor("breadth traversal", time); time = System.currentTimeMillis(); for (Iterator i = new DepthFirstIterator<>(g); i.hasNext();) { i.next(); } reportPerformanceFor("depth traversal", time); System.out.println("\n" + "Paused: graph is still in memory (to check mem consumption)."); System.out.print("press enter to free memory and finish..."); try { System.in.read(); } catch (IOException e) { e.printStackTrace(); } System.out.println("done."); } private static void reportPerformanceFor(String msg, long refTime) { double time = (System.currentTimeMillis() - refTime) / 1000.0; double mem = usedMemory() / (1024.0 * 1024.0); mem = Math.round(mem * 100) / 100.0; System.out.println(msg + " (" + time + " sec, " + mem + "MB)"); } private static long usedMemory() { Runtime rt = Runtime.getRuntime(); return rt.totalMemory() - rt.freeMemory(); } } WarnsdorffRuleKnightTourHeuristic.java000066400000000000000000000634371402514743400344500ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/* * (C) Copyright 2018-2021, by Kirill Vishnyakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; import org.jgrapht.alg.util.*; import java.util.*; /** * Enum type that represents two knight's tour types: closed and open. */ enum TourType { CLOSED, OPEN } /** * Class that represents container for knight's tour. */ class KnightTour { /** * Implementation of a doubly linked list data structure that is being used for storing a tour. * * @param type of a value storing in a node. */ class DoublyLinkedList { /** * Pointer to the head of the list. */ private Node head; /** * Pointer to the tail of the list. */ private Node tail; /** * Pointer to the start node. Start node is the node from which we start any traversal * operation on the list. */ private Node startNode; /** * Size of the list. */ private int size; public DoublyLinkedList() { head = null; tail = null; startNode = null; size = 0; } public int getSize() { return size; } public boolean isEmpty() { return head == null; } /** * Adds element to the end of the list. * * @param element we want to add. */ public void add(E element) { Node node = new Node<>(element); size++; if (isEmpty()) { node.next = null; node.prev = null; head = node; tail = node; return; } tail.next = node; node.prev = tail; node.next = null; tail = node; } /** * Removes tail element. */ public void remove() { if (isEmpty()) { throw new IndexOutOfBoundsException("The list is empty!"); } size--; if (tail.prev == null) { head = null; tail = null; return; } tail = tail.prev; tail.next = null; } public Node getHead() { return head; } public Node getTail() { return tail; } public void clear() { head = null; tail = null; size = 0; } public void setStartNode(Node startNode) { this.startNode = startNode; } public Node getStartNode() { return startNode; } public void setSize(int i) { size = i; } } /** * Static class that represents a node. * * @param type of the value stored in the node. * */ static class Node { /** * Pointer to the next node. */ private Node next; /** * Pointer to the previous node. */ private Node prev; /** * Value that is being stored in the node. */ private E value; /** * Boolean flag that is being used in traversal function, such as toList. True if the node * was visited, otherwise false. */ private boolean visited = false; public Node(E value) { this.value = value; } public Node() { } public boolean isVisited() { return !visited; } public void setVisited(boolean visited) { this.visited = visited; } public E getValue() { return value; } public Node getNext() { return next; } public Node getPrev() { return prev; } public void setPrev(Node prev) { this.prev = prev; } public void setNext(Node next) { this.next = next; } } /** * Doubly linked list that stores nodes in order of their appearance in the knight's tour. */ private final DoublyLinkedList> list; /* * Let's call each of the following 8 cells structured: * * (enumeration starts with 0 to make the relation between cells and indices in structured array * more clear) * * 0). (2, 0); 1). (0, 1); 2). (n - 1, 0); 3). (n - 2, 2); 4). (1, m - 3); 5). (0, m - 1); 6). * (n - 1, m - 2); 7). (n - 3, m - 1); * * ######################################### #**0***********************************2# * #1**************************************# #*************************************3*# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #*4*************************************# * #**************************************6# #5***********************************7**# * ######################################### * * Structured cells are needed in the the merging procedure in the Parberry's algorithm. */ /** * ArrayList that stores pointers on the structured cells. */ private final ArrayList>> structured; /** * Used in toList function. */ private List> arrayList; /** * Constructor of knight's tour container. */ public KnightTour() { structured = new ArrayList<>(Collections.nCopies(8, new KnightTour.Node<>())); list = new DoublyLinkedList<>(); arrayList = null; } /** * Converts knight's tour represented as DoublyLinkedList to ArrayList. * * @return ArrayList that contains knight's tour. */ public List> toList() { if (arrayList != null) { return arrayList; } Node> startNode = list.getStartNode(); startNode.setVisited(true); arrayList = new ArrayList<>(); arrayList.add(startNode.getValue()); /* * Traverse of the list. */ while (startNode.getNext().isVisited() || startNode.getPrev().isVisited()) { if (startNode.getNext().isVisited()) startNode = startNode.getNext(); else { startNode = startNode.getPrev(); } arrayList.add(startNode.getValue()); startNode.setVisited(true); } return arrayList; } public DoublyLinkedList> getList() { return list; } public ArrayList>> getStructured() { return structured; } } /** * Implementation of {@literal }Warnsdorff's * rule{@literal } - heuristic for finding a knight's tour on chessboards. * * A knight's tour is a sequence of moves of a knight on a chessboard such that the knight visits * every square only once. If the knight ends on a square that is one knight's move from the * beginning square (so that it could tour the board again immediately, following the same path), * the tour is closed, otherwise it is open. * * The knight's tour problem is the mathematical problem of finding a knight's tour. * * Description of the Warnsdorff's rule: set a start cell. Always proceed to the cell that have the * fewest onward moves. In case of a tie(i.e. there exist more than one possible choice for the next * cell) go to the cell with largest Euclidean distance from the center of the board. * * This implementation also allows you to find a structured knight's tour. * * Knight's tour on board of size $n \times m$ is called structured if it contains the following $8$ * UNDIRECTED moves: * * 1). $(1, 0) \to (0, 2)$ - denoted as $1$ on the picture below. 2). $(2, 0) \to (0, 1)$ - denoted * as $2$ on the picture below. 3). $(n - 3, 0) \to (n - 1, 1)$ - denoted as $3$ on the picture * below. 4). $(n - 2, 0) \to (n - 1, 2)$ - denoted as $4$ on the picture below. 5). $(0, m - 3) \to * (1, m - 1)$ - denoted as $5$ on the picture below. 6). $(0, m - 2) \to (2, m - 1)$ - denoted as * $6$ on the picture below. 7). $(n - 3, m - 1) \to (n - 1, m - 2)$ - denoted as $7$ on the picture * below. 8). $(n - 2, m - 1) \to (n - 1, m - 3)$ - denoted as $8$ on the picture below. * * ######################################### #*12*********************************34*# * #2*************************************3# #1*************************************4# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #***************************************# * #***************************************# #6*************************************8# * #5*************************************7# #*65*********************************78*# * ######################################### * * If you are confused with the formal definition of the structured knight's tour please refer to * illustration on the page $3$ of the paper "An efficient algorithm for the Knight’s tour problem " * by Ian Parberry. * * One more feature of this implementation is that it provides an option to return a shifted * knight's tour, where all cell's coordinates are shifted by some values. Basically it is the same * as knight's tour of some piece of the board. */ public class WarnsdorffRuleKnightTourHeuristic { /** * Width of the board. */ private int n; /** * Height of the board. */ private int m; /** * 2d array that stores information whether or not the cell has been visited. */ private boolean[][] chessBoard; /** * Auxiliary array for offset in x coordinate when performing a move. */ private final static int[] DX = new int[] { 1, 2, 2, 1, -1, -2, -2, -1 }; /** * Auxiliary array for offset in y coordinate when performing a move. */ private final static int[] DY = new int[] { 2, 1, -1, -2, -2, -1, 1, 2 }; /** * Constructor. * * @param n width and height of the board. */ public WarnsdorffRuleKnightTourHeuristic(int n) { if (n < 3) { throw new IllegalArgumentException("Incorrect board size!"); } this.n = n; this.m = n; chessBoard = new boolean[n][n]; } /** * Constructor. * * @param n width of the board. * @param m height of the board. */ public WarnsdorffRuleKnightTourHeuristic(int n, int m) { if ((n < 3 && m < 3) || n <= 1 || m <= 1) { throw new IllegalArgumentException("Incorrect board size!"); } this.n = n; this.m = m; chessBoard = new boolean[n][m]; } /** * Calculates the number of the unvisited neighbours of the given cell. * * @param currentCell represents cell for which we want to find the unvisited neighbours. * @return number of unvisited edges. */ private int getNumberOfUnusedNeighbours(Pair currentCell) { int ans = 0; for (int i = 0; i < 8; i++) { int newX = currentCell.getFirst() + DX[i]; int newY = currentCell.getSecond() + DY[i]; if (newX >= 0 && newX < n && newY >= 0 && newY < m && !chessBoard[newX][newY]) { ans++; } } return ans; } /** * Function for handling a tie case. In case of a tie the next cell will be the cell with the * largest Euclidean distance from the center of the board. * * @param array that stores the cells with equal number of unvisited neighbours. * @return index of the next cell in the input array. */ private int handleTie(ArrayList> array) { int index = -1; int distance = -1; int xCenter = n / 2; int yCenter = m / 2; for (int i = 0; i < array.size(); i++) { int x = array.get(i).getFirst(); int y = array.get(i).getSecond(); if ((x - xCenter) * (x - xCenter) + (y - yCenter) * (y - yCenter) > distance) { distance = (x - xCenter) * (x - xCenter) + (y - yCenter) * (y - yCenter); index = i; } } return index; } /** * Finds the next cell to move. * * @param cell represents start point of the move. * @return cell represents end point of the move. */ private Pair getMoveWarnsdorff(Pair cell) { int curValue = Integer.MAX_VALUE; Pair currentCell = new Pair<>(-1, -1); Pair nextCell = new Pair<>(-1, -1); ArrayList> tie = new ArrayList<>(); for (int i = 0; i < 8; i++) { int newX = cell.getFirst() + DX[i]; int newY = cell.getSecond() + DY[i]; currentCell.setFirst(newX); currentCell.setSecond(newY); if (newX >= 0 && newX < n && newY >= 0 && newY < m && !chessBoard[newX][newY]) { int adjValue = getNumberOfUnusedNeighbours(currentCell); if (adjValue < curValue) { curValue = adjValue; nextCell.setFirst(currentCell.getFirst()); nextCell.setSecond(currentCell.getSecond()); tie.clear(); tie.add(new Pair<>(currentCell.getFirst(), currentCell.getSecond())); } else if (adjValue == curValue) { tie.add(new Pair<>(newX, newY)); } } } if (tie.size() > 1) { int index = handleTie(tie); nextCell.setFirst(tie.get(index).getFirst()); nextCell.setSecond(tie.get(index).getSecond()); } return nextCell; } /** * Checks type of the found tour. * * @param startX start coordinate on x-axis. * @param startY start coordinate on y-axis. * @param endX end coordinate on x-axis. * @param endY end coordinate on y-axis. * @param type type of the tour we want to find. * @return true, if the found tour satisfies the required invariants, otherwise false. */ private boolean checkType(int startX, int startY, int endX, int endY, TourType type) { if (type == TourType.CLOSED) { return Math.abs(startX - endX) == 1 && Math.abs(startY - endY) == 2 || Math.abs(startX - endX) == 2 && Math.abs(startY - endY) == 1; } return !(Math.abs(startX - endX) == 1 && Math.abs(startY - endY) == 2 || Math.abs(startX - endX) == 2 && Math.abs(startY - endY) == 1); } /** * Checks if the found tour is structured. Note, we don't know the direction of the edges in the * knight's tour, so we have to check both options, i.e. $a \to b$ and $b \to a$. * * @param moves preformed in the tour. * @param structured true if user asked to find a structured knight's tour, false otherwise. * @return true if the user didn't ask to find a structured knight's tour or if the tour * contains all the moves needed for tour to be structured, false otherwise. */ private boolean checkStructured( HashSet, Pair>> moves, boolean structured) { return !structured || ((moves.contains(new Pair<>(new Pair<>(1, 0), new Pair<>(0, 2))) || moves.contains(new Pair<>(new Pair<>(0, 2), new Pair<>(1, 0)))) && moves.contains(new Pair<>(new Pair<>(2, 0), new Pair<>(0, 1))) || moves.contains(new Pair<>(new Pair<>(0, 1), new Pair<>(2, 0))) && moves.contains(new Pair<>(new Pair<>(n - 3, 0), new Pair<>(n - 1, 1))) || moves.contains(new Pair<>(new Pair<>(n - 1, 1), new Pair<>(n - 3, 0))) && moves.contains(new Pair<>(new Pair<>(n - 2, 0), new Pair<>(n - 1, 2))) || moves.contains(new Pair<>(new Pair<>(n - 1, 2), new Pair<>(n - 2, 0))) && moves.contains(new Pair<>(new Pair<>(0, m - 3), new Pair<>(1, m - 1))) || moves.contains(new Pair<>(new Pair<>(1, m - 1), new Pair<>(0, m - 3))) && moves.contains(new Pair<>(new Pair<>(0, m - 2), new Pair<>(2, m - 1))) || moves.contains(new Pair<>(new Pair<>(2, m - 1), new Pair<>(0, m - 2))) && moves.contains(new Pair<>(new Pair<>(n - 3, m - 1), new Pair<>(n - 1, m - 2))) || moves.contains(new Pair<>(new Pair<>(n - 1, m - 2), new Pair<>(n - 3, m - 1))) && moves.contains(new Pair<>(new Pair<>(n - 2, m - 1), new Pair<>(n - 1, m - 3))) || moves.contains(new Pair<>(new Pair<>(n - 1, m - 3), new Pair<>(n - 2, m - 2)))); } /** * Converts doubly linked list of chessboard cells to the set of moves. * * @param tour we have found. * @return set of moves of the input tour. */ private HashSet, Pair>> getMoves( KnightTour.DoublyLinkedList> tour) { HashSet, Pair>> moves = new HashSet<>(); KnightTour.Node> headNode = tour.getHead(); KnightTour.Node> nextNode = headNode.getNext(); while (nextNode != null) { moves.add(new Pair<>(headNode.getValue(), nextNode.getValue())); headNode = headNode.getNext(); nextNode = nextNode.getNext(); } return moves; } /** * Checks existence of the knight's tour. * * @param type of the tour. * @return true if the tour exists, otherwise false. */ private boolean checkExistence(TourType type) { int newN = Math.min(n, m); int newM = Math.max(n, m); /* * Allen Schwenk, 1991 Which Rectangular Chessboards Have a Knight's Tour?. * * Theorem: An n x m chessboard with n <= m has a closed knight's tour unless one or more of * these three condition holds: (a) n and m are both odd; (b) n = 1, 2, 4; (c) n = 3 and m = * 4, 6, 8. */ if (type == TourType.CLOSED) { return !((newN % 2 == 1 && newM % 2 == 1) || newN == 1 || newN == 2 || newN == 4 || (newN == 3 && (newM == 4 || newM == 6 || newM == 8))); } /* * Regarding open knight's tour existence, refer to * http://gaebler.us/share/Knight_tour.html. * * Rob Gaebler, Tsu-wang Yang, Knight's Tours (August 13, 1999). */ return (newN == 3 && newM == 4 || newN == 3 && newM >= 7 || newN >= 4 && newM >= 5); } /** * Updates the pointer on the cell in structured array if the last added cell was structured. If * it is a non-structured cell then returns -1. * * @param cell last added to the tour cell. * @return the index of the corresponding cell in the structured array and -1 if the last added * cell is not a structured cell . */ private int updateStructuredPosition(Pair cell) { if (cell.getFirst() == 2 && cell.getSecond() == 0) { return 0; } else if (cell.getFirst() == 0 && cell.getSecond() == 1) { return 1; } else if (cell.getFirst() == n - 1 && cell.getSecond() == 0) { return 2; } else if (cell.getFirst() == n - 2 && cell.getSecond() == 2) { return 3; } else if (cell.getFirst() == 1 && cell.getSecond() == m - 3) { return 4; } else if (cell.getFirst() == 0 && cell.getSecond() == m - 1) { return 5; } else if (cell.getFirst() == n - 1 && cell.getSecond() == m - 2) { return 6; } else if (cell.getFirst() == n - 3 && cell.getSecond() == m - 1) { return 7; } return -1; } /** * Generates a knight's tour that satisfies the input parameters. * * Warnsdorff's rule heuristic is an example of a greedy method, which we use to select the next * cell to move, and thus may fail to find a tour. However, another greedy heuristic is used to * prevent failing: in case of a tie we will select a cell with the largest euclidean distance * from the center of the board. Such combination of greedy methods significantly increases our * chances to find a tour. * * @param type of the tour. * @param structured true if we want the tour to be structured, otherwise false. * @param shiftX the value will be added to each cell's x-coordinate to reach effect of * shifting. * @param shiftY the value will be added to each cell's t-coordinate to reach effect of * shifting. * @return knight's tour. */ public KnightTour getTour(TourType type, boolean structured, int shiftX, int shiftY) { if (shiftX < 0 || shiftY < 0) { throw new IllegalArgumentException("Incorrect shift value!"); } if (!checkExistence(type)) { throw new IllegalArgumentException("No solution exist for such configuration!"); } KnightTour tour = new KnightTour(); Random rand = new Random(); int startX, startY; Pair currentCell = new Pair<>(-1, -1); int visited; int run = 0; boolean[][] wasStartingVertex = new boolean[n][m]; boolean found = false; while (!found) { visited = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { chessBoard[i][j] = false; } } tour.getList().clear(); startX = rand.nextInt(n); startY = rand.nextInt(m); currentCell.setFirst(startX); currentCell.setSecond(startY); while (wasStartingVertex[startX][startY]) { startX = rand.nextInt(n); startY = rand.nextInt(m); currentCell.setFirst(startX); currentCell.setSecond(startY); } wasStartingVertex[startX][startY] = true; run++; while (visited < n * m) { chessBoard[currentCell.getFirst()][currentCell.getSecond()] = true; tour.getList().add(currentCell); /* * If we have added the structured cell then update pointer on that cell in the * structured array. */ if (structured) { int val = updateStructuredPosition(currentCell); if (val != -1) { tour.getStructured().set(val, tour.getList().getTail()); } } visited++; currentCell = getMoveWarnsdorff(currentCell); if (currentCell.getFirst() == -1) { break; } } Pair endCell = tour.getList().getTail().getValue(); if (visited == n * m && checkType(startX, startY, endCell.getFirst(), endCell.getSecond(), type)) { HashSet, Pair>> moves = getMoves(tour.getList()); if (checkStructured(moves, structured)) { found = true; } } /* * Try again if there is no unused start cells are left. */ if (run == (n * m) && !found) { return null; } } /* * Perform shifting. */ KnightTour.Node> node = tour.getList().getHead(); while (node != null) { node.getValue().setFirst(node.getValue().getFirst() + shiftX); node.getValue().setSecond(node.getValue().getSecond() + shiftY); node = node.getNext(); } /* * Make the list cyclic. */ tour.getList().getHead().setPrev(tour.getList().getTail()); tour.getList().getTail().setNext(tour.getList().getHead()); /* * Set the start node. */ tour.getList().setStartNode(tour.getList().getHead()); return tour; } } jgrapht-jgrapht-1.5.1/jgrapht-demo/src/main/java/org/jgrapht/demo/package-info.java000066400000000000000000000001411402514743400301700ustar00rootroot00000000000000/** * Demo programs that help to get started with JGraphT. */ package org.jgrapht.demo; jgrapht-jgrapht-1.5.1/jgrapht-demo/src/test/000077500000000000000000000000001402514743400207455ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/test/java/000077500000000000000000000000001402514743400216665ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/test/java/org/000077500000000000000000000000001402514743400224555ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/test/java/org/jgrapht/000077500000000000000000000000001402514743400241145ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/test/java/org/jgrapht/demo/000077500000000000000000000000001402514743400250405ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/test/java/org/jgrapht/demo/ParberryKnightTourTest.java000066400000000000000000000163571402514743400323640ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Kirill Vishnyakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; import org.jgrapht.alg.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class ParberryKnightTourTest { private ParberryKnightTour par; private boolean checkMove(int x1, int y1, int x2, int y2) { return (Math.abs(x1 - x2) == 1 && Math.abs(y1 - y2) == 2) || (Math.abs(x1 - x2) == 2 && Math.abs(y1 - y2) == 1); } private boolean checkCorrectnessParberry(List> list, int n, int m) { if (n * m != list.size()) { return false; } if (!(Math.abs(list.get(0).getFirst() - list.get(list.size() - 1).getFirst()) == 1 && Math.abs(list.get(0).getSecond() - list.get(list.size() - 1).getSecond()) == 2 || Math.abs(list.get(0).getFirst() - list.get(list.size() - 1).getFirst()) == 2 && Math.abs(list.get(0).getSecond() - list.get(list.size() - 1).getSecond()) == 1)) { return false; } boolean[][] used = new boolean[n][m]; used[list.get(0).getFirst()][list.get(0).getSecond()] = true; for (int i = 1; i < list.size(); i++) { if (!checkMove( list.get(i).getFirst(), list.get(i).getSecond(), list.get(i - 1).getFirst(), list.get(i - 1).getSecond()) || used[list.get(i).getFirst()][list.get(i).getSecond()]) { return false; } used[list.get(i).getFirst()][list.get(i).getSecond()] = true; } return true; } @Test public void testParberry64x64() { par = new ParberryKnightTour(64, 64); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 64, 64)); } @Test public void testParberry128x128() { par = new ParberryKnightTour(128, 128); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 128, 128)); } @Test public void testParberry12x12() { par = new ParberryKnightTour(12, 12); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 12, 12)); } @Test public void testParberry8x8() { par = new ParberryKnightTour(8, 8); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 8, 8)); } @Test public void testParberry14x14() { par = new ParberryKnightTour(14, 14); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 14, 14)); } @Test public void testParberry38x38() { par = new ParberryKnightTour(38, 38); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 38, 38)); } @Test public void testParberry70x72() { par = new ParberryKnightTour(70, 72); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 70, 72)); } @Test public void testParberry14x16() { par = new ParberryKnightTour(14, 16); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 14, 16)); } @Test public void testParberry24x26() { par = new ParberryKnightTour(24, 26); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 24, 26)); } @Test public void testParberry78x80() { par = new ParberryKnightTour(78, 80); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 78, 80)); } @Test public void testParberry140x142() { par = new ParberryKnightTour(140, 142); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 140, 142)); } @Test public void testParberry282x284() { par = new ParberryKnightTour(282, 284); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 282, 284)); } @Test public void testParberry696x698() { par = new ParberryKnightTour(696, 698); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 696, 698)); } @Test public void testParberry150x150() { par = new ParberryKnightTour(150, 150); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 150, 150)); } @Test public void testParberry76x76() { par = new ParberryKnightTour(76, 76); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 76, 76)); } @Test public void testParberry34x36() { par = new ParberryKnightTour(34, 36); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 34, 36)); } @Test public void testParberry340x342() { par = new ParberryKnightTour(340, 342); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 340, 342)); } @Test public void testParberry6x6() { par = new ParberryKnightTour(6, 6); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 6, 6)); } @Test public void testParberry6x8() { par = new ParberryKnightTour(6, 8); assertTrue(checkCorrectnessParberry(par.getTour().toList(), 6, 8)); } @Test(expected = IllegalArgumentException.class) public void testParberryIncorrectBoardConf1() { par = new ParberryKnightTour(2, 2); } @Test(expected = IllegalArgumentException.class) public void testParberryIncorrectBoardConf2() { par = new ParberryKnightTour(21, 22); } @Test(expected = IllegalArgumentException.class) public void testParberryIncorrectBoardConf3() { par = new ParberryKnightTour(73, 73); } @Test(expected = IllegalArgumentException.class) public void testParberryIncorrectBoardConf4() { par = new ParberryKnightTour(-20, 20); } @Test(expected = IllegalArgumentException.class) public void testParberryIncorrectBoardConf5() { par = new ParberryKnightTour(40, 44); } @Test public void parberryDoubleInvocationToArrayList() { par = new ParberryKnightTour(48, 50); KnightTour cont = par.getTour(); List> arr1 = cont.toList(); List> arr2 = cont.toList(); assertEquals(arr1.size(), arr2.size()); } @Test public void parberryDoubleInvocationToArrayListAndGenerateTour() { par = new ParberryKnightTour(40, 40); KnightTour cont = par.getTour(); List> arr1 = cont.toList(); List> arr2 = cont.toList(); assertEquals(arr1.size(), arr2.size()); cont = par.getTour(); arr1 = cont.toList(); arr2 = cont.toList(); assertEquals(arr1.size(), arr2.size()); } } WarnsdorffRuleKnightTourHeuristicTest.java000066400000000000000000000333531402514743400353350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-demo/src/test/java/org/jgrapht/demo/* * (C) Copyright 2018-2021, by Kirill Vishnyakov and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.demo; import org.jgrapht.alg.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; public class WarnsdorffRuleKnightTourHeuristicTest { private KnightTour container; private WarnsdorffRuleKnightTourHeuristic solver; @Before public void setup() { container = new KnightTour(); } private boolean checkClosed(Pair start, Pair end) { return Math.abs(start.getFirst() - end.getFirst()) == 1 && Math.abs(start.getSecond() - end.getSecond()) == 2 || Math.abs(start.getFirst() - end.getFirst()) == 2 && Math.abs(start.getSecond() - end.getSecond()) == 1; } private boolean checkOpen(Pair start, Pair end) { return !checkClosed(start, end); } private boolean checkMoveCorrectness( List> tour, int n, int m, int shiftX, int shiftY) { boolean[][] used = new boolean[shiftX + n][shiftY + m]; used[tour.get(0).getFirst()][tour.get(0).getSecond()] = true; for (int i = 1; i < tour.size(); i++) { if (!((Math.abs(tour.get(i - 1).getFirst() - tour.get(i).getFirst()) == 1 && Math.abs(tour.get(i - 1).getSecond() - tour.get(i).getSecond()) == 2) || Math.abs(tour.get(i - 1).getFirst() - tour.get(i).getFirst()) == 2 && Math.abs(tour.get(i - 1).getSecond() - tour.get(i).getSecond()) == 1)) { return false; } assertTrue(tour.get(i).getFirst() >= shiftX); assertTrue(tour.get(i).getFirst() < shiftX + n); assertTrue(tour.get(i).getSecond() >= shiftY); assertTrue(tour.get(i).getSecond() < shiftY + m); if (used[tour.get(i).getFirst()][tour.get(i).getSecond()]) { return false; } used[tour.get(i).getFirst()][tour.get(i).getSecond()] = true; } return true; } private boolean checkStructured( List> tour, boolean structured, int n, int m, int shiftX, int shiftY) { HashSet, Pair>> moves = new HashSet<>(); for (int i = 1; i < tour.size(); i++) { moves.add(new Pair<>(tour.get(i - 1), tour.get(i))); } return !structured || ((moves .contains(new Pair<>(new Pair<>(1 + shiftX, shiftY), new Pair<>(shiftX, 2 + shiftY))) || moves .contains( new Pair<>(new Pair<>(shiftX, 2 + shiftY), new Pair<>(1 + shiftX, shiftY)))) && moves .contains( new Pair<>(new Pair<>(2 + shiftX, shiftY), new Pair<>(shiftX, 1 + shiftY))) || moves .contains( new Pair<>(new Pair<>(shiftX, 1 + shiftY), new Pair<>(2 + shiftX, shiftY))) && moves .contains( new Pair<>( new Pair<>(n - 3 + shiftX, shiftY), new Pair<>(n - 1 + shiftX, 1 + shiftY))) || moves .contains( new Pair<>( new Pair<>(n - 1 + shiftX, 1 + shiftY), new Pair<>(n - 3 + shiftX, shiftY))) && moves .contains( new Pair<>( new Pair<>(n - 2 + shiftX, shiftY), new Pair<>(n - 1 + shiftX, 2 + shiftY))) || moves .contains( new Pair<>( new Pair<>(n - 1 + shiftX, 2 + shiftY), new Pair<>(n - 2 + shiftX, shiftY))) && moves .contains( new Pair<>( new Pair<>(shiftX, m - 3 + shiftY), new Pair<>(1 + shiftX, m - 1 + shiftY))) || moves .contains( new Pair<>( new Pair<>(1 + shiftX, m - 1 + shiftY), new Pair<>(shiftX, m - 3 + shiftY))) && moves .contains( new Pair<>( new Pair<>(shiftX, m - 2 + shiftY), new Pair<>(2 + shiftX, m - 1 + shiftY))) || moves .contains( new Pair<>( new Pair<>(2 + shiftX, m - 1 + shiftY), new Pair<>(shiftX, m - 2 + shiftY))) && moves .contains( new Pair<>( new Pair<>(n - 3 + shiftX, m - 1 + shiftY), new Pair<>(n - 1 + shiftX, m - 2 + shiftY))) || moves .contains( new Pair<>( new Pair<>(n - 1 + shiftX, m - 2 + shiftY), new Pair<>(n - 3 + shiftX, m - 1 + shiftY))) && moves .contains( new Pair<>( new Pair<>(n - 2 + shiftX, m - 1 + shiftY), new Pair<>(n - 1 + shiftX, m - 3 + shiftY))) || moves .contains( new Pair<>( new Pair<>(n - 1 + shiftX, m - 3 + shiftY), new Pair<>(n - 2 + shiftX, n - 2 + shiftY)))); } private void checkCorrectness( List> tour, TourType type, int n, int m, int shiftX, int shiftY, boolean structured) { if (type == TourType.CLOSED) { assertTrue(checkClosed(tour.get(0), tour.get(tour.size() - 1))); } else { assertTrue(checkOpen(tour.get(0), tour.get(tour.size() - 1))); } assertTrue(checkStructured(tour, structured, n, m, shiftX, shiftY)); assertEquals(n * m, tour.size()); assertTrue(checkMoveCorrectness(tour, n, m, shiftX, shiftY)); } @Test public void warnsdorff8x8OpenWithShift() { solver = new WarnsdorffRuleKnightTourHeuristic(8); container = solver.getTour(TourType.OPEN, false, 10, 143); checkCorrectness(container.toList(), TourType.OPEN, 8, 8, 10, 143, false); } @Test public void warnsdorff10x10Open() { solver = new WarnsdorffRuleKnightTourHeuristic(10); container = solver.getTour(TourType.OPEN, false, 0, 0); checkCorrectness(container.toList(), TourType.OPEN, 10, 10, 0, 0, false); } @Test public void warnsdorff37x10Open() { solver = new WarnsdorffRuleKnightTourHeuristic(37, 10); container = solver.getTour(TourType.OPEN, false, 0, 0); checkCorrectness(container.toList(), TourType.OPEN, 37, 10, 0, 0, false); } @Test public void warnsdorff41x41Open() { solver = new WarnsdorffRuleKnightTourHeuristic(41, 41); container = solver.getTour(TourType.OPEN, false, 0, 0); checkCorrectness(container.toList(), TourType.OPEN, 41, 41, 0, 0, false); } @Test public void warnsdorff41x41OpenStructured() { solver = new WarnsdorffRuleKnightTourHeuristic(41, 41); container = solver.getTour(TourType.OPEN, true, 0, 0); checkCorrectness(container.toList(), TourType.OPEN, 41, 41, 0, 0, true); } @Test public void warnsdorff10x10Closed() { solver = new WarnsdorffRuleKnightTourHeuristic(10); container = solver.getTour(TourType.CLOSED, false, 0, 0); checkCorrectness(container.toList(), TourType.CLOSED, 10, 10, 0, 0, false); } @Test public void warnsdorff34x34ClosedWithShift() { solver = new WarnsdorffRuleKnightTourHeuristic(34); container = solver.getTour(TourType.CLOSED, false, 20, 89); checkCorrectness(container.toList(), TourType.CLOSED, 34, 34, 20, 89, false); } @Test public void warnsdorff63x23OpenStructured() { solver = new WarnsdorffRuleKnightTourHeuristic(63, 23); container = solver.getTour(TourType.OPEN, true, 0, 0); checkCorrectness(container.toList(), TourType.OPEN, 63, 23, 0, 0, true); } @Test public void warnsdorff49x34Closed() { solver = new WarnsdorffRuleKnightTourHeuristic(49, 34); container = solver.getTour(TourType.CLOSED, false, 0, 0); checkCorrectness(container.toList(), TourType.CLOSED, 49, 34, 0, 0, false); } @Test public void warnsdorff34x34ClosedStructuredWithShift() { solver = new WarnsdorffRuleKnightTourHeuristic(34); container = solver.getTour(TourType.CLOSED, true, 20, 89); checkCorrectness(container.toList(), TourType.CLOSED, 34, 34, 20, 89, true); } @Test public void warnsdorff18x34ClosedStructuredWithShift() { solver = new WarnsdorffRuleKnightTourHeuristic(18, 34); container = solver.getTour(TourType.CLOSED, true, 7, 7); checkCorrectness(container.toList(), TourType.CLOSED, 18, 34, 7, 7, true); } @Test public void warnsdorff42x42ClosedStructured() { solver = new WarnsdorffRuleKnightTourHeuristic(42, 42); container = solver.getTour(TourType.CLOSED, true, 0, 0); checkCorrectness(container.toList(), TourType.CLOSED, 42, 42, 0, 0, true); } @Test public void warnsdorff40x20OpenStructured() { solver = new WarnsdorffRuleKnightTourHeuristic(40, 20); container = solver.getTour(TourType.OPEN, true, 0, 0); checkCorrectness(container.toList(), TourType.OPEN, 40, 20, 0, 0, true); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectConfigurationOpen() { solver = new WarnsdorffRuleKnightTourHeuristic(2, 200); container = solver.getTour(TourType.OPEN, false, 0, 0); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectConfigurationOpen3x5() { solver = new WarnsdorffRuleKnightTourHeuristic(5, 3); container = solver.getTour(TourType.OPEN, false, 0, 0); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectConfigurationOpen3x6() { solver = new WarnsdorffRuleKnightTourHeuristic(3, 6); container = solver.getTour(TourType.OPEN, false, 0, 0); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectConfigurationOpen4x4() { solver = new WarnsdorffRuleKnightTourHeuristic(4, 4); container = solver.getTour(TourType.OPEN, false, 0, 0); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectTourConfigurationClosedBothOdd() { solver = new WarnsdorffRuleKnightTourHeuristic(31, 31); container = solver.getTour(TourType.CLOSED, false, 0, 0); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectTourConfigurationClosed4x6() { solver = new WarnsdorffRuleKnightTourHeuristic(4, 6); container = solver.getTour(TourType.CLOSED, false, 0, 0); } @Test public void warnsdorffOpenNonStructured4x6() { solver = new WarnsdorffRuleKnightTourHeuristic(4, 6); container = solver.getTour(TourType.OPEN, false, 0, 0); checkCorrectness(container.toList(), TourType.OPEN, 4, 6, 0, 0, false); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectBoardConfiguration0x100() { solver = new WarnsdorffRuleKnightTourHeuristic(0, 100); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectBoardConfigurationBothNegative() { solver = new WarnsdorffRuleKnightTourHeuristic(-132); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectBoardConfigurationBothNegative2() { solver = new WarnsdorffRuleKnightTourHeuristic(-132, -140); } @Test(expected = IllegalArgumentException.class) public void warnsdorffIncorrectBoardConfigurationOneDimSmall() { solver = new WarnsdorffRuleKnightTourHeuristic(1, 10); } @Test public void warnsdorffDoubleInvocationToArrayList() { solver = new WarnsdorffRuleKnightTourHeuristic(49, 34); container = solver.getTour(TourType.CLOSED, false, 0, 0); List> arr1 = container.toList(); List> arr2 = container.toList(); assertEquals(arr1.size(), arr2.size()); } @Test public void warnsdorffDoubleInvocationToArrayListAndgetTour() { solver = new WarnsdorffRuleKnightTourHeuristic(40, 40); container = solver.getTour(TourType.CLOSED, false, 0, 0); List> arr1 = container.toList(); List> arr2 = container.toList(); assertEquals(arr1.size(), arr2.size()); container = solver.getTour(TourType.OPEN, true, 10, 10); arr1 = container.toList(); arr2 = container.toList(); assertEquals(arr1.size(), arr2.size()); } } jgrapht-jgrapht-1.5.1/jgrapht-dist/000077500000000000000000000000001402514743400172165ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-dist/pom.xml000066400000000000000000000077461402514743400205510ustar00rootroot00000000000000 4.0.0 org.jgrapht jgrapht 1.5.1 jgrapht-dist JGraphT - Distributable Archives pom ${project.parent.basedir} GNU Lesser General Public License Version 2.1, February 1999 http://jgrapht.sourceforge.net/LGPL.html repo Eclipse Public License (EPL) 2.0 http://www.eclipse.org/legal/epl-v20.html repo ${project.groupId} jgrapht-core ${project.groupId} jgrapht-io ${project.groupId} jgrapht-ext ${project.groupId} jgrapht-guava ${project.groupId} jgrapht-demo ${project.groupId} jgrapht-opt ${project.groupId} jgrapht-unimi-dsi maven-assembly-plugin assembly package single true jgrapht-${project.version} ${basedir}/src/assembly false posix org.apache.maven.plugins maven-resources-plugin org.apache.maven.plugins maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.felix maven-bundle-plugin org.apache.maven.plugins maven-deploy-plugin true jgrapht-jgrapht-1.5.1/jgrapht-dist/src/000077500000000000000000000000001402514743400200055ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-dist/src/assembly/000077500000000000000000000000001402514743400216245ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-dist/src/assembly/assembly.xml000066400000000000000000000126721402514743400241750ustar00rootroot00000000000000 assembly zip tar.gz false lib runtime org.jgrapht:jgrapht-ext:*:combined false lib runtime jgrapht-${artifact.version}${dashClassifier?}.${artifact.extension} org.jgrapht:jgrapht-ext:*:combined ${main.basedir}/README.md ${main.basedir}/HISTORY.md ${main.basedir}/CONTRIBUTORS.md ${main.basedir}/license-LGPL.txt ${main.basedir}/license-EPL.txt ${main.basedir}/etc/licenses/antlr-license.txt 3rd-party-licenses ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses commons-lang3-license.txt ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses commons-math3-license.txt ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses commons-text-license.txt ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses dsiutils-license.txt ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses fastutil-license.txt ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses guava-license.txt ${main.basedir}/etc/licenses/jgraphx-license.txt 3rd-party-licenses ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses jheaps-license.txt ${main.basedir}/license-EPL.txt 3rd-party-licenses jsap-license.txt ${main.basedir}/license-EPL.txt 3rd-party-licenses logback-license.txt ${main.basedir}/etc/licenses/mit-license.txt 3rd-party-licenses slf4j-api-license.txt ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses sux4j-license.txt ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses webgraph-license.txt ${main.basedir}/etc/licenses/apache-license-2.0.txt 3rd-party-licenses webgraph-big-license.txt ${main.basedir} source **/target/** ${main.basedir}/target/site/apidocs javadoc **/* jgrapht-jgrapht-1.5.1/jgrapht-ext/000077500000000000000000000000001402514743400170535ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/pom.xml000066400000000000000000000044611402514743400203750ustar00rootroot00000000000000 4.0.0 org.jgrapht jgrapht 1.5.1 jgrapht-ext JGraphT - Ext ${project.parent.basedir} GNU Lesser General Public License Version 2.1, February 1999 http://jgrapht.sourceforge.net/LGPL.html repo Eclipse Public License (EPL) 2.0 http://www.eclipse.org/legal/epl-v20.html repo org.apache.maven.plugins maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-resources-plugin org.apache.felix maven-bundle-plugin ${project.groupId} jgrapht-core com.github.vlsi.mxgraph jgraphx 4.1.0 junit junit test jgrapht-jgrapht-1.5.1/jgrapht-ext/src/000077500000000000000000000000001402514743400176425ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/main/000077500000000000000000000000001402514743400205665ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/main/java/000077500000000000000000000000001402514743400215075ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/main/java/module-info.java000066400000000000000000000002341402514743400245670ustar00rootroot00000000000000module org.jgrapht.ext { exports org.jgrapht.ext; requires transitive org.jgrapht.core; requires transitive com.github.vlsi.mxgraph.jgraphx; } jgrapht-jgrapht-1.5.1/jgrapht-ext/src/main/java/org/000077500000000000000000000000001402514743400222765ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/main/java/org/jgrapht/000077500000000000000000000000001402514743400237355ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/main/java/org/jgrapht/ext/000077500000000000000000000000001402514743400245355ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/main/java/org/jgrapht/ext/JGraphXAdapter.java000066400000000000000000000213201402514743400302020ustar00rootroot00000000000000/* * (C) Copyright 2013-2021, by JeanYves Tinevez and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.ext; import com.mxgraph.model.*; import com.mxgraph.view.*; import org.jgrapht.*; import org.jgrapht.event.*; import java.util.*; /** *

    * Adapter to draw a JGraphT graph with the JGraphX drawing library. *

    * *

    * This adapter will not convert JGraphX to JGraphT - this should be handled in another class * entirely. *

    * *

    * Note: If this class is used with an edge type such as String, you must either supply unique * String names via addEdge(v1, v2, "edge123"), or use a custom edge factory which does so. * Otherwise, if you use addEdge(v1, v2), the edge will be created with an empty String "" as value * and saved (in JGraphT as well as in this class), which results in the edge not saving correctly. *

    * * @param the graph vertex type * @param the graph edge type * * @author JeanYves Tinevez */ public class JGraphXAdapter extends mxGraph implements GraphListener { /** * The graph to be drawn. Has vertices "V" and edges "E". */ private Graph graphT; /** * Maps the JGraphT-Vertices onto JGraphX-mxICells. {@link #cellToVertexMap} is for the opposite * direction. */ private HashMap vertexToCellMap = new HashMap<>(); /** * Maps the JGraphT-Edges onto JGraphX-mxICells. {@link #cellToEdgeMap} is for the opposite * direction. */ private HashMap edgeToCellMap = new HashMap<>(); /** * Maps the JGraphX-mxICells onto JGraphT-Edges. {@link #edgeToCellMap} is for the opposite * direction. */ private HashMap cellToVertexMap = new HashMap<>(); /** * Maps the JGraphX-mxICells onto JGraphT-Vertices. {@link #vertexToCellMap} is for the opposite * direction. */ private HashMap cellToEdgeMap = new HashMap<>(); /** * Constructs and draws a new ListenableGraph. If the graph changes through the ListenableGraph, * the JGraphXAdapter will automatically add/remove the new edge/vertex as it implements the * GraphListener interface. Throws a IllegalArgumentException if the graph is null. * * @param graph casted to graph */ public JGraphXAdapter(ListenableGraph graph) { // call normal constructor with graph class this((Graph) graph); graph.addGraphListener(this); } /** * Constructs and draws a new mxGraph from a JGraphT graph. Changes on the JGraphT graph will * not edit this mxGraph any further; use the constructor with the ListenableGraph parameter * instead or use this graph as a normal mxGraph. Throws an IllegalArgumentException if the * parameter is null. * * @param graph is a graph */ public JGraphXAdapter(Graph graph) { super(); // Don't accept null as jgrapht graph if (graph == null) { throw new IllegalArgumentException(); } else { this.graphT = graph; } // generate the drawing insertJGraphT(graph); setAutoSizeCells(true); } /** * Returns Hashmap which maps the vertices onto their visualization mxICells. * * @return {@link #vertexToCellMap} */ public HashMap getVertexToCellMap() { return vertexToCellMap; } /** * Returns Hashmap which maps the edges onto their visualization mxICells. * * @return {@link #edgeToCellMap} */ public HashMap getEdgeToCellMap() { return edgeToCellMap; } /** * Returns Hashmap which maps the visualization mxICells onto their edges. * * @return {@link #cellToEdgeMap} */ public HashMap getCellToEdgeMap() { return cellToEdgeMap; } /** * Returns Hashmap which maps the visualization mxICells onto their vertices. * * @return {@link #cellToVertexMap} */ public HashMap getCellToVertexMap() { return cellToVertexMap; } @Override public void vertexAdded(GraphVertexChangeEvent e) { addJGraphTVertex(e.getVertex()); } @Override public void vertexRemoved(GraphVertexChangeEvent e) { mxICell cell = vertexToCellMap.remove(e.getVertex()); removeCells(new Object[] { cell }); // remove vertex from hashmaps cellToVertexMap.remove(cell); vertexToCellMap.remove(e.getVertex()); // remove all edges that connected to the vertex ArrayList removedEdges = new ArrayList<>(); // first, generate a list of all edges that have to be deleted // so we don't change the cellToEdgeMap.values by deleting while // iterating // we have to iterate over this because the graphT has already // deleted the vertex and edges so we can't query what the edges were for (E edge : cellToEdgeMap.values()) { if (!graphT.containsEdge(edge)) { removedEdges.add(edge); } } // then delete all entries of the previously generated list for (E edge : removedEdges) { removeEdge(edge); } } @Override public void edgeAdded(GraphEdgeChangeEvent e) { addJGraphTEdge(e.getEdge()); } @Override public void edgeRemoved(GraphEdgeChangeEvent e) { removeEdge(e.getEdge()); } /** * Removes a jgrapht edge and its visual representation from this graph completely. * * @param edge The edge that will be removed */ private void removeEdge(E edge) { mxICell cell = edgeToCellMap.remove(edge); removeCells(new Object[] { cell }); // remove edge from hashmaps cellToEdgeMap.remove(cell); edgeToCellMap.remove(edge); } /** * Draws a new vertex into the graph. * * @param vertex vertex to be added to the graph */ private void addJGraphTVertex(V vertex) { getModel().beginUpdate(); try { // create a new JGraphX vertex at position 0 mxICell cell = (mxICell) insertVertex(defaultParent, null, vertex, 0, 0, 0, 0); // update cell size so cell isn't "above" graph updateCellSize(cell); // Save reference between vertex and cell vertexToCellMap.put(vertex, cell); cellToVertexMap.put(cell, vertex); } finally { getModel().endUpdate(); } } /** * Draws a new egde into the graph. * * @param edge edge to be added to the graph. Source and target vertices are needed. */ private void addJGraphTEdge(E edge) { getModel().beginUpdate(); try { // find vertices of edge V sourceVertex = graphT.getEdgeSource(edge); V targetVertex = graphT.getEdgeTarget(edge); // if the one of the vertices is not drawn, don't draw the edge if (!(vertexToCellMap.containsKey(sourceVertex) && vertexToCellMap.containsKey(targetVertex))) { return; } // get mxICells Object sourceCell = vertexToCellMap.get(sourceVertex); Object targetCell = vertexToCellMap.get(targetVertex); // add edge between mxICells mxICell cell = (mxICell) insertEdge(defaultParent, null, edge, sourceCell, targetCell); // update cell size so cell isn't "above" graph updateCellSize(cell); // Save reference between vertex and cell edgeToCellMap.put(edge, cell); cellToEdgeMap.put(cell, edge); } finally { getModel().endUpdate(); } } /** * Draws a given graph with all its vertices and edges. * * @param graph the graph to be added to the existing graph. */ private void insertJGraphT(Graph graph) { for (V vertex : graph.vertexSet()) { addJGraphTVertex(vertex); } for (E edge : graph.edgeSet()) { addJGraphTEdge(edge); } } } jgrapht-jgrapht-1.5.1/jgrapht-ext/src/main/java/org/jgrapht/ext/package-info.java000066400000000000000000000001301402514743400277160ustar00rootroot00000000000000/** * Extensions and integration means to other products. */ package org.jgrapht.ext; jgrapht-jgrapht-1.5.1/jgrapht-ext/src/test/000077500000000000000000000000001402514743400206215ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/test/java/000077500000000000000000000000001402514743400215425ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/test/java/org/000077500000000000000000000000001402514743400223315ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/test/java/org/jgrapht/000077500000000000000000000000001402514743400237705ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/test/java/org/jgrapht/ext/000077500000000000000000000000001402514743400245705ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-ext/src/test/java/org/jgrapht/ext/JGraphXAdapterTest.java000066400000000000000000000320351402514743400311020ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Barak Naveh and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.ext; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import com.mxgraph.model.*; import static org.junit.Assert.*; /** * Test methods for the class JGraphXAdapter. */ public class JGraphXAdapterTest { /** * Test scenarios under normal conditions. */ @Test public void genericTest() { ListenableGraph jGraphT = new DefaultListenableGraph<>(new DefaultDirectedGraph<>(DefaultEdge.class)); // fill graph with data String v1 = "Vertex 1"; String v2 = "Vertex 2"; String v3 = "Vertex 3"; String v4 = "Vertex 4"; jGraphT.addVertex(v1); jGraphT.addVertex(v2); jGraphT.addVertex(v3); jGraphT.addVertex(v4); final int expectedEdges = 5; jGraphT.addEdge(v1, v2); jGraphT.addEdge(v1, v3); jGraphT.addEdge(v1, v4); jGraphT.addEdge(v2, v3); jGraphT.addEdge(v3, v4); // Create jgraphx graph and test it JGraphXAdapter graphX = new JGraphXAdapter(jGraphT); testMapping(graphX); // test if all values are in the jgraphx graph Object[] expectedArray = { v1, v2, v3, v4 }; Arrays.sort(expectedArray); Object[] realArray = graphX.getCellToVertexMap().values().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); realArray = graphX.getVertexToCellMap().keySet().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); int edgesCount = graphX.getCellToEdgeMap().values().size(); Assert.assertEquals(expectedEdges, edgesCount); edgesCount = graphX.getEdgeToCellMap().keySet().size(); Assert.assertEquals(expectedEdges, edgesCount); } /** * Tests the correct implementation of the GraphListener interface. */ @Test public void listenerTest() { ListenableGraph jGraphT = new DefaultListenableGraph<>(new DefaultDirectedGraph<>(String.class)); JGraphXAdapter graphX = new JGraphXAdapter(jGraphT); // add some data to the jgrapht graph - changes should be propagated // through jgraphxadapters graphlistener interface String v1 = "Vertex 1"; String v2 = "Vertex 2"; String v3 = "Vertex 3"; String v4 = "Vertex 4"; jGraphT.addVertex(v1); jGraphT.addVertex(v2); jGraphT.addVertex(v3); jGraphT.addVertex(v4); jGraphT.addEdge(v1, v2, "Edge 1"); jGraphT.addEdge(v1, v3, "Edge 2"); jGraphT.addEdge(v1, v4, "Edge 3"); jGraphT.addEdge(v2, v3, "Edge 4"); jGraphT.addEdge(v3, v4, "Edge 5"); int expectedEdges = jGraphT.edgeSet().size(); testMapping(graphX); // test if all values are in the jgraphx graph Object[] expectedArray = { v1, v2, v3, v4 }; Arrays.sort(expectedArray); Object[] realArray = graphX.getCellToVertexMap().values().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); realArray = graphX.getVertexToCellMap().keySet().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); int edgesCount = graphX.getCellToEdgeMap().values().size(); Assert.assertEquals(expectedEdges, edgesCount); edgesCount = graphX.getEdgeToCellMap().keySet().size(); Assert.assertEquals(expectedEdges, edgesCount); // remove some data from the jgraphT graph jGraphT.removeVertex(v4); jGraphT.removeVertex(v3); jGraphT.removeEdge(v1, v2); int expectedEdgesAfterRemove = jGraphT.edgeSet().size(); // test if all values are in the jgraphx graph expectedArray = new Object[] { v1, v2 }; Arrays.sort(expectedArray); realArray = graphX.getCellToVertexMap().values().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); realArray = graphX.getVertexToCellMap().keySet().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); edgesCount = graphX.getCellToEdgeMap().values().size(); Assert.assertEquals(expectedEdgesAfterRemove, edgesCount); edgesCount = graphX.getEdgeToCellMap().keySet().size(); Assert.assertEquals(expectedEdgesAfterRemove, edgesCount); } /** * Tests conditions if graph is initialized without a JgraphT graph. */ @Test public void nullInitializationTest() { try { new JGraphXAdapter(null); fail("Expected illegal argument exception"); } catch (IllegalArgumentException e) { // expected result Assert.assertTrue(true); } catch (Exception e) { fail("Unexpected error encountered during " + " creation of JGraphXAdapter with null"); } } /** * Tests the JGraphXAdapter with 1.000 nodes and 1.000 edges. */ @Test public void loadTest() { final int maxVertices = 1000; final int maxEdges = 1000; ListenableGraph jGraphT = new DefaultListenableGraph<>(new DefaultDirectedGraph<>(DefaultEdge.class)); for (int i = 0; i < maxVertices; i++) { jGraphT.addVertex(i); } for (int i = 0; i < maxEdges; i++) { jGraphT.addEdge(i, (i + 1) % jGraphT.vertexSet().size()); } JGraphXAdapter graphX = null; try { graphX = new JGraphXAdapter(jGraphT); } catch (Exception e) { fail( "Unexpected error while creating JgraphXAdapter with" + maxVertices + " vertices and " + maxEdges + " Edges"); } testMapping(graphX); } /** * Tests if JGraphXAdapter works with not-listenable Graphs. */ @Test public void notListenableTest() { Graph jGraphT = new DefaultDirectedGraph(String.class); // fill graph with data String v1 = "Vertex 1"; String v2 = "Vertex 2"; String v3 = "Vertex 3"; String v4 = "Vertex 4"; jGraphT.addVertex(v1); jGraphT.addVertex(v2); jGraphT.addVertex(v3); final int expectedEdges = 3; jGraphT.addEdge(v1, v2, "Edge 1"); jGraphT.addEdge(v1, v3, "Edge 2"); jGraphT.addEdge(v2, v3, "Edge 3"); JGraphXAdapter graphX = new JGraphXAdapter(jGraphT); jGraphT.addVertex(v4); jGraphT.addEdge(v1, v4, "Edge 4"); jGraphT.addEdge(v3, v4, "Edge 5"); testMapping(graphX); // test if all values are in the jgraphx graph Object[] expectedArray = { v1, v2, v3 }; Arrays.sort(expectedArray); Object[] realArray = graphX.getCellToVertexMap().values().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); realArray = graphX.getVertexToCellMap().keySet().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); int edgesCount = graphX.getCellToEdgeMap().values().size(); Assert.assertEquals(expectedEdges, edgesCount); edgesCount = graphX.getEdgeToCellMap().keySet().size(); Assert.assertEquals(expectedEdges, edgesCount); } /** * Test if duplicate Entries are saved only once. */ @Test public void duplicateEntriesTest() { ListenableGraph jGraphT = new DefaultListenableGraph<>(new DefaultDirectedGraph<>(DefaultEdge.class)); JGraphXAdapter graphX = new JGraphXAdapter(jGraphT); // fill graph with data String v1 = "Vertex 1"; String v2 = "Vertex 2"; String v3 = "Vertex 3"; String v4 = "Vertex 4"; DefaultEdge edge1 = new DefaultEdge(); jGraphT.addVertex(v1); jGraphT.addVertex(v2); jGraphT.addVertex(v3); jGraphT.addVertex(v4); jGraphT.addVertex(v1); jGraphT.addVertex(v2); jGraphT.addVertex(v3); jGraphT.addVertex(v4); /* * edge1 is added 3 times with different source/target vertices it should only add it once. * A new edge is added with source-target combination already in the graph it should not be * added to the graph. */ final int expectedEdges = 3; jGraphT.addEdge(v1, v2, edge1); jGraphT.addEdge(v1, v2, new DefaultEdge()); assertThrows(IntrusiveEdgeException.class, () -> jGraphT.addEdge(v1, v3, edge1)); assertThrows(IntrusiveEdgeException.class, () -> jGraphT.addEdge(v1, v4, edge1)); jGraphT.addEdge(v2, v3); jGraphT.addEdge(v3, v4); testMapping(graphX); // test if all values are in the jgraphx graph Object[] expectedArray = { v1, v2, v3, v4 }; Arrays.sort(expectedArray); Object[] realArray = graphX.getCellToVertexMap().values().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); realArray = graphX.getVertexToCellMap().keySet().toArray(); Arrays.sort(realArray); Assert.assertArrayEquals(expectedArray, realArray); int edgesCount = graphX.getCellToEdgeMap().values().size(); Assert.assertEquals(expectedEdges, edgesCount); edgesCount = graphX.getEdgeToCellMap().keySet().size(); Assert.assertEquals(expectedEdges, edgesCount); } // ========================Helper Methods=============================== /** * Tests the mapping of the graph for consistency. Mapping includes: - getCellToEdgeMap - * getEdgeToCellMap - getCellToVertexMap - getVertexToCellMap * * @param graph The graph to be tested * * @param The class used for the edges of the JGraphXAdapter * * @param The class used for the vertices of the JGraphXAdapter */ private void testMapping(JGraphXAdapter graph) { // Edges HashMap cellToEdgeMap = graph.getCellToEdgeMap(); HashMap edgeToCellMap = graph.getEdgeToCellMap(); // Test for null if (cellToEdgeMap == null) { fail("GetCellToEdgeMap returned null"); } if (edgeToCellMap == null) { fail("GetEdgeToCellMap returned null"); } // Compare keys to values if (!compare(edgeToCellMap.values(), cellToEdgeMap.keySet())) { fail("CellToEdgeMap has not the " + "same keys as the values in EdgeToCellMap"); } if (!compare(cellToEdgeMap.values(), edgeToCellMap.keySet())) { fail("EdgeToCellMap has not the " + "same keys as the values in CellToEdgeMap"); } // Vertices HashMap cellToVertexMap = graph.getCellToVertexMap(); HashMap vertexToCellMap = graph.getVertexToCellMap(); // Test for null if (cellToVertexMap == null) { fail("GetVertexToCellMap returned null"); } if (vertexToCellMap == null) { fail("GetCellToVertexMap returned null"); } // Compare keys to values if (!compare(vertexToCellMap.values(), cellToVertexMap.keySet())) { fail("CellToVertexMap has not the same " + "keys as the values in VertexToCellMap"); } if (!compare(cellToVertexMap.values(), vertexToCellMap.keySet())) { fail("VertexToCellMap has not the same " + "keys as the values in CellToVertexMap"); } } /** * Compares a collection to a set by creating a new set from the collection and using equals. * * @param collection The collection that is compared * * @param set The set that is compared * * @param The classtype of the set and collection. * * @return True, if set and collection are equivalent; False if not. * */ private boolean compare(Collection collection, Set set) { Set compareSet = new HashSet(); compareSet.addAll(collection); return set.equals(compareSet); } } jgrapht-jgrapht-1.5.1/jgrapht-guava/000077500000000000000000000000001402514743400173565ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/pom.xml000066400000000000000000000073761402514743400207100ustar00rootroot00000000000000 4.0.0 org.jgrapht jgrapht 1.5.1 jgrapht-guava JGraphT - Guava Adapter ${project.parent.basedir} GNU Lesser General Public License Version 2.1, February 1999 http://jgrapht.sourceforge.net/LGPL.html repo Eclipse Public License (EPL) 2.0 http://www.eclipse.org/legal/epl-v20.html repo org.apache.maven.plugins maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-resources-plugin org.apache.felix maven-bundle-plugin ${project.groupId} jgrapht-core com.google.guava guava 29.0-jre com.google.code.findbugs jsr305 com.google.errorprone error_prone_annotations com.google.j2objc j2objc-annotations org.codehaus.mojo animal-sniffer-annotations org.checkerframework checker-compat-qual org.checkerframework checker-qual com.google.guava failureaccess com.google.guava listenablefuture junit junit test jgrapht-jgrapht-1.5.1/jgrapht-guava/src/000077500000000000000000000000001402514743400201455ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/000077500000000000000000000000001402514743400210715ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/000077500000000000000000000000001402514743400220125ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/module-info.java000066400000000000000000000002741402514743400250760ustar00rootroot00000000000000module org.jgrapht.guava { exports org.jgrapht.graph.guava; requires transitive org.jgrapht.core; requires transitive com.google.common; requires transitive org.jheaps; } jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/000077500000000000000000000000001402514743400226015ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/000077500000000000000000000000001402514743400242405ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/000077500000000000000000000000001402514743400253415ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/000077500000000000000000000000001402514743400264445ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseGraphAdapter.java000066400000000000000000000252521402514743400324520ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.*; import org.jgrapht.graph.AbstractGraph; import org.jgrapht.graph.*; import java.io.*; import java.util.*; import java.util.function.*; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toSet; /** * A base abstract implementation for the graph adapter class using Guava's {@link Graph}. This is a * helper class in order to support both mutable and immutable graphs. * * @author Dimitrios Michail * * @param the graph vertex type * @param type of the underlying Guava's graph */ public abstract class BaseGraphAdapter> extends AbstractGraph> implements Graph>, Cloneable, Serializable { private static final long serialVersionUID = -6742507788742087708L; protected static final String LOOPS_NOT_ALLOWED = "loops not allowed"; protected transient Set unmodifiableVertexSet = null; protected transient Set> unmodifiableEdgeSet = null; protected Supplier vertexSupplier; protected Supplier> edgeSupplier; protected transient G graph; protected ElementOrderMethod vertexOrderMethod; protected transient ElementOrder vertexOrder; /** * Create a new adapter. * * @param graph the graph */ public BaseGraphAdapter(G graph) { this(graph, null, null); } /** * Create a new adapter. * * @param graph the graph * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public BaseGraphAdapter( G graph, Supplier vertexSupplier, Supplier> edgeSupplier) { this(graph, vertexSupplier, edgeSupplier, ElementOrderMethod.internal()); } /** * Create a new adapter. * * @param graph the graph * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param vertexOrderMethod the method used to ensure a total order of the graph vertices. This * is required in order to make edge source/targets be consistent. */ public BaseGraphAdapter( G graph, Supplier vertexSupplier, Supplier> edgeSupplier, ElementOrderMethod vertexOrderMethod) { this.vertexSupplier = vertexSupplier; this.edgeSupplier = edgeSupplier; this.graph = Objects.requireNonNull(graph); this.vertexOrderMethod = Objects.requireNonNull(vertexOrderMethod); this.vertexOrder = createVertexOrder(vertexOrderMethod); } @Override public Supplier getVertexSupplier() { return vertexSupplier; } /** * Set the vertex supplier that the graph uses whenever it needs to create new vertices. * *

    * A graph uses the vertex supplier to create new vertex objects whenever a user calls method * {@link Graph#addVertex()}. Users can also create the vertex in user code and then use method * {@link Graph#addVertex(Object)} to add the vertex. * *

    * In contrast with the {@link Supplier} interface, the vertex supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new vertex to be added in a graph v must not be equal * to any other vertex in the graph. More formally, the graph must not contain any vertex * v2 such that v2.equals(v). * * @param vertexSupplier the vertex supplier */ public void setVertexSupplier(Supplier vertexSupplier) { this.vertexSupplier = vertexSupplier; } @Override public Supplier> getEdgeSupplier() { return edgeSupplier; } /** * Set the edge supplier that the graph uses whenever it needs to create new edges. * *

    * A graph uses the edge supplier to create new edge objects whenever a user calls method * {@link Graph#addEdge(Object, Object)}. Users can also create the edge in user code and then * use method {@link Graph#addEdge(Object, Object, Object)} to add the edge. * *

    * In contrast with the {@link Supplier} interface, the edge supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new edge to be added in a graph e must not be equal to * any other edge in the graph (even if the graph allows edge-multiplicity). More formally, the * graph must not contain any edge e2 such that e2.equals(e). * * @param edgeSupplier the edge supplier */ public void setEdgeSupplier(Supplier> edgeSupplier) { this.edgeSupplier = edgeSupplier; } @Override public EndpointPair getEdge(V sourceVertex, V targetVertex) { if (sourceVertex == null || targetVertex == null) { return null; } else if (!graph.hasEdgeConnecting(sourceVertex, targetVertex)) { return null; } else { return createEdge(sourceVertex, targetVertex); } } @Override public Set vertexSet() { if (unmodifiableVertexSet == null) { unmodifiableVertexSet = Collections.unmodifiableSet(graph.nodes()); } return unmodifiableVertexSet; } @Override public V getEdgeSource(EndpointPair e) { if (graph.isDirected()) { return e.nodeU(); } else { V u = e.nodeU(); V v = e.nodeV(); int c = vertexOrder.compare(u, v); if (c <= 0) { return u; } return v; } } @Override public V getEdgeTarget(EndpointPair e) { if (graph.isDirected()) { return e.nodeV(); } else { V u = e.nodeU(); V v = e.nodeV(); int c = vertexOrder.compare(u, v); if (c <= 0) { return v; } return u; } } @Override public GraphType getType() { return (graph.isDirected() ? new DefaultGraphType.Builder().directed() : new DefaultGraphType.Builder().undirected()) .weighted(false).allowMultipleEdges(false).allowSelfLoops(graph.allowsSelfLoops()) .build(); } @Override public boolean containsEdge(EndpointPair e) { return graph.edges().contains(e); } @Override public boolean containsVertex(V v) { return graph.nodes().contains(v); } @Override public Set> edgeSet() { if (unmodifiableEdgeSet == null) { unmodifiableEdgeSet = Collections.unmodifiableSet(graph.edges()); } return unmodifiableEdgeSet; } @Override public int degreeOf(V vertex) { return graph.degree(vertex); } @Override public Set> edgesOf(V vertex) { return graph.incidentEdges(vertex); } @Override public int inDegreeOf(V vertex) { return graph.inDegree(vertex); } @Override public Set> incomingEdgesOf(V vertex) { return graph .predecessors(vertex).stream().map(other -> createEdge(other, vertex)) .collect(collectingAndThen(toSet(), Collections::unmodifiableSet)); } @Override public int outDegreeOf(V vertex) { return graph.outDegree(vertex); } @Override public Set> outgoingEdgesOf(V vertex) { return graph .successors(vertex).stream().map(other -> createEdge(vertex, other)) .collect(collectingAndThen(toSet(), Collections::unmodifiableSet)); } @Override public double getEdgeWeight(EndpointPair e) { if (e == null) { throw new NullPointerException(); } else if (!graph.hasEdgeConnecting(e.nodeU(), e.nodeV())) { throw new IllegalArgumentException("no such edge in graph: " + e.toString()); } else { return Graph.DEFAULT_EDGE_WEIGHT; } } @Override public Set> getAllEdges(V sourceVertex, V targetVertex) { if (sourceVertex == null || targetVertex == null || !graph.nodes().contains(sourceVertex) || !graph.nodes().contains(targetVertex)) { return null; } else if (!graph.hasEdgeConnecting(sourceVertex, targetVertex)) { return Collections.emptySet(); } else { return Collections.singleton(createEdge(sourceVertex, targetVertex)); } } /** * Create an edge. * * @param s the source vertex * @param t the target vertex * @return the edge */ final EndpointPair createEdge(V s, V t) { return graph.isDirected() ? EndpointPair.ordered(s, t) : EndpointPair.unordered(s, t); } /** * Create the internal vertex order implementation. * * @param vertexOrderMethod method to use * @return the vertex order */ protected ElementOrder createVertexOrder(ElementOrderMethod vertexOrderMethod) { switch (vertexOrderMethod.getType()) { case COMPARATOR: return ElementOrder.comparator(vertexOrderMethod.comparator()); case GUAVA_COMPARATOR: if (!graph .nodeOrder().type().equals(com.google.common.graph.ElementOrder.Type.SORTED)) { throw new IllegalArgumentException( "Guava comparator only usable if node order is SORTED!"); } return ElementOrder.comparator(graph.nodeOrder().comparator()); case NATURAL: return ElementOrder.natural(); case INTERNAL: default: return ElementOrder.internal(); } } } jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseNetworkAdapter.java000066400000000000000000000230251402514743400330360ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.*; import org.jgrapht.graph.AbstractGraph; import org.jgrapht.graph.*; import java.io.*; import java.util.*; import java.util.function.*; /** * A base abstract implementation for the graph adapter class using Guava's {@link Network}. This is * a helper class in order to support both mutable and immutable networks. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type * @param type of the underlying Guava's network */ public abstract class BaseNetworkAdapter> extends AbstractGraph implements Graph, Cloneable, Serializable { private static final long serialVersionUID = -6233085794632237761L; protected static final String LOOPS_NOT_ALLOWED = "loops not allowed"; protected transient Set unmodifiableVertexSet = null; protected transient Set unmodifiableEdgeSet = null; protected Supplier vertexSupplier; protected Supplier edgeSupplier; protected transient N network; protected ElementOrderMethod vertexOrderMethod; protected transient ElementOrder vertexOrder; /** * Create a new network adapter. * * @param network the mutable network */ public BaseNetworkAdapter(N network) { this(network, null, null); } /** * Create a new network adapter. * * @param network the mutable network * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public BaseNetworkAdapter(N network, Supplier vertexSupplier, Supplier edgeSupplier) { this(network, vertexSupplier, edgeSupplier, ElementOrderMethod.internal()); } /** * Create a new network adapter. * * @param network the mutable network * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param vertexOrderMethod the method used to ensure a total order of the graph vertices. This * is required in order to make edge source/targets be consistent. */ public BaseNetworkAdapter( N network, Supplier vertexSupplier, Supplier edgeSupplier, ElementOrderMethod vertexOrderMethod) { this.vertexSupplier = vertexSupplier; this.edgeSupplier = edgeSupplier; this.network = Objects.requireNonNull(network); this.vertexOrderMethod = Objects.requireNonNull(vertexOrderMethod); this.vertexOrder = createVertexOrder(vertexOrderMethod); } @Override public Supplier getVertexSupplier() { return vertexSupplier; } /** * Set the vertex supplier that the graph uses whenever it needs to create new vertices. * *

    * A graph uses the vertex supplier to create new vertex objects whenever a user calls method * {@link Graph#addVertex()}. Users can also create the vertex in user code and then use method * {@link Graph#addVertex(Object)} to add the vertex. * *

    * In contrast with the {@link Supplier} interface, the vertex supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new vertex to be added in a graph v must not be equal * to any other vertex in the graph. More formally, the graph must not contain any vertex * v2 such that v2.equals(v). * * @param vertexSupplier the vertex supplier */ public void setVertexSupplier(Supplier vertexSupplier) { this.vertexSupplier = vertexSupplier; } @Override public Supplier getEdgeSupplier() { return edgeSupplier; } /** * Set the edge supplier that the graph uses whenever it needs to create new edges. * *

    * A graph uses the edge supplier to create new edge objects whenever a user calls method * {@link Graph#addEdge(Object, Object)}. Users can also create the edge in user code and then * use method {@link Graph#addEdge(Object, Object, Object)} to add the edge. * *

    * In contrast with the {@link Supplier} interface, the edge supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new edge to be added in a graph e must not be equal to * any other edge in the graph (even if the graph allows edge-multiplicity). More formally, the * graph must not contain any edge e2 such that e2.equals(e). * * @param edgeSupplier the edge supplier */ public void setEdgeSupplier(Supplier edgeSupplier) { this.edgeSupplier = edgeSupplier; } @Override public E getEdge(V sourceVertex, V targetVertex) { return network .edgesConnecting(sourceVertex, targetVertex).stream().findFirst().orElse(null); } @Override public Set vertexSet() { if (unmodifiableVertexSet == null) { unmodifiableVertexSet = Collections.unmodifiableSet(network.nodes()); } return unmodifiableVertexSet; } @Override public V getEdgeSource(E e) { if (network.isDirected()) { return network.incidentNodes(e).nodeU(); } else { V u = network.incidentNodes(e).nodeU(); V v = network.incidentNodes(e).nodeV(); int c = vertexOrder.compare(u, v); if (c <= 0) { return u; } return v; } } @Override public V getEdgeTarget(E e) { if (network.isDirected()) { return network.incidentNodes(e).nodeV(); } else { V u = network.incidentNodes(e).nodeU(); V v = network.incidentNodes(e).nodeV(); int c = vertexOrder.compare(u, v); if (c <= 0) { return v; } return u; } } @Override public GraphType getType() { return (network.isDirected() ? new DefaultGraphType.Builder().directed() : new DefaultGraphType.Builder().undirected()) .weighted(false).allowMultipleEdges(network.allowsParallelEdges()) .allowSelfLoops(network.allowsSelfLoops()).build(); } @Override public boolean containsEdge(E e) { return network.edges().contains(e); } @Override public boolean containsVertex(V v) { return network.nodes().contains(v); } @Override public Set edgeSet() { if (unmodifiableEdgeSet == null) { unmodifiableEdgeSet = Collections.unmodifiableSet(network.edges()); } return unmodifiableEdgeSet; } @Override public int degreeOf(V vertex) { return network.degree(vertex); } @Override public Set edgesOf(V vertex) { return network.incidentEdges(vertex); } @Override public int inDegreeOf(V vertex) { return network.inDegree(vertex); } @Override public Set incomingEdgesOf(V vertex) { return network.inEdges(vertex); } @Override public int outDegreeOf(V vertex) { return network.outDegree(vertex); } @Override public Set outgoingEdgesOf(V vertex) { return network.outEdges(vertex); } @Override public double getEdgeWeight(E e) { if (e == null) { throw new NullPointerException(); } else if (!network.edges().contains(e)) { throw new IllegalArgumentException("no such edge in graph: " + e.toString()); } else { return Graph.DEFAULT_EDGE_WEIGHT; } } @Override public Set getAllEdges(V sourceVertex, V targetVertex) { return network.edgesConnecting(sourceVertex, targetVertex); } /** * Create the internal vertex order implementation. * * @param vertexOrderMethod method to use * @return the vertex order */ protected ElementOrder createVertexOrder(ElementOrderMethod vertexOrderMethod) { switch (vertexOrderMethod.getType()) { case COMPARATOR: return ElementOrder.comparator(vertexOrderMethod.comparator()); case GUAVA_COMPARATOR: if (!network .nodeOrder().type().equals(com.google.common.graph.ElementOrder.Type.SORTED)) { throw new IllegalArgumentException( "Guava comparator only usable if node order is SORTED!"); } return ElementOrder.comparator(network.nodeOrder().comparator()); case NATURAL: return ElementOrder.natural(); case INTERNAL: default: return ElementOrder.internal(); } } } jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/BaseValueGraphAdapter.java000066400000000000000000000270701402514743400334470ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.*; import org.jgrapht.graph.AbstractGraph; import org.jgrapht.graph.*; import java.io.*; import java.util.*; import java.util.function.*; import static java.util.stream.Collectors.collectingAndThen; import static java.util.stream.Collectors.toSet; /** * A base abstract implementation for the graph adapter class using Guava's {@link ValueGraph}. This * is a helper class in order to support both mutable and immutable value graphs. * * @author Dimitrios Michail * * @param the graph vertex type * @param the value type * @param type of the underlying Guava's value graph */ public abstract class BaseValueGraphAdapter> extends AbstractGraph> implements Graph>, Cloneable, Serializable { private static final long serialVersionUID = 3833510139696864917L; protected static final String LOOPS_NOT_ALLOWED = "loops not allowed"; protected transient Set unmodifiableVertexSet = null; protected transient Set> unmodifiableEdgeSet = null; protected Supplier vertexSupplier; protected Supplier> edgeSupplier; protected ToDoubleFunction valueConverter; protected transient VG valueGraph; protected ElementOrderMethod vertexOrderMethod; protected transient ElementOrder vertexOrder; /** * Create a new adapter. * * @param valueGraph the mutable value graph * @param valueConverter a function that converts a value to a double */ public BaseValueGraphAdapter(VG valueGraph, ToDoubleFunction valueConverter) { this(valueGraph, valueConverter, null, null); } /** * Create a new adapter. * * @param valueGraph the mutable value graph * @param valueConverter a function that converts a value to a double * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public BaseValueGraphAdapter( VG valueGraph, ToDoubleFunction valueConverter, Supplier vertexSupplier, Supplier> edgeSupplier) { this( valueGraph, valueConverter, vertexSupplier, edgeSupplier, ElementOrderMethod.internal()); } /** * Create a new adapter. * * @param valueGraph the mutable value graph * @param valueConverter a function that converts a value to a double * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param vertexOrderMethod the method used to ensure a total order of the graph vertices. This * is required in order to make edge source/targets be consistent. */ public BaseValueGraphAdapter( VG valueGraph, ToDoubleFunction valueConverter, Supplier vertexSupplier, Supplier> edgeSupplier, ElementOrderMethod vertexOrderMethod) { this.vertexSupplier = vertexSupplier; this.edgeSupplier = edgeSupplier; this.valueGraph = Objects.requireNonNull(valueGraph); this.valueConverter = Objects.requireNonNull(valueConverter); this.vertexOrderMethod = Objects.requireNonNull(vertexOrderMethod); this.vertexOrder = createVertexOrder(vertexOrderMethod); } @Override public Supplier getVertexSupplier() { return vertexSupplier; } /** * Set the vertex supplier that the graph uses whenever it needs to create new vertices. * *

    * A graph uses the vertex supplier to create new vertex objects whenever a user calls method * {@link Graph#addVertex()}. Users can also create the vertex in user code and then use method * {@link Graph#addVertex(Object)} to add the vertex. * *

    * In contrast with the {@link Supplier} interface, the vertex supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new vertex to be added in a graph v must not be equal * to any other vertex in the graph. More formally, the graph must not contain any vertex * v2 such that v2.equals(v). * * @param vertexSupplier the vertex supplier */ public void setVertexSupplier(Supplier vertexSupplier) { this.vertexSupplier = vertexSupplier; } @Override public Supplier> getEdgeSupplier() { return edgeSupplier; } /** * Set the edge supplier that the graph uses whenever it needs to create new edges. * *

    * A graph uses the edge supplier to create new edge objects whenever a user calls method * {@link Graph#addEdge(Object, Object)}. Users can also create the edge in user code and then * use method {@link Graph#addEdge(Object, Object, Object)} to add the edge. * *

    * In contrast with the {@link Supplier} interface, the edge supplier has the additional * requirement that a new and distinct result is returned every time it is invoked. More * specifically for a new edge to be added in a graph e must not be equal to * any other edge in the graph (even if the graph allows edge-multiplicity). More formally, the * graph must not contain any edge e2 such that e2.equals(e). * * @param edgeSupplier the edge supplier */ public void setEdgeSupplier(Supplier> edgeSupplier) { this.edgeSupplier = edgeSupplier; } @Override public EndpointPair getEdge(V sourceVertex, V targetVertex) { if (sourceVertex == null || targetVertex == null) { return null; } else if (!valueGraph.hasEdgeConnecting(sourceVertex, targetVertex)) { return null; } else { return createEdge(sourceVertex, targetVertex); } } @Override public Set vertexSet() { if (unmodifiableVertexSet == null) { unmodifiableVertexSet = Collections.unmodifiableSet(valueGraph.nodes()); } return unmodifiableVertexSet; } @Override public V getEdgeSource(EndpointPair e) { if (valueGraph.isDirected()) { return e.nodeU(); } else { V u = e.nodeU(); V v = e.nodeV(); int c = vertexOrder.compare(u, v); if (c <= 0) { return u; } return v; } } @Override public V getEdgeTarget(EndpointPair e) { if (valueGraph.isDirected()) { return e.nodeV(); } else { V u = e.nodeU(); V v = e.nodeV(); int c = vertexOrder.compare(u, v); if (c <= 0) { return v; } return u; } } @Override public GraphType getType() { return (valueGraph.isDirected() ? new DefaultGraphType.Builder().directed() : new DefaultGraphType.Builder().undirected()) .weighted(true).allowMultipleEdges(false) .allowSelfLoops(valueGraph.allowsSelfLoops()).build(); } @Override public boolean containsEdge(EndpointPair e) { return valueGraph.edges().contains(e); } @Override public boolean containsVertex(V v) { return valueGraph.nodes().contains(v); } @Override public Set> edgeSet() { if (unmodifiableEdgeSet == null) { unmodifiableEdgeSet = Collections.unmodifiableSet(valueGraph.edges()); } return unmodifiableEdgeSet; } @Override public int degreeOf(V vertex) { return valueGraph.degree(vertex); } @Override public Set> edgesOf(V vertex) { return valueGraph.incidentEdges(vertex); } @Override public int inDegreeOf(V vertex) { return valueGraph.inDegree(vertex); } @Override public Set> incomingEdgesOf(V vertex) { return valueGraph .predecessors(vertex).stream().map(other -> createEdge(other, vertex)) .collect(collectingAndThen(toSet(), Collections::unmodifiableSet)); } @Override public int outDegreeOf(V vertex) { return valueGraph.outDegree(vertex); } @Override public Set> outgoingEdgesOf(V vertex) { return valueGraph .successors(vertex).stream().map(other -> createEdge(vertex, other)) .collect(collectingAndThen(toSet(), Collections::unmodifiableSet)); } @Override public double getEdgeWeight(EndpointPair e) { if (e == null) { throw new NullPointerException(); } else if (!valueGraph.hasEdgeConnecting(e.nodeU(), e.nodeV())) { throw new IllegalArgumentException("no such edge in graph: " + e.toString()); } else { return valueGraph .edgeValue(e.nodeU(), e.nodeV()).map(valueConverter::applyAsDouble) .orElse(Graph.DEFAULT_EDGE_WEIGHT); } } @Override public Set> getAllEdges(V sourceVertex, V targetVertex) { if (sourceVertex == null || targetVertex == null || !valueGraph.nodes().contains(sourceVertex) || !valueGraph.nodes().contains(targetVertex)) { return null; } else if (!valueGraph.hasEdgeConnecting(sourceVertex, targetVertex)) { return Collections.emptySet(); } else { return Collections.singleton(createEdge(sourceVertex, targetVertex)); } } /** * Create an edge * * @param s the source vertex * @param t the target vertex * @return the edge */ final EndpointPair createEdge(V s, V t) { return valueGraph.isDirected() ? EndpointPair.ordered(s, t) : EndpointPair.unordered(s, t); } /** * Create the internal vertex order implementation. * * @param vertexOrderMethod method to use * @return the vertex order */ protected ElementOrder createVertexOrder(ElementOrderMethod vertexOrderMethod) { switch (vertexOrderMethod.getType()) { case COMPARATOR: return ElementOrder.comparator(vertexOrderMethod.comparator()); case GUAVA_COMPARATOR: if (!valueGraph .nodeOrder().type().equals(com.google.common.graph.ElementOrder.Type.SORTED)) { throw new IllegalArgumentException( "Guava comparator only usable if node order is SORTED!"); } return ElementOrder.comparator(valueGraph.nodeOrder().comparator()); case NATURAL: return ElementOrder.natural(); case INTERNAL: default: return ElementOrder.internal(); } } } jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ElementOrder.java000066400000000000000000000104741402514743400317020ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import java.io.Serializable; import java.util.Comparator; import java.util.HashMap; import java.util.Map; /** * Helper class to maintain a total order for a set of elements. * *

    * The user can choose between using a comparator, using the natural ordering of the elements or * maintaining internally a mapping to long integers. In the latter case the user is also * responsible for notifying this class whenever elements are removed, in order to cleanup any * internal state. Construction of elements is performed in a lazy manner. * * @author Dimitrios Michail */ class ElementOrder implements Serializable { private static final long serialVersionUID = -3732847114940656189L; private Comparator comparator; private Map indices; private long nextId; /** * Create a new element order. * * @param comparator the comparator to use * @param indices internal map from elements to long indices */ private ElementOrder(Comparator comparator, Map indices) { this.indices = indices; this.comparator = comparator; this.nextId = 0; } /** * Create an element order with a comparator * * @param the element type * @param comparator the comparator * @return the element order */ public static ElementOrder comparator(Comparator comparator) { return new ElementOrder<>(comparator, null); } /** * Create an element order with the natural ordering * * @param the element type * @return the element order */ public static ElementOrder natural() { return new ElementOrder<>(null, null); } /** * Create an internal element order which maintains a map from elements to long values. * * @param the element type * @return the element order */ public static ElementOrder internal() { return new ElementOrder<>(null, new HashMap<>()); } /** * Compare two elements * * @param v first element * @param u second element * @return the value {@code 0} if {@code v} is equal to {@code u}; a value less than {@code 0} * if {@code v} is less than {@code u}; and a value greater than {@code 0} if {@code v} * is greater than {@code u}. */ @SuppressWarnings("unchecked") public int compare(V v, V u) { if (comparator != null) { return comparator.compare(v, u); } if (indices != null) { long vid = indices.computeIfAbsent(v, this::computeNextId); long uid = indices.computeIfAbsent(u, this::computeNextId); return Long.compare(vid, uid); } return ((Comparable) v).compareTo(u); } /** * Get the minimum of two elements. * * @param v first element * @param u second element * @return the minimum of two elements */ public V min(V v, V u) { return compare(v, u) <= 0 ? v : u; } /** * Notify about a new element. * * @param v the element */ public void notifyAddition(V v) { if (indices != null) { indices.computeIfAbsent(v, this::computeNextId); } } /** * Notify about an element being removed. This method only affects the case that an internal map * to long integers is maintained. * * @param v the element */ public void notifyRemoval(V v) { if (indices != null) { indices.remove(v); } } private long computeNextId(V vertex) { return nextId++; } } jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ElementOrderMethod.java000066400000000000000000000071331402514743400330410ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import java.io.Serializable; import java.util.Comparator; /** * Represents the method of ensuring the existence of a total order of a set of elements. * * @author Dimitrios Michail * * @param the element type */ public class ElementOrderMethod implements Serializable { private static final long serialVersionUID = 6774881812704056362L; private Type type; private Comparator comparator; private ElementOrderMethod(Type type, Comparator comparator) { this.type = type; this.comparator = comparator; } /** * Get the natural ordering method * * @param the element type * @return the natural ordering method */ public static ElementOrderMethod natural() { return new ElementOrderMethod<>(Type.NATURAL, null); } /** * Get the internal ordering method. This represents the method of explicitly maintaining a map * from the elements to long integers. Thus, it incurs a penalty in space and in lookups. * * @param the element type * @return the internal ordering method */ public static ElementOrderMethod internal() { return new ElementOrderMethod<>(Type.INTERNAL, null); } /** * Get the comparator ordering method. * * @param comparator the actual comparator * @param the element type * @return the comparator ordering method */ public static ElementOrderMethod comparator(Comparator comparator) { return new ElementOrderMethod(Type.COMPARATOR, comparator); } /** * Get the guava comparator ordering method. * * @param the element type * @return the comparator ordering method */ public static ElementOrderMethod guavaComparator() { return new ElementOrderMethod(Type.GUAVA_COMPARATOR, null); } /** * Get the comparator. Returns null if the method does not use an explicit comparator. * * @return the comparator or null if the method does not use an explicit comparator */ public Comparator comparator() { return comparator; } /** * Get the type * * @return the type */ public Type getType() { return type; } /** * Element order method type */ public enum Type { /** * Usage of an actual comparator instance to order the elements. */ COMPARATOR, /** * Natural ordering. This method may result in {@link ClassCastException} if the elements * are not comparable. */ NATURAL, /** * Use the Guava node order comparator. */ GUAVA_COMPARATOR, /** * An internal numbering scheme backed by a map. This incurs space penalty and additional * hashtable lookups on each comparison. */ INTERNAL, } } ImmutableDoubleValueGraphAdapter.java000066400000000000000000000050271402514743400355660ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import java.io.*; import java.util.function.*; /** * A graph adapter class using Guava's {@link ImmutableValueGraph} specialized with double values. * *

    * The adapter uses class {@link EndpointPair} to represent edges. Since the underlying value graph * is immutable, the resulting graph is unmodifiable. * *

    * Each edge in {@link ImmutableValueGraph} is associated with a double value which is mapped to the * edge weight in the resulting {@link Graph}. Thus, the graph is weighted and calling method * {@link #getEdgeWeight(Object)} will return the value of an edge. * *

    * See the example below on how to create such an adapter:

    * *
     * MutableValueGraph<String, Double> mutableValueGraph =
     *     ValueGraphBuilder.directed().allowsSelfLoops(true).build();
     * 
     * mutableValueGraph.addNode("v1");
     * mutableValueGraph.addNode("v2");
     * mutableValueGraph.putEdgeValue("v1", "v2", 3.0);
     * 
     * ImmutableValueGraph<String, Double> immutableValueGraph = ImmutableValueGraph.copyOf(mutableValueGraph);
     * 
     * Graph<String, EndpointPair<String>> graph = new ImmutableDoubleValueGraphAdapter<>(immutableValueGraph);
     * 
     * System.out.println(graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")); // outputs 3.0
     * 
    * *
    * * @author Dimitrios Michail * * @param the graph vertex type */ public class ImmutableDoubleValueGraphAdapter extends ImmutableValueGraphAdapter { private static final long serialVersionUID = 8730006126353129360L; /** * Create a new adapter. * * @param valueGraph the value graph */ public ImmutableDoubleValueGraphAdapter(ImmutableValueGraph valueGraph) { super(valueGraph, (ToDoubleFunction & Serializable) x -> x); } } jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/ImmutableGraphAdapter.java000066400000000000000000000141401402514743400335110ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.Graphs; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; /** * A graph adapter class using Guava's {@link ImmutableGraph}. * *

    * The adapter uses class {@link EndpointPair} to represent edges. Since the underlying graph is * immutable, the resulting graph is unmodifiable. * *

    * See the example below on how to create such an adapter:

    * *
     * MutableGraph<String> mutableGraph = GraphBuilder.directed().allowsSelfLoops(true).build();
     * 
     * mutableGraph.addNode("v1");
     * mutableGraph.addNode("v2");
     * mutableGraph.addEdge("v1", "v2");
     * 
     * ImmutableGraph<String> immutableGraph = ImmutableGraph.copyOf(mutableGraph);
     * 
     * Graph<String, EndpointPair<String>> graph = new ImmutableGraphAdapter<>(immutableGraph);
     * 
    * *
    * * @author Dimitrios Michail * * @param the graph vertex type */ public class ImmutableGraphAdapter extends BaseGraphAdapter> implements Graph>, Cloneable, Serializable { private static final long serialVersionUID = -6619929013881511474L; protected static final String GRAPH_IS_IMMUTABLE = "Graph is immutable"; /** * Create a new adapter. * * @param graph the graph */ public ImmutableGraphAdapter(ImmutableGraph graph) { super(graph); } @Override public EndpointPair addEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean addEdge(V sourceVertex, V targetVertex, EndpointPair e) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public V addVertex() { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean addVertex(V v) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public EndpointPair removeEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean removeEdge(EndpointPair e) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean removeVertex(V v) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public void setEdgeWeight(EndpointPair e, double weight) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public GraphType getType() { return super.getType().asUnmodifiable(); } /** * Returns a shallow copy of this graph instance. Neither edges nor vertices are cloned. * * @return a shallow copy of this graph. * * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ @Override public Object clone() { try { ImmutableGraphAdapter newGraph = TypeUtil.uncheckedCast(super.clone()); newGraph.vertexSupplier = this.vertexSupplier; newGraph.edgeSupplier = this.edgeSupplier; newGraph.unmodifiableVertexSet = null; newGraph.unmodifiableEdgeSet = null; newGraph.graph = ImmutableGraph.copyOf(Graphs.copyOf(this.graph)); newGraph.vertexOrder = createVertexOrder(newGraph.vertexOrderMethod); return newGraph; } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new RuntimeException(); } } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // write type oos.writeObject(getType()); // write vertices int n = vertexSet().size(); oos.writeInt(n); for (V v : vertexSet()) { oos.writeObject(v); } // write edges int m = edgeSet().size(); oos.writeInt(m); for (EndpointPair e : edgeSet()) { V u = e.nodeU(); V v = e.nodeV(); oos.writeObject(u); oos.writeObject(v); } } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); GraphType type = (GraphType) ois.readObject(); if (type.isMixed() || type.isAllowingMultipleEdges()) { throw new IOException("Graph type not supported"); } MutableGraph mutableGraph = (type.isDirected() ? GraphBuilder.directed() : GraphBuilder.undirected()) .allowsSelfLoops(type.isAllowingSelfLoops()).build(); // read vertices int n = ois.readInt(); for (int i = 0; i < n; i++) { V v = (V) ois.readObject(); mutableGraph.addNode(v); } // read edges int m = ois.readInt(); for (int i = 0; i < m; i++) { V s = (V) ois.readObject(); V t = (V) ois.readObject(); mutableGraph.putEdge(s, t); } // setup the vertex order vertexOrder = createVertexOrder(vertexOrderMethod); // setup the immutable copy this.graph = ImmutableGraph.copyOf(mutableGraph); } } ImmutableNetworkAdapter.java000066400000000000000000000143171402514743400340300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.Graphs; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; /** * A graph adapter class using Guava's {@link ImmutableNetwork}. * *

    * Since the underlying network is immutable, the resulting graph is unmodifiable. * *

    * Example usage:

    * *
     * MutableNetwork<String, DefaultEdge> mutableNetwork =
     *     NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
     * 
     * mutableNetwork.addNode("v1");
     * 
     * ImmutableNetworkGraph<String, DefaultEdge> immutableNetwork =
     *     ImmutableNetwork.copyOf(mutableNetwork);
     * 
     * Graph<String, DefaultEdge> graph = new ImmutableNetworkAdapter<>(immutableNetwork);
     * 
    * *
    * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class ImmutableNetworkAdapter extends BaseNetworkAdapter> implements Graph, Cloneable, Serializable { private static final long serialVersionUID = 8776276294297681092L; protected static final String GRAPH_IS_IMMUTABLE = "Graph is immutable"; /** * Create a new network adapter. * * @param network the immutable network */ public ImmutableNetworkAdapter(ImmutableNetwork network) { super(network); } @Override public E addEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public V addVertex() { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean addVertex(V v) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public E removeEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean removeEdge(E e) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean removeVertex(V v) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public double getEdgeWeight(E e) { return Graph.DEFAULT_EDGE_WEIGHT; } @Override public void setEdgeWeight(E e, double weight) { throw new UnsupportedOperationException("Graph is unweighted"); } @Override public GraphType getType() { return super.getType().asUnmodifiable(); } /** * Returns a shallow copy of this graph instance. Neither edges nor vertices are cloned. * * @return a shallow copy of this graph. * * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ @Override public Object clone() { try { ImmutableNetworkAdapter newGraph = TypeUtil.uncheckedCast(super.clone()); newGraph.vertexSupplier = this.vertexSupplier; newGraph.edgeSupplier = this.edgeSupplier; newGraph.unmodifiableVertexSet = null; newGraph.unmodifiableEdgeSet = null; newGraph.network = ImmutableNetwork.copyOf(Graphs.copyOf(this.network)); newGraph.vertexOrder = createVertexOrder(newGraph.vertexOrderMethod); return newGraph; } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new RuntimeException(); } } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // write type oos.writeObject(getType()); // write vertices int n = vertexSet().size(); oos.writeInt(n); for (V v : vertexSet()) { oos.writeObject(v); } // write edges int m = edgeSet().size(); oos.writeInt(m); for (E e : edgeSet()) { oos.writeObject(getEdgeSource(e)); oos.writeObject(getEdgeTarget(e)); oos.writeObject(e); } } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); GraphType type = (GraphType) ois.readObject(); if (type.isMixed()) { throw new IOException("Graph type not supported"); } MutableNetwork mutableNetwork = (type.isDirected() ? NetworkBuilder.directed() : NetworkBuilder.undirected()) .allowsParallelEdges(type.isAllowingMultipleEdges()) .allowsSelfLoops(type.isAllowingSelfLoops()).build(); // read vertices int n = ois.readInt(); for (int i = 0; i < n; i++) { V v = (V) ois.readObject(); mutableNetwork.addNode(v); } // read edges int m = ois.readInt(); for (int i = 0; i < m; i++) { V s = (V) ois.readObject(); V t = (V) ois.readObject(); E e = (E) ois.readObject(); mutableNetwork.addEdge(s, t, e); } // setup the vertex order vertexOrder = createVertexOrder(vertexOrderMethod); // setup the immutable copy this.network = ImmutableNetwork.copyOf(mutableNetwork); } } ImmutableValueGraphAdapter.java000066400000000000000000000165401402514743400344350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.Graphs; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; import java.util.function.*; /** * A graph adapter class using Guava's {@link ImmutableValueGraph}. * *

    * The adapter uses class {@link EndpointPair} to represent edges. Since the underlying value graph * is immutable, the resulting graph is unmodifiable. * *

    * The class uses a converter from Guava's values to JGraphT's double weights. Thus, the resulting * graph is weighted. * *

    * Assume for example that the following class is the value type:

    * *
     * class MyValue
     *     implements
     *     Serializable
     * {
     *     private double value;
     *
     *     public MyValue(double value)
     *     {
     *         this.value = value;
     *     }
     *
     *     public double getValue()
     *     {
     *         return value;
     *     }
     * }
     * 
    * *
    * * Then one could create an adapter using the following code:
    * *
     * MutableValueGraph<String, MyValue> valueGraph =
     *     ValueGraphBuilder.directed().allowsSelfLoops(true).build();
     * valueGraph.addNode("v1");
     * valueGraph.addNode("v2");
     * valueGraph.putEdgeValue("v1", "v2", new MyValue(5.0));
     * 
     * ImmutableValueGraph<String, MyValue> immutableValueGraph =
     *     ImmutableValueGraph.copyOf(valueGraph);
     * 
     * Graph<String, EndpointPair<String>> graph = new ImmutableValueGraphAdapter<>(
     *     immutableValueGraph, (ToDoubleFunction<MyValue> & Serializable) MyValue::getValue);
     * 
     * double weight = graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")); // should return 5.0
     * 
    * *
    * * @author Dimitrios Michail * * @param the graph vertex type * @param the value type */ public class ImmutableValueGraphAdapter extends BaseValueGraphAdapter> implements Graph>, Cloneable, Serializable { private static final long serialVersionUID = 2629294259825656044L; protected static final String GRAPH_IS_IMMUTABLE = "Graph is immutable"; /** * Create a new adapter. * * @param valueGraph the value graph * @param valueConverter a function that converts a value to a double */ public ImmutableValueGraphAdapter( ImmutableValueGraph valueGraph, ToDoubleFunction valueConverter) { super(valueGraph, valueConverter); } @Override public EndpointPair addEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean addEdge(V sourceVertex, V targetVertex, EndpointPair e) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public V addVertex() { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean addVertex(V v) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public EndpointPair removeEdge(V sourceVertex, V targetVertex) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean removeEdge(EndpointPair e) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public boolean removeVertex(V v) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public void setEdgeWeight(EndpointPair e, double weight) { throw new UnsupportedOperationException(GRAPH_IS_IMMUTABLE); } @Override public GraphType getType() { return super.getType().asUnmodifiable(); } /** * Returns a shallow copy of this graph instance. Neither edges nor vertices are cloned. * * @return a shallow copy of this graph. * * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ @Override public Object clone() { try { ImmutableValueGraphAdapter newGraph = TypeUtil.uncheckedCast(super.clone()); newGraph.vertexSupplier = this.vertexSupplier; newGraph.edgeSupplier = this.edgeSupplier; newGraph.unmodifiableVertexSet = null; newGraph.unmodifiableEdgeSet = null; newGraph.valueConverter = this.valueConverter; newGraph.valueGraph = ImmutableValueGraph.copyOf(Graphs.copyOf(this.valueGraph)); newGraph.vertexOrder = createVertexOrder(newGraph.vertexOrderMethod); return newGraph; } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new RuntimeException(); } } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // write type oos.writeObject(getType()); // write vertices int n = vertexSet().size(); oos.writeInt(n); for (V v : vertexSet()) { oos.writeObject(v); } // write edges int m = edgeSet().size(); oos.writeInt(m); for (EndpointPair e : edgeSet()) { V u = e.nodeU(); V v = e.nodeV(); oos.writeObject(u); oos.writeObject(v); oos.writeObject(valueGraph.edgeValue(u, v).get()); } } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); GraphType type = (GraphType) ois.readObject(); if (type.isMixed() || type.isAllowingMultipleEdges()) { throw new IOException("Graph type not supported"); } MutableValueGraph mutableValueGraph = (type.isDirected() ? ValueGraphBuilder.directed() : ValueGraphBuilder.undirected()) .allowsSelfLoops(type.isAllowingSelfLoops()).build(); // read vertices int n = ois.readInt(); for (int i = 0; i < n; i++) { V v = (V) ois.readObject(); mutableValueGraph.addNode(v); } // read edges int m = ois.readInt(); for (int i = 0; i < m; i++) { V s = (V) ois.readObject(); V t = (V) ois.readObject(); W w = (W) ois.readObject(); mutableValueGraph.putEdgeValue(s, t, w); } // setup the vertex order vertexOrder = createVertexOrder(vertexOrderMethod); // setup the immutable copy this.valueGraph = ImmutableValueGraph.copyOf(mutableValueGraph); } } MutableDoubleValueGraphAdapter.java000066400000000000000000000067521402514743400352460ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import java.io.*; import java.util.function.*; /** * A graph adapter class using Guava's {@link MutableValueGraph} specialized with double values. * *

    * The adapter uses class {@link EndpointPair} to represent edges. Changes in the adapter such as * adding or removing vertices and edges are reflected in the underlying value graph. * *

    * Each edge in {@link MutableValueGraph} is associated with a double value which is mapped to the * edge weight in the resulting {@link Graph}. Thus, the graph is weighted and calling methods * {@link #getEdgeWeight(Object)} and {@link #setEdgeWeight(EndpointPair, double)} will get and set * the value of an edge. * *

    * See the example below on how to create such an adapter:

    * *
     * MutableValueGraph<String, Double> mutableValueGraph =
     *     ValueGraphBuilder.directed().allowsSelfLoops(true).build();
     * 
     * mutableValueGraph.addNode("v1");
     * mutableValueGraph.addNode("v2");
     * mutableValueGraph.putEdgeValue("v1", "v2", 3.0);
     * 
     * Graph<String, EndpointPair<String>> graph = new MutableDoubleValueGraphAdapter<>(mutableValueGraph);
     * 
     * System.out.println(graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")); // outputs 3.0
     * 
     * graph.setEdgeWeight(EndpointPair.ordered("v1", "v2"), 7.0);
     * 
     * System.out.println(graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")); // outputs 7.0
     * 
    * *
    * * @author Dimitrios Michail * * @param the graph vertex type */ public class MutableDoubleValueGraphAdapter extends MutableValueGraphAdapter { private static final long serialVersionUID = -6335845255406679994L; /** * Create a new adapter. * * @param valueGraph the value graph */ public MutableDoubleValueGraphAdapter(MutableValueGraph valueGraph) { this(valueGraph, null, null); } /** * Create a new adapter. * * @param valueGraph the value graph * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public MutableDoubleValueGraphAdapter( MutableValueGraph valueGraph, Supplier vertexSupplier, Supplier> edgeSupplier) { super( valueGraph, Graph.DEFAULT_EDGE_WEIGHT, (ToDoubleFunction & Serializable) x -> x, vertexSupplier, edgeSupplier); } @Override public void setEdgeWeight(EndpointPair e, double weight) { if (e == null) { throw new NullPointerException(); } if (!containsEdge(e)) { throw new IllegalArgumentException("no such edge in graph: " + e.toString()); } super.valueGraph.putEdgeValue(e.nodeU(), e.nodeV(), weight); } } jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableGraphAdapter.java000066400000000000000000000211661402514743400331710ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.Graphs; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; import java.util.function.*; /** * A graph adapter class using Guava's {@link MutableGraph}. * *

    * The adapter uses class {@link EndpointPair} to represent edges. Changes in the adapter such as * adding or removing vertices and edges are reflected in the underlying graph. * *

    * See the example below on how to create such an adapter:

    * *
     * MutableGraph<String> mutableGraph = GraphBuilder.directed().allowsSelfLoops(true).build();
     * 
     * mutableGraph.addNode("v1");
     * mutableGraph.addNode("v2");
     * mutableGraph.addEdge("v1", "v2");
     * 
     * Graph<String, EndpointPair<String>> graph = new MutableGraphAdapter<>(mutableGraph);
     * 
    * *
    * * @author Dimitrios Michail * * @param the graph vertex type */ public class MutableGraphAdapter extends BaseGraphAdapter> implements Graph>, Cloneable, Serializable { private static final long serialVersionUID = -7556855931445010748L; /** * Create a new adapter. * * @param graph the graph */ public MutableGraphAdapter(MutableGraph graph) { this(graph, null, null); } /** * Create a new adapter. * * @param graph the graph * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public MutableGraphAdapter( MutableGraph graph, Supplier vertexSupplier, Supplier> edgeSupplier) { super(graph, vertexSupplier, edgeSupplier, ElementOrderMethod.internal()); } /** * Create a new adapter. * * @param graph the graph * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param vertexOrderMethod the method used to ensure a total order of the graph vertices. This * is required in order to make edge source/targets be consistent. */ public MutableGraphAdapter( MutableGraph graph, Supplier vertexSupplier, Supplier> edgeSupplier, ElementOrderMethod vertexOrderMethod) { super(graph, vertexSupplier, edgeSupplier, vertexOrderMethod); } @Override public EndpointPair addEdge(V sourceVertex, V targetVertex) { assertVertexExist(sourceVertex); assertVertexExist(targetVertex); if (containsEdge(sourceVertex, targetVertex)) { return null; } if (!graph.allowsSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } graph.putEdge(sourceVertex, targetVertex); return createEdge(sourceVertex, targetVertex); } /** * {@inheritDoc} * * The provided edge object can either be null or must respect the source and target vertices * that are provided as parameters. * * @throws IllegalArgumentException if edge e is not null and the sourceVertex parameter does * not match the node U of the endpoint-pair * @throws IllegalArgumentException if edge e is not null and the targetVertex parameter does * not match the node V of the endpoint-pair */ @Override public boolean addEdge(V sourceVertex, V targetVertex, EndpointPair e) { assertVertexExist(sourceVertex); assertVertexExist(targetVertex); if (e != null) { if (!sourceVertex.equals(e.nodeU())) { throw new IllegalArgumentException( "Provided edge must have node U equal to source vertex"); } if (!targetVertex.equals(e.nodeV())) { throw new IllegalArgumentException( "Provided edge must have node V equal to target vertex"); } } if (containsEdge(sourceVertex, targetVertex)) { return false; } if (!graph.allowsSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } graph.putEdge(sourceVertex, targetVertex); return true; } @Override public V addVertex() { if (vertexSupplier == null) { throw new UnsupportedOperationException("The graph contains no vertex supplier"); } V v = vertexSupplier.get(); if (graph.addNode(v)) { return v; } return null; } @Override public boolean addVertex(V v) { return graph.addNode(v); } @Override public EndpointPair removeEdge(V sourceVertex, V targetVertex) { EndpointPair e = getEdge(sourceVertex, targetVertex); if (e != null) { graph.removeEdge(sourceVertex, targetVertex); } return e; } @Override public boolean removeEdge(EndpointPair e) { if (e == null) { return false; } return graph.removeEdge(e.nodeU(), e.nodeV()); } @Override public boolean removeVertex(V v) { vertexOrder.notifyRemoval(v); return graph.removeNode(v); } @Override public void setEdgeWeight(EndpointPair e, double weight) { throw new UnsupportedOperationException("Graph is unweighted"); } /** * Returns a shallow copy of this graph instance. Neither edges nor vertices are cloned. * * @return a shallow copy of this graph. * * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ @Override public Object clone() { try { MutableGraphAdapter newGraph = TypeUtil.uncheckedCast(super.clone()); newGraph.vertexSupplier = this.vertexSupplier; newGraph.edgeSupplier = this.edgeSupplier; newGraph.unmodifiableVertexSet = null; newGraph.unmodifiableEdgeSet = null; newGraph.graph = Graphs.copyOf(this.graph); newGraph.vertexOrder = createVertexOrder(newGraph.vertexOrderMethod); return newGraph; } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new RuntimeException(); } } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // write type oos.writeObject(getType()); // write vertices int n = vertexSet().size(); oos.writeInt(n); for (V v : vertexSet()) { oos.writeObject(v); } // write edges int m = edgeSet().size(); oos.writeInt(m); for (EndpointPair e : edgeSet()) { V u = e.nodeU(); V v = e.nodeV(); oos.writeObject(u); oos.writeObject(v); } } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); GraphType type = (GraphType) ois.readObject(); if (type.isMixed() || type.isAllowingMultipleEdges()) { throw new IOException("Graph type not supported"); } graph = (type.isDirected() ? GraphBuilder.directed() : GraphBuilder.undirected()) .allowsSelfLoops(type.isAllowingSelfLoops()).build(); // read vertices int n = ois.readInt(); for (int i = 0; i < n; i++) { V v = (V) ois.readObject(); graph.addNode(v); } // read edges int m = ois.readInt(); for (int i = 0; i < m; i++) { V s = (V) ois.readObject(); V t = (V) ois.readObject(); graph.putEdge(s, t); } // setup the vertex order vertexOrder = createVertexOrder(vertexOrderMethod); } } jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/MutableNetworkAdapter.java000066400000000000000000000203071402514743400335550ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.Graphs; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; import java.util.function.*; /** * A graph adapter class using Guava's {@link MutableNetwork}. * *

    * Changes in the adapter such as adding or removing vertices and edges are reflected in the * underlying network. * * Example usage:

    * *
     * MutableNetwork<String, DefaultEdge> mutableNetwork =
     *     NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build();
     * 
     * Graph<String, DefaultEdge> graph = new MutableNetworkAdapter<>(
     *     mutableNetwork, SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER);
     * 
     * graph.addVertex("v1");
     * 
     * System.out.println(mutableNetwork.nodes().contains("v1")); // outputs true
     * 
    * *
    * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class MutableNetworkAdapter extends BaseNetworkAdapter> implements Graph, Cloneable, Serializable { private static final long serialVersionUID = 7450826703235510224L; protected static final String GRAPH_IS_UNWEIGHTED = "Graph is unweighted"; /** * Create a new network adapter. * * @param network the mutable network */ public MutableNetworkAdapter(MutableNetwork network) { this(network, null, null); } /** * Create a new network adapter. * * @param network the mutable network * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public MutableNetworkAdapter( MutableNetwork network, Supplier vertexSupplier, Supplier edgeSupplier) { super(network, vertexSupplier, edgeSupplier, ElementOrderMethod.internal()); } /** * Create a new network adapter. * * @param network the mutable network * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param vertexOrderMethod the method used to ensure a total order of the graph vertices. This * is required in order to make edge source/targets be consistent. */ public MutableNetworkAdapter( MutableNetwork network, Supplier vertexSupplier, Supplier edgeSupplier, ElementOrderMethod vertexOrderMethod) { super(network, vertexSupplier, edgeSupplier, vertexOrderMethod); } @Override public E addEdge(V sourceVertex, V targetVertex) { assertVertexExist(sourceVertex); assertVertexExist(targetVertex); if (!network.allowsParallelEdges() && containsEdge(sourceVertex, targetVertex)) { return null; } if (!network.allowsSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } if (edgeSupplier == null) { throw new UnsupportedOperationException("The graph contains no edge supplier"); } E e = edgeSupplier.get(); if (network.addEdge(sourceVertex, targetVertex, e)) { return e; } return null; } @Override public boolean addEdge(V sourceVertex, V targetVertex, E e) { if (e == null) { throw new NullPointerException(); } assertVertexExist(sourceVertex); assertVertexExist(targetVertex); if (!network.allowsParallelEdges() && containsEdge(sourceVertex, targetVertex)) { return false; } if (!network.allowsSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } if (network.addEdge(sourceVertex, targetVertex, e)) { return true; } return false; } @Override public V addVertex() { if (vertexSupplier == null) { throw new UnsupportedOperationException("The graph contains no vertex supplier"); } V v = vertexSupplier.get(); if (network.addNode(v)) { return v; } return null; } @Override public boolean addVertex(V v) { return network.addNode(v); } @Override public E removeEdge(V sourceVertex, V targetVertex) { E e = getEdge(sourceVertex, targetVertex); if (e != null) { network.removeEdge(e); } return e; } @Override public boolean removeEdge(E e) { return network.removeEdge(e); } @Override public boolean removeVertex(V v) { vertexOrder.notifyRemoval(v); return network.removeNode(v); } @Override public void setEdgeWeight(E e, double weight) { throw new UnsupportedOperationException(GRAPH_IS_UNWEIGHTED); } /** * Returns a shallow copy of this graph instance. Neither edges nor vertices are cloned. * * @return a shallow copy of this graph. * * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ @Override public Object clone() { try { MutableNetworkAdapter newGraph = TypeUtil.uncheckedCast(super.clone()); newGraph.vertexSupplier = this.vertexSupplier; newGraph.edgeSupplier = this.edgeSupplier; newGraph.unmodifiableVertexSet = null; newGraph.unmodifiableEdgeSet = null; newGraph.network = Graphs.copyOf(this.network); newGraph.vertexOrder = createVertexOrder(newGraph.vertexOrderMethod); return newGraph; } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new RuntimeException(); } } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // write type oos.writeObject(getType()); // write vertices int n = vertexSet().size(); oos.writeInt(n); for (V v : vertexSet()) { oos.writeObject(v); } // write edges int m = edgeSet().size(); oos.writeInt(m); for (E e : edgeSet()) { oos.writeObject(getEdgeSource(e)); oos.writeObject(getEdgeTarget(e)); oos.writeObject(e); } } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); GraphType type = (GraphType) ois.readObject(); if (type.isMixed()) { throw new IOException("Graph type not supported"); } this.network = (type.isDirected() ? NetworkBuilder.directed() : NetworkBuilder.undirected()) .allowsParallelEdges(type.isAllowingMultipleEdges()) .allowsSelfLoops(type.isAllowingSelfLoops()).build(); // read vertices int n = ois.readInt(); for (int i = 0; i < n; i++) { V v = (V) ois.readObject(); network.addNode(v); } // read edges int m = ois.readInt(); for (int i = 0; i < m; i++) { V s = (V) ois.readObject(); V t = (V) ois.readObject(); E e = (E) ois.readObject(); network.addEdge(s, t, e); } // setup the vertex order vertexOrder = createVertexOrder(vertexOrderMethod); } } MutableValueGraphAdapter.java000066400000000000000000000273571402514743400341170ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.Graphs; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * A graph adapter class using Guava's {@link MutableValueGraph}. * *

    * The adapter uses class {@link EndpointPair} to represent edges. Changes in the adapter such as * adding or removing vertices and edges are reflected in the underlying value graph. * *

    * The class uses a converter from Guava's values to JGraphT's double weights. Thus, the resulting * graph is weighted. Assume for example that the following class is the value type:

    * *
     * class MyValue
     *     implements
     *     Serializable
     * {
     *     private double value;
     *
     *     public MyValue(double value)
     *     {
     *         this.value = value;
     *     }
     *
     *     public double getValue()
     *     {
     *         return value;
     *     }
     * }
     * 
    * *
    * * Then one could create an adapter using the following code:
    * *
     * MutableValueGraph<String, MyValue> valueGraph =
     *     ValueGraphBuilder.directed().allowsSelfLoops(true).build();
     * valueGraph.addNode("v1");
     * valueGraph.addNode("v2");
     * valueGraph.putEdgeValue("v1", "v2", new MyValue(5.0));
     * 
     * Graph<String, EndpointPair<String>> graph = new MutableValueGraphAdapter<>(
     *     valueGraph, new MyValue(1.0), (ToDoubleFunction<MyValue> & Serializable) MyValue::getValue);
     * 
     * double weight = graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")); // should return 5.0
     * 
    * *
    * *

    * This is a one-way conversion meaning that calling {@link #setEdgeWeight(EndpointPair, double)} * will throw an unsupported operation exception. Adjusting the weights can be done directly (by * keeping an external reference) on the underlying {@link MutableValueGraph} and calling * {@link MutableValueGraph#putEdgeValue(Object, Object, Object)}. Changes on the values will be * propagated upstream using the provided value converter. * * @author Dimitrios Michail * * @param the graph vertex type * @param the value type */ public class MutableValueGraphAdapter extends BaseValueGraphAdapter> implements Graph>, Cloneable, Serializable { private static final long serialVersionUID = -5095044027783397573L; protected final W defaultValue; /** * Create a new adapter. * * @param valueGraph the value graph * @param defaultValue a default value to be used when creating new edges * @param valueConverter a function that converts a value to a double */ public MutableValueGraphAdapter( MutableValueGraph valueGraph, W defaultValue, ToDoubleFunction valueConverter) { this(valueGraph, defaultValue, valueConverter, null, null); } /** * Create a new adapter. * * @param valueGraph the value graph * @param defaultValue a default value to be used when creating new edges * @param valueConverter a function that converts a value to a double * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier */ public MutableValueGraphAdapter( MutableValueGraph valueGraph, W defaultValue, ToDoubleFunction valueConverter, Supplier vertexSupplier, Supplier> edgeSupplier) { super( valueGraph, valueConverter, vertexSupplier, edgeSupplier, ElementOrderMethod.internal()); this.defaultValue = Objects.requireNonNull(defaultValue); } /** * Create a new adapter. * * @param valueGraph the value graph * @param defaultValue a default value to be used when creating new edges * @param valueConverter a function that converts a value to a double * @param vertexSupplier the vertex supplier * @param edgeSupplier the edge supplier * @param vertexOrderMethod the method used to ensure a total order of the graph vertices. This * is required in order to make edge source/targets be consistent. */ public MutableValueGraphAdapter( MutableValueGraph valueGraph, W defaultValue, ToDoubleFunction valueConverter, Supplier vertexSupplier, Supplier> edgeSupplier, ElementOrderMethod vertexOrderMethod) { super(valueGraph, valueConverter, vertexSupplier, edgeSupplier, vertexOrderMethod); this.defaultValue = Objects.requireNonNull(defaultValue); } @Override public EndpointPair addEdge(V sourceVertex, V targetVertex) { assertVertexExist(sourceVertex); assertVertexExist(targetVertex); if (containsEdge(sourceVertex, targetVertex)) { return null; } if (!valueGraph.allowsSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } valueGraph.putEdgeValue(sourceVertex, targetVertex, defaultValue); return createEdge(sourceVertex, targetVertex); } /** * {@inheritDoc} * * The provided edge object can either be null or must respect the source and target vertices * that are provided as parameters. * * @throws IllegalArgumentException if edge e is not null and the sourceVertex parameter does * not match the node U of the endpoint-pair * @throws IllegalArgumentException if edge e is not null and the targetVertex parameter does * not match the node V of the endpoint-pair */ @Override public boolean addEdge(V sourceVertex, V targetVertex, EndpointPair e) { assertVertexExist(sourceVertex); assertVertexExist(targetVertex); if (e != null) { if (!sourceVertex.equals(e.nodeU())) { throw new IllegalArgumentException( "Provided edge must have node U equal to source vertex"); } if (!targetVertex.equals(e.nodeV())) { throw new IllegalArgumentException( "Provided edge must have node V equal to target vertex"); } } if (containsEdge(sourceVertex, targetVertex)) { return false; } if (!valueGraph.allowsSelfLoops() && sourceVertex.equals(targetVertex)) { throw new IllegalArgumentException(LOOPS_NOT_ALLOWED); } valueGraph.putEdgeValue(sourceVertex, targetVertex, defaultValue); return true; } @Override public V addVertex() { if (vertexSupplier == null) { throw new UnsupportedOperationException("The graph contains no vertex supplier"); } V v = vertexSupplier.get(); if (valueGraph.addNode(v)) { return v; } return null; } @Override public boolean addVertex(V v) { return valueGraph.addNode(v); } @Override public EndpointPair removeEdge(V sourceVertex, V targetVertex) { EndpointPair e = getEdge(sourceVertex, targetVertex); if (e != null) { valueGraph.removeEdge(sourceVertex, targetVertex); } return e; } @Override public boolean removeEdge(EndpointPair e) { if (e == null) { return false; } return valueGraph.removeEdge(e.nodeU(), e.nodeV()) != null; } @Override public boolean removeVertex(V v) { vertexOrder.notifyRemoval(v); return valueGraph.removeNode(v); } /** * {@inheritDoc} * * This method always throws an {@link UnsupportedOperationException} since the adapter works * one-way from values to weights. Adjusting the weights can be done by adjusting the values in * the underlying {@link ValueGraph} which will automatically be propagated using the provided * converter. * * @param e edge on which to set weight * @param weight new weight for edge * @throws UnsupportedOperationException if the graph does not support weights */ @Override public void setEdgeWeight(EndpointPair e, double weight) { throw new UnsupportedOperationException( "Not supported operation. Change directly the underlying value graph"); } /** * Returns a shallow copy of this graph instance. Neither edges nor vertices are cloned. * * @return a shallow copy of this graph. * * @throws RuntimeException in case the clone is not supported * * @see java.lang.Object#clone() */ @Override public Object clone() { try { MutableValueGraphAdapter newGraph = TypeUtil.uncheckedCast(super.clone()); newGraph.vertexSupplier = this.vertexSupplier; newGraph.edgeSupplier = this.edgeSupplier; newGraph.unmodifiableVertexSet = null; newGraph.unmodifiableEdgeSet = null; newGraph.valueConverter = this.valueConverter; newGraph.valueGraph = Graphs.copyOf(this.valueGraph); newGraph.vertexOrder = createVertexOrder(newGraph.vertexOrderMethod); return newGraph; } catch (CloneNotSupportedException e) { e.printStackTrace(); throw new RuntimeException(); } } private void writeObject(ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); // write type oos.writeObject(getType()); // write vertices int n = vertexSet().size(); oos.writeInt(n); for (V v : vertexSet()) { oos.writeObject(v); } // write edges int m = edgeSet().size(); oos.writeInt(m); for (EndpointPair e : edgeSet()) { V u = e.nodeU(); V v = e.nodeV(); oos.writeObject(u); oos.writeObject(v); oos.writeObject(valueGraph.edgeValue(u, v).get()); } } @SuppressWarnings("unchecked") private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); GraphType type = (GraphType) ois.readObject(); if (type.isMixed() || type.isAllowingMultipleEdges()) { throw new IOException("Graph type not supported"); } valueGraph = (type.isDirected() ? ValueGraphBuilder.directed() : ValueGraphBuilder.undirected()) .allowsSelfLoops(type.isAllowingSelfLoops()).build(); // read vertices int n = ois.readInt(); for (int i = 0; i < n; i++) { V v = (V) ois.readObject(); valueGraph.addNode(v); } // read edges int m = ois.readInt(); for (int i = 0; i < m; i++) { V s = (V) ois.readObject(); V t = (V) ois.readObject(); W w = (W) ois.readObject(); valueGraph.putEdgeValue(s, t, w); } // setup the vertex order vertexOrder = createVertexOrder(vertexOrderMethod); } } jgrapht-jgrapht-1.5.1/jgrapht-guava/src/main/java/org/jgrapht/graph/guava/package-info.java000066400000000000000000000004321402514743400316320ustar00rootroot00000000000000/** * Guava adapters allow you * to run JGraphT algorithms directly against * Guava's common.graph data structures. */ package org.jgrapht.graph.guava; jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/000077500000000000000000000000001402514743400211245ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/000077500000000000000000000000001402514743400220455ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/000077500000000000000000000000001402514743400226345ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/000077500000000000000000000000001402514743400242735ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/graph/000077500000000000000000000000001402514743400253745ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/000077500000000000000000000000001402514743400264775ustar00rootroot00000000000000ImmutableGraphAdapterTest.java000066400000000000000000000175361402514743400343410ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Check Incoming/Outgoing edges in directed and undirected graphs. * * @author Dimitrios Michail */ public class ImmutableGraphAdapterTest { /** * Test the most general version of the directed graph. */ @Test public void testDirectedGraph() { MutableGraph graph = GraphBuilder.directed().allowsSelfLoops(true).build(); graph.addNode("v1"); graph.addNode("v2"); graph.addNode("v3"); graph.addNode("v4"); graph.addNode("v5"); graph.putEdge("v1", "v2"); graph.putEdge("v2", "v3"); graph.putEdge("v2", "v4"); graph.putEdge("v4", "v4"); graph.putEdge("v5", "v2"); Graph> g = new ImmutableGraphAdapter<>(ImmutableGraph.copyOf(graph)); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertEquals(1, g.degreeOf("v1")); assertEquals(4, g.degreeOf("v2")); assertEquals(1, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(1, g.degreeOf("v5")); EndpointPair e12 = EndpointPair.ordered("v1", "v2"); EndpointPair e23 = EndpointPair.ordered("v2", "v3"); EndpointPair e24 = EndpointPair.ordered("v2", "v4"); EndpointPair e44 = EndpointPair.ordered("v4", "v4"); EndpointPair e52 = EndpointPair.ordered("v5", "v2"); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52), g.edgesOf("v5")); assertEquals(0, g.inDegreeOf("v1")); assertEquals(2, g.inDegreeOf("v2")); assertEquals(1, g.inDegreeOf("v3")); assertEquals(2, g.inDegreeOf("v4")); assertEquals(0, g.inDegreeOf("v5")); assertEquals(Set.of(), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(2, g.outDegreeOf("v2")); assertEquals(0, g.outDegreeOf("v3")); assertEquals(1, g.outDegreeOf("v4")); assertEquals(1, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e23, e24), g.outgoingEdgesOf("v2")); assertEquals(Set.of(), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52), g.outgoingEdgesOf("v5")); // test indeed immutable try { g.addVertex("new"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.addEdge("v1", "v5"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.addEdge("v1", "v5", null); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.removeVertex("v1"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.removeEdge("v1", "v2"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.removeEdge(e12); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } } /** * Test the most general version of the directed graph. */ @Test public void testSerialization() throws Exception { MutableGraph graph = GraphBuilder.directed().allowsSelfLoops(true).build(); graph.addNode("v1"); graph.addNode("v2"); graph.addNode("v3"); graph.addNode("v4"); graph.addNode("v5"); graph.putEdge("v1", "v2"); graph.putEdge("v2", "v3"); graph.putEdge("v2", "v4"); graph.putEdge("v4", "v4"); graph.putEdge("v5", "v2"); Graph> initialGraph = new ImmutableGraphAdapter<>(ImmutableGraph.copyOf(graph)); Graph> g = SerializationTestUtils.serializeAndDeserialize(initialGraph); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertEquals(1, g.degreeOf("v1")); assertEquals(4, g.degreeOf("v2")); assertEquals(1, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(1, g.degreeOf("v5")); EndpointPair e12 = EndpointPair.ordered("v1", "v2"); EndpointPair e23 = EndpointPair.ordered("v2", "v3"); EndpointPair e24 = EndpointPair.ordered("v2", "v4"); EndpointPair e44 = EndpointPair.ordered("v4", "v4"); EndpointPair e52 = EndpointPair.ordered("v5", "v2"); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52), g.edgesOf("v5")); assertEquals(0, g.inDegreeOf("v1")); assertEquals(2, g.inDegreeOf("v2")); assertEquals(1, g.inDegreeOf("v3")); assertEquals(2, g.inDegreeOf("v4")); assertEquals(0, g.inDegreeOf("v5")); assertEquals(Set.of(), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(2, g.outDegreeOf("v2")); assertEquals(0, g.outDegreeOf("v3")); assertEquals(1, g.outDegreeOf("v4")); assertEquals(1, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e23, e24), g.outgoingEdgesOf("v2")); assertEquals(Set.of(), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52), g.outgoingEdgesOf("v5")); } } ImmutableNetworkAdapterTest.java000066400000000000000000000176701402514743400347300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.graph.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Check Incoming/Outgoing edges in directed and undirected graphs. * * @author Dimitrios Michail */ public class ImmutableNetworkAdapterTest { /** * Test the most general version of the directed graph. */ @Test public void testDirectedGraph() { MutableNetwork network = NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(); network.addNode("v1"); network.addNode("v2"); network.addNode("v3"); network.addNode("v4"); network.addNode("v5"); DefaultEdge e12 = new DefaultEdge(); network.addEdge("v1", "v2", e12); DefaultEdge e23_1 = new DefaultEdge(); network.addEdge("v2", "v3", e23_1); DefaultEdge e23_2 = new DefaultEdge(); network.addEdge("v2", "v3", e23_2); DefaultEdge e24 = new DefaultEdge(); network.addEdge("v2", "v4", e24); DefaultEdge e44 = new DefaultEdge(); network.addEdge("v4", "v4", e44); DefaultEdge e55_1 = new DefaultEdge(); network.addEdge("v5", "v5", e55_1); DefaultEdge e52 = new DefaultEdge(); network.addEdge("v5", "v2", e52); DefaultEdge e55_2 = new DefaultEdge(); network.addEdge("v5", "v5", e55_2); Graph g = new ImmutableNetworkAdapter<>(ImmutableNetwork.copyOf(network)); assertTrue(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertEquals(1, g.degreeOf("v1")); assertEquals(5, g.degreeOf("v2")); assertEquals(2, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(5, g.degreeOf("v5")); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23_1, e23_2), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52, e55_1, e55_2), g.edgesOf("v5")); assertEquals(0, g.inDegreeOf("v1")); assertEquals(2, g.inDegreeOf("v2")); assertEquals(2, g.inDegreeOf("v3")); assertEquals(2, g.inDegreeOf("v4")); assertEquals(2, g.inDegreeOf("v5")); assertEquals(Set.of(), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23_1, e23_2), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(e55_1, e55_2), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(3, g.outDegreeOf("v2")); assertEquals(0, g.outDegreeOf("v3")); assertEquals(1, g.outDegreeOf("v4")); assertEquals(3, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e23_1, e23_2, e24), g.outgoingEdgesOf("v2")); assertEquals(Set.of(), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52, e55_1, e55_2), g.outgoingEdgesOf("v5")); // test indeed immutable try { g.addVertex("new"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.addEdge("v1", "v5"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.addEdge("v1", "v5", new DefaultEdge()); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.removeVertex("v1"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.removeEdge("v1", "v2"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.removeEdge(e12); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } } /** * Tests serialization */ @Test public void testSerialization() throws Exception { MutableNetwork network = NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(); network.addNode("v1"); network.addNode("v2"); network.addNode("v3"); network.addNode("v4"); network.addNode("v5"); DefaultEdge e12 = new DefaultEdge(); network.addEdge("v1", "v2", e12); DefaultEdge e23_1 = new DefaultEdge(); network.addEdge("v2", "v3", e23_1); DefaultEdge e23_2 = new DefaultEdge(); network.addEdge("v2", "v3", e23_2); DefaultEdge e24 = new DefaultEdge(); network.addEdge("v2", "v4", e24); DefaultEdge e44 = new DefaultEdge(); network.addEdge("v4", "v4", e44); DefaultEdge e55_1 = new DefaultEdge(); network.addEdge("v5", "v5", e55_1); DefaultEdge e52 = new DefaultEdge(); network.addEdge("v5", "v2", e52); DefaultEdge e55_2 = new DefaultEdge(); network.addEdge("v5", "v5", e55_2); Graph g = new ImmutableNetworkAdapter<>(ImmutableNetwork.copyOf(network)); assertTrue(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertFalse(g.getType().isModifiable()); assertTrue(g.containsEdge("v5", "v2")); Graph g2 = SerializationTestUtils.serializeAndDeserialize(g); assertTrue(g2.getType().isAllowingMultipleEdges()); assertTrue(g2.getType().isAllowingSelfLoops()); assertTrue(g2.getType().isDirected()); assertFalse(g2.getType().isUndirected()); assertFalse(g2.getType().isWeighted()); assertTrue(g2.getType().isAllowingCycles()); assertFalse(g2.getType().isModifiable()); assertTrue(g2.containsVertex("v1")); assertTrue(g2.containsVertex("v2")); assertTrue(g2.containsVertex("v3")); assertTrue(g2.containsVertex("v4")); assertTrue(g2.containsVertex("v5")); assertTrue(g2.vertexSet().size() == 5); assertTrue(g2.edgeSet().size() == 8); assertTrue(g2.containsEdge("v1", "v2")); assertTrue(g2.containsEdge("v2", "v3")); assertTrue(g2.containsEdge("v2", "v4")); assertTrue(g2.containsEdge("v4", "v4")); assertTrue(g2.containsEdge("v5", "v5")); assertTrue(g2.containsEdge("v5", "v2")); assertEquals(g.toString(), g2.toString()); } } ImmutableValueGraphAdapterTest.java000066400000000000000000000316551402514743400353340ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.graph.*; import org.junit.*; import java.io.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.*; /** * Check Incoming/Outgoing edges in directed and undirected graphs. * * @author Dimitrios Michail */ public class ImmutableValueGraphAdapterTest { /** * Test the most general version of the directed graph. */ @Test public void testWeights() { MutableValueGraph graph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); graph.addNode("v1"); graph.addNode("v2"); graph.addNode("v3"); graph.addNode("v4"); graph.addNode("v5"); graph.putEdgeValue("v1", "v2", new MyValue(2.0)); graph.putEdgeValue("v2", "v3", new MyValue(3.0)); graph.putEdgeValue("v2", "v4", new MyValue(4.0)); graph.putEdgeValue("v4", "v4", new MyValue(5.0)); graph.putEdgeValue("v5", "v2", new MyValue(6.0)); Graph> g = new ImmutableValueGraphAdapter<>( ImmutableValueGraph.copyOf(graph), (ToDoubleFunction & Serializable) MyValue::getValue); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertEquals(2.0, g.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(EndpointPair.ordered("v2", "v3")), 1e-9); assertEquals(4.0, g.getEdgeWeight(EndpointPair.ordered("v2", "v4")), 1e-9); assertEquals(5.0, g.getEdgeWeight(EndpointPair.ordered("v4", "v4")), 1e-9); assertEquals(6.0, g.getEdgeWeight(EndpointPair.ordered("v5", "v2")), 1e-9); try { g.setEdgeWeight(EndpointPair.ordered("v1", "v2"), 1.0); fail("Immutable"); } catch (UnsupportedOperationException e) { // ignore } } /** * Test special case of double value type */ @Test public void testDoubleWeights() { MutableValueGraph graph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); graph.addNode("v1"); graph.addNode("v2"); graph.addNode("v3"); graph.addNode("v4"); graph.addNode("v5"); graph.putEdgeValue("v1", "v2", 2.0); graph.putEdgeValue("v2", "v3", 3.0); graph.putEdgeValue("v2", "v4", 4.0); graph.putEdgeValue("v4", "v4", 5.0); graph.putEdgeValue("v5", "v2", 6.0); Graph> g = new ImmutableDoubleValueGraphAdapter<>(ImmutableValueGraph.copyOf(graph)); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertEquals(2.0, g.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(EndpointPair.ordered("v2", "v3")), 1e-9); assertEquals(4.0, g.getEdgeWeight(EndpointPair.ordered("v2", "v4")), 1e-9); assertEquals(5.0, g.getEdgeWeight(EndpointPair.ordered("v4", "v4")), 1e-9); assertEquals(6.0, g.getEdgeWeight(EndpointPair.ordered("v5", "v2")), 1e-9); try { g.setEdgeWeight(EndpointPair.ordered("v1", "v2"), 1.0); fail("Immutable"); } catch (UnsupportedOperationException e) { // ignore } } /** * Example on javadoc */ @Test public void testExample() { MutableValueGraph mutableValueGraph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); mutableValueGraph.addNode("v1"); mutableValueGraph.addNode("v2"); mutableValueGraph.putEdgeValue("v1", "v2", new MyValue(5.0)); ImmutableValueGraph immutableValueGraph = ImmutableValueGraph.copyOf(mutableValueGraph); Graph> graph = new ImmutableValueGraphAdapter<>( immutableValueGraph, (ToDoubleFunction & Serializable) MyValue::getValue); assertEquals(graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 5.0, 1e-9); } /** * Test the most general version of the directed graph. */ @Test public void testDirectedGraph() { MutableValueGraph graph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); graph.addNode("v1"); graph.addNode("v2"); graph.addNode("v3"); graph.addNode("v4"); graph.addNode("v5"); graph.putEdgeValue("v1", "v2", new MyValue(2.0)); graph.putEdgeValue("v2", "v3", new MyValue(3.0)); graph.putEdgeValue("v2", "v4", new MyValue(4.0)); graph.putEdgeValue("v4", "v4", new MyValue(5.0)); graph.putEdgeValue("v5", "v2", new MyValue(6.0)); Graph> g = new ImmutableValueGraphAdapter<>( ImmutableValueGraph.copyOf(graph), (ToDoubleFunction & Serializable) MyValue::getValue); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertEquals(1, g.degreeOf("v1")); assertEquals(4, g.degreeOf("v2")); assertEquals(1, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(1, g.degreeOf("v5")); EndpointPair e12 = EndpointPair.ordered("v1", "v2"); EndpointPair e23 = EndpointPair.ordered("v2", "v3"); EndpointPair e24 = EndpointPair.ordered("v2", "v4"); EndpointPair e44 = EndpointPair.ordered("v4", "v4"); EndpointPair e52 = EndpointPair.ordered("v5", "v2"); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52), g.edgesOf("v5")); assertEquals(0, g.inDegreeOf("v1")); assertEquals(2, g.inDegreeOf("v2")); assertEquals(1, g.inDegreeOf("v3")); assertEquals(2, g.inDegreeOf("v4")); assertEquals(0, g.inDegreeOf("v5")); assertEquals(Set.of(), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(2, g.outDegreeOf("v2")); assertEquals(0, g.outDegreeOf("v3")); assertEquals(1, g.outDegreeOf("v4")); assertEquals(1, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e23, e24), g.outgoingEdgesOf("v2")); assertEquals(Set.of(), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52), g.outgoingEdgesOf("v5")); // test indeed immutable try { g.addVertex("new"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.addEdge("v1", "v5"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.addEdge("v1", "v5", null); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.removeVertex("v1"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.removeEdge("v1", "v2"); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } try { g.removeEdge(e12); fail("Network not immutable"); } catch (UnsupportedOperationException e) { // nothing } } /** * Test the most general version of the directed graph. */ @Test public void testSerialization() throws Exception { MutableValueGraph graph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); graph.addNode("v1"); graph.addNode("v2"); graph.addNode("v3"); graph.addNode("v4"); graph.addNode("v5"); graph.putEdgeValue("v1", "v2", new MyValue(2.0)); graph.putEdgeValue("v2", "v3", new MyValue(3.0)); graph.putEdgeValue("v2", "v4", new MyValue(4.0)); graph.putEdgeValue("v4", "v4", new MyValue(5.0)); graph.putEdgeValue("v5", "v2", new MyValue(6.0)); Graph> initialGraph = new ImmutableValueGraphAdapter<>( ImmutableValueGraph.copyOf(graph), (ToDoubleFunction & Serializable) MyValue::getValue); Graph> g = SerializationTestUtils.serializeAndDeserialize(initialGraph); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertEquals(1, g.degreeOf("v1")); assertEquals(4, g.degreeOf("v2")); assertEquals(1, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(1, g.degreeOf("v5")); EndpointPair e12 = EndpointPair.ordered("v1", "v2"); EndpointPair e23 = EndpointPair.ordered("v2", "v3"); EndpointPair e24 = EndpointPair.ordered("v2", "v4"); EndpointPair e44 = EndpointPair.ordered("v4", "v4"); EndpointPair e52 = EndpointPair.ordered("v5", "v2"); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52), g.edgesOf("v5")); assertEquals(0, g.inDegreeOf("v1")); assertEquals(2, g.inDegreeOf("v2")); assertEquals(1, g.inDegreeOf("v3")); assertEquals(2, g.inDegreeOf("v4")); assertEquals(0, g.inDegreeOf("v5")); assertEquals(Set.of(), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(2, g.outDegreeOf("v2")); assertEquals(0, g.outDegreeOf("v3")); assertEquals(1, g.outDegreeOf("v4")); assertEquals(1, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e23, e24), g.outgoingEdgesOf("v2")); assertEquals(Set.of(), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52), g.outgoingEdgesOf("v5")); } private static class MyValue implements Serializable { private static final long serialVersionUID = 1L; private double value; public MyValue(double value) { this.value = value; } public double getValue() { return value; } } } MutableGraphAdapterTest.java000066400000000000000000000274211402514743400340050ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.alg.interfaces.*; import org.jgrapht.alg.vertexcover.*; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Check Incoming/Outgoing edges in directed and undirected graphs. * * @author Dimitrios Michail */ public class MutableGraphAdapterTest { /** * Test the most general version of the directed graph. */ @Test public void testDirectedGraph() { Graph> g = new MutableGraphAdapter<>(GraphBuilder.directed().allowsSelfLoops(true).build()); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); EndpointPair e12 = g.addEdge("v1", "v2"); EndpointPair e23 = g.addEdge("v2", "v3"); EndpointPair e24 = g.addEdge("v2", "v4"); EndpointPair e44 = g.addEdge("v4", "v4"); EndpointPair e55 = g.addEdge("v5", "v5"); EndpointPair e52 = g.addEdge("v5", "v2"); assertEquals(1, g.degreeOf("v1")); assertEquals(4, g.degreeOf("v2")); assertEquals(1, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(3, g.degreeOf("v5")); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52, e55), g.edgesOf("v5")); assertEquals(0, g.inDegreeOf("v1")); assertEquals(2, g.inDegreeOf("v2")); assertEquals(1, g.inDegreeOf("v3")); assertEquals(2, g.inDegreeOf("v4")); assertEquals(1, g.inDegreeOf("v5")); assertEquals(Set.of(), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(e55), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(2, g.outDegreeOf("v2")); assertEquals(0, g.outDegreeOf("v3")); assertEquals(1, g.outDegreeOf("v4")); assertEquals(2, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e23, e24), g.outgoingEdgesOf("v2")); assertEquals(Set.of(), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52, e55), g.outgoingEdgesOf("v5")); } /** * Test the most general version of the undirected graph. */ @Test public void testUndirectedGraph() { Graph> g = new MutableGraphAdapter<>(GraphBuilder.undirected().allowsSelfLoops(true).build()); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertFalse(g.getType().isDirected()); assertTrue(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); EndpointPair e12 = g.addEdge("v1", "v2"); EndpointPair e23 = g.addEdge("v2", "v3"); EndpointPair e24 = g.addEdge("v2", "v4"); EndpointPair e44 = g.addEdge("v4", "v4"); EndpointPair e55 = g.addEdge("v5", "v5"); EndpointPair e52 = g.addEdge("v5", "v2"); assertEquals(1, g.degreeOf("v1")); assertEquals(4, g.degreeOf("v2")); assertEquals(1, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(3, g.degreeOf("v5")); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52, e55), g.edgesOf("v5")); assertEquals(1, g.inDegreeOf("v1")); assertEquals(4, g.inDegreeOf("v2")); assertEquals(1, g.inDegreeOf("v3")); assertEquals(3, g.inDegreeOf("v4")); assertEquals(3, g.inDegreeOf("v5")); assertEquals(Set.of(e12), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(e52, e55), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(4, g.outDegreeOf("v2")); assertEquals(1, g.outDegreeOf("v3")); assertEquals(3, g.outDegreeOf("v4")); assertEquals(3, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.outgoingEdgesOf("v2")); assertEquals(Set.of(e23), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52, e55), g.outgoingEdgesOf("v5")); } @Test public void testAlgorithmInvocation() { // @example:createGuavaGraph:begin MutableGraph guava = GraphBuilder.undirected().build(); guava.addNode("ul"); guava.addNode("um"); guava.addNode("ur"); guava.addNode("ml"); guava.addNode("mm"); guava.addNode("mr"); guava.addNode("ll"); guava.addNode("lm"); guava.addNode("lr"); guava.putEdge("ul", "um"); guava.putEdge("um", "ur"); guava.putEdge("ml", "mm"); guava.putEdge("mm", "mr"); guava.putEdge("ll", "lm"); guava.putEdge("lm", "lr"); guava.putEdge("ul", "ml"); guava.putEdge("ml", "ll"); guava.putEdge("um", "mm"); guava.putEdge("mm", "lm"); guava.putEdge("ur", "mr"); guava.putEdge("mr", "lr"); // @example:createGuavaGraph:end // @example:adaptGuavaGraph:begin Graph> jgrapht = new MutableGraphAdapter<>(guava); // @example:adaptGuavaGraph:end // @example:findVertexCover:begin VertexCoverAlgorithm alg = new RecursiveExactVCImpl<>(jgrapht); VertexCoverAlgorithm.VertexCover cover = alg.getVertexCover(); Set expectedCover = Set.of("um", "ml", "mr", "lm"); assertEquals(expectedCover, cover); // @example:findVertexCover:end } /** * Tests serialization */ @Test public void testSerialization() throws Exception { Graph> g = new MutableGraphAdapter<>(GraphBuilder.directed().allowsSelfLoops(true).build()); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); g.addEdge("v1", "v2"); g.addEdge("v2", "v3"); g.addEdge("v2", "v4"); g.addEdge("v4", "v4"); g.addEdge("v5", "v5"); g.addEdge("v5", "v2"); Graph> g2 = SerializationTestUtils.serializeAndDeserialize(g); assertFalse(g2.getType().isAllowingMultipleEdges()); assertTrue(g2.getType().isAllowingSelfLoops()); assertTrue(g2.getType().isDirected()); assertFalse(g2.getType().isUndirected()); assertFalse(g2.getType().isWeighted()); assertTrue(g2.getType().isAllowingCycles()); assertTrue(g2.containsVertex("v1")); assertTrue(g2.containsVertex("v2")); assertTrue(g2.containsVertex("v3")); assertTrue(g2.containsVertex("v4")); assertTrue(g2.containsVertex("v5")); assertTrue(g2.vertexSet().size() == 5); assertTrue(g2.edgeSet().size() == 6); assertTrue(g2.containsEdge("v1", "v2")); assertTrue(g2.containsEdge("v2", "v3")); assertTrue(g2.containsEdge("v2", "v4")); assertTrue(g2.containsEdge("v4", "v4")); assertTrue(g2.containsEdge("v5", "v5")); assertTrue(g2.containsEdge("v5", "v2")); assertEquals(g.toString(), g2.toString()); } /** * Tests serialization */ @Test public void testSerialization1() throws Exception { Graph g = new MutableNetworkAdapter<>( NetworkBuilder .undirected().allowsParallelEdges(false).allowsSelfLoops(true).build(), SupplierUtil.createRandomUUIDStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertFalse(g.getType().isDirected()); assertTrue(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addEdge("v1", "v2"); g.addEdge("v2", "v3"); g.addEdge("v3", "v3"); Graph g2 = SerializationTestUtils.serializeAndDeserialize(g); assertFalse(g2.getType().isAllowingMultipleEdges()); assertTrue(g2.getType().isAllowingSelfLoops()); assertFalse(g2.getType().isDirected()); assertTrue(g2.getType().isUndirected()); assertFalse(g2.getType().isWeighted()); assertTrue(g2.getType().isAllowingCycles()); assertTrue(g2.containsVertex("v1")); assertTrue(g2.containsVertex("v2")); assertTrue(g2.containsVertex("v3")); assertTrue(g2.vertexSet().size() == 3); assertTrue(g2.edgeSet().size() == 3); assertTrue(g2.containsEdge("v1", "v2")); assertTrue(g2.containsEdge("v2", "v3")); assertTrue(g2.containsEdge("v3", "v3")); } @Test public void testEdgeCoherenceSameGraph() { final MutableGraph g = GraphBuilder.undirected().build(); g.putEdge(0, 1); final MutableGraphAdapter a = new MutableGraphAdapter<>(g); EndpointPair e1 = a.getEdge(0, 1); EndpointPair e2 = a.getEdge(1, 0); assertEquals(e1, e2); assertEquals(a.getEdgeSource(e1), a.getEdgeSource(e2)); assertEquals(a.getEdgeTarget(e1), a.getEdgeTarget(e2)); } } MutableNetworkAdapterTest.java000066400000000000000000000315071402514743400343750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import com.google.common.graph.*; import org.jgrapht.Graph; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import static org.junit.Assert.*; /** * Check Incoming/Outgoing edges in directed and undirected graphs. * * @author Dimitrios Michail */ public class MutableNetworkAdapterTest { /** * Javadoc example */ @Test public void testExample1() { MutableNetwork mutableNetwork = NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(); Graph graph = new MutableNetworkAdapter<>(mutableNetwork); graph.addVertex("v1"); assertTrue(mutableNetwork.nodes().contains("v1")); } /** * Test the most general version of the directed graph. */ @Test public void testDirectedGraph() { Graph g = new MutableNetworkAdapter<>( NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(), SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER); assertTrue(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); DefaultEdge e12 = g.addEdge("v1", "v2"); DefaultEdge e23_1 = g.addEdge("v2", "v3"); DefaultEdge e23_2 = g.addEdge("v2", "v3"); DefaultEdge e24 = g.addEdge("v2", "v4"); DefaultEdge e44 = g.addEdge("v4", "v4"); DefaultEdge e55_1 = g.addEdge("v5", "v5"); DefaultEdge e52 = g.addEdge("v5", "v2"); DefaultEdge e55_2 = g.addEdge("v5", "v5"); assertEquals(1, g.degreeOf("v1")); assertEquals(5, g.degreeOf("v2")); assertEquals(2, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(5, g.degreeOf("v5")); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23_1, e23_2), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52, e55_1, e55_2), g.edgesOf("v5")); assertEquals(0, g.inDegreeOf("v1")); assertEquals(2, g.inDegreeOf("v2")); assertEquals(2, g.inDegreeOf("v3")); assertEquals(2, g.inDegreeOf("v4")); assertEquals(2, g.inDegreeOf("v5")); assertEquals(Set.of(), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23_1, e23_2), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(e55_1, e55_2), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(3, g.outDegreeOf("v2")); assertEquals(0, g.outDegreeOf("v3")); assertEquals(1, g.outDegreeOf("v4")); assertEquals(3, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e23_1, e23_2, e24), g.outgoingEdgesOf("v2")); assertEquals(Set.of(), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52, e55_1, e55_2), g.outgoingEdgesOf("v5")); } /** * Test the most general version of the undirected graph. */ @Test public void testUndirectedGraph() { Graph g = new MutableNetworkAdapter<>( NetworkBuilder.undirected().allowsParallelEdges(true).allowsSelfLoops(true).build(), null, SupplierUtil.DEFAULT_EDGE_SUPPLIER); assertTrue(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertFalse(g.getType().isDirected()); assertTrue(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); DefaultEdge e12 = g.addEdge("v1", "v2"); DefaultEdge e23_1 = g.addEdge("v2", "v3"); DefaultEdge e23_2 = g.addEdge("v2", "v3"); DefaultEdge e24 = g.addEdge("v2", "v4"); DefaultEdge e44 = g.addEdge("v4", "v4"); DefaultEdge e55_1 = g.addEdge("v5", "v5"); DefaultEdge e52 = g.addEdge("v5", "v2"); DefaultEdge e55_2 = g.addEdge("v5", "v5"); assertEquals(1, g.degreeOf("v1")); assertEquals(5, g.degreeOf("v2")); assertEquals(2, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(5, g.degreeOf("v5")); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23_1, e23_2), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52, e55_1, e55_2), g.edgesOf("v5")); assertEquals(1, g.inDegreeOf("v1")); assertEquals(5, g.inDegreeOf("v2")); assertEquals(2, g.inDegreeOf("v3")); assertEquals(3, g.inDegreeOf("v4")); assertEquals(5, g.inDegreeOf("v5")); assertEquals(Set.of(e12), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23_1, e23_2), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(e52, e55_1, e55_2), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(5, g.outDegreeOf("v2")); assertEquals(2, g.outDegreeOf("v3")); assertEquals(3, g.outDegreeOf("v4")); assertEquals(5, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.outgoingEdgesOf("v2")); assertEquals(Set.of(e23_1, e23_2), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52, e55_1, e55_2), g.outgoingEdgesOf("v5")); } /** * Tests serialization */ @Test public void testSerialization() throws Exception { Graph g = new MutableNetworkAdapter<>( NetworkBuilder.directed().allowsParallelEdges(true).allowsSelfLoops(true).build(), null, SupplierUtil.DEFAULT_EDGE_SUPPLIER); assertTrue(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); g.addEdge("v1", "v2"); g.addEdge("v2", "v3"); g.addEdge("v2", "v3"); g.addEdge("v2", "v4"); g.addEdge("v4", "v4"); g.addEdge("v5", "v5"); g.addEdge("v5", "v2"); g.addEdge("v5", "v5"); Graph g2 = SerializationTestUtils.serializeAndDeserialize(g); assertTrue(g2.getType().isAllowingMultipleEdges()); assertTrue(g2.getType().isAllowingSelfLoops()); assertTrue(g2.getType().isDirected()); assertFalse(g2.getType().isUndirected()); assertFalse(g2.getType().isWeighted()); assertTrue(g2.getType().isAllowingCycles()); assertTrue(g2.containsVertex("v1")); assertTrue(g2.containsVertex("v2")); assertTrue(g2.containsVertex("v3")); assertTrue(g2.containsVertex("v4")); assertTrue(g2.containsVertex("v5")); assertTrue(g2.vertexSet().size() == 5); assertTrue(g2.edgeSet().size() == 8); assertTrue(g2.containsEdge("v1", "v2")); assertTrue(g2.containsEdge("v2", "v3")); assertTrue(g2.containsEdge("v2", "v4")); assertTrue(g2.containsEdge("v4", "v4")); assertTrue(g2.containsEdge("v5", "v5")); assertTrue(g2.containsEdge("v5", "v2")); assertEquals(g.toString(), g2.toString()); } /** * Tests serialization */ @Test public void testSerialization1() throws Exception { Graph g = new MutableNetworkAdapter<>( NetworkBuilder .undirected().allowsParallelEdges(false).allowsSelfLoops(true).build(), null, SupplierUtil.DEFAULT_EDGE_SUPPLIER); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertFalse(g.getType().isDirected()); assertTrue(g.getType().isUndirected()); assertFalse(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addEdge("v1", "v2"); g.addEdge("v2", "v3"); g.addEdge("v3", "v3"); Graph g2 = SerializationTestUtils.serializeAndDeserialize(g); assertFalse(g2.getType().isAllowingMultipleEdges()); assertTrue(g2.getType().isAllowingSelfLoops()); assertFalse(g2.getType().isDirected()); assertTrue(g2.getType().isUndirected()); assertFalse(g2.getType().isWeighted()); assertTrue(g2.getType().isAllowingCycles()); assertTrue(g2.containsVertex("v1")); assertTrue(g2.containsVertex("v2")); assertTrue(g2.containsVertex("v3")); assertTrue(g2.vertexSet().size() == 3); assertTrue(g2.edgeSet().size() == 3); assertTrue(g2.containsEdge("v1", "v2")); assertTrue(g2.containsEdge("v2", "v3")); assertTrue(g2.containsEdge("v3", "v3")); } @Test public void testEdgeCoherenceSameNetwork() { final MutableNetwork g = NetworkBuilder.undirected().allowsParallelEdges(true).allowsSelfLoops(true).build(); DefaultEdge e = new DefaultEdge(); g.addEdge("0", "1", e); final MutableNetworkAdapter a = new MutableNetworkAdapter<>(g); DefaultEdge e1 = a.getEdge("0", "1"); DefaultEdge e2 = a.getEdge("1", "0"); assertEquals(e1, e2); assertEquals(a.getEdgeSource(e1), a.getEdgeSource(e2)); assertEquals(a.getEdgeTarget(e1), a.getEdgeTarget(e2)); } @Test public void testEdgeCoherenceSameNetworkWithComparator() { final MutableNetwork g = NetworkBuilder.undirected().allowsParallelEdges(true).allowsSelfLoops(true).build(); DefaultEdge e = new DefaultEdge(); g.addEdge(0, 1, e); final MutableNetworkAdapter a = new MutableNetworkAdapter<>( g, null, null, ElementOrderMethod.comparator(Comparator. naturalOrder())); DefaultEdge e1 = a.getEdge(0, 1); DefaultEdge e2 = a.getEdge(1, 0); assertEquals(e1, e2); assertEquals(a.getEdgeSource(e1), a.getEdgeSource(e2)); assertEquals(a.getEdgeTarget(e1), a.getEdgeTarget(e2)); } @Test public void testEdgeCoherenceSameNetworkWithNaturalOrder() { final MutableNetwork g = NetworkBuilder.undirected().allowsParallelEdges(true).allowsSelfLoops(true).build(); DefaultEdge e = new DefaultEdge(); g.addEdge(0, 1, e); final MutableNetworkAdapter a = new MutableNetworkAdapter<>(g, null, null, ElementOrderMethod.natural()); DefaultEdge e1 = a.getEdge(0, 1); DefaultEdge e2 = a.getEdge(1, 0); assertEquals(e1, e2); assertEquals(a.getEdgeSource(e1), a.getEdgeSource(e2)); assertEquals(a.getEdgeTarget(e1), a.getEdgeTarget(e2)); } } MutableValueGraphAdapterTest.java000066400000000000000000000407221402514743400350010ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import org.jgrapht.Graph; import org.junit.*; import java.io.*; import java.util.*; import java.util.function.*; import com.google.common.graph.*; import static org.junit.Assert.*; /** * Check Incoming/Outgoing edges in directed and undirected graphs. * * @author Dimitrios Michail */ public class MutableValueGraphAdapterTest { /** * Test value propagation */ @Test public void testWeights() { MutableValueGraph graph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); graph.addNode("v1"); graph.addNode("v2"); graph.addNode("v3"); graph.addNode("v4"); graph.addNode("v5"); graph.putEdgeValue("v1", "v2", new MyValue(2.0)); graph.putEdgeValue("v2", "v3", new MyValue(3.0)); graph.putEdgeValue("v2", "v4", new MyValue(4.0)); graph.putEdgeValue("v4", "v4", new MyValue(5.0)); graph.putEdgeValue("v5", "v2", new MyValue(6.0)); Graph> g = new MutableValueGraphAdapter<>( graph, new MyValue(1.0d), (ToDoubleFunction & Serializable) MyValue::getValue); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertEquals(2.0, g.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(EndpointPair.ordered("v2", "v3")), 1e-9); assertEquals(4.0, g.getEdgeWeight(EndpointPair.ordered("v2", "v4")), 1e-9); assertEquals(5.0, g.getEdgeWeight(EndpointPair.ordered("v4", "v4")), 1e-9); assertEquals(6.0, g.getEdgeWeight(EndpointPair.ordered("v5", "v2")), 1e-9); // add edge and make sure that weight is default g.addEdge("v1", "v5"); assertEquals(1.0d, g.getEdgeWeight(EndpointPair.ordered("v1", "v5")), 1e-9); // check that the adapter is only one way try { g.setEdgeWeight(EndpointPair.ordered("v1", "v2"), 1.0); fail("One way adapter only"); } catch (UnsupportedOperationException e) { // ignore } } /** * Test two ways values in special case where value type is double. */ @Test public void testDoubleWeights() { MutableValueGraph graph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); graph.addNode("v1"); graph.addNode("v2"); graph.addNode("v3"); graph.addNode("v4"); graph.addNode("v5"); graph.putEdgeValue("v1", "v2", 2.0); graph.putEdgeValue("v2", "v3", 3.0); graph.putEdgeValue("v2", "v4", 4.0); graph.putEdgeValue("v4", "v4", 5.0); graph.putEdgeValue("v5", "v2", 6.0); Graph> g = new MutableDoubleValueGraphAdapter<>(graph); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); assertEquals(2.0, g.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(EndpointPair.ordered("v2", "v3")), 1e-9); assertEquals(4.0, g.getEdgeWeight(EndpointPair.ordered("v2", "v4")), 1e-9); assertEquals(5.0, g.getEdgeWeight(EndpointPair.ordered("v4", "v4")), 1e-9); assertEquals(6.0, g.getEdgeWeight(EndpointPair.ordered("v5", "v2")), 1e-9); // add edge and make sure that weight is default g.addEdge("v1", "v5"); assertEquals(1.0d, g.getEdgeWeight(EndpointPair.ordered("v1", "v5")), 1e-9); g.setEdgeWeight(EndpointPair.ordered("v1", "v2"), 99.0); assertEquals(99.0d, g.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 1e-9); } /** * Example on javadoc */ @Test public void testExample() { MutableValueGraph valueGraph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); valueGraph.addNode("v1"); valueGraph.addNode("v2"); valueGraph.putEdgeValue("v1", "v2", new MyValue(5.0)); Graph> graph = new MutableValueGraphAdapter<>( valueGraph, new MyValue(1.0), (ToDoubleFunction & Serializable) MyValue::getValue); assertEquals(graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 5.0, 1e-9); valueGraph.putEdgeValue("v1", "v2", new MyValue(9.0)); assertEquals(graph.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 9.0, 1e-9); } /** * Example on javadoc */ @Test public void testExampleDoubleWeights() { MutableValueGraph graph = ValueGraphBuilder.directed().allowsSelfLoops(true).build(); graph.addNode("v1"); graph.addNode("v2"); graph.putEdgeValue("v1", "v2", 3.0); Graph> g = new MutableDoubleValueGraphAdapter<>(graph); assertEquals(3.0, g.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 1e-9); g.setEdgeWeight(EndpointPair.ordered("v1", "v2"), 7.0); assertEquals(7.0, g.getEdgeWeight(EndpointPair.ordered("v1", "v2")), 1e-9); } /** * Test the most general version of the directed graph. */ @Test public void testDirectedGraph() { Graph> g = new MutableValueGraphAdapter<>( ValueGraphBuilder.directed().allowsSelfLoops(true).build(), new MyValue(1.0), (ToDoubleFunction & Serializable) MyValue::getValue); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); EndpointPair e12 = g.addEdge("v1", "v2"); EndpointPair e23 = g.addEdge("v2", "v3"); EndpointPair e24 = g.addEdge("v2", "v4"); EndpointPair e44 = g.addEdge("v4", "v4"); EndpointPair e55 = g.addEdge("v5", "v5"); EndpointPair e52 = g.addEdge("v5", "v2"); assertEquals(1, g.degreeOf("v1")); assertEquals(4, g.degreeOf("v2")); assertEquals(1, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(3, g.degreeOf("v5")); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52, e55), g.edgesOf("v5")); assertEquals(0, g.inDegreeOf("v1")); assertEquals(2, g.inDegreeOf("v2")); assertEquals(1, g.inDegreeOf("v3")); assertEquals(2, g.inDegreeOf("v4")); assertEquals(1, g.inDegreeOf("v5")); assertEquals(Set.of(), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(e55), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(2, g.outDegreeOf("v2")); assertEquals(0, g.outDegreeOf("v3")); assertEquals(1, g.outDegreeOf("v4")); assertEquals(2, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e23, e24), g.outgoingEdgesOf("v2")); assertEquals(Set.of(), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52, e55), g.outgoingEdgesOf("v5")); } /** * Test the most general version of the undirected graph. */ @Test public void testUndirectedGraph() { Graph> g = new MutableValueGraphAdapter<>( ValueGraphBuilder.undirected().allowsSelfLoops(true).build(), new MyValue(1.0), (ToDoubleFunction & Serializable) MyValue::getValue); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertFalse(g.getType().isDirected()); assertTrue(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); EndpointPair e12 = g.addEdge("v1", "v2"); EndpointPair e23 = g.addEdge("v2", "v3"); EndpointPair e24 = g.addEdge("v2", "v4"); EndpointPair e44 = g.addEdge("v4", "v4"); EndpointPair e55 = g.addEdge("v5", "v5"); EndpointPair e52 = g.addEdge("v5", "v2"); assertEquals(1, g.degreeOf("v1")); assertEquals(4, g.degreeOf("v2")); assertEquals(1, g.degreeOf("v3")); assertEquals(3, g.degreeOf("v4")); assertEquals(3, g.degreeOf("v5")); assertEquals(Set.of(e12), g.edgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.edgesOf("v2")); assertEquals(Set.of(e23), g.edgesOf("v3")); assertEquals(Set.of(e24, e44), g.edgesOf("v4")); assertEquals(Set.of(e52, e55), g.edgesOf("v5")); assertEquals(1, g.inDegreeOf("v1")); assertEquals(4, g.inDegreeOf("v2")); assertEquals(1, g.inDegreeOf("v3")); assertEquals(3, g.inDegreeOf("v4")); assertEquals(3, g.inDegreeOf("v5")); assertEquals(Set.of(e12), g.incomingEdgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.incomingEdgesOf("v2")); assertEquals(Set.of(e23), g.incomingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.incomingEdgesOf("v4")); assertEquals(Set.of(e52, e55), g.incomingEdgesOf("v5")); assertEquals(1, g.outDegreeOf("v1")); assertEquals(4, g.outDegreeOf("v2")); assertEquals(1, g.outDegreeOf("v3")); assertEquals(3, g.outDegreeOf("v4")); assertEquals(3, g.outDegreeOf("v5")); assertEquals(Set.of(e12), g.outgoingEdgesOf("v1")); assertEquals(Set.of(e12, e23, e24, e52), g.outgoingEdgesOf("v2")); assertEquals(Set.of(e23), g.outgoingEdgesOf("v3")); assertEquals(Set.of(e24, e44), g.outgoingEdgesOf("v4")); assertEquals(Set.of(e52, e55), g.outgoingEdgesOf("v5")); } /** * Tests serialization */ @Test public void testSerialization() throws Exception { Graph> g = new MutableValueGraphAdapter<>( ValueGraphBuilder.directed().allowsSelfLoops(true).build(), new MyValue(1.0), (ToDoubleFunction & Serializable) MyValue::getValue); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertTrue(g.getType().isDirected()); assertFalse(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addVertex("v4"); g.addVertex("v5"); g.addEdge("v1", "v2"); g.addEdge("v2", "v3"); g.addEdge("v2", "v4"); g.addEdge("v4", "v4"); g.addEdge("v5", "v5"); g.addEdge("v5", "v2"); Graph> g2 = SerializationTestUtils.serializeAndDeserialize(g); assertFalse(g2.getType().isAllowingMultipleEdges()); assertTrue(g2.getType().isAllowingSelfLoops()); assertTrue(g2.getType().isDirected()); assertFalse(g2.getType().isUndirected()); assertTrue(g2.getType().isWeighted()); assertTrue(g2.getType().isAllowingCycles()); assertTrue(g2.containsVertex("v1")); assertTrue(g2.containsVertex("v2")); assertTrue(g2.containsVertex("v3")); assertTrue(g2.containsVertex("v4")); assertTrue(g2.containsVertex("v5")); assertTrue(g2.vertexSet().size() == 5); assertTrue(g2.edgeSet().size() == 6); assertTrue(g2.containsEdge("v1", "v2")); assertTrue(g2.containsEdge("v2", "v3")); assertTrue(g2.containsEdge("v2", "v4")); assertTrue(g2.containsEdge("v4", "v4")); assertTrue(g2.containsEdge("v5", "v5")); assertTrue(g2.containsEdge("v5", "v2")); assertEquals(g.toString(), g2.toString()); } /** * Tests serialization */ @Test public void testSerialization1() throws Exception { Graph> g = new MutableValueGraphAdapter<>( ValueGraphBuilder.undirected().allowsSelfLoops(true).build(), new MyValue(1.0), (ToDoubleFunction & Serializable) MyValue::getValue); assertFalse(g.getType().isAllowingMultipleEdges()); assertTrue(g.getType().isAllowingSelfLoops()); assertFalse(g.getType().isDirected()); assertTrue(g.getType().isUndirected()); assertTrue(g.getType().isWeighted()); assertTrue(g.getType().isAllowingCycles()); g.addVertex("v1"); g.addVertex("v2"); g.addVertex("v3"); g.addEdge("v1", "v2"); g.addEdge("v2", "v3"); g.addEdge("v3", "v3"); Graph> g2 = SerializationTestUtils.serializeAndDeserialize(g); assertFalse(g2.getType().isAllowingMultipleEdges()); assertTrue(g2.getType().isAllowingSelfLoops()); assertFalse(g2.getType().isDirected()); assertTrue(g2.getType().isUndirected()); assertTrue(g2.getType().isWeighted()); assertTrue(g2.getType().isAllowingCycles()); assertTrue(g2.containsVertex("v1")); assertTrue(g2.containsVertex("v2")); assertTrue(g2.containsVertex("v3")); assertTrue(g2.vertexSet().size() == 3); assertTrue(g2.edgeSet().size() == 3); assertTrue(g2.containsEdge("v1", "v2")); assertTrue(g2.containsEdge("v2", "v3")); assertTrue(g2.containsEdge("v3", "v3")); } private static class MyValue implements Serializable { private static final long serialVersionUID = 1L; private double value; public MyValue(double value) { this.value = value; } public double getValue() { return value; } } @Test public void testEdgeCoherenceSameNetworkWithComparator() { MutableValueGraph g = ValueGraphBuilder.undirected().allowsSelfLoops(true).build(); g.addNode(0); g.addNode(1); g.putEdgeValue(0, 1, new MyValue(1.0)); final Graph> a = new MutableValueGraphAdapter<>( g, new MyValue(1.0d), (ToDoubleFunction & Serializable) MyValue::getValue, null, null, ElementOrderMethod.comparator(Comparator. naturalOrder())); EndpointPair e1 = a.getEdge(0, 1); EndpointPair e2 = a.getEdge(1, 0); assertEquals(e1, e2); assertEquals(a.getEdgeSource(e1), a.getEdgeSource(e2)); assertEquals(a.getEdgeTarget(e1), a.getEdgeTarget(e2)); } } SerializationTestUtils.java000066400000000000000000000026511402514743400337650ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-guava/src/test/java/org/jgrapht/graph/guava/* * (C) Copyright 2003-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.graph.guava; import java.io.*; /** * Serialization test utils for the serialization and deserialization of JGraphT objects. * * @author John V. Sichi */ public class SerializationTestUtils { // don't instantiate this class private SerializationTestUtils() { } @SuppressWarnings("unchecked") public static T serializeAndDeserialize(T obj) throws Exception { ByteArrayOutputStream bout = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bout); out.writeObject(obj); out.flush(); ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); ObjectInputStream in = new ObjectInputStream(bin); obj = (T) in.readObject(); return obj; } } jgrapht-jgrapht-1.5.1/jgrapht-io/000077500000000000000000000000001402514743400166625ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/pom.xml000066400000000000000000000151401402514743400202000ustar00rootroot00000000000000 4.0.0 org.jgrapht jgrapht 1.5.1 jgrapht-io JGraphT - I/O ${project.parent.basedir} GNU Lesser General Public License Version 2.1, February 1999 http://jgrapht.sourceforge.net/LGPL.html repo Eclipse Public License (EPL) 2.0 http://www.eclipse.org/legal/epl-v20.html repo org.apache.maven.plugins maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-resources-plugin org.apache.felix maven-bundle-plugin org.antlr antlr4-maven-plugin 4.8-1 antlr antlr4 com.google.code.maven-replacer-plugin replacer 1.5.3 generate-sources replace ${project.basedir} target/generated-sources/antlr4/**/*.java org.apache.maven.plugins maven-javadoc-plugin ${project.build.sourceDirectory}:${project.build.directory}/generated-sources/antlr4 org.eclipse.m2e lifecycle-mapping 1.0.0 com.google.code.maven-replacer-plugin replacer [1.5.3,) replace false ${project.groupId} jgrapht-core org.xmlunit xmlunit-core test junit junit test org.antlr antlr4-runtime 4.8-1 org.apache.commons commons-text 1.8 jgrapht-jgrapht-1.5.1/jgrapht-io/src/000077500000000000000000000000001402514743400174515ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/000077500000000000000000000000001402514743400203755ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/000077500000000000000000000000001402514743400216015ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/000077500000000000000000000000001402514743400223705ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/000077500000000000000000000000001402514743400240275ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/io/000077500000000000000000000000001402514743400244365ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/io/CSV.g4000066400000000000000000000021161402514743400253250ustar00rootroot00000000000000/* * (C) Copyright 2016-2017, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * This program and the accompanying materials are dual-licensed under * either * * (a) the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation, or (at your option) any * later version. * * or (per the licensee's choosing) * * (b) the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation. */ grammar CSV; @lexer::members { char sep = ','; public void setSep(char sep) { this.sep = sep; } private char getSep() { return sep; } } file: header record+ ; header : record ; record : field (SEPARATOR field)* '\r'? '\n' ; field : TEXT #TextField | STRING #StringField | #EmptyField ; SEPARATOR: { _input.LA(1) == sep }? . ; TEXT : TEXTCHAR+ ; fragment TEXTCHAR: { (_input.LA(1) != sep && _input.LA(1) != '\n' && _input.LA(1) != '\r' && _input.LA(1) != '"') }? .; STRING : '"' ('""'|~'"')* '"' ; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/io/DOT.g4000066400000000000000000000051561402514743400253270ustar00rootroot00000000000000/* * (C) Copyright 2016-2017, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * This program and the accompanying materials are dual-licensed under * either * * (a) the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation, or (at your option) any * later version. * * or (per the licensee's choosing) * * (b) the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation. */ grammar DOT; graph : graphHeader compoundStatement ; compoundStatement : '{' ( statement ';'? )* '}' ; graphHeader : STRICT? ( GRAPH | DIGRAPH ) graphIdentifier? ; graphIdentifier : identifier ; statement : nodeStatement | edgeStatement | attributeStatement | identifierPairStatement | subgraphStatement ; identifierPairStatement : identifierPair ; attributeStatement : ( GRAPH | NODE | EDGE ) attributesList ; attributesList : ( '[' aList? ']' )+ ; aList : ( identifierPair (';'|',')? )+ ; edgeStatement : ( nodeStatementNoAttributes | subgraphStatement ) ( ('->' | '--') ( nodeStatementNoAttributes | subgraphStatement ) )+ attributesList? ; nodeStatement : nodeIdentifier attributesList? ; nodeStatementNoAttributes : nodeIdentifier ; nodeIdentifier : identifier (port)? ; port : ':' identifier ( ':' identifier )? ; subgraphStatement : ( SUBGRAPH identifier? )? compoundStatement ; identifierPair : identifier '=' identifier ; identifier : Id | String | HtmlString | Numeral ; // LEXER STRICT : ('S'|'s')('T'|'t')('R'|'r')('I'|'i')('C'|'c')('T'|'t') ; GRAPH : ('G'|'g')('R'|'r')('A'|'a')('P'|'p')('H'|'h') ; DIGRAPH : ('D'|'d')('I'|'i')('G'|'g')('R'|'r')('A'|'a')('P'|'p')('H'|'h') ; NODE : ('N'|'n')('O'|'o')('D'|'d')('E'|'e') ; EDGE : ('E'|'e')('D'|'d')('G'|'g')('E'|'e') ; SUBGRAPH : ('S'|'s')('U'|'u')('B'|'b')('G'|'g')('R'|'r')('A'|'a')('P'|'p')('H'|'h') ; Numeral : '-'? ( '.' Digit+ | Digit+ ( '.' Digit* )? ) ; String : '"' SCharSequence? '"' ; Id : Letter ( Letter | Digit )* ; HtmlString : '<' ( HtmlTag | ~[<>] )* '>' ; fragment HtmlTag : '<' .*? '>' ; fragment SCharSequence : SChar+ ; fragment SChar : ~["\\] | '\\' ["\\] | '\\\n' | '\\\r\n' ; fragment Digit : [0-9] ; fragment Letter : [a-zA-Z\u0080-\u00FF_] ; WS : [ \t\n\r]+ -> skip ; COMMENT : '/*' .*? '*/' -> skip ; LINE_COMMENT : '//' .*? '\r'? '\n' -> skip ; PREPROC : '#' .*? '\n' -> skip ; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/io/Gml.g4000066400000000000000000000017511402514743400254150ustar00rootroot00000000000000/* * (C) Copyright 2016-2017, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * This program and the accompanying materials are dual-licensed under * either * * (a) the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation, or (at your option) any * later version. * * or (per the licensee's choosing) * * (b) the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation. */ grammar Gml; gml : keyValuePair* ; keyValuePair : ID STRING #StringKeyValue | ID NUMBER #NumberKeyValue | ID '[' keyValuePair* ']' #ListKeyValue ; NUMBER : '-'? ( '.' DIGIT+ | DIGIT+ ( '.' DIGIT* )? ) ; fragment DIGIT : [0-9] ; fragment LETTER : [a-zA-Z\u0080-\u00FF_] ; STRING : '"' ( '\\"' | . )*? '"' ; ID : LETTER ( LETTER | DIGIT )* ; COMMENT : '#' .*? '\n' -> skip ; WS : [ \t\n\r]+ -> skip ; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/io/Json.g4000066400000000000000000000014531402514743400256060ustar00rootroot00000000000000/** Taken from "The Definitive ANTLR 4 Reference" by Terence Parr */ grammar Json; json : value ; obj : '{' pair (',' pair)* '}' | '{' '}' ; pair : STRING ':' value ; array : '[' value (',' value)* ']' | '[' ']' ; value : STRING | NUMBER | obj | array | 'true' | 'false' | 'null' ; STRING : '"' (ESC | SAFECODEPOINT)* '"' ; fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ; fragment UNICODE : 'u' HEX HEX HEX HEX ; fragment HEX : [0-9a-fA-F] ; fragment SAFECODEPOINT : ~ ["\\\u0000-\u001F] ; NUMBER : '-'? INT ('.' [0-9] +)? EXP? ; fragment INT : '0' | [1-9] [0-9]* ; // no leading zeros fragment EXP : [Ee] [+\-]? INT ; // \- since - means "range" inside [...] WS : [ \t\n\r] + -> skip ; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/nio/000077500000000000000000000000001402514743400246145ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/nio/csv/000077500000000000000000000000001402514743400254075ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/nio/csv/CSV.g4000066400000000000000000000021161402514743400262760ustar00rootroot00000000000000/* * (C) Copyright 2016-2017, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * This program and the accompanying materials are dual-licensed under * either * * (a) the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation, or (at your option) any * later version. * * or (per the licensee's choosing) * * (b) the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation. */ grammar CSV; @lexer::members { char sep = ','; public void setSep(char sep) { this.sep = sep; } private char getSep() { return sep; } } file: header record+ ; header : record ; record : field (SEPARATOR field)* '\r'? '\n' ; field : TEXT #TextField | STRING #StringField | #EmptyField ; SEPARATOR: { _input.LA(1) == sep }? . ; TEXT : TEXTCHAR+ ; fragment TEXTCHAR: { (_input.LA(1) != sep && _input.LA(1) != '\n' && _input.LA(1) != '\r' && _input.LA(1) != '"') }? .; STRING : '"' ('""'|~'"')* '"' ; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/nio/dot/000077500000000000000000000000001402514743400254025ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/nio/dot/DOT.g4000066400000000000000000000051561402514743400262730ustar00rootroot00000000000000/* * (C) Copyright 2016-2017, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * This program and the accompanying materials are dual-licensed under * either * * (a) the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation, or (at your option) any * later version. * * or (per the licensee's choosing) * * (b) the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation. */ grammar DOT; graph : graphHeader compoundStatement ; compoundStatement : '{' ( statement ';'? )* '}' ; graphHeader : STRICT? ( GRAPH | DIGRAPH ) graphIdentifier? ; graphIdentifier : identifier ; statement : nodeStatement | edgeStatement | attributeStatement | identifierPairStatement | subgraphStatement ; identifierPairStatement : identifierPair ; attributeStatement : ( GRAPH | NODE | EDGE ) attributesList ; attributesList : ( '[' aList? ']' )+ ; aList : ( identifierPair (';'|',')? )+ ; edgeStatement : ( nodeStatementNoAttributes | subgraphStatement ) ( ('->' | '--') ( nodeStatementNoAttributes | subgraphStatement ) )+ attributesList? ; nodeStatement : nodeIdentifier attributesList? ; nodeStatementNoAttributes : nodeIdentifier ; nodeIdentifier : identifier (port)? ; port : ':' identifier ( ':' identifier )? ; subgraphStatement : ( SUBGRAPH identifier? )? compoundStatement ; identifierPair : identifier '=' identifier ; identifier : Id | String | HtmlString | Numeral ; // LEXER STRICT : ('S'|'s')('T'|'t')('R'|'r')('I'|'i')('C'|'c')('T'|'t') ; GRAPH : ('G'|'g')('R'|'r')('A'|'a')('P'|'p')('H'|'h') ; DIGRAPH : ('D'|'d')('I'|'i')('G'|'g')('R'|'r')('A'|'a')('P'|'p')('H'|'h') ; NODE : ('N'|'n')('O'|'o')('D'|'d')('E'|'e') ; EDGE : ('E'|'e')('D'|'d')('G'|'g')('E'|'e') ; SUBGRAPH : ('S'|'s')('U'|'u')('B'|'b')('G'|'g')('R'|'r')('A'|'a')('P'|'p')('H'|'h') ; Numeral : '-'? ( '.' Digit+ | Digit+ ( '.' Digit* )? ) ; String : '"' SCharSequence? '"' ; Id : Letter ( Letter | Digit )* ; HtmlString : '<' ( HtmlTag | ~[<>] )* '>' ; fragment HtmlTag : '<' .*? '>' ; fragment SCharSequence : SChar+ ; fragment SChar : ~["\\] | '\\' ["\\] | '\\\n' | '\\\r\n' ; fragment Digit : [0-9] ; fragment Letter : [a-zA-Z\u0080-\u00FF_] ; WS : [ \t\n\r]+ -> skip ; COMMENT : '/*' .*? '*/' -> skip ; LINE_COMMENT : '//' .*? '\r'? '\n' -> skip ; PREPROC : '#' .*? '\n' -> skip ; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/nio/gml/000077500000000000000000000000001402514743400253735ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/nio/gml/Gml.g4000066400000000000000000000017511402514743400263520ustar00rootroot00000000000000/* * (C) Copyright 2016-2017, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * This program and the accompanying materials are dual-licensed under * either * * (a) the terms of the GNU Lesser General Public License version 2.1 * as published by the Free Software Foundation, or (at your option) any * later version. * * or (per the licensee's choosing) * * (b) the terms of the Eclipse Public License v1.0 as published by * the Eclipse Foundation. */ grammar Gml; gml : keyValuePair* ; keyValuePair : ID STRING #StringKeyValue | ID NUMBER #NumberKeyValue | ID '[' keyValuePair* ']' #ListKeyValue ; NUMBER : '-'? ( '.' DIGIT+ | DIGIT+ ( '.' DIGIT* )? ) ; fragment DIGIT : [0-9] ; fragment LETTER : [a-zA-Z\u0080-\u00FF_] ; STRING : '"' ( '\\"' | . )*? '"' ; ID : LETTER ( LETTER | DIGIT )* ; COMMENT : '#' .*? '\n' -> skip ; WS : [ \t\n\r]+ -> skip ; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/nio/json/000077500000000000000000000000001402514743400255655ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/antlr4/org/jgrapht/nio/json/Json.g4000066400000000000000000000014531402514743400267350ustar00rootroot00000000000000/** Taken from "The Definitive ANTLR 4 Reference" by Terence Parr */ grammar Json; json : value ; obj : '{' pair (',' pair)* '}' | '{' '}' ; pair : STRING ':' value ; array : '[' value (',' value)* ']' | '[' ']' ; value : STRING | NUMBER | obj | array | 'true' | 'false' | 'null' ; STRING : '"' (ESC | SAFECODEPOINT)* '"' ; fragment ESC : '\\' (["\\/bfnrt] | UNICODE) ; fragment UNICODE : 'u' HEX HEX HEX HEX ; fragment HEX : [0-9a-fA-F] ; fragment SAFECODEPOINT : ~ ["\\\u0000-\u001F] ; NUMBER : '-'? INT ('.' [0-9] +)? EXP? ; fragment INT : '0' | [1-9] [0-9]* ; // no leading zeros fragment EXP : [Ee] [+\-]? INT ; // \- since - means "range" inside [...] WS : [ \t\n\r] + -> skip ; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/000077500000000000000000000000001402514743400213165ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/module-info.java000066400000000000000000000011461402514743400244010ustar00rootroot00000000000000module org.jgrapht.io { exports org.jgrapht.nio; exports org.jgrapht.nio.csv; exports org.jgrapht.nio.dimacs; exports org.jgrapht.nio.dot; exports org.jgrapht.nio.gexf; exports org.jgrapht.nio.gml; exports org.jgrapht.nio.graph6; exports org.jgrapht.nio.graphml; exports org.jgrapht.nio.json; exports org.jgrapht.nio.lemon; exports org.jgrapht.nio.matrix; exports org.jgrapht.nio.tsplib; requires transitive org.jgrapht.core; requires transitive org.apache.commons.text; requires transitive java.xml; requires transitive org.antlr.antlr4.runtime; } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/000077500000000000000000000000001402514743400221055ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/000077500000000000000000000000001402514743400235445ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/000077500000000000000000000000001402514743400243315ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/Attribute.java000066400000000000000000000017721402514743400271460ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; /** * An attribute * * @author Dimitrios Michail */ public interface Attribute { /** * Get the value of the attribute * * @return the value of the attribute */ String getValue(); /** * Get the type of the attribute * * @return the type of the attribute */ AttributeType getType(); } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/AttributeType.java000066400000000000000000000041111402514743400277760ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; /** * Denotes the type of an attribute. * * @author Dimitrios Michail */ public enum AttributeType { NULL("null"), BOOLEAN("boolean"), INT("int"), LONG("long"), FLOAT("float"), DOUBLE("double"), STRING("string"), HTML("html"), UNKNOWN("unknown"), IDENTIFIER("identifier"); private String name; private AttributeType(String name) { this.name = name; } /** * Get a string representation of the attribute type * * @return the string representation of the attribute type */ public String toString() { return name; } /** * Create a type from a string representation * * @param value the name of the type * @return the attribute type */ public static AttributeType create(String value) { switch (value) { case "null": return NULL; case "boolean": return BOOLEAN; case "int": return INT; case "long": return LONG; case "float": return FLOAT; case "double": return DOUBLE; case "string": return STRING; case "html": return HTML; case "unknown": return UNKNOWN; case "identifier": return IDENTIFIER; } throw new IllegalArgumentException("Type " + value + " is unknown"); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/BaseEventDrivenImporter.java000066400000000000000000000231211402514743400317410ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; import org.jgrapht.alg.util.*; import java.util.*; import java.util.function.*; /** * Base implementation for an importer which uses consumers to notify interested parties. Note that * this importer does not compute anything, it simply calls the appropriate consumers to do the * actual work. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public abstract class BaseEventDrivenImporter { private List> vertexCountConsumers; private List> edgeCountConsumers; private List> vertexConsumers; private List>> vertexWithAttributesConsumers; private List> edgeConsumers; private List>> edgeWithAttributesConsumers; private List> graphAttributeConsumers; private List, Attribute>> vertexAttributeConsumers; private List, Attribute>> edgeAttributeConsumers; private List> importEventConsumers; /** * Constructor */ public BaseEventDrivenImporter() { this.vertexCountConsumers = new ArrayList<>(); this.edgeCountConsumers = new ArrayList<>(); this.vertexConsumers = new ArrayList<>(); this.vertexWithAttributesConsumers = new ArrayList<>(); this.edgeConsumers = new ArrayList<>(); this.edgeWithAttributesConsumers = new ArrayList<>(); this.graphAttributeConsumers = new ArrayList<>(); this.vertexAttributeConsumers = new ArrayList<>(); this.edgeAttributeConsumers = new ArrayList<>(); this.importEventConsumers = new ArrayList<>(); } /** * Add an ImportEvent consumer. * * @param consumer the consumer */ public void addImportEventConsumer(Consumer consumer) { importEventConsumers.add(consumer); } /** * Remove an ImportEvent consumer. * * @param consumer the consumer */ public void removeImportEventConsumer(Consumer consumer) { importEventConsumers.remove(consumer); } /** * Add a vertex count consumer. * * @param consumer the consumer */ public void addVertexCountConsumer(Consumer consumer) { vertexCountConsumers.add(consumer); } /** * Remove a vertex count consumer. * * @param consumer the consumer */ public void removeVertexCountConsumer(Consumer consumer) { vertexCountConsumers.remove(consumer); } /** * Add an edge count consumer. * * @param consumer the consumer */ public void addEdgeCountConsumer(Consumer consumer) { edgeCountConsumers.add(consumer); } /** * Remove an edge count consumer. * * @param consumer the consumer */ public void removeEdgeCountConsumer(Consumer consumer) { edgeCountConsumers.remove(consumer); } /** * Add a vertex consumer. * * @param consumer the consumer */ public void addVertexConsumer(Consumer consumer) { vertexConsumers.add(consumer); } /** * Remove a vertex consumer. * * @param consumer the consumer */ public void removeVertexConsumer(Consumer consumer) { vertexConsumers.remove(consumer); } /** * Add an edge consumer. * * @param consumer the consumer */ public void addEdgeConsumer(Consumer consumer) { edgeConsumers.add(consumer); } /** * Remove an edge consumer. * * @param consumer the consumer */ public void removeEdgeConsumer(Consumer consumer) { edgeConsumers.remove(consumer); } /** * Add a graph attribute consumer. * * @param consumer the consumer */ public void addGraphAttributeConsumer(BiConsumer consumer) { graphAttributeConsumers.add(consumer); } /** * Remove a graph attribute consumer. * * @param consumer the consumer */ public void removeGraphAttributeConsumer(BiConsumer consumer) { graphAttributeConsumers.remove(consumer); } /** * Add a vertex attribute consumer. * * @param consumer the consumer */ public void addVertexAttributeConsumer(BiConsumer, Attribute> consumer) { vertexAttributeConsumers.add(consumer); } /** * Remove a vertex attribute consumer. * * @param consumer the consumer */ public void removeVertexAttributeConsumer(BiConsumer, Attribute> consumer) { vertexAttributeConsumers.remove(consumer); } /** * Add a vertex with attributes consumer. * * @param consumer the consumer */ public void addVertexWithAttributesConsumer(BiConsumer> consumer) { vertexWithAttributesConsumers.add(consumer); } /** * Remove a vertex with attributes consumer * * @param consumer the consumer */ public void removeVertexWithAttributesConsumer(BiConsumer> consumer) { vertexWithAttributesConsumers.remove(consumer); } /** * Add an edge attribute consumer. * * @param consumer the consumer */ public void addEdgeAttributeConsumer(BiConsumer, Attribute> consumer) { edgeAttributeConsumers.add(consumer); } /** * Remove an edge attribute consumer. * * @param consumer the consumer */ public void removeEdgeAttributeConsumer(BiConsumer, Attribute> consumer) { edgeAttributeConsumers.remove(consumer); } /** * Add an edge with attributes consumer. * * @param consumer the consumer */ public void addEdgeWithAttributesConsumer(BiConsumer> consumer) { edgeWithAttributesConsumers.add(consumer); } /** * Remove an edge with attributes consumer * * @param consumer the consumer */ public void removeEdgeWithAttributesConsumer(BiConsumer> consumer) { edgeWithAttributesConsumers.remove(consumer); } /** * Notify for the vertex count. * * @param vertexCount the number of vertices in the graph */ protected void notifyVertexCount(Integer vertexCount) { vertexCountConsumers.forEach(c -> c.accept(vertexCount)); } /** * Notify for the edge count. * * @param edgeCount the number of edges in the graph */ protected void notifyEdgeCount(Integer edgeCount) { edgeCountConsumers.forEach(c -> c.accept(edgeCount)); } /** * Notify for a vertex. * * @param v the vertex */ protected void notifyVertex(V v) { vertexConsumers.forEach(c -> c.accept(v)); } /** * Notify for a vertex with attributes. * * @param v the vertex * @param attrs the attributes */ protected void notifyVertexWithAttributes(V v, Map attrs) { vertexWithAttributesConsumers.forEach(c -> c.accept(v, attrs)); } /** * Notify for an edge. * * @param e the edge */ protected void notifyEdge(E e) { edgeConsumers.forEach(c -> c.accept(e)); } /** * Notify for an edge with attributes. * * @param e the edge * @param attrs the attributes */ protected void notifyEdgeWithAttributes(E e, Map attrs) { edgeWithAttributesConsumers.forEach(c -> c.accept(e, attrs)); } /** * Notify for a graph attribute * * @param key the attribute key * @param value the attribute */ protected void notifyGraphAttribute(String key, Attribute value) { graphAttributeConsumers.forEach(c -> c.accept(key, value)); } /** * Notify for a vertex attribute * * @param v the vertex * @param key the attribute key * @param value the attribute */ protected void notifyVertexAttribute(V v, String key, Attribute value) { vertexAttributeConsumers.forEach(c -> c.accept(Pair.of(v, key), value)); } /** * Notify for an edge attribute * * @param e the edge * @param key the attribute key * @param value the attribute */ protected void notifyEdgeAttribute(E e, String key, Attribute value) { edgeAttributeConsumers.forEach(c -> c.accept(Pair.of(e, key), value)); } /** * Notify for an importer ImportEvent * * @param importEvent the ImportEvent */ protected void notifyImportEvent(ImportEvent importEvent) { importEventConsumers.forEach(c -> c.accept(importEvent)); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/BaseExporter.java000066400000000000000000000166101402514743400276030ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; import java.util.*; import java.util.function.*; /** * Base implementation for an exporter. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public abstract class BaseExporter { /** * A graph id provider */ protected Optional> graphIdProvider; /** * A graph attribute provider */ protected Optional>> graphAttributeProvider; /** * A vertex id provider */ protected Function vertexIdProvider; /** * A vertex attribute provider */ protected Optional>> vertexAttributeProvider; /** * An edge id provider */ protected Optional> edgeIdProvider; /** * An edge attribute provider */ protected Optional>> edgeAttributeProvider; /** * Constructor * * @param vertexIdProvider the vertex id provider to use. Cannot be null. */ public BaseExporter(Function vertexIdProvider) { this.vertexIdProvider = Objects.requireNonNull(vertexIdProvider); this.graphIdProvider = Optional.empty(); this.graphAttributeProvider = Optional.empty(); this.vertexAttributeProvider = Optional.empty(); this.edgeIdProvider = Optional.empty(); this.edgeAttributeProvider = Optional.empty(); } /** * Get the graph id provider. * * @return the graph id provider as an {@link Optional}. */ public Optional> getGraphIdProvider() { return graphIdProvider; } /** * Set the graph id provider. * * @param graphIdProvider the graph id provider */ public void setGraphIdProvider(Supplier graphIdProvider) { this.graphIdProvider = Optional.ofNullable(graphIdProvider); } /** * Get the graph attribute provider. * * @return the graph attribute provider as an {@link Optional}. */ public Optional>> getGraphAttributeProvider() { return graphAttributeProvider; } /** * Set the graph attribute provider. * * @param graphAttributeProvider the graph attribute provider */ public void setGraphAttributeProvider(Supplier> graphAttributeProvider) { this.graphAttributeProvider = Optional.ofNullable(graphAttributeProvider); } /** * Get vertex id provider. * * @return the vertex id provider */ public Function getVertexIdProvider() { return vertexIdProvider; } /** * Set the vertex id provider * * @param vertexIdProvider the vertex id provider */ public void setVertexIdProvider(Function vertexIdProvider) { this.vertexIdProvider = Objects.requireNonNull(vertexIdProvider); } /** * Get the vertex attribute provider * * @return the vertex attribute provider as an {@link Optional} */ public Optional>> getVertexAttributeProvider() { return vertexAttributeProvider; } /** * Set the vertex attribute provider * * @param vertexAttributeProvider the vertex attribute provider */ public void setVertexAttributeProvider( Function> vertexAttributeProvider) { this.vertexAttributeProvider = Optional.ofNullable(vertexAttributeProvider); } /** * Get the edge id provider * * @return the edge id provider as an {@link Optional}. */ public Optional> getEdgeIdProvider() { return edgeIdProvider; } /** * Set edge id provider * * @param edgeIdProvider the edge id provider */ public void setEdgeIdProvider(Function edgeIdProvider) { this.edgeIdProvider = Optional.ofNullable(edgeIdProvider); } /** * Get the edge attribute provider * * @return the edge attribute provider as an {@link Optional} */ public Optional>> getEdgeAttributeProvider() { return edgeAttributeProvider; } /** * Set the edge attribute provider. * * @param edgeAttributeProvider the edge attribute provider */ public void setEdgeAttributeProvider(Function> edgeAttributeProvider) { this.edgeAttributeProvider = Optional.ofNullable(edgeAttributeProvider); } /** * Get the graph id if present * * @return an {@link Optional} of the graph id */ protected Optional getGraphId() { return graphIdProvider.map(x -> x.get()); } /** * Get the vertex id * * @param v the vertex * @return the id of the vertex */ protected String getVertexId(V v) { return vertexIdProvider.apply(v); } /** * Get an optional of the edge id * * @param e the edge * @return the edge id */ protected Optional getEdgeId(E e) { return edgeIdProvider.map(x -> x.apply(e)); } /** * Get vertex attributes * * @param v the vertex v * @return the vertex attributes as an {@link Optional} */ protected Optional> getVertexAttributes(V v) { return vertexAttributeProvider.map(x -> x.apply(v)); } /** * Get an optional of a vertex attribute * * @param v the vertex v * @param key the attribute key * @return the attribute as an {@link Optional} */ protected Optional getVertexAttribute(V v, String key) { return vertexAttributeProvider.map(x -> x.apply(v).get(key)); } /** * Get edge attributes * * @param e the edge e * @return the edge attributes as an {@link Optional} */ protected Optional> getEdgeAttributes(E e) { return edgeAttributeProvider.map(x -> x.apply(e)); } /** * Get an optional of an edge attribute * * @param e the edge e * @param key the attribute key * @return the attribute as an {@link Optional} */ protected Optional getEdgeAttribute(E e, String key) { return edgeAttributeProvider.map(x -> x.apply(e).get(key)); } /** * Get an optional of a graph attribute * * @param key the attribute key * @return the attribute as an {@link Optional} */ protected Optional getGraphAttribute(String key) { return graphAttributeProvider.map(x -> x.get().get(key)); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/DefaultAttribute.java000066400000000000000000000102351402514743400304450ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; import java.io.*; /** * Default implementation of an attribute. * * @param the underlying type * * @author Dimitrios Michail */ public class DefaultAttribute implements Attribute, Serializable { private static final long serialVersionUID = 366113727410278952L; /** * The null attribute. */ public static Attribute NULL = new DefaultAttribute<>(null, AttributeType.NULL); private T value; private AttributeType type; /** * Create a new attribute * * @param value the value * @param type the type */ public DefaultAttribute(T value, AttributeType type) { this.value = value; this.type = type; } /** * Get the string value of the attribute * * @return the string value of the attribute */ @Override public String getValue() { return String.valueOf(value); } @Override public String toString() { return String.valueOf(value); } /** * Get the type of the attribute * * @return the type of the attribute */ @Override public AttributeType getType() { return type; } /** * Create a boolean attribute * * @param value the value * @return the attribute */ public static Attribute createAttribute(Boolean value) { return new DefaultAttribute<>(value, AttributeType.BOOLEAN); } /** * Create an integer attribute * * @param value the value * @return the attribute */ public static Attribute createAttribute(Integer value) { return new DefaultAttribute<>(value, AttributeType.INT); } /** * Create a long attribute * * @param value the value * @return the attribute */ public static Attribute createAttribute(Long value) { return new DefaultAttribute<>(value, AttributeType.LONG); } /** * Create a float attribute * * @param value the value * @return the attribute */ public static Attribute createAttribute(Float value) { return new DefaultAttribute<>(value, AttributeType.FLOAT); } /** * Create a double attribute * * @param value the value * @return the attribute */ public static Attribute createAttribute(Double value) { return new DefaultAttribute<>(value, AttributeType.DOUBLE); } /** * Create a string attribute * * @param value the value * @return the attribute */ public static Attribute createAttribute(String value) { return new DefaultAttribute<>(value, AttributeType.STRING); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((type == null) ? 0 : type.hashCode()); result = prime * result + ((value == null) ? 0 : value.hashCode()); return result; } @Override @SuppressWarnings("rawtypes") public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DefaultAttribute other = (DefaultAttribute) obj; if (type != other.type) return false; if (value == null) { if (other.value != null) return false; } else if (!value.equals(other.value)) return false; return true; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/EventDrivenImporter.java000066400000000000000000000123311402514743400311470ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; import org.jgrapht.alg.util.*; import java.io.*; import java.nio.charset.*; import java.util.Map; import java.util.function.*; /** * Interface for an importer using consumers. * * @param the graph vertex type * @param the graph edge type */ public interface EventDrivenImporter { /** * Add an ImportEvent consumer. * * @param consumer the consumer */ void addImportEventConsumer(Consumer consumer); /** * Remove an ImportEvent consumer. * * @param consumer the consumer */ void removeImportEventConsumer(Consumer consumer); /** * Add a vertex count consumer. * * @param consumer the consumer */ void addVertexCountConsumer(Consumer consumer); /** * Remove a vertex count consumer. * * @param consumer the consumer */ void removeVertexCountConsumer(Consumer consumer); /** * Add an edge count consumer. * * @param consumer the consumer */ void addEdgeCountConsumer(Consumer consumer); /** * Remove an edge count consumer. * * @param consumer the consumer */ void removeEdgeCountConsumer(Consumer consumer); /** * Add a vertex consumer. * * @param consumer the consumer */ void addVertexConsumer(Consumer consumer); /** * Remove a vertex consumer. * * @param consumer the consumer */ void removeVertexConsumer(Consumer consumer); /** * Add a vertex with attributes consumer. * * @param consumer the consumer */ void addVertexWithAttributesConsumer(BiConsumer> consumer); /** * Remove a vertex with attributes consumer * * @param consumer the consumer */ void removeVertexWithAttributesConsumer(BiConsumer> consumer); /** * Add an edge consumer. * * @param consumer the consumer */ void addEdgeConsumer(Consumer consumer); /** * Remove an edge consumer. * * @param consumer the consumer */ void removeEdgeConsumer(Consumer consumer); /** * Add an edge with attributes consumer. * * @param consumer the consumer */ void addEdgeWithAttributesConsumer(BiConsumer> consumer); /** * Remove an edge with attributes consumer * * @param consumer the consumer */ void removeEdgeWithAttributesConsumer(BiConsumer> consumer); /** * Add a graph attribute consumer. * * @param consumer the consumer */ void addGraphAttributeConsumer(BiConsumer consumer); /** * Remove a graph attribute consumer. * * @param consumer the consumer */ void removeGraphAttributeConsumer(BiConsumer consumer); /** * Add a vertex attribute consumer. * * @param consumer the consumer */ void addVertexAttributeConsumer(BiConsumer, Attribute> consumer); /** * Remove a vertex attribute consumer. * * @param consumer the consumer */ void removeVertexAttributeConsumer(BiConsumer, Attribute> consumer); /** * Add an edge attribute consumer. * * @param consumer the consumer */ void addEdgeAttributeConsumer(BiConsumer, Attribute> consumer); /** * Remove an edge attribute consumer. * * @param consumer the consumer */ void removeEdgeAttributeConsumer(BiConsumer, Attribute> consumer); /** * Import a graph * * @param input the input reader * @throws ImportException in case any error occurs, such as I/O or parse error */ void importInput(Reader input); /** * Import a graph * * @param in the input stream * @throws ImportException in case any error occurs, such as I/O or parse error */ default void importInput(InputStream in) { importInput(new InputStreamReader(in, StandardCharsets.UTF_8)); } /** * Import a graph * * @param file the file to read from * @throws ImportException in case any error occurs, such as I/O or parse error */ default void importInput(File file) { try { importInput(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); } catch (IOException e) { throw new ImportException(e); } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/ExportException.java000066400000000000000000000052741402514743400303440ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; /** * An exception that the library throws in case of graph export errors. */ public class ExportException extends RuntimeException { private static final long serialVersionUID = 1L; /** * Constructs an {@code ExportException} with {@code null} as its error detail message. */ public ExportException() { super(); } /** * Constructs an {@code ExportException} with the specified detail message. * * @param message The detail message (which is saved for later retrieval by the * {@link #getMessage()} method) */ public ExportException(String message) { super(message); } /** * Constructs an {@code ExportException} with the specified detail message and cause. * *

    * Note that the detail message associated with {@code cause} is not automatically * incorporated into this exception's detail message. * * @param message The detail message (which is saved for later retrieval by the * {@link #getMessage()} method) * * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} * method). (A null value is permitted, and indicates that the cause is nonexistent or * unknown.) */ public ExportException(String message, Throwable cause) { super(message, cause); } /** * Constructs an {@code ExportException} with the specified cause and a detail message of * {@code (cause==null ? null : cause.toString())} (which typically contains the class and * detail message of {@code cause}). This constructor is useful for IO exceptions that are * little more than wrappers for other throwables. * * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} * method). (A null value is permitted, and indicates that the cause is nonexistent or * unknown.) * */ public ExportException(Throwable cause) { super(cause); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/GraphExporter.java000066400000000000000000000043121402514743400277660ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; import org.jgrapht.*; import java.io.*; import java.nio.charset.*; /** * Interface for graph exporters * * @param the graph vertex type * @param the graph edge type */ public interface GraphExporter { /** * Export a graph to the given {@link OutputStream}. *

    * It is the callers responsibility to ensure the {@code OutputStream} is closed after this * method returned. *

    * * @param g the graph to export * @param out the output stream * @throws ExportException in case any error occurs */ default void exportGraph(Graph g, OutputStream out) { exportGraph(g, new OutputStreamWriter(out, StandardCharsets.UTF_8)); } /** * Export a graph using the given {@link Writer}. *

    * It is the callers responsibility to ensure the {@code Writer} is closed after this method * returned. *

    * * @param g the graph to export * @param writer the output writer * @throws ExportException in case any error occurs */ void exportGraph(Graph g, Writer writer); /** * Export a graph to the given {@link File}. * * @param g the graph to export * @param file the file to write to * @throws ExportException in case any error occurs */ default void exportGraph(Graph g, File file) { try (FileWriter writer = new FileWriter(file)) { exportGraph(g, writer); } catch (IOException e) { throw new ExportException(e); } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/GraphImporter.java000066400000000000000000000045241402514743400277640ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; import org.jgrapht.*; import java.io.*; import java.nio.charset.*; /** * Interface for graph importers * * @param the graph vertex type * @param the graph edge type */ public interface GraphImporter { /** * Import a graph from the given {@link InputStream}. *

    * It is the callers responsibility to ensure the {@code InputStream} is closed after this * method returned. *

    * * @param g the graph * @param in the input stream * @throws ImportException in case any error occurs, such as I/O or parse error */ default void importGraph(Graph g, InputStream in) { importGraph(g, new InputStreamReader(in, StandardCharsets.UTF_8)); } /** * Import a graph using the given {@link Reader}. *

    * It is the callers responsibility to ensure the {@code Reader} is closed after this method * returned. *

    * * @param g the graph * @param in the input reader * @throws ImportException in case any error occurs, such as I/O or parse error */ void importGraph(Graph g, Reader in); /** * Import a graph from the given {@link File}. * * @param g the graph * @param file the file to read from * @throws ImportException in case any error occurs, such as I/O or parse error */ default void importGraph(Graph g, File file) { try ( InputStreamReader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)) { importGraph(g, reader); } catch (IOException e) { throw new ImportException(e); } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/ImportEvent.java000066400000000000000000000015741402514743400274570ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; /** * Special events which may happen during import. * * @author Dimitrios Michail */ public enum ImportEvent { /** * Start of the import */ START, /** * End of the import */ END } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/ImportException.java000066400000000000000000000053211402514743400303260ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Wil Selwood and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; /** * An exception that the library throws in case of graph import errors. * * @author Wil Selwood */ public class ImportException extends RuntimeException { private static final long serialVersionUID = 1L; /** * Constructs an {@code ImportException} with {@code null} as its error detail message. */ public ImportException() { super(); } /** * Constructs an {@code ImportException} with the specified detail message. * * @param message The detail message (which is saved for later retrieval by the * {@link #getMessage()} method) */ public ImportException(String message) { super(message); } /** * Constructs an {@code ImportException} with the specified detail message and cause. * *

    * Note that the detail message associated with {@code cause} is not automatically * incorporated into this exception's detail message. * * @param message The detail message (which is saved for later retrieval by the * {@link #getMessage()} method) * * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} * method). (A null value is permitted, and indicates that the cause is nonexistent or * unknown.) */ public ImportException(String message, Throwable cause) { super(message, cause); } /** * Constructs an {@code ImportException} with the specified cause and a detail message of * {@code (cause==null ? null : cause.toString())} (which typically contains the class and * detail message of {@code cause}). This constructor is useful for IO exceptions that are * little more than wrappers for other throwables. * * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} * method). (A null value is permitted, and indicates that the cause is nonexistent or * unknown.) * */ public ImportException(Throwable cause) { super(cause); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/IntegerIdProvider.java000066400000000000000000000032331402514743400305620ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by Trevor Harmon and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; import java.util.*; import java.util.function.*; /** * Assign a unique integer identifier to a set of elements. * * Each instance of provider maintains an internal map between every element it has ever seen and * the unique integer representing that element. * * @param the element type * * @author Trevor Harmon */ public class IntegerIdProvider implements Function { private int nextId = 1; private final Map idMap; /** * Create a new provider */ public IntegerIdProvider() { this(1); } /** * Create a new provider. * * @param nextId identifier to start from */ public IntegerIdProvider(int nextId) { this.nextId = nextId; this.idMap = new HashMap<>(); } @Override public String apply(T t) { Integer id = idMap.get(t); if (id == null) { id = nextId++; idMap.put(t, id); } return String.valueOf(id); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/csv/000077500000000000000000000000001402514743400251245ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVEventDrivenImporter.java000066400000000000000000000367201402514743400323260ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.csv; import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.misc.*; import org.antlr.v4.runtime.tree.*; import org.jgrapht.alg.util.Triple; import org.jgrapht.nio.*; import java.io.*; import java.util.*; /** * Imports a graph from a CSV Format or any other Delimiter-separated value format. * *

    * The importer supports various different formats which can be adjusted using the * {@link #setFormat(CSVFormat) setFormat} method. The supported formats are the same CSV formats * used by Gephi. For some * of the formats, the behavior of the importer can be adjusted using the * {@link #setParameter(org.jgrapht.nio.csv.CSVFormat.Parameter, boolean) setParameter} method. See * {@link CSVFormat} for a description of the formats. *

    * *

    * The importer respects rfc4180. The caller can * also adjust the separator to something like semicolon or pipe instead of comma. In such a case, * all fields are unescaped using the new separator. See * Delimiter-separated values * for more information. *

    * *

    * This importer does not distinguish between {@link CSVFormat#EDGE_LIST} and * {@link CSVFormat#ADJACENCY_LIST}. In both cases it assumes the format is * {@link CSVFormat#ADJACENCY_LIST}. *

    * * @see CSVFormat * * @author Dimitrios Michail */ public class CSVEventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { private static final char DEFAULT_DELIMITER = ','; private CSVFormat format; private char delimiter; private final Set parameters; /** * Constructs a new importer using the {@link CSVFormat#ADJACENCY_LIST} format as default. */ public CSVEventDrivenImporter() { this(CSVFormat.ADJACENCY_LIST, DEFAULT_DELIMITER); } /** * Constructs a new importer. * * @param format format to use out of the supported ones */ public CSVEventDrivenImporter(CSVFormat format) { this(format, DEFAULT_DELIMITER); } /** * Constructs a new importer. * * @param format format to use out of the supported ones * @param delimiter delimiter to use (comma, semicolon, pipe, etc.) */ public CSVEventDrivenImporter(CSVFormat format, char delimiter) { this.format = format; if (!DSVUtils.isValidDelimiter(delimiter)) { throw new IllegalArgumentException("Character cannot be used as a delimiter"); } this.delimiter = delimiter; this.parameters = new HashSet<>(); } /** * Get the format that the importer is using. * * @return the input format */ public CSVFormat getFormat() { return format; } /** * Set the format of the importer * * @param format the format to use */ public void setFormat(CSVFormat format) { this.format = format; } /** * Get the delimiter (comma, semicolon, pipe, etc). * * @return the delimiter */ public char getDelimiter() { return delimiter; } /** * Set the delimiter (comma, semicolon, pipe, etc). * * @param delimiter the delimiter to use */ public void setDelimiter(char delimiter) { if (!DSVUtils.isValidDelimiter(delimiter)) { throw new IllegalArgumentException("Character cannot be used as a delimiter"); } this.delimiter = delimiter; } /** * Return if a particular parameter of the exporter is enabled * * @param p the parameter * @return {@code true} if the parameter is set, {@code false} otherwise */ public boolean isParameter(CSVFormat.Parameter p) { return parameters.contains(p); } /** * Set the value of a parameter of the exporter * * @param p the parameter * @param value the value to set */ public void setParameter(CSVFormat.Parameter p, boolean value) { if (value) { parameters.add(p); } else { parameters.remove(p); } } @Override public void importInput(Reader input) throws ImportException { notifyImportEvent(ImportEvent.START); switch (format) { case EDGE_LIST: case ADJACENCY_LIST: read(input, new AdjacencyListCSVListener()); break; case MATRIX: read(input, new MatrixCSVListener()); break; } notifyImportEvent(ImportEvent.END); } private void read(Reader input, CSVBaseListener listener) throws ImportException { try { ThrowingErrorListener errorListener = new ThrowingErrorListener(); // create lexer CSVLexer lexer = new CSVLexer(CharStreams.fromReader(input)); lexer.setSep(delimiter); lexer.removeErrorListeners(); lexer.addErrorListener(errorListener); // create parser CSVParser parser = new CSVParser(new CommonTokenStream(lexer)); parser.removeErrorListeners(); parser.addErrorListener(errorListener); // Specify our entry point CSVParser.FileContext graphContext = parser.file(); // Walk it and attach our listener ParseTreeWalker walker = new ParseTreeWalker(); walker.walk(listener, graphContext); } catch (IOException e) { throw new ImportException("Failed to import CSV graph: " + e.getMessage(), e); } catch (ParseCancellationException pe) { throw new ImportException("Failed to import CSV graph: " + pe.getMessage(), pe); } catch (IllegalArgumentException iae) { throw new ImportException("Failed to import CSV graph: " + iae.getMessage(), iae); } } private class ThrowingErrorListener extends BaseErrorListener { @Override public void syntaxError( Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) throws ParseCancellationException { throw new ParseCancellationException( "line " + line + ":" + charPositionInLine + " " + msg); } } // listener for the edge list format private class AdjacencyListCSVListener extends RowCSVListener { private boolean assumeEdgeWeights; public AdjacencyListCSVListener() { super(); this.assumeEdgeWeights = parameters.contains(CSVFormat.Parameter.EDGE_WEIGHTS); } @Override protected void handleRow() { // first is source String source = row.get(0); if (source.isEmpty()) { throw new ParseCancellationException("Source vertex cannot be empty"); } if (!vertices.contains(source)) { vertices.add(source); notifyVertex(source); } row.remove(0); // remaining are targets (if weighted pairs of target-weight) int step = assumeEdgeWeights ? 2 : 1; for (int i = 0; i < row.size(); i += step) { String target = row.get(i); if (target.isEmpty()) { throw new ParseCancellationException("Target vertex cannot be empty"); } if (!vertices.contains(target)) { vertices.add(target); notifyVertex(target); } Double weight = null; if (assumeEdgeWeights) { try { weight = Double.parseDouble(row.get(i + 1)); } catch (NumberFormatException nfe) { throw new ParseCancellationException("Failed to parse edge weight"); } } notifyEdge(Triple.of(source, target, weight)); } } } // listener for the edge list format private class MatrixCSVListener extends RowCSVListener { private boolean assumeNodeIds; private boolean assumeEdgeWeights; private boolean assumeZeroWhenNoEdge; private int verticesCount; private int currentVertex; private String currentVertexName; private Map columnIndex; public MatrixCSVListener() { super(); this.assumeNodeIds = parameters.contains(CSVFormat.Parameter.MATRIX_FORMAT_NODEID); this.assumeEdgeWeights = parameters.contains(CSVFormat.Parameter.EDGE_WEIGHTS); ; this.assumeZeroWhenNoEdge = parameters.contains(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE); this.verticesCount = 0; this.currentVertex = 1; this.currentVertexName = null; this.columnIndex = new HashMap<>(); } @Override protected void handleRow() { if (assumeNodeIds) { if (!header) { currentVertexName = row.get(0); } row.remove(0); } else { currentVertexName = String.valueOf(currentVertex); } if (header) { if (assumeNodeIds) { createVerticesFromNodeIds(); } else { createVertices(); createEdges(); currentVertex++; } } else { createEdges(); currentVertex++; } } private void createVerticesFromNodeIds() { // header line contains nodes verticesCount = row.size(); if (verticesCount < 1) { throw new ParseCancellationException("Failed to parse header with vertices"); } int v = 1; for (String vertexName : row) { if (vertexName.trim().isEmpty()) { throw new ParseCancellationException( "Failed to parse header with vertices (empty name)"); } if (!vertices.contains(vertexName)) { vertices.add(vertexName); notifyVertex(vertexName); } columnIndex.put(v, vertexName); v++; } } private void createVertices() { // header line contains nodes verticesCount = row.size(); if (verticesCount < 1) { throw new ParseCancellationException("Failed to parse header with vertices"); } int v = 1; for (v = 1; v <= verticesCount; v++) { String vertexName = String.valueOf(v); if (!vertices.contains(vertexName)) { vertices.add(vertexName); notifyVertex(vertexName); } columnIndex.put(v, vertexName); } } private void createEdges() { if (row.size() != verticesCount) { throw new ParseCancellationException( "Row contains fewer than " + verticesCount + " entries"); } int target = 1; for (String entry : row) { // try to parse an integer try { Integer entryAsInteger = Integer.parseInt(entry); if (entryAsInteger == 0) { if (!assumeZeroWhenNoEdge && assumeEdgeWeights) { notifyEdge(Triple.of(currentVertexName, columnIndex.get(target), 0d)); } } else { if (assumeEdgeWeights) { notifyEdge( Triple .of( currentVertexName, columnIndex.get(target), Double.valueOf(entryAsInteger))); } else { notifyEdge(Triple.of(currentVertexName, columnIndex.get(target), null)); } } target++; continue; } catch (NumberFormatException nfe) { // nothing } // try to parse a double try { Double entryAsDouble = Double.parseDouble(entry); if (assumeEdgeWeights) { notifyEdge( Triple.of(currentVertexName, columnIndex.get(target), entryAsDouble)); } else { throw new ParseCancellationException( "Double entry found when expecting no weights"); } } catch (NumberFormatException nfe) { // nothing } target++; } } } // base listener private abstract class RowCSVListener extends CSVBaseListener { protected List row; protected Set vertices; protected boolean header; public RowCSVListener() { this.row = new ArrayList<>(); this.vertices = new HashSet<>(); this.header = false; } @Override public void enterHeader(CSVParser.HeaderContext ctx) { header = true; } @Override public void exitHeader(CSVParser.HeaderContext ctx) { header = false; } @Override public void enterRecord(CSVParser.RecordContext ctx) { row.clear(); } @Override public void exitRecord(CSVParser.RecordContext ctx) { if (row.isEmpty()) { throw new ParseCancellationException("Empty CSV record"); } handleRow(); } @Override public void exitTextField(CSVParser.TextFieldContext ctx) { row.add(ctx.TEXT().getText()); } @Override public void exitStringField(CSVParser.StringFieldContext ctx) { row.add(DSVUtils.unescapeDSV(ctx.STRING().getText(), delimiter)); } @Override public void exitEmptyField(CSVParser.EmptyFieldContext ctx) { row.add(""); } protected abstract void handleRow(); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVExporter.java000066400000000000000000000207151402514743400301600ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.csv; import org.jgrapht.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Exports a graph into a CSV Format or any other Delimiter-separated value format. * *

    * The exporter supports three different formats which can be adjusted using the * {@link #setFormat(CSVFormat) setFormat} method. The supported formats are the same CSV formats * used by Gephi . For some * of the formats, the behavior of the exporter can be adjusted using the * {@link #setParameter(org.jgrapht.nio.csv.CSVFormat.Parameter, boolean) setParameter} method. See * {@link CSVFormat} for a description of the formats. *

    * *

    * The default output respects rfc4180. The caller * can also adjust the separator to something like semicolon or pipe instead of comma. In such a * case, all fields are escaped using the new separator. See * Delimiter-separated values * for more information. *

    * * @see CSVFormat * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class CSVExporter extends BaseExporter implements GraphExporter { private static final char DEFAULT_DELIMITER = ','; private final Set parameters; private CSVFormat format; private char delimiter; /** * Creates a new CSVExporter with {@link CSVFormat#ADJACENCY_LIST} format and integer name * provider for the vertices. */ public CSVExporter() { this(CSVFormat.ADJACENCY_LIST); } /** * Creates a new CSVExporter with integer id providers for the vertices. * * @param format the format to use */ public CSVExporter(CSVFormat format) { this(format, DEFAULT_DELIMITER); } /** * Creates a new CSVExporter with integer id providers for the vertices. * * @param format the format to use * @param delimiter delimiter to use */ public CSVExporter(CSVFormat format, char delimiter) { this(new IntegerIdProvider<>(), format, delimiter); } /** * Constructs a new CSVExporter with the given ID providers and format. * * @param vertexIdProvider for generating vertex IDs. Must not be null. * @param format the format to use * @param delimiter delimiter to use */ public CSVExporter(Function vertexIdProvider, CSVFormat format, char delimiter) { super(vertexIdProvider); this.format = format; if (!DSVUtils.isValidDelimiter(delimiter)) { throw new IllegalArgumentException("Character cannot be used as a delimiter"); } this.delimiter = delimiter; this.parameters = new HashSet<>(); } /** * Exports a graph * * @param g the graph * @param writer the writer */ @Override public void exportGraph(Graph g, Writer writer) { PrintWriter out = new PrintWriter(writer); switch (format) { case EDGE_LIST: exportAsEdgeList(g, out); break; case ADJACENCY_LIST: exportAsAdjacencyList(g, out); break; case MATRIX: exportAsMatrix(g, out); break; } out.flush(); } /** * Return if a particular parameter of the exporter is enabled * * @param p the parameter * @return {@code true} if the parameter is set, {@code false} otherwise */ public boolean isParameter(CSVFormat.Parameter p) { return parameters.contains(p); } /** * Set the value of a parameter of the exporter * * @param p the parameter * @param value the value to set */ public void setParameter(CSVFormat.Parameter p, boolean value) { if (value) { parameters.add(p); } else { parameters.remove(p); } } /** * Get the format of the exporter * * @return the format of the exporter */ public CSVFormat getFormat() { return format; } /** * Set the format of the exporter * * @param format the format to use */ public void setFormat(CSVFormat format) { this.format = format; } /** * Get the delimiter (comma, semicolon, pipe, etc). * * @return the delimiter */ public char getDelimiter() { return delimiter; } /** * Set the delimiter (comma, semicolon, pipe, etc). * * @param delimiter the delimiter to use */ public void setDelimiter(char delimiter) { if (!DSVUtils.isValidDelimiter(delimiter)) { throw new IllegalArgumentException("Character cannot be used as a delimiter"); } this.delimiter = delimiter; } private void exportAsEdgeList(Graph g, PrintWriter out) { boolean exportEdgeWeights = parameters.contains(CSVFormat.Parameter.EDGE_WEIGHTS); for (E e : g.edgeSet()) { exportEscapedField(out, getVertexId(g.getEdgeSource(e))); out.print(delimiter); exportEscapedField(out, getVertexId(g.getEdgeTarget(e))); if (exportEdgeWeights) { out.print(delimiter); exportEscapedField(out, String.valueOf(g.getEdgeWeight(e))); } out.println(); } } private void exportAsAdjacencyList(Graph g, PrintWriter out) { boolean exportEdgeWeights = parameters.contains(CSVFormat.Parameter.EDGE_WEIGHTS); for (V v : g.vertexSet()) { exportEscapedField(out, getVertexId(v)); for (E e : g.outgoingEdgesOf(v)) { V w = Graphs.getOppositeVertex(g, e, v); out.print(delimiter); exportEscapedField(out, getVertexId(w)); if (exportEdgeWeights) { out.print(delimiter); exportEscapedField(out, String.valueOf(g.getEdgeWeight(e))); } } out.println(); } } private void exportAsMatrix(Graph g, PrintWriter out) { boolean exportNodeId = parameters.contains(CSVFormat.Parameter.MATRIX_FORMAT_NODEID); boolean exportEdgeWeights = parameters.contains(CSVFormat.Parameter.EDGE_WEIGHTS); boolean zeroWhenNoEdge = parameters.contains(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE); if (exportNodeId) { for (V v : g.vertexSet()) { out.print(delimiter); exportEscapedField(out, getVertexId(v)); } out.println(); } int n = g.vertexSet().size(); for (V v : g.vertexSet()) { if (exportNodeId) { exportEscapedField(out, getVertexId(v)); out.print(delimiter); } int i = 0; for (V u : g.vertexSet()) { E e = g.getEdge(v, u); if (e == null) { if (zeroWhenNoEdge) { exportEscapedField(out, "0"); } } else { if (exportEdgeWeights) { exportEscapedField(out, String.valueOf(g.getEdgeWeight(e))); } else { exportEscapedField(out, "1"); } } if (i++ < n - 1) { out.print(delimiter); } } out.println(); } } private void exportEscapedField(PrintWriter out, String field) { out.print(DSVUtils.escapeDSV(field, delimiter)); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVFormat.java000066400000000000000000000106461402514743400276020ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.csv; /** * Supported CSV formats. * *
      *
    • *

      * Format {@link #EDGE_LIST} contains one edge per line. The following example * *

       * a,b
       * b,c
       * 
      * * represents a graph with two edges: a->b and b->c.
    • * *
    • *

      * Format {@link #ADJACENCY_LIST} contains the adjacency list of each vertex per line. The first * field on a line is a vertex while the remaining fields are its neighbors. * *

       * a,b
       * b,c,d
       * c,a,c,d
       * 
      * * represents a graph with edges: a->b, b->c, b->d, c->a, c->c, c->d. * *

      * Mixed variants of {@link #EDGE_LIST} and {@link #ADJACENCY_LIST} are also considered valid. As an * example consider the following input * *

       * a,b
       * b,a
       * d,a
       * c,a,b
       * b,d,a
       * 
      * * which represents a graph with edges: a->b, b->a, d->a, c->a, c->b, b->d, * b->a. Multiple occurrences of the same edge result into a multi-graph. * *

      * Weighted variants are also valid if {@link CSVFormat.Parameter#EDGE_WEIGHTS} is set. In this case * the target vertex must be followed by the edge weight. The following example illustrates the * weighted variant: * *

       * a,b,2.0
       * b,a,3.0
       * d,a,2.0
       * c,a,1.5,b,2.5
       * b,d,3.3,a,5.5
       * 
      * *
    • *
    • *

      * Format {@link #MATRIX} outputs an adjacency matrix representation of the graph. Each line * represents a vertex. * * The following * *

       * 0,1,0,1,0
       * 1,0,0,0,0
       * 0,0,1,0,0
       * 0,1,0,1,0
       * 0,0,0,0,0
       * 
      * * represents a graph with five vertices 1,2,3,4,5 which contains edges: 1->2, 1->4, 2->1, * 3->3, 4->2, 4->4. * *

      * In case {@link CSVFormat.Parameter#MATRIX_FORMAT_ZERO_WHEN_NO_EDGE} is not set the equivalent * format would be: * *

       * ,1,,1,
       * 1,,,,
       * ,,1,,
       * ,1,,1,
       * ,,,,
       * 
      * *

      * Weighted variants are also valid if {@link CSVFormat.Parameter#EDGE_WEIGHTS} is set. The above * example would then be: * *

       * ,1.0,,1.0,
       * 1.0,,,,
       * ,,1.0,,
       * ,1.0,,1.0,
       * ,,,,
       * 
      * * If additionally {@link CSVFormat.Parameter#MATRIX_FORMAT_ZERO_WHEN_NO_EDGE} is set then a zero as * an integer means that the corresponding edge is missing, while a zero as a double means than the * edge exists and has zero weight. * *

      * If parameter {@link CSVFormat.Parameter#MATRIX_FORMAT_NODEID} is set then node identifiers are * also included as in the following example: * *

       * ,a,b,c,d,e
       * a,,1,,1,
       * b,1,,,,
       * c,,,1,,
       * d,,1,,1,
       * e,,,,,
       * 
      * * In the above example the first line contains the node identifiers and the first field of each * line contain the vertex it corresponds to. In case node identifiers are present line-shuffled * input is also valid such as: * *
       * ,a,b,c,d,e
       * c,,,1,,
       * b,1,,,,
       * e,,,,,
       * d,,1,,1,
       * a,,1,,1,
       * 
      * * The last example represents the graph with edges: a->b, a->d, b->a, c->c, d->b, * d->d. * *
    • *
    * * @author Dimitrios Michail * */ public enum CSVFormat { /** * Edge list */ EDGE_LIST, /** * Adjacency list */ ADJACENCY_LIST, /** * Matrix */ MATRIX; /** * Parameters that affect the behavior of CVS importers/exporters. */ public enum Parameter { /** * Whether to import/export edge weights. */ EDGE_WEIGHTS, /** * Whether to import/export node ids. Only valid for the {@link CSVFormat#MATRIX MATRIX} * format. */ MATRIX_FORMAT_NODEID, /** * Whether the input/output contains zero for missing edges. Only valid for the * {@link CSVFormat#MATRIX MATRIX} format. */ MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/csv/CSVImporter.java000066400000000000000000000234401402514743400301470ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.csv; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Imports a graph from a CSV Format or any other Delimiter-separated value format. * *

    * The importer supports various different formats which can be adjusted using the * {@link #setFormat(CSVFormat) setFormat} method. The supported formats are the same CSV formats * used by Gephi . For some * of the formats, the behavior of the importer can be adjusted using the * {@link #setParameter(org.jgrapht.nio.csv.CSVFormat.Parameter, boolean) setParameter} method. See * {@link CSVFormat} for a description of the formats. *

    * *

    * The importer respects rfc4180. The caller can * also adjust the separator to something like semicolon or pipe instead of comma. In such a case, * all fields are unescaped using the new separator. See * Delimiter- separated * values for more information. *

    * *

    * This importer does not distinguish between {@link CSVFormat#EDGE_LIST} and * {@link CSVFormat#ADJACENCY_LIST}. In both cases it assumes the format is * {@link CSVFormat#ADJACENCY_LIST}. *

    * *

    * The graph vertices and edges are build using the corresponding graph suppliers. The id of the * vertices in the original file are reported as a vertex attribute named "ID". * *

    * The default behavior of the importer is to use the graph vertex supplier in order to create * vertices. The user can also bypass vertex creation by providing a custom vertex factory method * using {@link #setVertexFactory(Function)}. The factory method is responsible to create a new * graph vertex given the vertex identifier read from file. * * @see CSVFormat * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class CSVImporter extends BaseEventDrivenImporter implements GraphImporter { private static final char DEFAULT_DELIMITER = ','; private static final String DEFAULT_VERTEX_ID_KEY = "ID"; private static final String DEFAULT_WEIGHT_KEY = "weight"; private CSVFormat format; private char delimiter; private final Set parameters; private Function vertexFactory; /** * Constructs a new importer using the {@link CSVFormat#ADJACENCY_LIST} format as default. */ public CSVImporter() { this(CSVFormat.ADJACENCY_LIST, DEFAULT_DELIMITER); } /** * Constructs a new importer. * * @param format format to use out of the supported ones */ public CSVImporter(CSVFormat format) { this(format, DEFAULT_DELIMITER); } /** * Constructs a new importer. * * @param format format to use out of the supported ones * @param delimiter delimiter to use (comma, semicolon, pipe, etc.) */ public CSVImporter(CSVFormat format, char delimiter) { super(); this.format = format; if (!DSVUtils.isValidDelimiter(delimiter)) { throw new IllegalArgumentException("Character cannot be used as a delimiter"); } this.delimiter = delimiter; this.parameters = new HashSet<>(); } /** * Get the format that the importer is using. * * @return the input format */ public CSVFormat getFormat() { return format; } /** * Set the format of the importer * * @param format the format to use */ public void setFormat(CSVFormat format) { this.format = format; } /** * Get the delimiter (comma, semicolon, pipe, etc). * * @return the delimiter */ public char getDelimiter() { return delimiter; } /** * Set the delimiter (comma, semicolon, pipe, etc). * * @param delimiter the delimiter to use */ public void setDelimiter(char delimiter) { if (!DSVUtils.isValidDelimiter(delimiter)) { throw new IllegalArgumentException("Character cannot be used as a delimiter"); } this.delimiter = delimiter; } /** * Return if a particular parameter of the exporter is enabled * * @param p the parameter * @return {@code true} if the parameter is set, {@code false} otherwise */ public boolean isParameter(CSVFormat.Parameter p) { return parameters.contains(p); } /** * Set the value of a parameter of the exporter * * @param p the parameter * @param value the value to set */ public void setParameter(CSVFormat.Parameter p, boolean value) { if (value) { parameters.add(p); } else { parameters.remove(p); } } /** * Get the user custom vertex factory. This is null by default and the graph supplier is used * instead. * * @return the user custom vertex factory */ public Function getVertexFactory() { return vertexFactory; } /** * Set the user custom vertex factory. The default behavior is being null in which case the * graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the file. * The method is called with parameter the vertex identifier from the file and should return the * actual graph vertex to add to the graph. * * @param vertexFactory a vertex factory */ public void setVertexFactory(Function vertexFactory) { this.vertexFactory = vertexFactory; } /** * Import a graph. * *

    * The provided graph must be able to support the features of the graph that is read. For * example if the input contains self-loops then the graph provided must also support * self-loops. The same for multiple edges. * *

    * If the provided graph is a weighted graph, the importer also reads edge weights. * * @param graph the graph * @param input the input reader * @throws ImportException in case an error occurs, such as I/O or parse error */ @Override public void importGraph(Graph graph, Reader input) throws ImportException { CSVEventDrivenImporter genericImporter = new CSVEventDrivenImporter(); genericImporter.setDelimiter(delimiter); genericImporter.setFormat(format); genericImporter .setParameter( CSVFormat.Parameter.EDGE_WEIGHTS, isParameter(CSVFormat.Parameter.EDGE_WEIGHTS)); genericImporter .setParameter( CSVFormat.Parameter.MATRIX_FORMAT_NODEID, isParameter(CSVFormat.Parameter.MATRIX_FORMAT_NODEID)); genericImporter .setParameter( CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, isParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE)); Consumers consumers = new Consumers(graph); genericImporter.addVertexConsumer(consumers.vertexConsumer); genericImporter.addEdgeConsumer(consumers.edgeConsumer); genericImporter.importInput(input); } private class Consumers { private Graph graph; private GraphType graphType; private Map map; public Consumers(Graph graph) { this.graph = graph; this.graphType = graph.getType(); this.map = new HashMap<>(); } public final Consumer vertexConsumer = (t) -> { if (map.containsKey(t)) { throw new ImportException("Node " + t + " already exists"); } V v; if (vertexFactory != null) { v = vertexFactory.apply(t); graph.addVertex(v); } else { v = graph.addVertex(); } map.put(t, v); notifyVertex(v); notifyVertexAttribute(v, DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(t)); }; public final Consumer> edgeConsumer = (t) -> { String source = t.getFirst(); V from = map.get(t.getFirst()); if (from == null) { throw new ImportException("Node " + source + " does not exist"); } String target = t.getSecond(); V to = map.get(target); if (to == null) { throw new ImportException("Node " + target + " does not exist"); } E e = graph.addEdge(from, to); if (graphType.isWeighted() && t.getThird() != null) { graph.setEdgeWeight(e, t.getThird()); } notifyEdge(e); if (graphType.isWeighted() && t.getThird() != null) { notifyEdgeAttribute( e, DEFAULT_WEIGHT_KEY, DefaultAttribute.createAttribute(t.getThird())); } }; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/csv/DSVUtils.java000066400000000000000000000063071402514743400274520ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.csv; /** * Helper utilities for escaping and unescaping Delimiter-separated values. * * @author Dimitrios Michail */ class DSVUtils { private static final char DSV_QUOTE = '"'; private static final char DSV_LF = '\n'; private static final char DSV_CR = '\r'; private static final String DSV_QUOTE_AS_STRING = String.valueOf(DSV_QUOTE); /** * Test if a character can be used as a delimiter in a Delimiter-separated values file. * * @param delimiter the character to test * @return {@code true} if the character can be used as a delimiter, {@code} false otherwise */ public static boolean isValidDelimiter(char delimiter) { return delimiter != DSV_LF && delimiter != DSV_CR && delimiter != DSV_QUOTE; } /** * Escape a Delimiter-separated values string. * * @param input the input * @param delimiter the delimiter * @return the escaped output */ public static String escapeDSV(String input, char delimiter) { char[] specialChars = new char[] { delimiter, DSV_QUOTE, DSV_LF, DSV_CR }; boolean containsSpecial = false; for (int i = 0; i < specialChars.length; i++) { if (input.contains(String.valueOf(specialChars[i]))) { containsSpecial = true; break; } } if (containsSpecial) { return DSV_QUOTE_AS_STRING + input.replaceAll(DSV_QUOTE_AS_STRING, DSV_QUOTE_AS_STRING + DSV_QUOTE_AS_STRING) + DSV_QUOTE_AS_STRING; } return input; } /** * Unescape a Delimiter-separated values string. * * @param input the input * @param delimiter the delimiter * @return the unescaped output */ public static String unescapeDSV(String input, char delimiter) { char[] specialChars = new char[] { delimiter, DSV_QUOTE, DSV_LF, DSV_CR }; if (input.charAt(0) != DSV_QUOTE || input.charAt(input.length() - 1) != DSV_QUOTE) { return input; } String noQuotes = input.subSequence(1, input.length() - 1).toString(); boolean containsSpecial = false; for (int i = 0; i < specialChars.length; i++) { if (noQuotes.contains(String.valueOf(specialChars[i]))) { containsSpecial = true; break; } } if (containsSpecial) { return noQuotes .replaceAll(DSV_QUOTE_AS_STRING + DSV_QUOTE_AS_STRING, DSV_QUOTE_AS_STRING); } return input; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/csv/VisioExporter.java000066400000000000000000000060041402514743400306110ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Avner Linder and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.csv; import org.jgrapht.*; import org.jgrapht.nio.*; import java.io.*; import java.util.function.*; /** * Exports a graph to a CSV format that can be imported into MS Visio. * *

    * Tip: By default, the exported graph doesn't show link directions. To show link * directions:
    * *

      *
    1. Select All (Ctrl-A)
    2. *
    3. Right Click the selected items
    4. *
    5. Format/Line...
    6. *
    7. Line ends: End: (choose an arrow)
    8. *
    * * @param the graph vertex type * @param the graph edge type * * @author Avner Linder */ public class VisioExporter extends BaseExporter implements GraphExporter { /** * Creates a new VisioExporter. */ public VisioExporter() { this(new IntegerIdProvider<>()); } /** * Creates a new exporter. * * @param vertexIdProvider the vertex id provider to be used for naming the Visio shapes */ public VisioExporter(Function vertexIdProvider) { super(vertexIdProvider); } /** * Exports the specified graph into a Visio CSV file format. * * @param g the graph to be exported. * @param writer the writer to which the graph to be exported. */ @Override public void exportGraph(Graph g, Writer writer) { PrintWriter out = new PrintWriter(writer); for (V v : g.vertexSet()) { exportVertex(out, v); } for (E e : g.edgeSet()) { exportEdge(out, e, g); } out.flush(); } private void exportEdge(PrintWriter out, E edge, Graph g) { String sourceName = getVertexId(g.getEdgeSource(edge)); String targetName = getVertexId(g.getEdgeTarget(edge)); out.print("Link,"); // create unique ShapeId for link out.print(sourceName); out.print("-->"); out.print(targetName); // MasterName and Text fields left blank out.print(",,,"); out.print(sourceName); out.print(","); out.print(targetName); out.print("\n"); } private void exportVertex(PrintWriter out, V vertex) { String name = getVertexId(vertex); out.print("Shape,"); out.print(name); out.print(",,"); // MasterName field left empty out.print(name); out.print("\n"); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/csv/package-info.java000066400000000000000000000001001402514743400303020ustar00rootroot00000000000000/** * CSV importers/exporters */ package org.jgrapht.nio.csv; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/000077500000000000000000000000001402514743400255715ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSEventDrivenImporter.java000066400000000000000000000176721402514743400333050ustar00rootroot00000000000000/* * (C) Copyright 2010-2021, by Michael Behrisch and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dimacs; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; /** * A generic importer using consumers for DIMACS format. * *

    * See {@link DIMACSFormat} for a description of all the supported DIMACS formats. * *

    * In summary, one of the most common DIMACS formats was used in the * 2nd DIMACS challenge and follows * the following structure: * *

     * {@code
     * DIMACS G {
     *    c  ignored during parsing of the graph
     *    p edge  
     *    e  
     *    e  
     *    e  
     *    e  
     *    ...
     * }
     * }
     * 
    * * Although not specified directly in the DIMACS format documentation, this implementation also * allows for the a weighted variant: * *
     * {@code 
     * e    
     * }
     * 
    * *

    * By default this importer recomputes node identifiers starting from $0$ as they are encountered in * the file. It is also possible to instruct the importer to keep the original file numbering of the * nodes. Additionally you can also instruct the importer to use zero-based numbering or keep the * original number of DIMACS which starts from one. * * Note: the current implementation does not fully implement the DIMACS specifications! Special * (rarely used) fields specified as 'Optional Descriptors' are currently not supported (ignored). * * @author Michael Behrisch (adaptation of GraphReader class) * @author Joris Kinable * @author Dimitrios Michail * */ public class DIMACSEventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { private boolean zeroBasedNumbering; private boolean renumberVertices; private Map vertexMap; private int nextId; /** * Construct a new importer */ public DIMACSEventDrivenImporter() { super(); this.zeroBasedNumbering = true; this.renumberVertices = true; this.vertexMap = new HashMap<>(); } /** * Set whether to use zero-based numbering for vertices. * * The DIMACS format by default starts vertices numbering from one. If true then we will use * zero-based numbering. Default to true. * * @param zeroBasedNumbering whether to use zero-based numbering * @return the importer */ public DIMACSEventDrivenImporter zeroBasedNumbering(boolean zeroBasedNumbering) { this.zeroBasedNumbering = zeroBasedNumbering; return this; } /** * Set whether to renumber vertices or not. * * If true then the vertices are assigned new numbers from $0$ to $n-1$ in the order that they * are first encountered in the file. Otherwise, the original numbering (minus one in order to * get a zero-based numbering) of the DIMACS file is kept. Defaults to true. * * @param renumberVertices whether to renumber vertices or not * @return the importer */ public DIMACSEventDrivenImporter renumberVertices(boolean renumberVertices) { this.renumberVertices = renumberVertices; return this; } @Override public void importInput(Reader input) { // convert to buffered BufferedReader in; if (input instanceof BufferedReader) { in = (BufferedReader) input; } else { in = new BufferedReader(input); } if (zeroBasedNumbering) { this.nextId = 0; } else { this.nextId = 1; } notifyImportEvent(ImportEvent.START); // nodes final int size = readNodeCount(in); notifyVertexCount(size); // add edges String[] cols = skipComments(in); while (cols != null) { if (cols[0].equals("e") || cols[0].equals("a")) { if (cols.length < 3) { throw new ImportException("Failed to parse edge:" + Arrays.toString(cols)); } Integer source; try { source = Integer.parseInt(cols[1]); } catch (NumberFormatException e) { throw new ImportException( "Failed to parse edge source node:" + e.getMessage(), e); } Integer target; try { target = Integer.parseInt(cols[2]); } catch (NumberFormatException e) { throw new ImportException( "Failed to parse edge target node:" + e.getMessage(), e); } Integer from = mapVertexToInteger(String.valueOf(source)); Integer to = mapVertexToInteger(String.valueOf(target)); Double weight = null; if (cols.length > 3) { try { weight = Double.parseDouble(cols[3]); } catch (NumberFormatException e) { // ignore } } // notify notifyEdge(Triple.of(from, to, weight)); } cols = skipComments(in); } notifyImportEvent(ImportEvent.END); } private String[] split(final String src) { if (src == null) { return null; } return src.split("\\s+"); } private String[] skipComments(BufferedReader input) { String[] cols = null; try { cols = split(input.readLine()); while ((cols != null) && ((cols.length == 0) || cols[0].equals("c") || cols[0].startsWith("%"))) { cols = split(input.readLine()); } } catch (IOException e) { // ignore } return cols; } private int readNodeCount(BufferedReader input) throws ImportException { final String[] cols = skipComments(input); if (cols[0].equals("p")) { if (cols.length < 3) { throw new ImportException("Failed to read number of vertices."); } Integer nodes; try { nodes = Integer.parseInt(cols[2]); } catch (NumberFormatException e) { throw new ImportException("Failed to read number of vertices."); } if (nodes < 0) { throw new ImportException("Negative number of vertices."); } return nodes; } throw new ImportException("Failed to read number of vertices."); } /** * Map a vertex identifier to an integer. * * @param id the vertex identifier * @return the integer */ protected Integer mapVertexToInteger(String id) { if (renumberVertices) { return vertexMap.computeIfAbsent(id, (keyId) -> { return nextId++; }); } else { if (zeroBasedNumbering) { return Integer.valueOf(id) - 1; } else { return Integer.valueOf(id); } } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSExporter.java000066400000000000000000000113151402514743400311260ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dimacs; import org.jgrapht.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Exports a graph into DIMACS format. * *

    * For a description of the format see * http://dimacs.rutgers.edu/Challenges. Note that there are a lot of different formats based on * each different challenge, see {@link DIMACSFormat} for the supported formats. The exporter uses * the {@link DIMACSFormat#MAX_CLIQUE} by default. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class DIMACSExporter extends BaseExporter implements GraphExporter { /** * The default format used by the exporter. */ public static final DIMACSFormat DEFAULT_DIMACS_FORMAT = DIMACSFormat.MAX_CLIQUE; private static final String HEADER = "Generated using the JGraphT library"; private final Set parameters; private DIMACSFormat format; /** * Parameters that affect the behavior of the {@link DIMACSExporter} exporter. */ public enum Parameter { /** * If set the exporter outputs edge weights */ EXPORT_EDGE_WEIGHTS, } /** * Constructs a new exporter. */ public DIMACSExporter() { this(new IntegerIdProvider<>()); } /** * Constructs a new exporter with a given vertex ID provider. * * @param vertexIdProvider for generating vertex IDs. Must not be null. */ public DIMACSExporter(Function vertexIdProvider) { this(vertexIdProvider, DEFAULT_DIMACS_FORMAT); } /** * Constructs a new exporter with a given vertex ID provider. * * @param vertexIdProvider for generating vertex IDs. Must not be null. * @param format the format to use */ public DIMACSExporter(Function vertexIdProvider, DIMACSFormat format) { super(vertexIdProvider); this.format = Objects.requireNonNull(format, "Format cannot be null"); this.parameters = new HashSet<>(); } @Override public void exportGraph(Graph g, Writer writer) { PrintWriter out = new PrintWriter(writer); out.println("c"); out.println("c SOURCE: " + HEADER); out.println("c"); out .println( "p " + format.getProblem() + " " + g.vertexSet().size() + " " + g.edgeSet().size()); boolean exportEdgeWeights = parameters.contains(Parameter.EXPORT_EDGE_WEIGHTS); for (E edge : g.edgeSet()) { out.print(format.getEdgeDescriptor()); out.print(" "); out.print(getVertexId(g.getEdgeSource(edge))); out.print(" "); out.print(getVertexId(g.getEdgeTarget(edge))); if (exportEdgeWeights) { out.print(" "); out.print(Double.toString(g.getEdgeWeight(edge))); } out.println(); } out.flush(); } /** * Return if a particular parameter of the exporter is enabled * * @param p the parameter * @return {@code true} if the parameter is set, {@code false} otherwise */ public boolean isParameter(Parameter p) { return parameters.contains(p); } /** * Set the value of a parameter of the exporter * * @param p the parameter * @param value the value to set */ public void setParameter(Parameter p, boolean value) { if (value) { parameters.add(p); } else { parameters.remove(p); } } /** * Get the format of the exporter * * @return the format of the exporter */ public DIMACSFormat getFormat() { return format; } /** * Set the format of the exporter * * @param format the format to use */ public void setFormat(DIMACSFormat format) { this.format = Objects.requireNonNull(format, "Format cannot be null"); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSFormat.java000066400000000000000000000067031402514743400305530ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dimacs; /** * DIMACS challenge format. * *

    * For a general description of the formats see * http://dimacs.rutgers.edu/Challenges. Note that there are a lot of different formats based on * each different challenge. * * @author Dimitrios Michail */ public enum DIMACSFormat { /** * Shortest path challenge format. * *

    * This is the format used in * the 9th DIMACS implementation challenge. * * A shortest path graph file looks as follows: * *

         * {@code
         * c 
         * p sp  
         * a  
         * a  
         * a  
         * a  
         * ...
         * }
         * 
    * * A weighted variant where each edge has a floating-point weight is also supported: * *
         * {@code 
         * a    
         * }
         * 
    */ SHORTEST_PATH("sp", "a"), /** * Max-clique challenge format. * *

    * This is the format used in * the 2nd DIMACS implementation challenge. * * A graph file looks as follows: * *

         * {@code
         * c 
         * p edge  
         * e  
         * e  
         * e  
         * e  
         * ...
         * }
         * 
    * * A weighted variant where each edge has a floating-point weight is also supported: * *
         * {@code 
         * e    
         * }
         * 
    */ MAX_CLIQUE("edge", "e"), /** * Coloring format. * *

    * This is the format used in the 2nd * DIMACS implementation challenge. Same as the {@link DIMACSFormat#MAX_CLIQUE} but uses "col" * instead of "edge" in the problem definition line. */ COLORING("col", "e"); private final String problem; private final String edge; private DIMACSFormat(String problem, String edge) { this.problem = problem; this.edge = edge; } /** * Get the name of the problem. * * @return the name of the problem. */ public String getProblem() { return problem; } /** * Get the edge descriptor used in the format. * * @return the edge descriptor */ public String getEdgeDescriptor() { return edge; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/DIMACSImporter.java000066400000000000000000000147601402514743400311260ustar00rootroot00000000000000/* * (C) Copyright 2010-2021, by Michael Behrisch and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dimacs; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Imports a graph specified in DIMACS format. * *

    * See {@link DIMACSFormat} for a description of all the supported DIMACS formats. * *

    * In summary, one of the most common DIMACS formats was used in the * 2nd DIMACS challenge and follows * the following structure: * *

     * {@code
     * DIMACS G {
     *    c  ignored during parsing of the graph
     *    p edge  
     *    e  
     *    e  
     *    e  
     *    e  
     *    ...
     * }
     * }
     * 
    * * Although not specified directly in the DIMACS format documentation, this implementation also * allows for the a weighted variant: * *
     * {@code 
     * e    
     * }
     * 
    * * Note: the current implementation does not fully implement the DIMACS specifications! Special * (rarely used) fields specified as 'Optional Descriptors' are currently not supported (ignored). * * @author Michael Behrisch (adaptation of GraphReader class) * @author Joris Kinable * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class DIMACSImporter extends BaseEventDrivenImporter implements GraphImporter { /** * Default key used for vertex ID. */ public static final String DEFAULT_VERTEX_ID_KEY = "ID"; private Function vertexFactory; private final double defaultWeight; /** * Construct a new DIMACSImporter * * @param defaultWeight default edge weight */ public DIMACSImporter(double defaultWeight) { super(); this.defaultWeight = defaultWeight; } /** * Construct a new DIMACSImporter */ public DIMACSImporter() { this(Graph.DEFAULT_EDGE_WEIGHT); } /** * Get the user custom vertex factory. This is null by default and the graph supplier is used * instead. * * @return the user custom vertex factory */ public Function getVertexFactory() { return vertexFactory; } /** * Set the user custom vertex factory. The default behavior is being null in which case the * graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the file. * The method is called with parameter the vertex identifier from the file and should return the * actual graph vertex to add to the graph. * * @param vertexFactory a vertex factory */ public void setVertexFactory(Function vertexFactory) { this.vertexFactory = vertexFactory; } /** * Import a graph. * *

    * The provided graph must be able to support the features of the graph that is read. For * example if the file contains self-loops then the graph provided must also support self-loops. * The same for multiple edges. * *

    * If the provided graph is a weighted graph, the importer also reads edge weights. Otherwise * edge weights are ignored. * * @param graph the output graph * @param input the input reader * @throws ImportException in case an error occurs, such as I/O or parse error */ @Override public void importGraph(Graph graph, Reader input) throws ImportException { DIMACSEventDrivenImporter genericImporter = new DIMACSEventDrivenImporter().renumberVertices(false).zeroBasedNumbering(false); Consumers consumers = new Consumers(graph); genericImporter.addVertexCountConsumer(consumers.nodeCountConsumer); genericImporter.addEdgeConsumer(consumers.edgeConsumer); genericImporter.importInput(input); } private class Consumers { private Graph graph; private List list; public Consumers(Graph graph) { this.graph = graph; this.list = new ArrayList<>(); } public final Consumer nodeCountConsumer = n -> { for (int i = 1; i <= n; i++) { V v; if (vertexFactory != null) { v = vertexFactory.apply(i); graph.addVertex(v); } else { v = graph.addVertex(); } list.add(v); /* * Notify the first time we create the node. */ notifyVertex(v); notifyVertexAttribute( v, DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(i)); } }; public final Consumer> edgeConsumer = t -> { int source = t.getFirst(); V from = getElement(list, source - 1); if (from == null) { throw new ImportException("Node " + source + " does not exist"); } int target = t.getSecond(); V to = getElement(list, target - 1); if (to == null) { throw new ImportException("Node " + target + " does not exist"); } E e = graph.addEdge(from, to); if (graph.getType().isWeighted()) { double weight = t.getThird() == null ? defaultWeight : t.getThird(); graph.setEdgeWeight(e, weight); } notifyEdge(e); }; } private static E getElement(List list, int index) { return index < list.size() ? list.get(index) : null; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dimacs/package-info.java000066400000000000000000000001211402514743400307520ustar00rootroot00000000000000/** * DIMACS Challenges importers/exporters */ package org.jgrapht.nio.dimacs; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dot/000077500000000000000000000000001402514743400251175ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTEventDrivenImporter.java000066400000000000000000000617201402514743400323120ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dot; import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.misc.*; import org.apache.commons.text.*; import org.apache.commons.text.translate.*; import org.jgrapht.alg.util.Pair; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.Map.Entry; /** * Import a graph from a DOT file. * *

    * For a description of the format see * http://en.wikipedia.org/wiki/DOT_language and * * http://www.graphviz.org/doc/info/lang.html * *

    * The importer notifies interested parties using consumers. * * @author Dimitrios Michail */ public class DOTEventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { /** * Default key used for the graph ID. */ public static final String DEFAULT_GRAPH_ID_KEY = "ID"; // identifier unescape rule private final CharSequenceTranslator UNESCAPE_ID; private boolean notifyVertexAttributesOutOfOrder; private boolean notifyEdgeAttributesOutOfOrder; /** * Constructs a new importer. */ public DOTEventDrivenImporter() { this(true, true); } /** * Constructs a new importer. * * @param notifyVertexAttributesOutOfOrder whether to notify for vertex attributes out-of-order * even if they appear together in the input * @param notifyEdgeAttributesOutOfOrder whether to notify for edge attributes out-of-order even * if they appear together in the input */ public DOTEventDrivenImporter( boolean notifyVertexAttributesOutOfOrder, boolean notifyEdgeAttributesOutOfOrder) { super(); Map lookupMap = new HashMap<>(); lookupMap.put("\\\\", "\\"); lookupMap.put("\\\"", "\""); lookupMap.put("\\'", "'"); lookupMap.put("\\", ""); UNESCAPE_ID = new AggregateTranslator(new LookupTranslator(lookupMap)); this.notifyVertexAttributesOutOfOrder = notifyVertexAttributesOutOfOrder; this.notifyEdgeAttributesOutOfOrder = notifyEdgeAttributesOutOfOrder; } @Override public void importInput(Reader in) throws ImportException { try { /** * Create lexer with unbuffered input stream and use a token factory which copies * characters from the input stream into the text of the tokens. */ DOTLexer lexer = new DOTLexer(new UnbufferedCharStream(in)); lexer.setTokenFactory(new CommonTokenFactory(true)); lexer.removeErrorListeners(); ThrowingErrorListener errorListener = new ThrowingErrorListener(); lexer.addErrorListener(errorListener); /** * Create parser with unbuffered token stream. */ DOTParser parser = new DOTParser(new UnbufferedTokenStream<>(lexer)); parser.removeErrorListeners(); parser.addErrorListener(errorListener); /** * Disable parse tree building and attach listener. */ parser.setBuildParseTree(false); parser.addParseListener(new NotifyDOTListener()); /** * Parse */ notifyImportEvent(ImportEvent.START); parser.graph(); notifyImportEvent(ImportEvent.END); } catch (ParseCancellationException | IllegalArgumentException e) { throw new ImportException("Failed to import DOT graph: " + e.getMessage(), e); } } /* * Common error listener for both lexer and parser which throws an exception. */ private class ThrowingErrorListener extends BaseErrorListener { @Override public void syntaxError( Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) throws ParseCancellationException { throw new ParseCancellationException( "line " + line + ":" + charPositionInLine + " " + msg); } } /* * Listen on parser events and construct the graph. The listener is strongly dependent on the * grammar. */ private class NotifyDOTListener extends DOTBaseListener { private Set vertices; // stacks to maintain scope and state private Deque subgraphScopes; private Deque stack; public NotifyDOTListener() { this.vertices = new HashSet<>(); this.stack = new ArrayDeque<>(); this.subgraphScopes = new ArrayDeque<>(); } @Override public void enterGraph(DOTParser.GraphContext ctx) { stack.push(new State()); subgraphScopes.push(new SubgraphScope()); } @Override public void exitGraph(DOTParser.GraphContext ctx) { if (stack.isEmpty() || subgraphScopes.isEmpty()) { return; } subgraphScopes.pop(); stack.pop(); } @Override public void enterGraphHeader(DOTParser.GraphHeaderContext ctx) { // nothing } @Override public void exitGraphHeader(DOTParser.GraphHeaderContext ctx) { // nothing } @Override public void enterGraphIdentifier(DOTParser.GraphIdentifierContext ctx) { // add partial state stack.push(new State()); } @Override public void exitGraphIdentifier(DOTParser.GraphIdentifierContext ctx) { if (stack.isEmpty()) { return; } // read graph id State s = stack.pop(); State idPartial = s.children.peekFirst(); if (idPartial != null) { notifyGraphAttribute( DEFAULT_GRAPH_ID_KEY, DefaultAttribute.createAttribute(idPartial.getId())); } // add as child of parent if (!stack.isEmpty()) { stack.element().children.addLast(s); } } @Override public void enterAttributeStatement(DOTParser.AttributeStatementContext ctx) { // add partial state stack.push(new State()); } @Override public void exitAttributeStatement(DOTParser.AttributeStatementContext ctx) { if (stack.isEmpty() || subgraphScopes.isEmpty()) { return; } // read attributes State s = stack.pop(); State child = s.children.peekFirst(); if (child != null && child.attrs != null) { Map attrs = child.attrs; // update current scope SubgraphScope scope = subgraphScopes.element(); if (ctx.NODE() != null) { scope.nodeAttrs.putAll(attrs); } else if (ctx.EDGE() != null) { scope.edgeAttrs.putAll(attrs); } else if (ctx.GRAPH() != null) { scope.graphAttrs.putAll(attrs); } } } @Override public void enterAttributesList(DOTParser.AttributesListContext ctx) { // add partial state stack.push(new State()); } @Override public void exitAttributesList(DOTParser.AttributesListContext ctx) { if (stack.isEmpty()) { return; } // union children attributes State s = stack.pop(); for (State child : s.children) { if (child.attrs != null) { s.putAll(child.attrs); } } // add as child of parent s.children.clear(); if (!stack.isEmpty()) { stack.element().children.addLast(s); } } @Override public void enterAList(DOTParser.AListContext ctx) { // add partial state stack.push(new State()); } @Override public void exitAList(DOTParser.AListContext ctx) { if (stack.isEmpty()) { return; } // collect attributes in map State s = stack.pop(); Iterator it = s.children.iterator(); while (it.hasNext()) { State child = it.next(); if (child.ids != null && child.ids.size() == 1) { s.put(child.ids.get(0), null); } else if (child.ids != null && child.ids.size() >= 2) { s.put(child.ids.get(0), DefaultAttribute.createAttribute(child.ids.get(1))); } it.remove(); } // add as child of parent s.children.clear(); if (!stack.isEmpty()) { stack.element().children.addLast(s); } } @Override public void enterEdgeStatement(DOTParser.EdgeStatementContext ctx) { // add partial state stack.push(new State()); } @Override public void exitEdgeStatement(DOTParser.EdgeStatementContext ctx) { if (stack.isEmpty() || subgraphScopes.isEmpty()) { return; } State s = stack.pop(); // find attributes (last child) Map attrs = null; State last = s.children.peekLast(); if (last != null && last.attrs != null) { attrs = last.attrs; } Iterator it = s.children.iterator(); State cur, prev = null; while (it.hasNext()) { cur = it.next(); if (cur.attrs != null) { // last node with attributes break; } else if (prev != null) { for (String sourceVertex : prev.getVertices()) { for (String targetVertex : cur.getVertices()) { // find default attributes Map edgeAttrs = new HashMap<>(subgraphScopes.element().edgeAttrs); // add extra attributes if (attrs != null) { edgeAttrs.putAll(attrs); } Pair pe = Pair.of(sourceVertex, targetVertex); if (notifyEdgeAttributesOutOfOrder) { // notify individually notifyEdge(pe); for (Entry entry : edgeAttrs.entrySet()) { notifyEdgeAttribute(pe, entry.getKey(), entry.getValue()); } } else { // notify with all attributes notifyEdgeWithAttributes(pe, edgeAttrs); } } } } prev = cur; } } @Override public void enterIdentifierPairStatement(DOTParser.IdentifierPairStatementContext ctx) { // add partial state stack.push(new State()); } @Override public void exitIdentifierPairStatement(DOTParser.IdentifierPairStatementContext ctx) { if (stack.isEmpty() || subgraphScopes.isEmpty()) { return; } // read key value pair State s = stack.pop(); State idPairChild = s.children.peekFirst(); if (idPairChild == null || idPairChild.ids == null) { return; } String key = idPairChild.ids.get(0); String value = idPairChild.ids.get(1); // update attributes in current scope SubgraphScope scope = subgraphScopes.element(); scope.graphAttrs.put(key, DefaultAttribute.createAttribute(value)); if (subgraphScopes.size() == 1) { notifyGraphAttribute(key, DefaultAttribute.createAttribute(value)); } } @Override public void enterNodeStatement(DOTParser.NodeStatementContext ctx) { // add partial state stack.push(new State()); } @Override public void exitNodeStatement(DOTParser.NodeStatementContext ctx) { if (stack.isEmpty() || subgraphScopes.isEmpty()) { return; } // read node id State s = stack.pop(); Iterator it = s.children.iterator(); if (!it.hasNext()) { return; } State nodeIdPartialState = it.next(); String nodeId = nodeIdPartialState.getId(); // read attributes Map attrs = null; if (it.hasNext()) { attrs = it.next().attrs; } if (attrs == null) { attrs = Collections.emptyMap(); } // create or update vertex if (!vertices.contains(nodeId)) { SubgraphScope scope = subgraphScopes.element(); // find default attributes Map defaultAttrs = new HashMap<>(scope.nodeAttrs); // append extra attributes defaultAttrs.putAll(attrs); if (notifyVertexAttributesOutOfOrder) { notifyVertex(nodeId); for (Entry entry : defaultAttrs.entrySet()) { notifyVertexAttribute(nodeId, entry.getKey(), entry.getValue()); } } else { notifyVertexWithAttributes(nodeId, defaultAttrs); } vertices.add(nodeId); scope.addVertex(nodeId); } else { for (String key : attrs.keySet()) { notifyVertexAttribute(nodeId, key, attrs.get(key)); } } s.addVertex(nodeId); // add as child of parent s.children.clear(); if (!stack.isEmpty()) { stack.element().children.addLast(s); } } @Override public void enterNodeStatementNoAttributes(DOTParser.NodeStatementNoAttributesContext ctx) { // add partial state stack.push(new State()); } @Override public void exitNodeStatementNoAttributes(DOTParser.NodeStatementNoAttributesContext ctx) { if (stack.isEmpty() || subgraphScopes.isEmpty()) { return; } // read node id State s = stack.pop(); Iterator it = s.children.iterator(); if (!it.hasNext()) { return; } State nodeIdPartial = it.next(); String nodeId = nodeIdPartial.getId(); // create or update vertex if (!vertices.contains(nodeId)) { SubgraphScope scope = subgraphScopes.element(); // find default attributes Map defaultAttrs = new HashMap<>(scope.nodeAttrs); if (notifyVertexAttributesOutOfOrder) { notifyVertex(nodeId); for (Entry entry : defaultAttrs.entrySet()) { notifyVertexAttribute(nodeId, entry.getKey(), entry.getValue()); } } else { notifyVertexWithAttributes(nodeId, defaultAttrs); } vertices.add(nodeId); scope.addVertex(nodeId); } s.addVertex(nodeId); // add as child of parent s.children.clear(); if (!stack.isEmpty()) { stack.element().children.addLast(s); } } @Override public void enterNodeIdentifier(DOTParser.NodeIdentifierContext ctx) { // add partial state stack.push(new State()); } @Override public void exitNodeIdentifier(DOTParser.NodeIdentifierContext ctx) { if (stack.isEmpty()) { return; } // collect only first child (ignore ports) State s = stack.pop(); if (!s.children.isEmpty()) { s.addId(s.children.getFirst().getId()); // add as child of parent s.children.clear(); if (!stack.isEmpty()) { stack.element().children.addLast(s); } } } @Override public void enterSubgraphStatement(DOTParser.SubgraphStatementContext ctx) { // Create new scope with inherited attributes Map defaultGraphAttrs = subgraphScopes.element().graphAttrs; Map defaultNodeAttrs = subgraphScopes.element().nodeAttrs; Map defaultEdgeAttrs = subgraphScopes.element().edgeAttrs; SubgraphScope newState = new SubgraphScope(); newState.graphAttrs.putAll(defaultGraphAttrs); newState.nodeAttrs.putAll(defaultNodeAttrs); newState.edgeAttrs.putAll(defaultEdgeAttrs); subgraphScopes.push(newState); // Add partial state State s = new State(); s.subgraph = newState; stack.push(s); } @Override public void exitSubgraphStatement(DOTParser.SubgraphStatementContext ctx) { if (stack.isEmpty() || subgraphScopes.isEmpty()) { return; } // remove last scope SubgraphScope scope = subgraphScopes.pop(); State s = stack.pop(); // if not on root graph, append nodes to subgraph one level up if (scope.vertices != null && subgraphScopes.size() > 1) { subgraphScopes.element().addVertices(scope.vertices); } // add as child of parent s.children.clear(); if (!stack.isEmpty()) { stack.element().children.addLast(s); } } @Override public void enterIdentifierPair(DOTParser.IdentifierPairContext ctx) { // add partial state stack.push(new State()); } @Override public void exitIdentifierPair(DOTParser.IdentifierPairContext ctx) { if (stack.isEmpty()) { return; } // collect our two children as one pair State s = stack.pop(); Iterator it = s.children.iterator(); if (it.hasNext()) { s.addId(it.next().getId()); } if (it.hasNext()) { s.addId(it.next().getId()); } if (s.ids != null) { // add as child of parent s.children.clear(); if (!stack.isEmpty()) { stack.element().children.addLast(s); } } } @Override public void enterIdentifier(DOTParser.IdentifierContext ctx) { // add partial state stack.push(new State()); } @Override public void exitIdentifier(DOTParser.IdentifierContext ctx) { if (stack.isEmpty()) { return; } // collect actual identifier State s = stack.pop(); String id = null; if (ctx.Id() != null) { id = ctx.Id().toString(); } else if (ctx.String() != null) { id = unescapeId(ctx.String().toString()); } else if (ctx.HtmlString() != null) { id = unescapeHtmlString(ctx.HtmlString().toString()); } else if (ctx.Numeral() != null) { id = ctx.Numeral().toString(); } // record id if (id != null) { s.addId(id); // add as child of parent if (!stack.isEmpty()) { stack.element().children.addLast(s); } } } } /* * Partial parsed state depending on node type. */ private class State { LinkedList children; List ids; Map attrs; List vertices; SubgraphScope subgraph; public State() { this.children = new LinkedList<>(); this.ids = null; this.attrs = null; this.vertices = null; this.subgraph = null; } public String getId() { if (ids == null || ids.isEmpty()) { return ""; } else { return ids.get(0); } } public void addId(String id) { if (this.ids == null) { this.ids = new ArrayList<>(); } this.ids.add(id); } public void put(String key, Attribute value) { if (this.attrs == null) { this.attrs = new HashMap<>(); } this.attrs.put(key, value); } public void putAll(Map attrs) { if (this.attrs == null) { this.attrs = new HashMap<>(); } this.attrs.putAll(attrs); } public void addVertex(String v) { if (this.vertices == null) { this.vertices = new ArrayList<>(); } this.vertices.add(v); } public List getVertices() { if (vertices != null) { return vertices; } else if (subgraph != null && subgraph.vertices != null) { return subgraph.vertices; } return Collections.emptyList(); } } /* * Records default attributes per subgraph */ private class SubgraphScope { Map graphAttrs; Map nodeAttrs; Map edgeAttrs; List vertices; public SubgraphScope() { this.graphAttrs = new HashMap<>(); this.nodeAttrs = new HashMap<>(); this.edgeAttrs = new HashMap<>(); this.vertices = null; } public void addVertex(String v) { if (this.vertices == null) { this.vertices = new ArrayList<>(); } this.vertices.add(v); } public void addVertices(List v) { if (this.vertices == null) { this.vertices = new ArrayList<>(); } this.vertices.addAll(v); } } /** * Unescape a string DOT identifier. * * @param input the input * @return the unescaped output */ private String unescapeId(String input) { final char QUOTE = '"'; if (input.charAt(0) != QUOTE || input.charAt(input.length() - 1) != QUOTE) { return input; } String noQuotes = input.subSequence(1, input.length() - 1).toString(); String unescaped = UNESCAPE_ID.translate(noQuotes); return unescaped; } /** * Unescape an HTML string DOT identifier. * * @param input the input * @return the unescaped output */ private static String unescapeHtmlString(String input) { if (input.charAt(0) != '<' || input.charAt(input.length() - 1) != '>') { return input; } String noQuotes = input.subSequence(1, input.length() - 1).toString(); String unescaped = StringEscapeUtils.unescapeXml(noQuotes); return unescaped; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTExporter.java000066400000000000000000000176701402514743400301540ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by Trevor Harmon and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dot; import org.jgrapht.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.Map.*; import java.util.function.*; import java.util.regex.*; /** * Exports a graph into a DOT file. * *

    * For a description of the format see * http://en.wikipedia.org/wiki/DOT_language. *

    * * The user can adjust the behavior using the various providers. * * @param the graph vertex type * @param the graph edge type * * @author Trevor Harmon * @author Dimitrios Michail */ public class DOTExporter extends BaseExporter implements GraphExporter { /** * Default graph id used by the exporter. */ public static final String DEFAULT_GRAPH_ID = "G"; private static final String INDENT = " "; private final Map validatedIds; /** * Constructs a new DOTExporter object with an integer id provider. */ public DOTExporter() { this(new IntegerIdProvider<>()); } /** * Constructs a new DOTExporter object with the given id provider. Additional providers such as * attributes can be given using the appropriate setter methods. * * @param vertexIdProvider for generating vertex IDs. Must not be null. */ public DOTExporter(Function vertexIdProvider) { super(vertexIdProvider); this.validatedIds = new HashMap<>(); } /** * Exports a graph into a plain text file in DOT format. * * @param g the graph to be exported * @param writer the writer to which the graph to be exported */ @Override public void exportGraph(Graph g, Writer writer) { PrintWriter out = new PrintWriter(writer); out.println(computeHeader(g)); // graph attributes for (Entry attr : graphAttributeProvider .orElse(Collections::emptyMap).get().entrySet()) { out.print(INDENT); out.print(attr.getKey()); out.print('='); out.print(attr.getValue()); out.println(";"); } // vertex set for (V v : g.vertexSet()) { out.print(INDENT); out.print(getVertexID(v)); getVertexAttributes(v).ifPresent(m -> { renderAttributes(out, m); }); out.println(";"); } String connector = computeConnector(g); // edge set for (E e : g.edgeSet()) { String source = getVertexID(g.getEdgeSource(e)); String target = getVertexID(g.getEdgeTarget(e)); out.print(INDENT); out.print(source); out.print(connector); out.print(target); getEdgeAttributes(e).ifPresent(m -> { renderAttributes(out, m); }); out.println(";"); } out.println(computeFooter(g)); out.flush(); } /** * Compute the header * * @param graph the graph * @return the header */ private String computeHeader(Graph graph) { StringBuilder headerBuilder = new StringBuilder(); if (!graph.getType().isAllowingMultipleEdges()) { headerBuilder.append(DOTUtils.DONT_ALLOW_MULTIPLE_EDGES_KEYWORD).append(" "); } if (graph.getType().isDirected()) { headerBuilder.append(DOTUtils.DIRECTED_GRAPH_KEYWORD); } else { headerBuilder.append(DOTUtils.UNDIRECTED_GRAPH_KEYWORD); } headerBuilder.append(" ").append(computeGraphId(graph)).append(" {"); return headerBuilder.toString(); } /** * Compute the footer * * @param graph the graph * @return the footer */ private String computeFooter(Graph graph) { return "}"; } /** * Compute the connector * * @param graph the graph * @return the connector */ private String computeConnector(Graph graph) { StringBuilder connectorBuilder = new StringBuilder(); if (graph.getType().isDirected()) { connectorBuilder.append(" ").append(DOTUtils.DIRECTED_GRAPH_EDGEOP).append(" "); } else { connectorBuilder.append(" ").append(DOTUtils.UNDIRECTED_GRAPH_EDGEOP).append(" "); } return connectorBuilder.toString(); } /** * Get the id of the graph. * * @param graph the graph * @return the graph id */ private String computeGraphId(Graph graph) { String graphId = getGraphId().orElse(DEFAULT_GRAPH_ID); if (!DOTUtils.isValidID(graphId)) { throw new ExportException( "Generated graph ID '" + graphId + "' is not valid with respect to the .dot language"); } return graphId; } private void renderAttributes(PrintWriter out, Map attributes) { if (attributes == null) { return; } out.print(" [ "); for (Map.Entry entry : attributes.entrySet()) { String name = entry.getKey(); renderAttribute(out, name, entry.getValue()); } out.print("]"); } private void renderAttribute(PrintWriter out, String attrName, Attribute attribute) { out.print(attrName + "="); final String attrValue = attribute.getValue(); if (AttributeType.HTML.equals(attribute.getType())) { out.print("<" + attrValue + ">"); } else if (AttributeType.IDENTIFIER.equals(attribute.getType())) { out.print(attrValue); } else { out.print("\"" + escapeDoubleQuotes(attrValue) + "\""); } out.print(" "); } private static String escapeDoubleQuotes(String labelName) { return labelName.replaceAll("\"", Matcher.quoteReplacement("\\\"")); } /** * Return a valid vertex ID (with respect to the .dot language definition as described in * http://www.graphviz.org/doc/info/lang.html * *

    * Quoted from above mentioned source: An ID is valid if it meets one of the following criteria: * *

      *
    • any string of alphabetic characters, underscores or digits, not beginning with a digit; *
    • a number [-]?(.[0-9]+ | [0-9]+(.[0-9]*)? ); *
    • any double-quoted string ("...") possibly containing escaped quotes (\"); *
    • an HTML string (<...>). *
    * * @throws ExportException if the given vertexIDProvider didn't generate a valid * vertex ID. */ private String getVertexID(V v) { String vertexId = validatedIds.get(v); if (vertexId == null) { /* * use the associated id provider for an ID of the given vertex */ vertexId = getVertexId(v); /* * test if it is a valid ID */ if (!DOTUtils.isValidID(vertexId)) { throw new ExportException( "Generated id '" + vertexId + "'for vertex '" + v + "' is not valid with respect to the .dot language"); } validatedIds.put(v, vertexId); } return vertexId; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTImporter.java000066400000000000000000000300161402514743400301320ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dot; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Import a graph from a DOT file. * *

    * For a description of the format see * http://en.wikipedia.org/wiki/DOT_language and * * http://www.graphviz.org/doc/info/lang.html * *

    * The provided graph object, where the imported graph will be stored, must be able to support the * features of the graph that is read. For example if the file contains self-loops then the graph * provided must also support self-loops. The same for multiple edges. Whether edges are directed or * not depends on the underlying implementation of the user provided graph object. * *

    * The graph vertices and edges are build using the corresponding graph suppliers. The id of the * vertices in the original dot file are reported as a vertex attribute named "ID". Thus, in case * vertices in the dot file also contain an "ID" attribute, such an attribute will be reported * multiple times. * *

    * The default behavior of the importer is to use the graph vertex supplier in order to create * vertices. The user can also bypass vertex creation by providing a custom vertex factory method * using {@link #setVertexFactory(Function)}. The factory method is responsible to create a new * graph vertex given the vertex identifier read from file. Additionally this importer also supports * creating vertices with {@link #setVertexWithAttributesFactory(BiFunction)}. This factory method * is responsible for creating a new graph vertex given the vertex identifier read from file * together with all available attributes of the vertex at the location of the file where the vertex * is first defined. * *

    * The default behavior of the importer is to use the graph edge supplier in order to create edges. * The user can also bypass edge creation by providing a custom edge factory method using * {@link #setEdgeWithAttributesFactory(Function)}. The factory method is responsible to create a * new graph edge given all available attributes of the edge at the location of the file where the * edge is first defined. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class DOTImporter extends BaseEventDrivenImporter implements GraphImporter { /** * Default key used for vertex ID. */ public static final String DEFAULT_VERTEX_ID_KEY = "ID"; private Function vertexFactory; private BiFunction, V> vertexWithAttributesFactory; private Function, E> edgeWithAttributesFactory; /** * Constructs a new importer. */ public DOTImporter() { super(); } @Override public void importGraph(Graph graph, Reader input) { final boolean verticesOutOfOrder = vertexWithAttributesFactory == null; final boolean edgesOutOfOrder = edgeWithAttributesFactory == null; DOTEventDrivenImporter genericImporter = new DOTEventDrivenImporter(verticesOutOfOrder, edgesOutOfOrder); Consumers consumers = new Consumers(graph); if (vertexWithAttributesFactory != null) { genericImporter.addVertexWithAttributesConsumer(consumers.vertexWithAttributesConsumer); } else { genericImporter.addVertexConsumer(consumers.vertexConsumer); } genericImporter.addVertexAttributeConsumer(consumers.vertexAttributeConsumer); if (edgeWithAttributesFactory != null) { genericImporter.addEdgeWithAttributesConsumer(consumers.edgeWithAttributesConsumer); } else { genericImporter.addEdgeConsumer(consumers.edgeConsumer); } genericImporter.addEdgeAttributeConsumer(consumers.edgeAttributeConsumer); genericImporter.addGraphAttributeConsumer(consumers.graphAttributeConsumer); genericImporter.importInput(input); } /** * Get the user custom vertex factory. This is null by default and the graph supplier is used * instead. * * @return the user custom vertex factory */ public Function getVertexFactory() { return vertexFactory; } /** * Set the user custom vertex factory. The default behavior is being null in which case the * graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the input. * The method is called with parameter the vertex identifier from the input and should return * the actual graph vertex to add to the graph. * * @param vertexFactory a vertex factory */ public void setVertexFactory(Function vertexFactory) { this.vertexFactory = vertexFactory; } /** * Get the user custom vertex factory with attributes. This is null by default and the graph * supplier is used instead. * * @return the user custom vertex factory with attributes. */ public BiFunction, V> getVertexWithAttributesFactory() { return vertexWithAttributesFactory; } /** * Set the user custom vertex factory with attributes. The default behavior is being null in * which case the graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the input. * The method is called with parameter the vertex identifier from the input and a set of * attributes and should return the actual graph vertex to add to the graph. Note that the set * of attributes might not be complete, as only attributes available at the first vertex * definition are collected. * * @param vertexWithAttributesFactory a vertex factory with attributes */ public void setVertexWithAttributesFactory( BiFunction, V> vertexWithAttributesFactory) { this.vertexWithAttributesFactory = vertexWithAttributesFactory; } /** * Get the user custom edges factory with attributes. This is null by default and the graph * supplier is used instead. * * @return the user custom edge factory with attributes. */ public Function, E> getEdgeWithAttributesFactory() { return edgeWithAttributesFactory; } /** * Set the user custom edge factory with attributes. The default behavior is being null in which * case the graph edge supplier is used. * * If supplied the edge factory is called every time a new edge is encountered in the input. The * method is called with parameter the set of attributes and should return the actual graph edge * to add to the graph. Note that the set of attributes might not be complete, as only * attributes available at the first edge definition are collected. * * @param edgeWithAttributesFactory an edge factory with attributes */ public void setEdgeWithAttributesFactory( Function, E> edgeWithAttributesFactory) { this.edgeWithAttributesFactory = edgeWithAttributesFactory; } private class Consumers { private Graph graph; private Map map; private Pair lastPair; private E lastEdge; public Consumers(Graph graph) { this.graph = graph; this.map = new HashMap<>(); } public final BiConsumer graphAttributeConsumer = (k, a) -> { notifyGraphAttribute(k, a); }; public final Consumer vertexConsumer = (t) -> { if (map.containsKey(t)) { throw new ImportException("Node " + t + " already exists"); } V v; if (vertexFactory != null) { v = vertexFactory.apply(t); graph.addVertex(v); } else { v = graph.addVertex(); } map.put(t, v); // notify individually notifyVertex(v); notifyVertexAttribute(v, DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(t)); }; public final BiConsumer> vertexWithAttributesConsumer = (t, attrs) -> { if (map.containsKey(t)) { throw new ImportException("Node " + t + " already exists"); } V v; if (vertexWithAttributesFactory != null) { v = vertexWithAttributesFactory.apply(t, attrs); graph.addVertex(v); } else { v = graph.addVertex(); } map.put(t, v); // notify with all collected attributes attrs.put(DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(t)); notifyVertexWithAttributes(v, attrs); }; public final BiConsumer, Attribute> vertexAttributeConsumer = (p, a) -> { String vertex = p.getFirst(); if (!map.containsKey(vertex)) { throw new ImportException("Node " + vertex + " does not exist"); } notifyVertexAttribute(map.get(vertex), p.getSecond(), a); }; public final Consumer> edgeConsumer = (p) -> { String source = p.getFirst(); V from = map.get(p.getFirst()); if (from == null) { throw new ImportException("Node " + source + " does not exist"); } String target = p.getSecond(); V to = map.get(target); if (to == null) { throw new ImportException("Node " + target + " does not exist"); } E e = graph.addEdge(from, to); notifyEdge(e); lastPair = p; lastEdge = e; }; public final BiConsumer, Map> edgeWithAttributesConsumer = (p, attrs) -> { String source = p.getFirst(); V from = map.get(p.getFirst()); if (from == null) { throw new ImportException("Node " + source + " does not exist"); } String target = p.getSecond(); V to = map.get(target); if (to == null) { throw new ImportException("Node " + target + " does not exist"); } E e; if (edgeWithAttributesFactory != null) { e = edgeWithAttributesFactory.apply(attrs); graph.addEdge(from, to, e); } else { e = graph.addEdge(from, to); } notifyEdgeWithAttributes(e, attrs); lastPair = p; lastEdge = e; }; public final BiConsumer, String>, Attribute> edgeAttributeConsumer = (p, a) -> { if (p.getFirst() == lastPair) { notifyEdgeAttribute(lastEdge, p.getSecond(), a); } }; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dot/DOTUtils.java000066400000000000000000000041521402514743400274330ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Christoph Zauner and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dot; import java.util.regex.*; /** * Class with DOT format related utilities. * * @author Christoph Zauner */ class DOTUtils { /** Keyword for representing strict graphs. */ static final String DONT_ALLOW_MULTIPLE_EDGES_KEYWORD = "strict"; /** Keyword for directed graphs. */ static final String DIRECTED_GRAPH_KEYWORD = "digraph"; /** Keyword for undirected graphs. */ static final String UNDIRECTED_GRAPH_KEYWORD = "graph"; /** Edge operation for directed graphs. */ static final String DIRECTED_GRAPH_EDGEOP = "->"; /** Edge operation for undirected graphs. */ static final String UNDIRECTED_GRAPH_EDGEOP = "--"; // patterns for IDs private static final Pattern ALPHA_DIG = Pattern.compile("[a-zA-Z_][\\w]*"); private static final Pattern DOUBLE_QUOTE = Pattern.compile("\".*\""); private static final Pattern DOT_NUMBER = Pattern.compile("[-]?([.][0-9]+|[0-9]+([.][0-9]*)?)"); private static final Pattern HTML = Pattern.compile("<.*>"); /** * Test if the ID candidate is a valid ID. * * @param idCandidate the ID candidate. * * @return true if it is valid; false otherwise. */ static boolean isValidID(String idCandidate) { return ALPHA_DIG.matcher(idCandidate).matches() || DOUBLE_QUOTE.matcher(idCandidate).matches() || DOT_NUMBER.matcher(idCandidate).matches() || HTML.matcher(idCandidate).matches(); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/dot/package-info.java000066400000000000000000000001001402514743400302750ustar00rootroot00000000000000/** * DOT importers/exporters */ package org.jgrapht.nio.dot; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/000077500000000000000000000000001402514743400252625ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/GEXFAttributeType.java000066400000000000000000000037461402514743400314160ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gexf; /** * Attribute types supported by GEXF. * * @author Dimitrios Michail */ public enum GEXFAttributeType { BOOLEAN("boolean"), INTEGER("integer"), LONG("long"), FLOAT("float"), DOUBLE("double"), STRING("string"), LISTSTRING("liststring"), ANYURI("anyURI"); private String name; private GEXFAttributeType(String name) { this.name = name; } /** * Get a string representation of the attribute type * * @return the string representation of the attribute type */ public String toString() { return name; } /** * Create a type from a string representation * * @param value the name of the type * @return the attribute type */ public static GEXFAttributeType create(String value) { switch (value) { case "boolean": return BOOLEAN; case "integer": return INTEGER; case "long": return LONG; case "float": return FLOAT; case "double": return DOUBLE; case "string": return STRING; case "liststring": return LISTSTRING; case "anyURI": return ANYURI; } throw new IllegalArgumentException("Type " + value + " is unknown"); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/GEXFExporter.java000066400000000000000000000522261402514743400304160ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gexf; import org.jgrapht.*; import org.jgrapht.nio.*; import org.xml.sax.*; import org.xml.sax.helpers.*; import javax.xml.transform.*; import javax.xml.transform.sax.*; import javax.xml.transform.stream.*; import java.io.*; import java.util.*; import java.util.Map.*; import java.util.function.*; /** * Exports a graph as GEXF (Graph Exchange XML Format). * *

    * For a description of the format see * https://gephi.org/gexf/format/schema.html. A nice primer for the format is located at * https://gephi.org/gexf/1.2draft/gexf-12draft-primer.pdf. *

    * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class GEXFExporter extends BaseExporter implements GraphExporter { private static final String LABEL_ATTRIBUTE_NAME = "label"; private static final String WEIGHT_ATTRIBUTE_NAME = "weight"; private static final String TYPE_ATTRIBUTE_NAME = "type"; private static final Set VERTEX_RESERVED_ATTRIBUTES = Set.of("id", LABEL_ATTRIBUTE_NAME); private static final Set EDGE_RESERVED_ATTRIBUTES = Set.of("id", "type", LABEL_ATTRIBUTE_NAME, "source", "target", WEIGHT_ATTRIBUTE_NAME); private int totalVertexAttributes = 0; private Map registeredVertexAttributes; private int totalEdgeAttributes = 0; private Map registeredEdgeAttributes; private final Set parameters; private String creator = "The JGraphT Library"; private String keywords; private String description; /** * Parameters that affect the behavior of the exporter. */ public enum Parameter { /** * If set the exporter outputs edge weights */ EXPORT_EDGE_WEIGHTS, /** * If set the exporter outputs edge labels. Labels are looked up from the edge attribute * provider. */ EXPORT_EDGE_LABELS, /** * If set the exporter outputs edge types. Edge types are looked up from the type of the * graph. Mixed graphs are not supported. */ EXPORT_EDGE_TYPES, /** * If set the exporter outputs the metadata information. This is true by default. */ EXPORT_META, } /** * Denotes the category of a GEXF-Attribute. */ public enum AttributeCategory { NODE("node"), EDGE("edge"); private String name; private AttributeCategory(String name) { this.name = name; } /** * Get a string representation of the attribute category * * @return the string representation of the attribute category */ public String toString() { return name; } } /** * Constructs a new exporter with integer id providers for the vertices and the edges. */ public GEXFExporter() { this(new IntegerIdProvider(0), new IntegerIdProvider(0)); } /** * Constructs a new exporter. * * @param vertexIdProvider for generating vertex identifiers. Must not be null. * @param edgeIdProvider for generating edge identifiers. Must not be null. */ public GEXFExporter(Function vertexIdProvider, Function edgeIdProvider) { super(vertexIdProvider); this.edgeIdProvider = Optional.of(edgeIdProvider); this.registeredVertexAttributes = new LinkedHashMap<>(); this.registeredEdgeAttributes = new LinkedHashMap<>(); this.parameters = new HashSet<>(); // enable meta by default this.setParameter(Parameter.EXPORT_META, true); } /** * Return if a particular parameter of the exporter is enabled * * @param p the parameter * @return {@code true} if the parameter is set, {@code false} otherwise */ public boolean isParameter(Parameter p) { return parameters.contains(p); } /** * Set the value of a parameter of the exporter * * @param p the parameter * @param value the value to set */ public void setParameter(Parameter p, boolean value) { if (value) { parameters.add(p); } else { parameters.remove(p); } } /** * Register a GEXF Attribute * * @param name the attribute name * @param category the attribute category * @param type the attribute type */ public void registerAttribute(String name, AttributeCategory category, GEXFAttributeType type) { registerAttribute(name, category, type, null); } /** * Register a GEXF Attribute * * @param name the attribute name * @param category the attribute category * @param type the attribute type * @param defaultValue default value */ public void registerAttribute( String name, AttributeCategory category, GEXFAttributeType type, String defaultValue) { registerAttribute(name, category, type, null, null); } /** * Register a GEXF Attribute * * @param name the attribute name * @param category the attribute category * @param type the attribute type * @param defaultValue default value * @param options the possible options */ public void registerAttribute( String name, AttributeCategory category, GEXFAttributeType type, String defaultValue, String options) { if (name == null) { throw new IllegalArgumentException("Attribute name cannot be null"); } if (category == null) { throw new IllegalArgumentException("Attribute category must be one of node or edge"); } if (category.equals(AttributeCategory.NODE)) { if (VERTEX_RESERVED_ATTRIBUTES.contains(name.toLowerCase())) { throw new IllegalArgumentException("Reserved vertex attribute name"); } registeredVertexAttributes .put( name, new AttributeDetails( String.valueOf(totalVertexAttributes++), type, defaultValue, options)); } else if (category.equals(AttributeCategory.EDGE)) { if (EDGE_RESERVED_ATTRIBUTES.contains(name.toLowerCase())) { throw new IllegalArgumentException("Reserved edge attribute name"); } registeredEdgeAttributes .put( name, new AttributeDetails( String.valueOf(totalEdgeAttributes++), type, defaultValue, options)); } } /** * Unregister a GraphML-Attribute * * @param name the attribute name * @param category the attribute category */ public void unregisterAttribute(String name, AttributeCategory category) { if (name == null) { throw new IllegalArgumentException("Attribute name cannot be null"); } if (category == null) { throw new IllegalArgumentException("Attribute category must be one of node or edge"); } if (category.equals(AttributeCategory.NODE)) { if (VERTEX_RESERVED_ATTRIBUTES.contains(name.toLowerCase())) { throw new IllegalArgumentException("Reserved vertex attribute name"); } registeredVertexAttributes.remove(name); } else if (category.equals(AttributeCategory.EDGE)) { if (EDGE_RESERVED_ATTRIBUTES.contains(name.toLowerCase())) { throw new IllegalArgumentException("Reserved edge attribute name"); } registeredEdgeAttributes.remove(name); } } /** * Get the creator for the meta field. * * @return the creator for the meta field */ public String getCreator() { return creator; } /** * Set the creator for the meta field. * * @param creator the creator for the meta field */ public void setCreator(String creator) { this.creator = creator; } /** * Get the keywords for the meta field. * * @return the keywords for the meta field */ public String getKeywords() { return keywords; } /** * Set the keywords for the meta field. * * @param keywords the keywords for the meta field */ public void setKeywords(String keywords) { this.keywords = keywords; } /** * Get the description for the meta field. * * @return the description for the meta field */ public String getDescription() { return description; } /** * Set the description for the meta field. * * @param description the description for the meta field */ public void setDescription(String description) { this.description = description; } /** * Exports a graph in GraphML format. * * @param g the graph * @param writer the writer to export the graph * @throws ExportException in case any error occurs during export */ @Override public void exportGraph(Graph g, Writer writer) { try { // Prepare an XML file to receive the data SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); TransformerHandler handler = factory.newTransformerHandler(); handler.getTransformer().setOutputProperty(OutputKeys.ENCODING, "UTF-8"); handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes"); handler.setResult(new StreamResult(new PrintWriter(writer))); // export handler.startDocument(); writeHeader(handler); writeMeta(handler); writeGraphStart(handler, g); writeVertexAttributes(handler); writeEdgeAttributes(handler); writeVertices(handler, g); writeEdges(handler, g); writeGraphEnd(handler); writeFooter(handler); handler.endDocument(); // flush writer.flush(); } catch (Exception e) { throw new ExportException("Failed to export as GEFX", e); } } private void writeHeader(TransformerHandler handler) throws SAXException { handler.startPrefixMapping("xsi", "http://www.w3.org/2001/XMLSchema-instance"); handler.endPrefixMapping("xsi"); AttributesImpl attr = new AttributesImpl(); attr .addAttribute( "", "", "xsi:schemaLocation", "CDATA", "http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd"); attr.addAttribute("", "", "version", "CDATA", "1.2"); handler.startElement("http://www.gexf.net/1.2draft", "", "gexf", attr); } private void writeMeta(TransformerHandler handler) throws SAXException { boolean exportMeta = parameters.contains(Parameter.EXPORT_META); if (!exportMeta) { return; } if (creator == null && description == null && keywords == null) { return; } handler.startElement("", "", "meta", null); if (creator != null) { handler.startElement("", "", "creator", null); handler.characters(creator.toCharArray(), 0, creator.length()); handler.endElement("", "", "creator"); } if (description != null) { handler.startElement("", "", "description", null); handler.characters(description.toCharArray(), 0, description.length()); handler.endElement("", "", "description"); } if (keywords != null) { handler.startElement("", "", "keywords", null); handler.characters(keywords.toCharArray(), 0, keywords.length()); handler.endElement("", "", "keywords"); } handler.endElement("", "", "meta"); } private void writeGraphStart(TransformerHandler handler, Graph g) throws SAXException { AttributesImpl attr = new AttributesImpl(); attr .addAttribute( "", "", "defaultedgetype", "CDATA", g.getType().isDirected() ? "directed" : "undirected"); handler.startElement("", "", "graph", attr); } private void writeGraphEnd(TransformerHandler handler) throws SAXException { handler.endElement("", "", "graph"); } private void writeFooter(TransformerHandler handler) throws SAXException { handler.endElement("", "", "gexf"); } private void writeVertexAttributes(TransformerHandler handler) throws SAXException { if (registeredVertexAttributes.isEmpty()) { return; } AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "class", "CDATA", "node"); handler.startElement("", "", "attributes", attr); for (Entry e : registeredVertexAttributes.entrySet()) { writeAttribute(handler, e.getKey(), e.getValue()); } handler.endElement("", "", "attributes"); } private void writeEdgeAttributes(TransformerHandler handler) throws SAXException { if (registeredEdgeAttributes.isEmpty()) { return; } AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "class", "CDATA", "edge"); handler.startElement("", "", "attributes", attr); for (Entry e : registeredEdgeAttributes.entrySet()) { writeAttribute(handler, e.getKey(), e.getValue()); } handler.endElement("", "", "attributes"); } private void writeAttribute(TransformerHandler handler, String name, AttributeDetails details) throws SAXException { AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "id", "CDATA", details.key); attr.addAttribute("", "", "title", "CDATA", name); attr.addAttribute("", "", "type", "CDATA", details.type.toString()); handler.startElement("", "", "attribute", attr); if (details.defaultValue != null) { handler.startElement("", "", "default", null); handler .characters(details.defaultValue.toCharArray(), 0, details.defaultValue.length()); handler.endElement("", "", "default"); } if (details.options != null) { handler.startElement("", "", "options", null); handler.characters(details.options.toCharArray(), 0, details.options.length()); handler.endElement("", "", "options"); } handler.endElement("", "", "attribute"); } private void writeVertexAttributeValues(TransformerHandler handler, V v) throws SAXException { Map vertexAttributes = getVertexAttributes(v).orElse(Collections.emptyMap()); if (vertexAttributes.isEmpty()) { return; } handler.startElement("", "", "attvalues", null); // check all registered for (Entry entry : registeredVertexAttributes.entrySet()) { AttributeDetails details = entry.getValue(); String name = entry.getKey(); String defaultValue = details.defaultValue; if (vertexAttributes.containsKey(name)) { Attribute attribute = vertexAttributes.get(name); String value = attribute.getValue(); if (defaultValue == null || !defaultValue.equals(value)) { if (value != null) { writeAttributeValue(handler, details.key, value); } } } } handler.endElement("", "", "attvalues"); } private void writeEdgeAttributeValues(TransformerHandler handler, E e) throws SAXException { Map edgeAttributes = getEdgeAttributes(e).orElse(Collections.emptyMap()); if (edgeAttributes.isEmpty()) { return; } handler.startElement("", "", "attvalues", null); // check all registered for (Entry entry : registeredEdgeAttributes.entrySet()) { AttributeDetails details = entry.getValue(); String name = entry.getKey(); String defaultValue = details.defaultValue; if (edgeAttributes.containsKey(name)) { Attribute attribute = edgeAttributes.get(name); String value = attribute.getValue(); if (defaultValue == null || !defaultValue.equals(value)) { if (value != null) { writeAttributeValue(handler, details.key, value); } } } } handler.endElement("", "", "attvalues"); } private void writeAttributeValue(TransformerHandler handler, String key, String value) throws SAXException { AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "for", "CDATA", key); attr.addAttribute("", "", "value", "CDATA", value); handler.startElement("", "", "attvalue", attr); handler.endElement("", "", "attvalue"); } private void writeVertices(TransformerHandler handler, Graph g) throws SAXException { handler.startElement("", "", "nodes", null); for (V v : g.vertexSet()) { AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "id", "CDATA", getVertexId(v)); attr .addAttribute( "", "", LABEL_ATTRIBUTE_NAME, "CDATA", getVertexAttribute(v, LABEL_ATTRIBUTE_NAME) .map(Attribute::getValue).orElse(getVertexId(v))); handler.startElement("", "", "node", attr); writeVertexAttributeValues(handler, v); handler.endElement("", "", "node"); } handler.endElement("", "", "nodes"); } private void writeEdges(TransformerHandler handler, Graph g) throws SAXException { boolean exportEdgeWeights = parameters.contains(Parameter.EXPORT_EDGE_WEIGHTS); boolean exportEdgeTypes = parameters.contains(Parameter.EXPORT_EDGE_TYPES); boolean exportEdgeLabels = parameters.contains(Parameter.EXPORT_EDGE_LABELS); boolean isGraphDirected = g.getType().isDirected(); handler.startElement("", "", "edges", null); for (E e : g.edgeSet()) { AttributesImpl attr = new AttributesImpl(); attr .addAttribute( "", "", "id", "CDATA", getEdgeId(e) .orElseThrow( () -> new IllegalArgumentException( "Missing or failing edge id provider."))); attr.addAttribute("", "", "source", "CDATA", getVertexId(g.getEdgeSource(e))); attr.addAttribute("", "", "target", "CDATA", getVertexId(g.getEdgeTarget(e))); if (exportEdgeTypes) { attr .addAttribute( "", "", TYPE_ATTRIBUTE_NAME, "CDATA", isGraphDirected ? "directed" : "undirected"); } if (exportEdgeWeights) { attr .addAttribute( "", "", WEIGHT_ATTRIBUTE_NAME, "CDATA", String.valueOf(g.getEdgeWeight(e))); } if (exportEdgeLabels) { getEdgeAttribute(e, LABEL_ATTRIBUTE_NAME).ifPresent(v -> { attr.addAttribute("", "", LABEL_ATTRIBUTE_NAME, "CDATA", v.getValue()); }); } handler.startElement("", "", "edge", attr); writeEdgeAttributeValues(handler, e); handler.endElement("", "", "edge"); } handler.endElement("", "", "edges"); } private class AttributeDetails { public String key; public GEXFAttributeType type; public String defaultValue; public String options; public AttributeDetails( String key, GEXFAttributeType type, String defaultValue, String options) { this.key = key; this.type = type; this.defaultValue = defaultValue; this.options = options; } } } SimpleGEXFEventDrivenImporter.java000066400000000000000000000436451402514743400336610ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gexf; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import org.xml.sax.*; import org.xml.sax.helpers.*; import javax.xml.*; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; import javax.xml.validation.*; import java.io.*; import java.util.*; /** * Imports a graph from a GEXF data source. The importer does not construct a graph but calls the * provided consumers with the appropriate arguments. Vertices are returned simply by their vertex * id and edges are returns as triples (source, target, weight) where weight maybe null. * *

    * The importer notifies lazily and completely out-of-order for any additional vertex, edge or graph * attributes in the input file. Users can register consumers for vertex, edge and graph attributes * after construction of the importer. Finally, default attribute values and any nested elements are * completely ignored. * *

    * This is a simple implementation with supports only a limited set of features of the GEXF * specification, oriented towards parsing speed. * *

    * For a description of the format see * https://gephi.org/gexf/format/index.html or the * GEXF Primer. *

    * *

    * Below is small example of a graph in GEXF format. * *

     * {@code
     * 
     * 
     *   
     *     
     *       
     *       
     *       
     *       
     *       
     *       
     *     
     *     
     *       
     *       
     *       
     *       
     *       
     *       
     *       
     *     
     *   
     * 
     * }
     * 
    * *

    * The importer by default validates the input using the 1.2draft * GEXF Schema. The user can (not * recommended) disable the validation by calling {@link #setSchemaValidation(boolean)}. Older * schemas are not supported. * * @author Dimitrios Michail */ public class SimpleGEXFEventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { private static final List SCHEMA_FILENAMES = List.of("viz.xsd", "gexf.xsd"); private boolean schemaValidation; /** * Constructs a new importer. */ public SimpleGEXFEventDrivenImporter() { super(); this.schemaValidation = true; } /** * Whether the importer validates the input * * @return true if the importer validates the input */ public boolean isSchemaValidation() { return schemaValidation; } /** * Set whether the importer should validate the input * * @param schemaValidation value for schema validation */ public void setSchemaValidation(boolean schemaValidation) { this.schemaValidation = schemaValidation; } @Override public void importInput(Reader input) { try { // parse XMLReader xmlReader = createXMLReader(); GEXFHandler handler = new GEXFHandler(); xmlReader.setContentHandler(handler); xmlReader.setErrorHandler(handler); notifyImportEvent(ImportEvent.START); xmlReader.parse(new InputSource(input)); notifyImportEvent(ImportEvent.END); } catch (Exception se) { throw new ImportException("Failed to parse GEXF", se); } } private Schema createSchema() throws SAXException { Source[] sources = SCHEMA_FILENAMES.stream().map(filename -> { InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename); if (is == null) { throw new ImportException("Failed to locate xsd: " + filename); } return is; }).map(is -> new StreamSource(is)).toArray(Source[]::new); SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); return factory.newSchema(sources); } private XMLReader createXMLReader() { try { // create parser SAXParserFactory spf = SAXParserFactory.newInstance(); if (schemaValidation) { spf.setSchema(createSchema()); } spf.setNamespaceAware(true); SAXParser saxParser = spf.newSAXParser(); // create reader return saxParser.getXMLReader(); } catch (Exception se) { throw new ImportException("Failed to parse GEXF", se); } } // content handler private class GEXFHandler extends DefaultHandler { private static final String GRAPH = "graph"; private final List GRAPH_ATTRS = List.of("defaultedgetype", "timeformat", "mode", "start", "end"); private static final String NODE = "node"; private static final String NODE_ID = "id"; private final List NODE_ATTRS = List.of("label", "pid"); private static final String EDGE = "edge"; private static final String EDGE_ID = "id"; private static final String EDGE_SOURCE = "source"; private static final String EDGE_TARGET = "target"; private static final String EDGE_WEIGHT = "weight"; private final List EDGE_ATTRS = List.of("type", "label"); private static final String ATTRIBUTES = "attributes"; private static final String ATTRIBUTES_CLASS = "class"; private static final String ATTRIBUTE = "attribute"; private static final String ATTRIBUTE_ID = "id"; private static final String ATTRIBUTE_TITLE = "title"; private static final String ATTRIBUTE_TYPE = "type"; private static final String ATTVALUES = "attvalues"; private static final String ATTVALUE = "attvalue"; private static final String ATTVALUE_FOR = "for"; private static final String ATTVALUE_VALUE = "value"; // parser state private int insideGraph; private int insideNode; private String currentNode; private int insideEdge; private Triple currentEdge; private int insideAttributes; private String attributesClass; private int insideAttribute; private int insideAttValues; private int insideAttValue; private Map nodeValidAttributes; private Map edgeValidAttributes; public GEXFHandler() { } @Override public void startDocument() throws SAXException { insideGraph = 0; insideNode = 0; currentNode = null; insideEdge = 0; currentEdge = null; insideAttributes = 0; attributesClass = null; insideAttribute = 0; insideAttValues = 0; insideAttValue = 0; nodeValidAttributes = new HashMap<>(); edgeValidAttributes = new HashMap<>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { switch (localName) { case GRAPH: insideGraph++; if (insideGraph == 1) { for (String attrName : GRAPH_ATTRS) { findAttribute(attrName, attributes).ifPresent(value -> { notifyGraphAttribute(attrName, DefaultAttribute.createAttribute(value)); }); } } break; case NODE: insideNode++; if (insideNode == 1 ^ insideEdge == 1) { String nodeId = findAttribute(NODE_ID, attributes) .orElseThrow( () -> new IllegalArgumentException("Node must have an identifier")); currentNode = nodeId; notifyVertex(currentNode); for (String attrName : NODE_ATTRS) { findAttribute(attrName, attributes).ifPresent(value -> { notifyVertexAttribute( currentNode, attrName, DefaultAttribute.createAttribute(value)); }); } } break; case EDGE: insideEdge++; if (insideNode == 1 ^ insideEdge == 1) { String sourceId = findAttribute(EDGE_SOURCE, attributes) .orElseThrow(() -> new IllegalArgumentException("Edge source missing")); String targetId = findAttribute(EDGE_TARGET, attributes) .orElseThrow(() -> new IllegalArgumentException("Edge target missing")); String edgeId = findAttribute(EDGE_ID, attributes).orElse(null); String edgeWeight = findAttribute(EDGE_WEIGHT, attributes).orElse(null); Double edgeWeightAsDouble = null; if (edgeWeight != null) { try { edgeWeightAsDouble = Double.parseDouble(edgeWeight); } catch (NumberFormatException e) { // ignore } } currentEdge = Triple.of(sourceId, targetId, edgeWeightAsDouble); notifyEdge(currentEdge); if (edgeId != null) { notifyEdgeAttribute( currentEdge, EDGE_ID, DefaultAttribute.createAttribute(edgeId)); } notifyEdgeAttribute( currentEdge, EDGE_SOURCE, DefaultAttribute.createAttribute(sourceId)); notifyEdgeAttribute( currentEdge, EDGE_TARGET, DefaultAttribute.createAttribute(targetId)); if (edgeWeightAsDouble != null) { notifyEdgeAttribute( currentEdge, "weight", DefaultAttribute.createAttribute(edgeWeightAsDouble)); } for (String attrName : EDGE_ATTRS) { findAttribute(attrName, attributes).ifPresent(value -> { notifyEdgeAttribute( currentEdge, attrName, DefaultAttribute.createAttribute(value)); }); } } break; case ATTRIBUTES: insideAttributes++; if (insideGraph == 1 && insideAttributes == 1) { attributesClass = findAttribute(ATTRIBUTES_CLASS, attributes) .orElseThrow( () -> new IllegalArgumentException("Attributes class missing")); } break; case ATTRIBUTE: insideAttribute++; if (insideGraph == 1 && insideAttributes == 1 && insideAttribute == 1) { String attributeId = findAttribute(ATTRIBUTE_ID, attributes) .orElseThrow(() -> new IllegalArgumentException("Attribute id missing")); String attributeTitle = findAttribute(ATTRIBUTE_TITLE, attributes) .orElseThrow(() -> new IllegalArgumentException("Attribute title missing")); String attributeType = findAttribute(ATTRIBUTE_TYPE, attributes) .orElseThrow(() -> new IllegalArgumentException("Attribute type missing")); Attribute curAttribute = new Attribute( attributeId, attributeTitle, GEXFAttributeType.create(attributeType)); if ("node".equals(attributesClass)) { nodeValidAttributes.put(curAttribute.id, curAttribute); } else if ("edge".equals(attributesClass)) { edgeValidAttributes.put(curAttribute.id, curAttribute); } else { throw new IllegalArgumentException("Wrong attribute class provided"); } } break; case ATTVALUES: insideAttValues++; break; case ATTVALUE: insideAttValue++; if (insideAttValues == 1 && insideAttValue == 1 && (insideNode == 1 ^ insideEdge == 1)) { String attValueFor = findAttribute(ATTVALUE_FOR, attributes) .orElseThrow(() -> new IllegalArgumentException("Attribute for missing")); String attValueValue = findAttribute(ATTVALUE_VALUE, attributes) .orElseThrow(() -> new IllegalArgumentException("Attribute value missing")); if (insideNode == 1 && currentNode != null) { Attribute attr = nodeValidAttributes.get(attValueFor); notifyVertexAttribute( currentNode, attr.title, new DefaultAttribute<>(attValueValue, toAttributeType(attr.type))); } else if (insideEdge == 1 && currentEdge != null) { Attribute attr = edgeValidAttributes.get(attValueFor); notifyEdgeAttribute( currentEdge, attr.title, new DefaultAttribute<>(attValueValue, toAttributeType(attr.type))); } } break; default: break; } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { switch (localName) { case GRAPH: insideGraph--; break; case NODE: insideNode--; if (insideNode == 0) { currentNode = null; } break; case EDGE: insideEdge--; if (insideEdge == 0) { currentEdge = null; } break; case ATTRIBUTES: insideAttributes--; if (insideAttributes == 0) { attributesClass = null; } break; case ATTRIBUTE: insideAttribute--; break; case ATTVALUES: insideAttValues--; break; case ATTVALUE: insideAttValue--; break; default: break; } } @Override public void warning(SAXParseException e) throws SAXException { throw e; } public void error(SAXParseException e) throws SAXException { throw e; } public void fatalError(SAXParseException e) throws SAXException { throw e; } private Optional findAttribute(String localName, Attributes attributes) { for (int i = 0; i < attributes.getLength(); i++) { String attrLocalName = attributes.getLocalName(i); if (attrLocalName.equals(localName)) { return Optional.ofNullable(attributes.getValue(i)); } } return Optional.empty(); } } private static class Attribute { String id; String title; GEXFAttributeType type; public Attribute(String id, String title, GEXFAttributeType type) { this.id = id; this.title = title; this.type = type; } } private static AttributeType toAttributeType(GEXFAttributeType type) { switch (type) { case BOOLEAN: return AttributeType.BOOLEAN; case INTEGER: return AttributeType.INT; case LONG: return AttributeType.LONG; case FLOAT: return AttributeType.FLOAT; case DOUBLE: return AttributeType.DOUBLE; case ANYURI: case LISTSTRING: case STRING: return AttributeType.STRING; default: return AttributeType.UNKNOWN; } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/SimpleGEXFImporter.java000066400000000000000000000245771402514743400315710ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gexf; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Imports a graph from a GEXF data source. * *

    * This is a simple implementation with supports only a limited set of features of the GEXF * specification, oriented towards parsing speed. * *

    * The importer uses the graph suppliers ({@link Graph#getVertexSupplier()} and * {@link Graph#getEdgeSupplier()}) in order to create new vertices and edges. Moreover, it notifies * lazily and completely out-of-order for any additional vertex, edge or graph attributes in the * input file. Users can register consumers for vertex, edge and graph attributes after construction * of the importer. Finally, default attribute values and any nested elements are completely * ignored. * *

    * For a description of the format see * https://gephi.org/gexf/format/index.html or the * GEXF Primer. *

    * *

    * Below is small example of a graph in GEXF format. * *

     * {@code
     * 
     * 
     *   
     *     
     *       
     *       
     *       
     *       
     *       
     *       
     *     
     *     
     *       
     *       
     *       
     *       
     *       
     *       
     *       
     *     
     *   
     * 
     * }
     * 
    * *

    * The importer reads the input into a graph which is provided by the user. In case the graph is * weighted and the corresponding edge attribute "weight" is defined, the importer also reads edge * weights. Otherwise edge weights are ignored. To test whether the graph is weighted, method * {@link Graph#getType()} can be used. * *

    * The provided graph object, where the imported graph will be stored, must be able to support the * features of the graph that is read. For example if the GEXF file contains self-loops then the * graph provided must also support self-loops. The same for multiple edges. Moreover, the parser * completely ignores the global attribute "defaultedgetype" and the edge attribute "type" which * denotes whether an edge is directed or not. Whether edges are directed or not depends on the * underlying implementation of the user provided graph object. * *

    * The importer by default validates the input using the 1.2draft * GEXF Schema. The user can (not * recommended) disable the validation by calling {@link #setSchemaValidation(boolean)}. Older * schemas are not supported. * *

    * The graph vertices and edges are build using the corresponding graph suppliers. The id of the * vertices in the input file are reported as a vertex attribute named * {@link #DEFAULT_VERTEX_ID_KEY}. * *

    * The default behavior of the importer is to use the graph vertex supplier in order to create * vertices. The user can also bypass vertex creation by providing a custom vertex factory method * using {@link #setVertexFactory(Function)}. The factory method is responsible to create a new * graph vertex given the vertex identifier read from file. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class SimpleGEXFImporter extends BaseEventDrivenImporter implements GraphImporter { /** * Default key used for vertex ID. */ public static final String DEFAULT_VERTEX_ID_KEY = "ID"; private static final String WEIGHT = "weight"; private boolean schemaValidation; private Function vertexFactory; /** * Constructs a new importer. */ public SimpleGEXFImporter() { super(); this.schemaValidation = true; } /** * Whether the importer validates the input * * @return true if the importer validates the input */ public boolean isSchemaValidation() { return schemaValidation; } /** * Set whether the importer should validate the input * * @param schemaValidation value for schema validation */ public void setSchemaValidation(boolean schemaValidation) { this.schemaValidation = schemaValidation; } /** * Get the user custom vertex factory. This is null by default and the graph supplier is used * instead. * * @return the user custom vertex factory */ public Function getVertexFactory() { return vertexFactory; } /** * Set the user custom vertex factory. The default behavior is being null in which case the * graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the file. * The method is called with parameter the vertex identifier from the file and should return the * actual graph vertex to add to the graph. * * @param vertexFactory a vertex factory */ public void setVertexFactory(Function vertexFactory) { this.vertexFactory = vertexFactory; } /** * Import a graph. * *

    * The provided graph must be able to support the features of the graph that is read. For * example if the GraphML file contains self-loops then the graph provided must also support * self-loops. The same for multiple edges. * * @param graph the output graph * @param input the input reader * @throws ImportException in case an error occurs, such as I/O or parse error */ @Override public void importGraph(Graph graph, Reader input) { SimpleGEXFEventDrivenImporter genericImporter = new SimpleGEXFEventDrivenImporter(); genericImporter.setSchemaValidation(schemaValidation); Consumers globalConsumer = new Consumers(graph); genericImporter.addGraphAttributeConsumer(globalConsumer.graphAttributeConsumer); genericImporter.addVertexAttributeConsumer(globalConsumer.vertexAttributeConsumer); genericImporter.addEdgeAttributeConsumer(globalConsumer.edgeAttributeConsumer); genericImporter.addVertexConsumer(globalConsumer.vertexConsumer); genericImporter.addEdgeConsumer(globalConsumer.edgeConsumer); genericImporter.importInput(input); } private class Consumers { private Graph graph; private Map nodesMap; private E lastEdge; private Triple lastTriple; public Consumers(Graph graph) { this.graph = graph; this.nodesMap = new HashMap<>(); this.lastEdge = null; this.lastTriple = null; } public final BiConsumer graphAttributeConsumer = (key, a) -> { notifyGraphAttribute(key, a); }; public final BiConsumer, Attribute> vertexAttributeConsumer = (vertexAndKey, a) -> { notifyVertexAttribute( mapNode(vertexAndKey.getFirst()), vertexAndKey.getSecond(), a); }; public final BiConsumer, String>, Attribute> edgeAttributeConsumer = (edgeAndKey, a) -> { Triple qe = edgeAndKey.getFirst(); if (qe == lastTriple) { if (qe.getThird() != null && WEIGHT.equals(edgeAndKey.getSecond()) && graph.getType().isWeighted()) { graph.setEdgeWeight(lastEdge, qe.getThird()); } notifyEdgeAttribute(lastEdge, edgeAndKey.getSecond(), a); } }; public final Consumer vertexConsumer = (vId) -> { V v = mapNode(vId); notifyVertex(v); notifyVertexAttribute(v, DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(vId)); }; public final Consumer> edgeConsumer = (qe) -> { if (lastTriple != qe) { String source = qe.getFirst(); String target = qe.getSecond(); Double weight = qe.getThird(); E e = graph.addEdge(mapNode(source), mapNode(target)); if (weight != null && graph.getType().isWeighted()) { graph.setEdgeWeight(e, weight); } lastEdge = e; lastTriple = qe; notifyEdge(lastEdge); } }; private V mapNode(String vId) { V vertex = nodesMap.get(vId); if (vertex == null) { if (vertexFactory != null) { vertex = vertexFactory.apply(vId); graph.addVertex(vertex); } else { vertex = graph.addVertex(); } nodesMap.put(vId, vertex); } return vertex; } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gexf/package-info.java000066400000000000000000000002751402514743400304550ustar00rootroot00000000000000/** * Graph Exchange XML Format (GEXF) importers/exporters. * * See here for a description of the format. */ package org.jgrapht.nio.gexf; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gml/000077500000000000000000000000001402514743400251105ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlEventDrivenImporter.java000066400000000000000000000344161402514743400323760ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gml; import org.antlr.v4.runtime.*; import org.antlr.v4.runtime.misc.*; import org.antlr.v4.runtime.tree.*; import org.apache.commons.text.*; import org.jgrapht.alg.util.Triple; import org.jgrapht.nio.*; import org.jgrapht.nio.gml.GmlParser.*; import java.io.*; import java.util.*; /** * Imports a graph from a GML file (Graph Modeling Language). * *

    * For a description of the format see * http://www.infosun.fmi.uni-passau.de/Graphlet/GML/. * *

    * Below is small example of a graph in GML format. * *

     * graph [
     *   node [ 
     *     id 1
     *   ]
     *   node [
     *     id 2
     *     label "Node 2 has an optional label"
     *   ]
     *   node [
     *     id 3
     *   ]
     *   edge [
     *     source 1
     *     target 2 
     *     weight 2.0
     *     label "Edge between 1 and 2"
     *   ]
     *   edge [
     *     source 2
     *     target 3
     *     weight 3.0
     *     label "Edge between 2 and 3"
     *   ]
     * ]
     * 
    * *

    * If the input file contains edge weights then the importer also reads edge weights. The importer * also supports reading additional string attributes such as label or custom user attributes. * String attributes are unescaped as if they are Java strings. * *

    * The parser completely ignores elements from the input that are not related to vertices or edges * of the graph. Moreover, complicated nested structures are simply returned as a whole. For * example, in the following graph * *

     * graph [
     *   node [ 
     *     id 1
     *   ]
     *   node [ 
     *     id 2
     *   ]
     *   edge [
     *     source 1
     *     target 2 
     *     points [ x 1.0 y 2.0 ]
     *   ]
     * ]
     * 
    * * the points attribute of the edge is returned as a string containing "[ x 1.0 y 2.0 ]". * * @author Dimitrios Michail */ public class GmlEventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { /** * Constructs a new importer. */ public GmlEventDrivenImporter() { super(); } @Override public void importInput(Reader input) throws ImportException { try { ThrowingErrorListener errorListener = new ThrowingErrorListener(); // create lexer GmlLexer lexer = new GmlLexer(CharStreams.fromReader(input)); lexer.removeErrorListeners(); lexer.addErrorListener(errorListener); // create parser GmlParser parser = new GmlParser(new CommonTokenStream(lexer)); parser.removeErrorListeners(); parser.addErrorListener(errorListener); // Specify our entry point GmlContext graphContext = parser.gml(); // Walk it and attach our listener ParseTreeWalker walker = new ParseTreeWalker(); NotifyGmlListener listener = new NotifyGmlListener(); notifyImportEvent(ImportEvent.START); walker.walk(listener, graphContext); listener.notifySingletons(); notifyImportEvent(ImportEvent.END); } catch (IOException e) { throw new ImportException("Failed to import gml graph: " + e.getMessage(), e); } catch (ParseCancellationException pe) { throw new ImportException("Failed to import gml graph: " + pe.getMessage(), pe); } catch (IllegalArgumentException iae) { throw new ImportException("Failed to import gml graph: " + iae.getMessage(), iae); } } private class ThrowingErrorListener extends BaseErrorListener { @Override public void syntaxError( Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) throws ParseCancellationException { throw new ParseCancellationException( "line " + line + ":" + charPositionInLine + " " + msg); } } // notify from parse tree private class NotifyGmlListener extends GmlBaseListener { private static final String NODE = "node"; private static final String EDGE = "edge"; private static final String GRAPH = "graph"; private static final String WEIGHT = "weight"; private static final String ID = "id"; private static final String SOURCE = "source"; private static final String TARGET = "target"; // current state of parser private boolean insideGraph; private boolean insideNode; private boolean insideEdge; private int level; private Integer nodeId; private Integer sourceId; private Integer targetId; private Double weight; private Map attributes; private StringBuilder stringBuffer; private int maxNodeId; private List singletons; public void notifySingletons() { for (Singleton s : singletons) { maxNodeId++; notifyVertex(maxNodeId); for (String attrKey : s.attributes.keySet()) { notifyVertexAttribute(maxNodeId, attrKey, s.attributes.get(attrKey)); } } } @Override public void enterGml(GmlParser.GmlContext ctx) { insideGraph = false; insideNode = false; insideEdge = false; level = 0; singletons = new ArrayList<>(); maxNodeId = 0; } @Override public void enterNumberKeyValue(GmlParser.NumberKeyValueContext ctx) { if (!insideNode && !insideEdge) { return; } if (level < 2) { return; } String key = ctx.ID().getText(); String value = ctx.NUMBER().getText(); if (level == 2) { if (insideNode) { if (key.equals(ID)) { try { nodeId = Integer.parseInt(value); } catch (NumberFormatException e) { // ignore error } } else { attributes.put(key, parseNumberAttribute(value)); } } else { // insideEdge assert insideEdge; switch (key) { case SOURCE: try { sourceId = Integer.parseInt(value); } catch (NumberFormatException e) { // ignore error } break; case TARGET: try { targetId = Integer.parseInt(value); } catch (NumberFormatException e) { // ignore error } break; case WEIGHT: try { weight = Double.parseDouble(value); } catch (NumberFormatException e) { // ignore error } break; default: attributes.put(key, parseNumberAttribute(value)); } } } else { assert level >= 3; /* * Inside a list. We simply concatenate everything here to allow the user to do * something fancier in user-code. */ stringBuffer.append(' '); stringBuffer.append(key); stringBuffer.append(' '); stringBuffer.append(value); } } @Override public void enterListKeyValue(GmlParser.ListKeyValueContext ctx) { String key = ctx.ID().getText(); if (level == 0 && key.equals(GRAPH)) { insideGraph = true; } else if (level == 1 && insideGraph && key.equals(NODE)) { insideNode = true; nodeId = null; attributes = new HashMap<>(); } else if (level == 1 && insideGraph && key.equals(EDGE)) { insideEdge = true; sourceId = null; targetId = null; weight = null; attributes = new HashMap<>(); } else if (insideNode || insideEdge) { if (level == 2) { stringBuffer = new StringBuilder(); stringBuffer.append('['); } else if (level >= 3) { stringBuffer.append(' '); stringBuffer.append(key); stringBuffer.append(' '); stringBuffer.append('['); } } level++; } @Override public void exitListKeyValue(GmlParser.ListKeyValueContext ctx) { String key = ctx.ID().getText(); level--; if (level == 0 && key.equals(GRAPH)) { insideGraph = false; } else if (level == 1 && insideGraph && key.equals(NODE)) { if (nodeId == null) { singletons.add(new Singleton(attributes)); } else { notifyVertex(nodeId); for (String attrKey : attributes.keySet()) { notifyVertexAttribute(nodeId, attrKey, attributes.get(attrKey)); } maxNodeId = Math.max(maxNodeId, nodeId); } insideNode = false; attributes = null; } else if (level == 1 && insideGraph && key.equals(EDGE)) { if (sourceId != null && targetId != null) { Triple et = Triple.of(sourceId, targetId, weight); notifyEdge(et); if (weight != null) { notifyEdgeAttribute(et, WEIGHT, DefaultAttribute.createAttribute(weight)); } for (String attrKey : attributes.keySet()) { notifyEdgeAttribute(et, attrKey, attributes.get(attrKey)); } } insideEdge = false; attributes = null; } else if (insideNode || insideEdge) { if (level == 2) { stringBuffer.append(' '); stringBuffer.append(']'); attributes .put( key, new DefaultAttribute<>(stringBuffer.toString(), AttributeType.UNKNOWN)); stringBuffer = null; } else if (level >= 3) { stringBuffer.append(' '); stringBuffer.append(']'); } } } @Override public void enterStringKeyValue(GmlParser.StringKeyValueContext ctx) { if (!insideNode && !insideEdge) { return; } if (level < 2) { return; } String key = ctx.ID().getText(); String text = ctx.STRING().getText(); String noQuotes = text.subSequence(1, text.length() - 1).toString(); String unescapedText = StringEscapeUtils.unescapeJava(noQuotes); if (level == 2) { /* * Store attribute */ if (key.equals(ID)) { throw new IllegalArgumentException("Invalid type for attribute id: string"); } else if (key.equals(SOURCE)) { throw new IllegalArgumentException("Invalid type for attribute source: string"); } else if (key.equals(TARGET)) { throw new IllegalArgumentException("Invalid type for attribute target: string"); } else if (key.equals(WEIGHT)) { throw new IllegalArgumentException("Invalid type for attribute weight: string"); } attributes.put(key, DefaultAttribute.createAttribute(unescapedText)); } else if (level >= 3) { /* * Inside a list. We simply concatenate everything here to allow the user to do * something fancier in user-code. */ stringBuffer.append(' '); stringBuffer.append(key); stringBuffer.append(' '); stringBuffer.append(text); } } private Attribute parseNumberAttribute(String value) { try { return DefaultAttribute.createAttribute(Integer.parseInt(value, 10)); } catch (NumberFormatException e) { // ignore } try { return DefaultAttribute.createAttribute(Long.parseLong(value, 10)); } catch (NumberFormatException e) { // ignore } try { return DefaultAttribute.createAttribute(Double.parseDouble(value)); } catch (NumberFormatException e) { // ignore } return DefaultAttribute.createAttribute(value); } } private class Singleton { Map attributes; public Singleton(Map attributes) { this.attributes = attributes; } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlExporter.java000066400000000000000000000263151402514743400302320ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gml; import org.apache.commons.text.*; import org.jgrapht.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Exports a graph into a GML file (Graph Modeling Language). * *

    * For a description of the format see * https://github.com/GunterMueller/UNI_PASSAU_FMI_Graph_Drawing/blob/master/GML/gml-technical-report.pdf. * *

    * The behavior of the exporter such as whether to print vertex labels, edge labels, and/or edge * weights can be adjusted using the {@link #setParameter(Parameter, boolean) setParameter} method. * When exporting labels, the exporter escapes them as Java strings. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class GmlExporter extends BaseExporter implements GraphExporter { private static final String CREATOR = "JGraphT GML Exporter"; private static final String VERSION = "1"; private static final String DELIM = " "; private static final String TAB1 = "\t"; private static final String TAB2 = "\t\t"; private static final String LABEL_ATTRIBUTE_KEY = "label"; private static final String WEIGHT_ATTRIBUTE_KEY = "weight"; private static final Set FORBIDDEN_VERTEX_CUSTOM_ATTRIBUTE_KEYS = Set.of("id"); private static final Set FORBIDDEN_EDGE_CUSTOM_ATTRIBUTE_KEYS = Set.of("id", "source", "target"); private final Set parameters; /** * Parameters that affect the behavior of the {@link GmlExporter} exporter. */ public enum Parameter { /** * If set the exporter outputs edge labels. The labels are found from the edge attribute * provider of the importer using the key "label". */ EXPORT_EDGE_LABELS, /** * If set the exporter outputs edge weights */ EXPORT_EDGE_WEIGHTS, /** * If set the exporter outputs all custom edge attributes. The attributes are located from * the edge attribute provider of the importer. Note that these attributes have lowest * priority compared to special handled ones like "label", or "weight" and cannot contain * special keys like "id", "source" and "target". */ EXPORT_CUSTOM_EDGE_ATTRIBUTES, /** * If set the exporter outputs vertex labels. The labels are found from the vertex attribute * provider of the importer using the key "label". */ EXPORT_VERTEX_LABELS, /** * If set the exporter outputs all custom vertex attributes. The attributes are located from * the vertex attribute provider of the importer. Note that these attributes have lowest * priority compared to special handled ones like "label" and cannot contain special keys * like "id". */ EXPORT_CUSTOM_VERTEX_ATTRIBUTES, /** * If set the exporter escapes all strings as Java strings, otherwise no escaping is * performed. */ ESCAPE_STRINGS_AS_JAVA, } /** * Creates a new GmlExporter object with integer id providers for the vertex identifiers. */ public GmlExporter() { this(new IntegerIdProvider<>()); } /** * Constructs a new GmlExporter object with the given id providers. * * @param vertexIdProvider for generating vertex IDs. Must not be null. */ public GmlExporter(Function vertexIdProvider) { super(vertexIdProvider); this.parameters = new HashSet<>(); } /** * Exports an graph into a plain text GML format. * * @param writer the writer * @param g the graph */ @Override public void exportGraph(Graph g, Writer writer) { PrintWriter out = new PrintWriter(writer); for (V from : g.vertexSet()) { // assign ids in vertex set iteration order getVertexId(from); } exportHeader(out); out.println("graph"); out.println("["); out.println(TAB1 + "label" + DELIM + quoted("")); if (g.getType().isDirected()) { out.println(TAB1 + "directed" + DELIM + "1"); } else { out.println(TAB1 + "directed" + DELIM + "0"); } exportVertices(out, g); exportEdges(out, g); out.println("]"); out.flush(); } /** * Return if a particular parameter of the exporter is enabled * * @param p the parameter * @return {@code true} if the parameter is set, {@code false} otherwise */ public boolean isParameter(Parameter p) { return parameters.contains(p); } /** * Set the value of a parameter of the exporter * * @param p the parameter * @param value the value to set */ public void setParameter(Parameter p, boolean value) { if (value) { parameters.add(p); } else { parameters.remove(p); } } private String quoted(final String s) { boolean escapeStringAsJava = parameters.contains(Parameter.ESCAPE_STRINGS_AS_JAVA); if (escapeStringAsJava) { return "\"" + StringEscapeUtils.escapeJava(s) + "\""; } else { return "\"" + s + "\""; } } private void exportHeader(PrintWriter out) { out.println("Creator" + DELIM + quoted(CREATOR)); out.println("Version" + DELIM + VERSION); } private void exportAttribute(PrintWriter out, String key, Attribute attribute) { AttributeType type = attribute.getType(); switch (type) { case INT: out.println(TAB2 + key + DELIM + Integer.valueOf(attribute.getValue())); break; case LONG: out.println(TAB2 + key + DELIM + Long.valueOf(attribute.getValue())); break; case FLOAT: out.println(TAB2 + key + DELIM + Float.valueOf(attribute.getValue())); break; case DOUBLE: out.println(TAB2 + key + DELIM + Double.valueOf(attribute.getValue())); break; default: out.println(TAB2 + key + DELIM + quoted(attribute.getValue())); break; } } private void exportVertices(PrintWriter out, Graph g) { boolean exportVertexLabels = parameters.contains(Parameter.EXPORT_VERTEX_LABELS); boolean exportCustomVertexAttributes = parameters.contains(Parameter.EXPORT_CUSTOM_VERTEX_ATTRIBUTES); for (V from : g.vertexSet()) { out.println(TAB1 + "node"); out.println(TAB1 + "["); out.println(TAB2 + "id" + DELIM + getVertexId(from)); if (exportVertexLabels) { String label = getVertexAttribute(from, LABEL_ATTRIBUTE_KEY) .map(Attribute::getValue).orElse(from.toString()); out.println(TAB2 + "label" + DELIM + quoted(label)); } if (exportCustomVertexAttributes) { getVertexAttributes(from).ifPresent(vertexAttributes -> { vertexAttributes.entrySet().stream().forEach(e -> { String customAttributeKey = e.getKey(); Attribute customAttributeValue = e.getValue(); if (FORBIDDEN_VERTEX_CUSTOM_ATTRIBUTE_KEYS.contains(customAttributeKey)) { throw new IllegalArgumentException( "Key " + customAttributeKey + " is reserved"); } if (LABEL_ATTRIBUTE_KEY.equals(customAttributeKey) && exportVertexLabels) { // give higher priority to vertex labels return; } exportAttribute(out, customAttributeKey, customAttributeValue); }); }); } out.println(TAB1 + "]"); } } private void exportEdges(PrintWriter out, Graph g) { boolean exportEdgeWeights = parameters.contains(Parameter.EXPORT_EDGE_WEIGHTS); boolean exportEdgeLabels = parameters.contains(Parameter.EXPORT_EDGE_LABELS); boolean exportCustomEdgeAttributes = parameters.contains(Parameter.EXPORT_CUSTOM_EDGE_ATTRIBUTES); for (E edge : g.edgeSet()) { out.println(TAB1 + "edge"); out.println(TAB1 + "["); getEdgeId(edge).ifPresent(eId -> { out.println(TAB2 + "id" + DELIM + eId); }); String s = getVertexId(g.getEdgeSource(edge)); out.println(TAB2 + "source" + DELIM + s); String t = getVertexId(g.getEdgeTarget(edge)); out.println(TAB2 + "target" + DELIM + t); if (exportEdgeLabels) { Attribute label = getEdgeAttribute(edge, LABEL_ATTRIBUTE_KEY) .orElse(DefaultAttribute.createAttribute(edge.toString())); exportAttribute(out, "label", label); } if (exportEdgeWeights && g.getType().isWeighted()) { exportAttribute( out, "weight", DefaultAttribute.createAttribute(g.getEdgeWeight(edge))); } if (exportCustomEdgeAttributes) { getEdgeAttributes(edge).ifPresent(edgeAttributes -> { edgeAttributes.entrySet().stream().forEach(e -> { String customAttributeKey = e.getKey(); Attribute customAttributeValue = e.getValue(); if (FORBIDDEN_EDGE_CUSTOM_ATTRIBUTE_KEYS.contains(customAttributeKey)) { throw new IllegalArgumentException( "Key " + customAttributeKey + " is reserved"); } if (LABEL_ATTRIBUTE_KEY.equals(customAttributeKey) && exportEdgeLabels) { // give higher priority to edge labels return; } if (WEIGHT_ATTRIBUTE_KEY.equals(customAttributeKey) && exportEdgeWeights) { // give higher priority to edge weights return; } exportAttribute(out, customAttributeKey, customAttributeValue); }); }); } out.println(TAB1 + "]"); } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gml/GmlImporter.java000066400000000000000000000170731402514743400302240ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gml; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Imports a graph from a GML file (Graph Modeling Language). * *

    * For a description of the format see * http://www.infosun.fmi.uni-passau.de/Graphlet/GML/. * *

    * Below is small example of a graph in GML format. * *

     * graph [
     *   node [ 
     *     id 1
     *   ]
     *   node [
     *     id 2
     *     label "Node 2 has an optional label"
     *   ]
     *   node [
     *     id 3
     *   ]
     *   edge [
     *     source 1
     *     target 2 
     *     weight 2.0
     *     label "Edge between 1 and 2"
     *   ]
     *   edge [
     *     source 2
     *     target 3
     *     weight 3.0
     *     label "Edge between 2 and 3"
     *   ]
     * ]
     * 
    * *

    * In case the graph is weighted then the importer also reads edge weights. Otherwise edge weights * are ignored. The importer also supports reading additional string attributes such as label or * custom user attributes. String attributes are unescaped as if they are Java strings. * *

    * The parser completely ignores elements from the input that are not related to vertices or edges * of the graph. Moreover, complicated nested structures are simply returned as a whole. For * example, in the following graph * *

     * graph [
     *   node [ 
     *     id 1
     *   ]
     *   node [ 
     *     id 2
     *   ]
     *   edge [
     *     source 1
     *     target 2 
     *     points [ x 1.0 y 2.0 ]
     *   ]
     * ]
     * 
    * * the points attribute of the edge is returned as a string containing "[ x 1.0 y 2.0 ]". * *

    * The graph vertices and edges are build using the corresponding graph suppliers. The id of the * vertices in the original file are reported as a vertex attribute named "ID". * *

    * The default behavior of the importer is to use the graph vertex supplier in order to create * vertices. The user can also bypass vertex creation by providing a custom vertex factory method * using {@link #setVertexFactory(Function)}. The factory method is responsible to create a new * graph vertex given the vertex identifier read from file. * * @param the vertex type * @param the edge type * * @author Dimitrios Michail */ public class GmlImporter extends BaseEventDrivenImporter implements GraphImporter { /** * Default key used for vertex ID. */ public static final String DEFAULT_VERTEX_ID_KEY = "ID"; private Function vertexFactory; /** * Constructs a new importer. */ public GmlImporter() { super(); } /** * Import a graph. * *

    * The provided graph must be able to support the features of the graph that is read. For * example if the GML file contains self-loops then the graph provided must also support * self-loops. The same for multiple edges. * *

    * If the provided graph is a weighted graph, the importer also reads edge weights. Otherwise * edge weights are ignored. * * @param graph the output graph * @param input the input reader * @throws ImportException in case an error occurs, such as I/O or parse error */ @Override public void importGraph(Graph graph, Reader input) { GmlEventDrivenImporter genericImporter = new GmlEventDrivenImporter(); Consumers consumers = new Consumers(graph); genericImporter.addVertexConsumer(consumers.vertexConsumer); genericImporter.addVertexAttributeConsumer(consumers.vertexAttributeConsumer); genericImporter.addEdgeConsumer(consumers.edgeConsumer); genericImporter.addEdgeAttributeConsumer(consumers.edgeAttributeConsumer); genericImporter.importInput(input); } /** * Get the user custom vertex factory. This is null by default and the graph supplier is used * instead. * * @return the user custom vertex factory */ public Function getVertexFactory() { return vertexFactory; } /** * Set the user custom vertex factory. The default behavior is being null in which case the * graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the file. * The method is called with parameter the vertex identifier from the file and should return the * actual graph vertex to add to the graph. * * @param vertexFactory a vertex factory */ public void setVertexFactory(Function vertexFactory) { this.vertexFactory = vertexFactory; } private class Consumers { private Graph graph; private GraphType graphType; private Map map; private Triple lastTriple; private E lastEdge; public Consumers(Graph graph) { this.graph = graph; this.graphType = graph.getType(); this.map = new HashMap<>(); } public final Consumer vertexConsumer = (t) -> { getVertex(t); }; public final BiConsumer, Attribute> vertexAttributeConsumer = (p, a) -> { Integer vertex = p.getFirst(); if (!map.containsKey(vertex)) { throw new ImportException("Node " + vertex + " does not exist"); } notifyVertexAttribute(map.get(vertex), p.getSecond(), a); }; public final Consumer> edgeConsumer = (t) -> { V from = getVertex(t.getFirst()); V to = getVertex(t.getSecond()); E e = graph.addEdge(from, to); if (graphType.isWeighted() && t.getThird() != null) { graph.setEdgeWeight(e, t.getThird()); } notifyEdge(e); lastTriple = t; lastEdge = e; }; public final BiConsumer, String>, Attribute> edgeAttributeConsumer = (p, a) -> { if (p.getFirst() == lastTriple) { notifyEdgeAttribute(lastEdge, p.getSecond(), a); } }; private V getVertex(Integer id) { V v = map.get(id); if (v == null) { if (vertexFactory != null) { v = vertexFactory.apply(id); graph.addVertex(v); } else { v = graph.addVertex(); } map.put(id, v); /* * Notify the first time we create the node. */ notifyVertex(v); notifyVertexAttribute( v, DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(id)); } return v; } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/gml/package-info.java000066400000000000000000000001001402514743400302660ustar00rootroot00000000000000/** * GML importers/exporters */ package org.jgrapht.nio.gml; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/000077500000000000000000000000001402514743400255205ustar00rootroot00000000000000Graph6Sparse6EventDrivenImporter.java000066400000000000000000000230141402514743400345730ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graph6; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; /** * Importer which reads graphs in graph6 or sparse6 format. * *

    * A description of the format can be found * here. graph6 and sparse6 are * formats for storing undirected graphs in a compact manner, using only printable ASCII characters. * Files in these formats have text format and contain one line per graph. graph6 is suitable for * small graphs, or large dense graphs. sparse6 is more space-efficient for large sparse graphs. * Typically, files storing graph6 graphs have the 'g6' extension. Similarly, files storing sparse6 * graphs have a 's6' file extension. sparse6 graphs support loops and multiple edges, graph6 graphs * do not. * *

    * Note that a g6/s6 string may contain backslashes '\'. Thus, escaping is required. E.g. * *

     * ":?@MnDA\oi"
     * 
    * * may result in undefined behavior. This should have been: * *
     * ":?@MnDA\\oi"
     * 
    * * @author Joris Kinable */ public class Graph6Sparse6EventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { private static final String GRAPH_STRING_SEEMS_TO_BE_CORRUPT_INVALID_NUMBER_OF_VERTICES = "Graph string seems to be corrupt. Invalid number of vertices."; enum Format { GRAPH6, SPARSE6 } // ~ Constructors ---------------------------------------------------------- /** * Construct a new importer */ public Graph6Sparse6EventDrivenImporter() { super(); } @Override public void importInput(Reader input) throws ImportException { // convert to buffered BufferedReader in; if (input instanceof BufferedReader) { in = (BufferedReader) input; } else { in = new BufferedReader(input); } notifyImportEvent(ImportEvent.START); // read line String g6 = null; try { g6 = in.readLine(); } catch (IOException e) { throw new ImportException("Failed to read graph: " + e.getMessage()); } if (g6.isEmpty()) { throw new ImportException("Failed to read graph: empty line"); } // remove any new line characters g6 = g6.replace("\n", "").replace("\r", ""); // do the actual parsing new Parser(g6).parse(); notifyImportEvent(ImportEvent.END); } /** * The actual parser. The parser assumes the input is a single line. */ private class Parser { private Format format; private byte[] bytes; private int byteIndex; private int bitIndex; private int n; /** * Create a new parser. * * @param inputLine an input line */ public Parser(String inputLine) { this.format = Format.GRAPH6; if (inputLine.startsWith(":")) { inputLine = inputLine.substring(1, inputLine.length()); this.format = Format.SPARSE6; } else if (inputLine.startsWith(">>sparse6<<:")) { inputLine = inputLine.substring(12, inputLine.length()); this.format = Format.SPARSE6; } else if (inputLine.startsWith(">>graph6<<")) { inputLine = inputLine.substring(10, inputLine.length()); } this.bytes = inputLine.getBytes(); this.byteIndex = 0; this.bitIndex = 0; this.n = 0; } public void parse() { validateInput(); readNumberOfVertices(); notifyVertexCount(n); for (int i = 0; i < n; i++) { notifyVertex(i); } if (format == Format.GRAPH6) readGraph6(); else readSparse6(); } private void readGraph6() throws ImportException { // check whether there's enough data int requiredBytes = (int) Math.ceil(n * (n - 1) / 12.0) + byteIndex; if (bytes.length < requiredBytes) throw new ImportException( "Graph string seems to be corrupt. Not enough data to read graph6 graph"); // Read the lower triangle of the adjacency matrix of G for (int i = 0; i < n; i++) { for (int j = 0; j < i; j++) { int bit = getBits(1); if (bit == 1) { notifyEdge(Pair.of(i, j)); } } } } private void readSparse6() throws ImportException { // number of bits needed to represent n-1 in binary int k = (int) Math.ceil(Math.log(n) / Math.log(2)); // Current vertex int v = 0; // The remaining bytes encode a sequence b[0] x[0] b[1] x[1] b[2] x[2] ... b[m] x[m] // Read blocks. In decoding, an incomplete (b,x) pair at the end is discarded. int dataBits = bytes.length * 6 - (byteIndex * 6 + bitIndex); while (dataBits >= 1 + k) { // while there's data remaining int b = getBits(1); // Read x[i] int x = getBits(k); // Read b[i] if (b == 1) v++; if (v >= n) // Ignore the last bit, this is just padding break; if (x > v) v = x; else notifyEdge(Pair.of(x, v)); dataBits -= 1 + k; } } /** * Check whether the g6 or s6 encoding contains any obvious errors * * @throws ImportException in case any error occurs, such as I/O or parse error */ private void validateInput() throws ImportException { for (byte b : bytes) if (b < 63 || b > 126) throw new ImportException( "Graph string seems to be corrupt. Illegal character detected: " + b); } /** * Read the number of vertices in the graph * * @throws ImportException in case any error occurs, such as I/O or parse error */ private void readNumberOfVertices() throws ImportException { // Determine whether the number of vertices is encoded in 1, 4 or 8 bytes. int n; if (bytes.length > 8 && bytes[0] == 126 && bytes[1] == 126) { byteIndex += 2; // Strip the first 2 garbage bytes n = getBits(36); if (n < 258048) throw new ImportException( GRAPH_STRING_SEEMS_TO_BE_CORRUPT_INVALID_NUMBER_OF_VERTICES); } else if (bytes.length > 4 && bytes[0] == 126) { byteIndex++; // Strip the first garbage byte n = getBits(18); if (n < 63 || n > 258047) throw new ImportException( GRAPH_STRING_SEEMS_TO_BE_CORRUPT_INVALID_NUMBER_OF_VERTICES); } else { n = getBits(6); if (n < 0 || n > 62) throw new ImportException( GRAPH_STRING_SEEMS_TO_BE_CORRUPT_INVALID_NUMBER_OF_VERTICES); } this.n = n; } /** * Converts the next k bits of data to an integer * * @param k number of bits * @return the next k bits of data represented by an integer */ private int getBits(int k) throws ImportException { int value = 0; // Read minimum{bits we need, remaining bits in current byte} if (bitIndex > 0 || k < 6) { int x = Math.min(k, 6 - bitIndex); int mask = (1 << x) - 1; int y = (bytes[byteIndex] - 63) >> (6 - bitIndex - x); y &= mask; value = (value << k) + y; k -= x; bitIndex += x; if (bitIndex == 6) { byteIndex++; bitIndex = 0; } } // Read blocks of 6 bits at a time int blocks = k / 6; for (int j = 0; j < blocks; j++) { value = (value << 6) + bytes[byteIndex] - 63; byteIndex++; k -= 6; } // Read remaining bits if (k > 0) { int y = bytes[byteIndex] - 63; y = y >> (6 - k); value = (value << k) + y; bitIndex = k; } return value; } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6Exporter.java000066400000000000000000000165641402514743400325230ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graph6; import org.jgrapht.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; /** * Exporter which exports graphs in graph6 or sparse6 format. A description of the format can be * found here. graph6 and sparse6 * are formats for storing undirected graphs in a compact manner, using only printable ASCII * characters. Files in these formats have text format and contain one line per graph. graph6 is * suitable for small graphs, or large dense graphs. sparse6 is more space-efficient for large * sparse graphs. Typically, files storing graph6 graphs have the 'g6' extension. Similarly, files * storing sparse6 graphs have a 's6' file extension. sparse6 graphs support loops and multiple * edges, graph6 graphs do not. *

    * In particular, the length of a Graph6 string representation of a graph depends only on the number * of vertices. However, this also means that graphs with few edges take as much space as graphs * with many edges. On the other hand, Sparse6 is a variable length format which can use * dramatically less space for sparse graphs but can have a much larger storage size for dense * graphs. * * @author Joris Kinable * * @param graph vertex type * @param graph edge type */ public class Graph6Sparse6Exporter implements GraphExporter { /** * Format type: graph6 (g6) or sparse6(s6) */ public enum Format { GRAPH6, SPARSE6 } private Format format; private ByteArrayOutputStream byteArrayOutputStream; /** * The default format used by the exporter. */ public static final Format DEFAULT_GRAPH6SPARSE6_FORMAT = Format.GRAPH6; /** * Constructs a new exporter with a given vertex ID provider. * */ public Graph6Sparse6Exporter() { this(DEFAULT_GRAPH6SPARSE6_FORMAT); } /** * Constructs a new exporter with a given vertex ID provider. * * @param format the format to use */ public Graph6Sparse6Exporter(Format format) { this.format = Objects.requireNonNull(format, "Format cannot be null"); } @Override public void exportGraph(Graph g, Writer writer) throws ExportException { GraphTests.requireUndirected(g); if (format == Format.GRAPH6 && !GraphTests.isSimple(g)) throw new ExportException( "Graphs exported in graph6 format cannot contain loops or multiple edges."); // Map all vertices to a unique integer List vertices = new ArrayList<>(g.vertexSet()); byteArrayOutputStream = new ByteArrayOutputStream(); currentByte = 0; bitIndex = 0; try { if (format == Format.SPARSE6) writeSparse6(g, vertices); else writeGraph6(g, vertices); } catch (IOException e) { e.printStackTrace(); } String g6 = ""; try { g6 = byteArrayOutputStream.toString("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } PrintWriter out = new PrintWriter(writer); out.print(g6); out.flush(); } private void writeSparse6(Graph g, List vertices) throws IOException { int[][] edges = new int[g.edgeSet().size()][2]; int index = 0; for (int j = 0; j < vertices.size(); j++) { for (int i = 0; i <= j; i++) { if (g.containsEdge(vertices.get(i), vertices.get(j))) { for (int p = 0; p < g.getAllEdges(vertices.get(i), vertices.get(j)).size(); p++) { edges[index][0] = i; edges[index][1] = j; index++; } } } } // sparse6 format always starts with ":" byteArrayOutputStream.write(":".getBytes()); writeNumberOfVertices(vertices.size()); // number of bits needed to represent n-1 in binary int k = (int) Math.ceil(Math.log(vertices.size()) / Math.log(2)); int m = 0; int v = 0; while (m < edges.length) { if (edges[m][1] > v + 1) { writeBit(true); writeIntInKBits(edges[m][1], k); v = edges[m][1]; } else if (edges[m][1] == v + 1) { writeBit(true); writeIntInKBits(edges[m][0], k); v++; m++; } else { writeBit(false); writeIntInKBits(edges[m][0], k); m++; } } // Pad right hand side with '1's to fill the last byte. This may not be the 'correct' way of // padding as // described in the sparse6 format descr, but it's hard to make sense of the sparse6 // description. This seems to work fine. if (bitIndex != 0) { int padding = 6 - bitIndex; for (int i = 0; i < padding; i++) writeBit(true); writeByte(); // push the last byte } } private void writeGraph6(Graph g, List vertices) throws IOException { writeNumberOfVertices(vertices.size()); // Write the lower triangle of the adjacency matrix of G as a bit vector x of length // n(n-1)/2, // using the ordering (0,1),(0,2),(1,2),(0,3),(1,3),(2,3),...,(n-1,n). for (int i = 0; i < vertices.size(); i++) for (int j = 0; j < i; j++) writeBit(g.containsEdge(vertices.get(i), vertices.get(j))); writeByte(); // Finish writing the last byte } private void writeNumberOfVertices(int n) throws IOException { assert n >= 0; if (n <= 62) byteArrayOutputStream.write(n + 63); else if (n <= 258047) { // write number in 4 bytes writeIntInKBits(63, 6); writeIntInKBits(n, 18); } else { // write number in 8 bytes writeIntInKBits(63, 6); writeIntInKBits(63, 6); writeIntInKBits(n, 36); } } private byte currentByte; private int bitIndex; private void writeIntInKBits(int number, int k) { for (int i = k - 1; i >= 0; i--) writeBit((number & (1 << i)) != 0); } private void writeBit(boolean bit) { if (bitIndex == 6) writeByte(); if (bit) currentByte |= 1 << (5 - bitIndex); bitIndex++; } private void writeByte() { byteArrayOutputStream.write(currentByte + 63); currentByte = 0; bitIndex = 0; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/Graph6Sparse6Importer.java000066400000000000000000000142621402514743400325050ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graph6; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Importer which reads graphs in graph6 or sparse6 format. * *

    * A description of the format can be found * here. graph6 and sparse6 are * formats for storing undirected graphs in a compact manner, using only printable ASCII characters. * Files in these formats have text format and contain one line per graph. graph6 is suitable for * small graphs, or large dense graphs. sparse6 is more space-efficient for large sparse graphs. * Typically, files storing graph6 graphs have the 'g6' extension. Similarly, files storing sparse6 * graphs have a 's6' file extension. sparse6 graphs support loops and multiple edges, graph6 graphs * do not. * *

    * Note that a g6/s6 string may contain backslashes '\'. Thus, escaping is required. E.g. * *

     * ":?@MnDA\oi"
     * 
    * * may result in undefined behavior. This should have been: * *
     * ":?@MnDA\\oi"
     * 
    * *

    * The graph vertices and edges are build using the corresponding graph suppliers. The id of the * vertices in the original file are reported as a vertex attribute named "ID". Note, however, that * in this file format the identifiers from the input files are always going to be integers starting * from zero, as the format does not retain such information in order to achieve compactness. * *

    * The default behavior of the importer is to use the graph vertex supplier in order to create * vertices. The user can also bypass vertex creation by providing a custom vertex factory method * using {@link #setVertexFactory(Function)}. The factory method is responsible to create a new * graph vertex given the vertex identifier read from file. * * @author Dimitrios Michail * * @param graph vertex type * @param graph edge type */ public class Graph6Sparse6Importer extends BaseEventDrivenImporter implements GraphImporter { /** * Default key used for vertex ID. */ public static final String DEFAULT_VERTEX_ID_KEY = "ID"; private Function vertexFactory; /** * Construct a new importer */ public Graph6Sparse6Importer() { super(); } /** * Get the user custom vertex factory. This is null by default and the graph supplier is used * instead. * * @return the user custom vertex factory */ public Function getVertexFactory() { return vertexFactory; } /** * Set the user custom vertex factory. The default behavior is being null in which case the * graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the file. * The method is called with parameter the vertex identifier from the file and should return the * actual graph vertex to add to the graph. * * @param vertexFactory a vertex factory */ public void setVertexFactory(Function vertexFactory) { this.vertexFactory = vertexFactory; } /** * Import a graph. * *

    * The provided graph must be able to support the features of the graph that is read. For * example if the file contains self-loops then the graph provided must also support self-loops. * The same for multiple edges. * * @param graph the output graph * @param input the input reader * @throws ImportException in case an error occurs, such as I/O or parse error */ @Override public void importGraph(Graph graph, Reader input) { Graph6Sparse6EventDrivenImporter genericImporter = new Graph6Sparse6EventDrivenImporter(); Consumers consumers = new Consumers(graph); genericImporter.addVertexConsumer(consumers.vertexConsumer); genericImporter.addEdgeConsumer(consumers.edgeConsumer); genericImporter.importInput(input); } private class Consumers { private Graph graph; private Map map; public Consumers(Graph graph) { this.graph = graph; this.map = new HashMap(); } public final Consumer vertexConsumer = (t) -> { if (map.containsKey(t)) { throw new ImportException("Node " + t + " reported twice"); } V v; if (vertexFactory != null) { v = vertexFactory.apply(t); graph.addVertex(v); } else { v = graph.addVertex(); } map.put(t, v); /* * Notify the first time we create the node. */ notifyVertex(v); notifyVertexAttribute(v, DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(t)); }; public final Consumer> edgeConsumer = (p) -> { int source = p.getFirst(); V from = map.get(p.getFirst()); if (from == null) { throw new ImportException("Node " + source + " does not exist"); } int target = p.getSecond(); V to = map.get(target); if (to == null) { throw new ImportException("Node " + target + " does not exist"); } E e = graph.addEdge(from, to); notifyEdge(e); }; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graph6/package-info.java000066400000000000000000000001341402514743400307050ustar00rootroot00000000000000/** * Graph6, sparse6 and digraph6 importers/exporters */ package org.jgrapht.nio.graph6; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/000077500000000000000000000000001402514743400257635ustar00rootroot00000000000000GraphMLEventDrivenImporter.java000066400000000000000000000530021402514743400337350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import org.xml.sax.*; import org.xml.sax.helpers.*; import javax.xml.*; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; import javax.xml.validation.*; import java.io.*; import java.util.*; import java.util.Map.*; /** * Imports a graph from a GraphML data source. * *

    * For a description of the format see * http://en.wikipedia.org/wiki/ GraphML or the * GraphML Primer. *

    * *

    * Below is small example of a graph in GraphML format. * *

     * {@code
     * 
     * 
     *   
     *     yellow
     *   
     *   
     *   
     *     
     *       green
     *     
     *     
     *     
     *       blue
     *     
     *     
     *       red
     *     
     *     
     *     
     *       turquoise
     *     
     *     
     *       1.0
     *     
     *     
     *       1.0
     *     
     *     
     *       2.0
     *     
     *     
     *     
     *     
     *     
     *       1.1
     *     
     *   
     * 
     * }
     * 
    * *

    * In case the corresponding edge key with attr.name="weight" is defined, the importer also reads * edge weights. Otherwise edge weights are ignored. * *

    * GraphML-Attributes Values are read as string key-value pairs and passed using vertex and edge * attribute consumers. * *

    * The importer by default validates the input using the 1.0 * GraphML Schema. The user can * (not recommended) disable the validation by calling {@link #setSchemaValidation(boolean)}. * * @author Dimitrios Michail */ public class GraphMLEventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { private static final String GRAPHML_SCHEMA_FILENAME = "graphml.xsd"; private static final String XLINK_SCHEMA_FILENAME = "xlink.xsd"; // special attributes private static final String EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME = "weight"; private String edgeWeightAttributeName = EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME; private boolean schemaValidation; /** * Constructs a new importer. */ public GraphMLEventDrivenImporter() { this.schemaValidation = true; } /** * Get the attribute name for edge weights * * @return the attribute name */ public String getEdgeWeightAttributeName() { return edgeWeightAttributeName; } /** * Set the attribute name to use for edge weights. * * @param edgeWeightAttributeName the attribute name */ public void setEdgeWeightAttributeName(String edgeWeightAttributeName) { if (edgeWeightAttributeName == null) { throw new IllegalArgumentException("Edge weight attribute name cannot be null"); } this.edgeWeightAttributeName = edgeWeightAttributeName; } /** * Whether the importer validates the input * * @return true if the importer validates the input */ public boolean isSchemaValidation() { return schemaValidation; } /** * Set whether the importer should validate the input * * @param schemaValidation value for schema validation */ public void setSchemaValidation(boolean schemaValidation) { this.schemaValidation = schemaValidation; } @Override public void importInput(Reader input) { try { // parse XMLReader xmlReader = createXMLReader(); GraphMLHandler handler = new GraphMLHandler(); xmlReader.setContentHandler(handler); xmlReader.setErrorHandler(handler); notifyImportEvent(ImportEvent.START); xmlReader.parse(new InputSource(input)); handler.notifyInterestedParties(); notifyImportEvent(ImportEvent.END); } catch (Exception se) { throw new ImportException("Failed to parse GraphML", se); } } private XMLReader createXMLReader() throws ImportException { try { SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); // create parser SAXParserFactory spf = SAXParserFactory.newInstance(); if (schemaValidation) { // load schema InputStream xsdStream = Thread .currentThread().getContextClassLoader() .getResourceAsStream(GRAPHML_SCHEMA_FILENAME); if (xsdStream == null) { throw new ImportException("Failed to locate GraphML xsd"); } InputStream xlinkStream = Thread .currentThread().getContextClassLoader() .getResourceAsStream(XLINK_SCHEMA_FILENAME); if (xlinkStream == null) { throw new ImportException("Failed to locate XLink xsd"); } Source[] sources = new Source[2]; sources[0] = new StreamSource(xlinkStream); sources[1] = new StreamSource(xsdStream); Schema schema = schemaFactory.newSchema(sources); spf.setSchema(schema); } spf.setNamespaceAware(true); SAXParser saxParser = spf.newSAXParser(); // create reader return saxParser.getXMLReader(); } catch (Exception se) { throw new ImportException("Failed to parse GraphML", se); } } // content handler private class GraphMLHandler extends DefaultHandler { private static final String GRAPH = "graph"; private static final String GRAPH_ID = "id"; private static final String NODE = "node"; private static final String NODE_ID = "id"; private static final String EDGE = "edge"; private static final String ALL = "all"; private static final String EDGE_SOURCE = "source"; private static final String EDGE_TARGET = "target"; private static final String KEY = "key"; private static final String KEY_FOR = "for"; private static final String KEY_ATTR_NAME = "attr.name"; private static final String KEY_ATTR_TYPE = "attr.type"; private static final String KEY_ID = "id"; private static final String DEFAULT = "default"; private static final String DATA = "data"; private static final String DATA_KEY = "key"; // collect graph elements here private Map nodes; private List edges; // record state of parser private boolean insideDefault; private boolean insideData; // temporary state while reading elements // stack needed due to nested graphs in GraphML private Data currentData; private Key currentKey; private Deque currentGraphElement; // collect custom keys private Map nodeValidKeys; private Map edgeValidKeys; // notify interested parties public void notifyInterestedParties() throws ImportException { if (nodes.isEmpty()) { return; } // nodes Set graphNodes = new HashSet<>(); for (Entry en : nodes.entrySet()) { String nodeId = en.getKey(); if (nodeId == null) { throw new ImportException("Node id missing"); } // create attributes Map collectedAttributes = en.getValue().attributes; Map finalAttributes = new LinkedHashMap<>(); for (Key validKey : nodeValidKeys.values()) { String validId = validKey.id; AttributeType validType = validKey.type; if (collectedAttributes.containsKey(validId)) { finalAttributes .put( validKey.attributeName, new DefaultAttribute<>( collectedAttributes.get(validId), validType)); } else if (validKey.defaultValue != null) { finalAttributes .put( validKey.attributeName, new DefaultAttribute<>(validKey.defaultValue, validType)); } } notifyVertex(nodeId); for (String key : finalAttributes.keySet()) { notifyVertexAttribute(nodeId, key, finalAttributes.get(key)); } graphNodes.add(nodeId); } // check how to handle special edge weight boolean handleSpecialEdgeWeights = false; double defaultSpecialEdgeWeight = Graph.DEFAULT_EDGE_WEIGHT; for (Key k : edgeValidKeys.values()) { if (k.attributeName.equals(edgeWeightAttributeName)) { handleSpecialEdgeWeights = true; String defaultValue = k.defaultValue; try { if (defaultValue != null) { defaultSpecialEdgeWeight = Double.parseDouble(defaultValue); } } catch (NumberFormatException e) { // ignore } // first key only which maps to special edge "weight" break; } } // create edges for (GraphElement p : edges) { if (p.id1 == null) { throw new ImportException("Edge source vertex missing"); } if (!graphNodes.contains(p.id1)) { throw new ImportException("Source vertex " + p.id1 + " not found"); } if (p.id2 == null) { throw new ImportException("Edge target vertex missing"); } if (!graphNodes.contains(p.id2)) { throw new ImportException("Target vertex " + p.id2 + " not found"); } // create attributes Map collectedAttributes = p.attributes; Map finalAttributes = new LinkedHashMap<>(); for (Key validKey : edgeValidKeys.values()) { String validId = validKey.id; AttributeType validType = validKey.type; if (collectedAttributes.containsKey(validId)) { finalAttributes .put( validKey.attributeName, new DefaultAttribute<>( collectedAttributes.get(validId), validType)); } else { if (validKey.defaultValue != null) { finalAttributes .put( validKey.attributeName, new DefaultAttribute<>(validKey.defaultValue, validType)); } } } Triple te = Triple.of(p.id1, p.id2, null); // special handling for weighted graphs if (handleSpecialEdgeWeights) { if (finalAttributes.containsKey(edgeWeightAttributeName)) { try { te .setThird( Double .parseDouble( finalAttributes .get(edgeWeightAttributeName).getValue())); } catch (NumberFormatException nfe) { te.setThird(defaultSpecialEdgeWeight); } } } notifyEdge(te); for (String key : finalAttributes.keySet()) { notifyEdgeAttribute(te, key, finalAttributes.get(key)); } } } @Override public void startDocument() throws SAXException { nodes = new LinkedHashMap<>(); edges = new ArrayList<>(); nodeValidKeys = new LinkedHashMap<>(); edgeValidKeys = new LinkedHashMap<>(); insideDefault = false; insideData = false; currentKey = null; currentData = null; currentGraphElement = new ArrayDeque<>(); currentGraphElement.push(new GraphElement("graphml")); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { switch (localName) { case GRAPH: currentGraphElement.push(new GraphElement(findAttribute(GRAPH_ID, attributes))); break; case NODE: currentGraphElement.push(new GraphElement(findAttribute(NODE_ID, attributes))); break; case EDGE: currentGraphElement .push( new GraphElement( findAttribute(EDGE_SOURCE, attributes), findAttribute(EDGE_TARGET, attributes))); break; case KEY: String keyId = findAttribute(KEY_ID, attributes); String keyFor = findAttribute(KEY_FOR, attributes); String keyAttrName = findAttribute(KEY_ATTR_NAME, attributes); String keyAttrType = findAttribute(KEY_ATTR_TYPE, attributes); currentKey = new Key(keyId, keyAttrName, null, null); if (keyAttrType != null) { currentKey.type = AttributeType.create(keyAttrType); } if (keyFor != null) { switch (keyFor) { case EDGE: currentKey.target = KeyTarget.EDGE; break; case NODE: currentKey.target = KeyTarget.NODE; break; case ALL: currentKey.target = KeyTarget.ALL; break; } } break; case DEFAULT: insideDefault = true; break; case DATA: insideData = true; currentData = new Data(findAttribute(DATA_KEY, attributes), null); break; default: break; } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { switch (localName) { case GRAPH: currentGraphElement.pop(); break; case NODE: GraphElement currentNode = currentGraphElement.pop(); if (nodes.containsKey(currentNode.id1)) { throw new SAXException("Node with id " + currentNode.id1 + " already exists"); } nodes.put(currentNode.id1, currentNode); break; case EDGE: GraphElement currentEdge = currentGraphElement.pop(); edges.add(currentEdge); break; case KEY: if (currentKey.isValid()) { switch (currentKey.target) { case NODE: nodeValidKeys.put(currentKey.id, currentKey); break; case EDGE: edgeValidKeys.put(currentKey.id, currentKey); break; case ALL: nodeValidKeys.put(currentKey.id, currentKey); edgeValidKeys.put(currentKey.id, currentKey); break; } } currentKey = null; break; case DEFAULT: insideDefault = false; break; case DATA: if (currentData.isValid()) { currentGraphElement.peek().attributes.put(currentData.key, currentData.value); } insideData = false; currentData = null; break; default: break; } } @Override public void characters(char ch[], int start, int length) throws SAXException { if (insideDefault) { if (currentKey.defaultValue != null) { currentKey.defaultValue += new String(ch, start, length); } else { currentKey.defaultValue = new String(ch, start, length); } } else if (insideData) { if (currentData.value != null) { currentData.value += new String(ch, start, length); } else { currentData.value = new String(ch, start, length); } } } @Override public void warning(SAXParseException e) throws SAXException { throw e; } public void error(SAXParseException e) throws SAXException { throw e; } public void fatalError(SAXParseException e) throws SAXException { throw e; } private String findAttribute(String localName, Attributes attributes) { for (int i = 0; i < attributes.getLength(); i++) { String attrLocalName = attributes.getLocalName(i); if (attrLocalName.equals(localName)) { return attributes.getValue(i); } } return null; } } // ----- Helper classes for storing partial parser results ----- private enum KeyTarget { NODE, EDGE, ALL } private class Key { String id; String attributeName; String defaultValue; KeyTarget target; AttributeType type; public Key(String id, String attributeName, String defaultValue, KeyTarget target) { this.id = id; this.attributeName = attributeName; this.defaultValue = defaultValue; this.target = target; this.type = AttributeType.STRING; } public boolean isValid() { return id != null && attributeName != null && target != null; } } private class Data { String key; String value; public Data(String key, String value) { this.key = key; this.value = value; } public boolean isValid() { return key != null && value != null; } } private class GraphElement { String id1; String id2; Map attributes; public GraphElement(String id1) { this.id1 = id1; this.id2 = null; this.attributes = new LinkedHashMap(); } public GraphElement(String id1, String id2) { this.id1 = id1; this.id2 = id2; this.attributes = new LinkedHashMap(); } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLExporter.java000066400000000000000000000463601402514743400316620ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by Trevor Harmon and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.*; import org.jgrapht.nio.*; import org.xml.sax.*; import org.xml.sax.helpers.*; import javax.xml.transform.*; import javax.xml.transform.sax.*; import javax.xml.transform.stream.*; import java.io.*; import java.util.*; import java.util.Map.*; import java.util.function.*; /** * Exports a graph as GraphML. * *

    * For a description of the format see * http://en.wikipedia.org/wiki/ GraphML. *

    * * @param the graph vertex type * @param the graph edge type * * @author Trevor Harmon * @author Dimitrios Michail */ public class GraphMLExporter extends BaseExporter implements GraphExporter { // registered attributes private Map registeredAttributes = new LinkedHashMap<>(); private static final String ATTRIBUTE_KEY_PREFIX = "key"; private int totalAttributes = 0; // special attributes private static final String VERTEX_LABEL_DEFAULT_ATTRIBUTE_NAME = "VertexLabel"; private static final String EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME = "weight"; private static final String EDGE_LABEL_DEFAULT_ATTRIBUTE_NAME = "EdgeLabel"; private String vertexLabelAttributeName = VERTEX_LABEL_DEFAULT_ATTRIBUTE_NAME; private String edgeWeightAttributeName = EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME; private String edgeLabelAttributeName = EDGE_LABEL_DEFAULT_ATTRIBUTE_NAME; /** * Whether to print edge weights in case the graph is weighted. */ private boolean exportEdgeWeights = false; /** * Whether to try to print vertex labels. They must be found in the corresponding attribute * provider. */ private boolean exportVertexLabels = false; /** * Whether to try to print edge labels. They must be found in the corresponding attribute * provider. */ private boolean exportEdgeLabels = false; /** * Constructs a new GraphMLExporter with integer id provider for the vertices. */ public GraphMLExporter() { this(new IntegerIdProvider<>()); } /** * Constructs a new GraphMLExporter. * * @param vertexIdProvider for generating vertex identifiers. Must not be null. */ public GraphMLExporter(Function vertexIdProvider) { super(vertexIdProvider); } /** * Denotes the category of a GraphML-Attribute. */ public enum AttributeCategory { GRAPH("graph"), NODE("node"), EDGE("edge"), ALL("all"); private String name; private AttributeCategory(String name) { this.name = name; } /** * Get a string representation of the attribute category * * @return the string representation of the attribute category */ public String toString() { return name; } } /** * Register a GraphML-Attribute * * @param name the attribute name * @param category the attribute category * @param type the attribute type */ public void registerAttribute(String name, AttributeCategory category, AttributeType type) { registerAttribute(name, category, type, null); } /** * Register a GraphML-Attribute * * @param name the attribute name * @param category the attribute category * @param type the attribute type * @param defaultValue default value */ public void registerAttribute( String name, AttributeCategory category, AttributeType type, String defaultValue) { if (name == null) { throw new IllegalArgumentException("Attribute name cannot be null"); } if (name.equals(vertexLabelAttributeName) || name.equals(edgeWeightAttributeName) || name.equals(edgeLabelAttributeName)) { throw new IllegalArgumentException("Reserved attribute name"); } if (category == null) { throw new IllegalArgumentException( "Attribute category must be one of node, edge, graph or all"); } String nextKey = ATTRIBUTE_KEY_PREFIX + (totalAttributes++); registeredAttributes.put(name, new AttributeDetails(nextKey, category, type, defaultValue)); } /** * Unregister a GraphML-Attribute * * @param name the attribute name */ public void unregisterAttribute(String name) { if (name == null) { throw new IllegalArgumentException("Attribute name cannot be null"); } if (name.equals(vertexLabelAttributeName) || name.equals(edgeWeightAttributeName) || name.equals(edgeLabelAttributeName)) { throw new IllegalArgumentException("Reserved attribute name"); } registeredAttributes.remove(name); } /** * Whether the exporter will print edge weights. * * @return {@code true} if the exporter prints edge weights, {@code false} otherwise */ public boolean isExportEdgeWeights() { return exportEdgeWeights; } /** * Set whether the exporter will print edge weights. * * @param exportEdgeWeights value to set */ public void setExportEdgeWeights(boolean exportEdgeWeights) { this.exportEdgeWeights = exportEdgeWeights; } /** * Whether the exporter will print vertex labels. * * @return {@code true} if the exporter prints vertex labels, {@code false} otherwise */ public boolean isExportVertexLabels() { return exportVertexLabels; } /** * Set whether the exporter will print vertex labels. * * @param exportVertexLabels value to set */ public void setExportVertexLabels(boolean exportVertexLabels) { this.exportVertexLabels = exportVertexLabels; } /** * Whether the exporter will print edge labels. * * @return {@code true} if the exporter prints edge labels, {@code false} otherwise */ public boolean isExportEdgeLabels() { return exportEdgeLabels; } /** * Set whether the exporter will print edge labels. * * @param exportEdgeLabels value to set */ public void setExportEdgeLabels(boolean exportEdgeLabels) { this.exportEdgeLabels = exportEdgeLabels; } /** * Get the attribute name for vertex labels * * @return the attribute name */ public String getVertexLabelAttributeName() { return vertexLabelAttributeName; } /** * Set the attribute name to use for vertex labels. * * @param vertexLabelAttributeName the attribute name */ public void setVertexLabelAttributeName(String vertexLabelAttributeName) { if (vertexLabelAttributeName == null) { throw new IllegalArgumentException("Vertex label attribute name cannot be null"); } String key = vertexLabelAttributeName.trim(); if (registeredAttributes.containsKey(key)) { throw new IllegalArgumentException("Reserved attribute name"); } this.vertexLabelAttributeName = key; } /** * Get the attribute name for edge labels * * @return the attribute name */ public String getEdgeLabelAttributeName() { return edgeLabelAttributeName; } /** * Set the attribute name to use for edge labels. * * @param edgeLabelAttributeName the attribute name */ public void setEdgeLabelAttributeName(String edgeLabelAttributeName) { if (edgeLabelAttributeName == null) { throw new IllegalArgumentException("Edge label attribute name cannot be null"); } String key = edgeLabelAttributeName.trim(); if (registeredAttributes.containsKey(key)) { throw new IllegalArgumentException("Reserved attribute name"); } this.edgeLabelAttributeName = key; } /** * Get the attribute name for edge weights * * @return the attribute name */ public String getEdgeWeightAttributeName() { return edgeWeightAttributeName; } /** * Set the attribute name to use for edge weights. * * @param edgeWeightAttributeName the attribute name */ public void setEdgeWeightAttributeName(String edgeWeightAttributeName) { if (edgeWeightAttributeName == null) { throw new IllegalArgumentException("Edge weight attribute name cannot be null"); } String key = edgeWeightAttributeName.trim(); if (registeredAttributes.containsKey(key)) { throw new IllegalArgumentException("Reserved attribute name"); } this.edgeWeightAttributeName = key; } /** * Exports a graph in GraphML format. * * @param g the graph * @param writer the writer to export the graph * @throws ExportException in case any error occurs during export */ @Override public void exportGraph(Graph g, Writer writer) { try { // Prepare an XML file to receive the GraphML data SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); TransformerHandler handler = factory.newTransformerHandler(); handler.getTransformer().setOutputProperty(OutputKeys.ENCODING, "UTF-8"); handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes"); handler.setResult(new StreamResult(new PrintWriter(writer))); // export handler.startDocument(); writeHeader(handler); writeKeys(handler); writeGraphStart(handler, g); writeNodes(handler, g); writeEdges(handler, g); writeGraphEnd(handler); writeFooter(handler); handler.endDocument(); // flush writer.flush(); } catch (Exception e) { throw new ExportException("Failed to export as GraphML", e); } } private void writeHeader(TransformerHandler handler) throws SAXException { handler.startPrefixMapping("xsi", "http://www.w3.org/2001/XMLSchema-instance"); handler.endPrefixMapping("xsi"); AttributesImpl attr = new AttributesImpl(); attr .addAttribute( "", "", "xsi:schemaLocation", "CDATA", "http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd"); handler.startElement("http://graphml.graphdrawing.org/xmlns", "", "graphml", attr); } private void writeGraphStart(TransformerHandler handler, Graph g) throws SAXException { // AttributesImpl attr = new AttributesImpl(); attr .addAttribute( "", "", "edgedefault", "CDATA", g.getType().isDirected() ? "directed" : "undirected"); handler.startElement("", "", "graph", attr); } private void writeGraphEnd(TransformerHandler handler) throws SAXException { handler.endElement("", "", "graph"); } private void writeFooter(TransformerHandler handler) throws SAXException { handler.endElement("", "", "graphml"); } private void writeKeys(TransformerHandler handler) throws SAXException { if (exportVertexLabels) { writeAttribute( handler, vertexLabelAttributeName, new AttributeDetails( "vertex_label_key", AttributeCategory.NODE, AttributeType.STRING, null)); } if (exportEdgeLabels) { writeAttribute( handler, edgeLabelAttributeName, new AttributeDetails( "edge_label_key", AttributeCategory.EDGE, AttributeType.STRING, null)); } if (exportEdgeWeights) { writeAttribute( handler, edgeWeightAttributeName, new AttributeDetails( "edge_weight_key", AttributeCategory.EDGE, AttributeType.DOUBLE, Double.toString(Graph.DEFAULT_EDGE_WEIGHT))); } for (String attributeName : registeredAttributes.keySet()) { AttributeDetails details = registeredAttributes.get(attributeName); writeAttribute(handler, attributeName, details); } } private void writeData(TransformerHandler handler, String key, String value) throws SAXException { AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "key", "CDATA", key); handler.startElement("", "", "data", attr); handler.characters(value.toCharArray(), 0, value.length()); handler.endElement("", "", "data"); } private void writeAttribute(TransformerHandler handler, String name, AttributeDetails details) throws SAXException { AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "id", "CDATA", details.key); attr.addAttribute("", "", "for", "CDATA", details.category.toString()); attr.addAttribute("", "", "attr.name", "CDATA", name); attr.addAttribute("", "", "attr.type", "CDATA", details.type.toString()); handler.startElement("", "", "key", attr); if (details.defaultValue != null) { handler.startElement("", "", "default", null); handler .characters(details.defaultValue.toCharArray(), 0, details.defaultValue.length()); handler.endElement("", "", "default"); } handler.endElement("", "", "key"); } private void writeNodes(TransformerHandler handler, Graph g) throws SAXException { // Add all the vertices as elements... for (V v : g.vertexSet()) { // AttributesImpl attr = new AttributesImpl(); attr.addAttribute("", "", "id", "CDATA", getVertexId(v)); handler.startElement("", "", "node", attr); Optional vertexLabelAttribute = getVertexAttribute(v, vertexLabelAttributeName); if (exportVertexLabels) { if (vertexLabelAttribute.isPresent()) { writeData(handler, "vertex_label_key", vertexLabelAttribute.get().getValue()); } else { writeData(handler, "vertex_label_key", String.valueOf(v)); } } // find vertex attributes Map vertexAttributes = getVertexAttributes(v).orElse(Collections.emptyMap()); // check all registered for (Entry e : registeredAttributes.entrySet()) { AttributeDetails details = e.getValue(); if (details.category.equals(AttributeCategory.NODE) || details.category.equals(AttributeCategory.ALL)) { String name = e.getKey(); String defaultValue = details.defaultValue; if (vertexAttributes.containsKey(name)) { Attribute attribute = vertexAttributes.get(name); String value = attribute.getValue(); if (defaultValue == null || !defaultValue.equals(value)) { if (value != null) { writeData(handler, details.key, value); } } } } } handler.endElement("", "", "node"); } } private void writeEdges(TransformerHandler handler, Graph g) throws SAXException { // Add all the edges as elements... for (E e : g.edgeSet()) { // AttributesImpl attr = new AttributesImpl(); getEdgeId(e).ifPresent(eId -> { attr.addAttribute("", "", "id", "CDATA", eId); }); attr.addAttribute("", "", "source", "CDATA", getVertexId(g.getEdgeSource(e))); attr.addAttribute("", "", "target", "CDATA", getVertexId(g.getEdgeTarget(e))); handler.startElement("", "", "edge", attr); Optional edgeLabelAttribute = getEdgeAttribute(e, edgeLabelAttributeName); if (exportEdgeLabels) { if (edgeLabelAttribute.isPresent()) { writeData(handler, "edge_label_key", edgeLabelAttribute.get().getValue()); } else { writeData(handler, "edge_label_key", String.valueOf(e)); } } if (exportEdgeWeights) { Double weight = g.getEdgeWeight(e); if (!weight.equals(Graph.DEFAULT_EDGE_WEIGHT)) { // not // default // value writeData(handler, "edge_weight_key", String.valueOf(weight)); } } // find edge attributes Map edgeAttributes = getEdgeAttributes(e).orElse(Collections.emptyMap()); // check all registered for (Entry entry : registeredAttributes.entrySet()) { AttributeDetails details = entry.getValue(); if (details.category.equals(AttributeCategory.EDGE) || details.category.equals(AttributeCategory.ALL)) { String name = entry.getKey(); String defaultValue = details.defaultValue; if (edgeAttributes.containsKey(name)) { Attribute attribute = edgeAttributes.get(name); String value = attribute.getValue(); if (defaultValue == null || !defaultValue.equals(value)) { if (value != null) { writeData(handler, details.key, value); } } } } } handler.endElement("", "", "edge"); } } private class AttributeDetails { public String key; public AttributeCategory category; public AttributeType type; public String defaultValue; public AttributeDetails( String key, AttributeCategory category, AttributeType type, String defaultValue) { this.key = key; this.category = category; this.type = type; this.defaultValue = defaultValue; } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/GraphMLImporter.java000066400000000000000000000266371402514743400316600ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Imports a graph from a GraphML data source. * *

    * For a description of the format see * http://en.wikipedia.org/wiki/ GraphML or the * GraphML Primer. *

    * *

    * Below is small example of a graph in GraphML format. * *

     * {@code
     * 
     * 
     *   
     *     yellow
     *   
     *   
     *   
     *     
     *       green
     *     
     *     
     *     
     *       blue
     *     
     *     
     *       red
     *     
     *     
     *     
     *       turquoise
     *     
     *     
     *       1.0
     *     
     *     
     *       1.0
     *     
     *     
     *       2.0
     *     
     *     
     *     
     *     
     *     
     *       1.1
     *     
     *   
     * 
     * }
     * 
    * *

    * The importer reads the input into a graph which is provided by the user. In case the graph is * weighted and the corresponding edge key with attr.name="weight" is defined, the importer also * reads edge weights. Otherwise edge weights are ignored. To test whether the graph is weighted, * method {@link Graph#getType()} can be used. * *

    * GraphML-Attributes Values are read as string key-value pairs and passed on to the vertex or edge * attribute consumers respectively. * *

    * The provided graph object, where the imported graph will be stored, must be able to support the * features of the graph that is read. For example if the GraphML file contains self-loops then the * graph provided must also support self-loops. The same for multiple edges. Moreover, the parser * completely ignores the attribute "edgedefault" which denotes whether an edge is directed or not. * Whether edges are directed or not depends on the underlying implementation of the user provided * graph object. * *

    * The importer by default validates the input using the 1.0 * GraphML Schema. The user can * (not recommended) disable the validation by calling {@link #setSchemaValidation(boolean)}. * *

    * The graph vertices and edges are build using the corresponding graph suppliers. The id of the * vertices in the original dot file are reported as a vertex attribute named "ID". Thus, in case * vertices in the dot file also contain an "ID" attribute, such an attribute will be reported * multiple times. * *

    * The default behavior of the importer is to use the graph vertex supplier in order to create * vertices. The user can also bypass vertex creation by providing a custom vertex factory method * using {@link #setVertexFactory(Function)}. The factory method is responsible to create a new * graph vertex given the vertex identifier read from file. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class GraphMLImporter extends BaseEventDrivenImporter implements GraphImporter { /** * Default key used for vertex ID. */ public static final String DEFAULT_VERTEX_ID_KEY = "ID"; // special attributes private static final String EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME = "weight"; private String edgeWeightAttributeName = EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME; private boolean schemaValidation; private Function vertexFactory; /** * Constructs a new importer. */ public GraphMLImporter() { this.schemaValidation = true; } /** * Get the attribute name for edge weights * * @return the attribute name */ public String getEdgeWeightAttributeName() { return edgeWeightAttributeName; } /** * Set the attribute name to use for edge weights. * * @param edgeWeightAttributeName the attribute name */ public void setEdgeWeightAttributeName(String edgeWeightAttributeName) { if (edgeWeightAttributeName == null) { throw new IllegalArgumentException("Edge weight attribute name cannot be null"); } this.edgeWeightAttributeName = edgeWeightAttributeName; } /** * Whether the importer validates the input * * @return true if the importer validates the input */ public boolean isSchemaValidation() { return schemaValidation; } /** * Set whether the importer should validate the input * * @param schemaValidation value for schema validation */ public void setSchemaValidation(boolean schemaValidation) { this.schemaValidation = schemaValidation; } /** * Get the user custom vertex factory. This is null by default and the graph supplier is used * instead. * * @return the user custom vertex factory */ public Function getVertexFactory() { return vertexFactory; } /** * Set the user custom vertex factory. The default behavior is being null in which case the * graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the file. * The method is called with parameter the vertex identifier from the file and should return the * actual graph vertex to add to the graph. * * @param vertexFactory a vertex factory */ public void setVertexFactory(Function vertexFactory) { this.vertexFactory = vertexFactory; } /** * Import a graph. * *

    * The provided graph must be able to support the features of the graph that is read. For * example if the GraphML file contains self-loops then the graph provided must also support * self-loops. The same for multiple edges. * *

    * If the provided graph is a weighted graph, the importer also reads edge weights. * *

    * GraphML-Attributes Values are read as string key-value pairs and propagated to the user as * events. * * @param graph the output graph * @param input the input reader * @throws ImportException in case an error occurs, such as I/O or parse error */ @Override public void importGraph(Graph graph, Reader input) { GraphMLEventDrivenImporter genericImporter = new GraphMLEventDrivenImporter(); genericImporter.setEdgeWeightAttributeName(edgeWeightAttributeName); genericImporter.setSchemaValidation(schemaValidation); Consumers globalConsumer = new Consumers(graph); genericImporter.addGraphAttributeConsumer(globalConsumer.graphAttributeConsumer); genericImporter.addVertexAttributeConsumer(globalConsumer.vertexAttributeConsumer); genericImporter.addEdgeAttributeConsumer(globalConsumer.edgeAttributeConsumer); genericImporter.addVertexConsumer(globalConsumer.vertexConsumer); genericImporter.addEdgeConsumer(globalConsumer.edgeConsumer); genericImporter.importInput(input); } private class Consumers { private Graph graph; private Map nodesMap; private E lastEdge; private Triple lastTriple; public Consumers(Graph graph) { this.graph = graph; this.nodesMap = new HashMap<>(); this.lastEdge = null; this.lastTriple = null; } public final BiConsumer graphAttributeConsumer = (key, a) -> { notifyGraphAttribute(key, a); }; public final BiConsumer, Attribute> vertexAttributeConsumer = (vertexAndKey, a) -> { notifyVertexAttribute( mapNode(vertexAndKey.getFirst()), vertexAndKey.getSecond(), a); }; public final BiConsumer, String>, Attribute> edgeAttributeConsumer = (edgeAndKey, a) -> { Triple qe = edgeAndKey.getFirst(); if (qe == lastTriple) { if (qe.getThird() != null && edgeWeightAttributeName.equals(edgeAndKey.getSecond()) && graph.getType().isWeighted()) { graph.setEdgeWeight(lastEdge, qe.getThird()); } notifyEdgeAttribute(lastEdge, edgeAndKey.getSecond(), a); } }; public final Consumer vertexConsumer = (vId) -> { V v = mapNode(vId); notifyVertex(v); notifyVertexAttribute(v, DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(vId)); }; public final Consumer> edgeConsumer = (qe) -> { if (lastTriple != qe) { String source = qe.getFirst(); String target = qe.getSecond(); Double weight = qe.getThird(); E e = graph.addEdge(mapNode(source), mapNode(target)); if (weight != null && graph.getType().isWeighted()) { graph.setEdgeWeight(e, weight); } lastEdge = e; lastTriple = qe; notifyEdge(lastEdge); } }; private V mapNode(String vId) { V vertex = nodesMap.get(vId); if (vertex == null) { if (vertexFactory != null) { vertex = vertexFactory.apply(vId); graph.addVertex(vertex); } else { vertex = graph.addVertex(); } nodesMap.put(vId, vertex); } return vertex; } } } SimpleGraphMLEdgeListImporter.java000066400000000000000000000174031402514743400343630ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Imports a GraphML file as an edge list. Vertices are numbered from $0$ to $n-1$ in the order they * are first encountered in the input file. * *

    * This is a simple implementation with supports only a limited set of features of the GraphML * specification. For a more rigorous parser use {@link GraphMLImporter}. This version is oriented * towards parsing speed. Default attribute values are completely ignored. * *

    * For a description of the format see * http://en.wikipedia.org/wiki/ GraphML or the * GraphML Primer. *

    * *

    * Below is small example of a graph in GraphML format. * *

     * {@code
     * 
     * 
     *   
     *   
     *   
     *     
     *       green
     *     
     *     
     *       black
     *          
     *     
     *       blue
     *     
     *     
     *       red
     *     
     *     
     *       white
     *     
     *     
     *       turquoise
     *     
     *     
     *       1.0
     *     
     *     
     *       1.0
     *     
     *     
     *       2.0
     *     
     *     
     *     
     *     
     *     
     *       1.1
     *     
     *   
     * 
     * }
     * 
    * *

    * The importer by default validates the input using the 1.0 * GraphML Schema. The user can * (not recommended) disable the validation by calling {@link #setSchemaValidation(boolean)}. * * @author Dimitrios Michail */ public class SimpleGraphMLEdgeListImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { private static final String EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME = "weight"; private boolean schemaValidation; private String edgeWeightAttributeName = EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME; /** * Constructs a new importer. */ public SimpleGraphMLEdgeListImporter() { super(); this.schemaValidation = true; } /** * Get the attribute name for edge weights * * @return the attribute name */ public String getEdgeWeightAttributeName() { return edgeWeightAttributeName; } /** * Set the attribute name to use for edge weights. * * @param edgeWeightAttributeName the attribute name */ public void setEdgeWeightAttributeName(String edgeWeightAttributeName) { this.edgeWeightAttributeName = Objects .requireNonNull(edgeWeightAttributeName, "Edge weight attribute name cannot be null"); } /** * Whether the importer validates the input * * @return true if the importer validates the input */ public boolean isSchemaValidation() { return schemaValidation; } /** * Set whether the importer should validate the input * * @param schemaValidation value for schema validation */ public void setSchemaValidation(boolean schemaValidation) { this.schemaValidation = schemaValidation; } @Override public void importInput(Reader input) throws ImportException { SimpleGraphMLEventDrivenImporter genericImporter = new SimpleGraphMLEventDrivenImporter(); genericImporter.setEdgeWeightAttributeName(edgeWeightAttributeName); genericImporter.setSchemaValidation(schemaValidation); Consumers consumers = new Consumers(); genericImporter.addImportEventConsumer(consumers.eventConsumer); genericImporter.addVertexConsumer(consumers.vertexConsumer); genericImporter.addEdgeConsumer(consumers.edgeConsumer); genericImporter.addEdgeAttributeConsumer(consumers.edgeAttributeConsumer); genericImporter.importInput(input); } private class Consumers { private int nodeCount; private Map vertexMap; private Triple lastIntegerTriple; private Triple lastTriple; public Consumers() { this.nodeCount = 0; this.vertexMap = new HashMap<>(); } public final Consumer eventConsumer = (e) -> { if (ImportEvent.END.equals(e)) { if (lastTriple != null) { notifyEdge(lastIntegerTriple); lastTriple = null; lastIntegerTriple = null; } } }; public final Consumer vertexConsumer = (v) -> { vertexMap.computeIfAbsent(v, k -> nodeCount++); }; public final BiConsumer, String>, Attribute> edgeAttributeConsumer = (edgeAndKey, a) -> { Triple q = edgeAndKey.getFirst(); String keyName = edgeAndKey.getSecond(); if (lastTriple == q && edgeWeightAttributeName.equals(keyName)) { lastTriple.setThird(q.getThird()); lastIntegerTriple.setThird(q.getThird()); } }; public final Consumer> edgeConsumer = (q) -> { if (q != lastTriple) { if (lastTriple != null) { notifyEdge(lastIntegerTriple); } lastTriple = q; lastIntegerTriple = createIntegerTriple(q); } }; private Triple createIntegerTriple( Triple e) { int source = vertexMap.computeIfAbsent(e.getFirst(), k -> { return Integer.valueOf(nodeCount++); }); int target = vertexMap.computeIfAbsent(e.getSecond(), k -> { return Integer.valueOf(nodeCount++); }); Double weight = e.getThird(); return Triple.of(source, target, weight); } } } SimpleGraphMLEventDrivenImporter.java000066400000000000000000000456731402514743400351260ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import org.xml.sax.*; import org.xml.sax.helpers.*; import javax.xml.*; import javax.xml.parsers.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; import javax.xml.validation.*; import java.io.*; import java.util.*; /** * Imports a graph from a GraphML data source. The importer does not construct a graph but calls the * provided consumers with the appropriate arguments. Vertices are returned simply by their vertex * id and edges are returns as triples (source, target, weight) where weight maybe null. * *

    * The importer notifies lazily and completely out-of-order for any additional vertex, edge or graph * attributes in the input file. Users can register consumers for vertex, edge and graph attributes * after construction of the importer. Finally, default attribute values are completely ignored. * Lazily here means that an edge is first reported with a null weight and its weight is reported * later using the edge attribute consumer. Since the same triple instance is used in all cases, an * edge may appear having a null weight when it is first reported and having a non-null weight after * the edge weight is reported. * *

    * This is a simple implementation with supports only a limited set of features of the GraphML * specification. For a more rigorous parser use {@link GraphMLImporter}. This version is oriented * towards parsing speed. * *

    * For a description of the format see * http://en.wikipedia.org/wiki/ GraphML or the * GraphML Primer. *

    * *

    * Below is small example of a graph in GraphML format. * *

     * {@code
     * 
     * 
     *   
     *   
     *   
     *     
     *       green
     *     
     *     
     *       black
     *          
     *     
     *       blue
     *     
     *     
     *       red
     *     
     *     
     *       white
     *     
     *     
     *       turquoise
     *     
     *     
     *       1.0
     *     
     *     
     *       1.0
     *     
     *     
     *       2.0
     *     
     *     
     *     
     *     
     *     
     *       1.1
     *     
     *   
     * 
     * }
     * 
    * *

    * The importer by default validates the input using the 1.0 * GraphML Schema. The user can * (not recommended) disable the validation by calling {@link #setSchemaValidation(boolean)}. * * @author Dimitrios Michail */ public class SimpleGraphMLEventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { private static final String GRAPHML_SCHEMA_FILENAME = "graphml.xsd"; private static final String XLINK_SCHEMA_FILENAME = "xlink.xsd"; private static final String EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME = "weight"; private boolean schemaValidation; private String edgeWeightAttributeName = EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME; /** * Constructs a new importer. */ public SimpleGraphMLEventDrivenImporter() { super(); this.schemaValidation = true; } /** * Get the attribute name for edge weights * * @return the attribute name */ public String getEdgeWeightAttributeName() { return edgeWeightAttributeName; } /** * Set the attribute name to use for edge weights. * * @param edgeWeightAttributeName the attribute name */ public void setEdgeWeightAttributeName(String edgeWeightAttributeName) { this.edgeWeightAttributeName = Objects .requireNonNull(edgeWeightAttributeName, "Edge weight attribute name cannot be null"); } /** * Whether the importer validates the input * * @return true if the importer validates the input */ public boolean isSchemaValidation() { return schemaValidation; } /** * Set whether the importer should validate the input * * @param schemaValidation value for schema validation */ public void setSchemaValidation(boolean schemaValidation) { this.schemaValidation = schemaValidation; } @Override public void importInput(Reader input) { try { // parse XMLReader xmlReader = createXMLReader(); GraphMLHandler handler = new GraphMLHandler(); xmlReader.setContentHandler(handler); xmlReader.setErrorHandler(handler); notifyImportEvent(ImportEvent.START); xmlReader.parse(new InputSource(input)); notifyImportEvent(ImportEvent.END); } catch (Exception se) { throw new ImportException("Failed to parse GraphML", se); } } private XMLReader createXMLReader() { try { SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); // create parser SAXParserFactory spf = SAXParserFactory.newInstance(); if (schemaValidation) { // load schema InputStream xsdStream = Thread .currentThread().getContextClassLoader() .getResourceAsStream(GRAPHML_SCHEMA_FILENAME); if (xsdStream == null) { throw new ImportException("Failed to locate GraphML xsd"); } InputStream xlinkStream = Thread .currentThread().getContextClassLoader() .getResourceAsStream(XLINK_SCHEMA_FILENAME); if (xlinkStream == null) { throw new ImportException("Failed to locate XLink xsd"); } Source[] sources = new Source[2]; sources[0] = new StreamSource(xlinkStream); sources[1] = new StreamSource(xsdStream); Schema schema = schemaFactory.newSchema(sources); spf.setSchema(schema); } spf.setNamespaceAware(true); SAXParser saxParser = spf.newSAXParser(); // create reader return saxParser.getXMLReader(); } catch (Exception se) { throw new ImportException("Failed to parse GraphML", se); } } // content handler private class GraphMLHandler extends DefaultHandler { private static final String GRAPH = "graph"; private static final String GRAPH_ID = "id"; private static final String GRAPH_EDGE_DEFAULT = "edgedefault"; private static final String NODE = "node"; private static final String NODE_ID = "id"; private static final String EDGE = "edge"; private static final String EDGE_ID = "id"; private static final String EDGE_SOURCE = "source"; private static final String EDGE_TARGET = "target"; private static final String ALL = "all"; private static final String KEY = "key"; private static final String KEY_FOR = "for"; private static final String KEY_ATTR_NAME = "attr.name"; private static final String KEY_ATTR_TYPE = "attr.type"; private static final String KEY_ID = "id"; private static final String DEFAULT = "default"; private static final String DATA = "data"; private static final String DATA_KEY = "key"; // parser state private int insideData; private int insideGraph; private int insideNode; private String currentNode; private int insideEdge; private Triple currentEdge; private Key currentKey; private String currentDataKey; private StringBuilder currentDataValue; private Map nodeValidKeys; private Map edgeValidKeys; private Map graphValidKeys; public GraphMLHandler() { } @Override public void startDocument() throws SAXException { insideData = 0; insideGraph = 0; insideNode = 0; currentNode = null; insideEdge = 0; currentEdge = null; currentKey = null; currentDataKey = null; currentDataValue = new StringBuilder(); nodeValidKeys = new HashMap<>(); edgeValidKeys = new HashMap<>(); graphValidKeys = new HashMap<>(); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { switch (localName) { case GRAPH: if (insideGraph > 0) { throw new IllegalArgumentException( "This importer does not support nested graphs"); } insideGraph++; findAttribute(GRAPH_ID, attributes) .ifPresent( value -> notifyGraphAttribute( GRAPH_ID, DefaultAttribute.createAttribute(value))); findAttribute(GRAPH_EDGE_DEFAULT, attributes) .ifPresent( value -> notifyGraphAttribute( GRAPH_EDGE_DEFAULT, DefaultAttribute.createAttribute(value))); break; case NODE: if (insideNode > 0 || insideEdge > 0) { throw new IllegalArgumentException( "Nodes cannot be inside other nodes or edges"); } insideNode++; String nodeId = findAttribute(NODE_ID, attributes) .orElseThrow( () -> new IllegalArgumentException("Node must have an identifier")); currentNode = nodeId; notifyVertex(currentNode); notifyVertexAttribute( currentNode, NODE_ID, DefaultAttribute.createAttribute(nodeId)); break; case EDGE: if (insideNode > 0 || insideEdge > 0) { throw new IllegalArgumentException( "Edges cannot be inside other nodes or edges"); } insideEdge++; String sourceId = findAttribute(EDGE_SOURCE, attributes) .orElseThrow(() -> new IllegalArgumentException("Edge source missing")); String targetId = findAttribute(EDGE_TARGET, attributes) .orElseThrow(() -> new IllegalArgumentException("Edge target missing")); String edgeId = findAttribute(EDGE_ID, attributes).orElse(null); currentEdge = Triple.of(sourceId, targetId, null); notifyEdge(currentEdge); if (edgeId != null) { notifyEdgeAttribute( currentEdge, EDGE_ID, DefaultAttribute.createAttribute(edgeId)); } notifyEdgeAttribute( currentEdge, EDGE_SOURCE, DefaultAttribute.createAttribute(sourceId)); notifyEdgeAttribute( currentEdge, EDGE_TARGET, DefaultAttribute.createAttribute(targetId)); break; case KEY: String keyId = findAttribute(KEY_ID, attributes) .orElseThrow(() -> new IllegalArgumentException("Key id missing")); String keyAttrName = findAttribute(KEY_ATTR_NAME, attributes) .orElseThrow(() -> new IllegalArgumentException("Key attribute name missing")); currentKey = new Key( keyId, keyAttrName, findAttribute(KEY_ATTR_TYPE, attributes) .map(AttributeType::create).orElse(AttributeType.UNKNOWN), findAttribute(KEY_FOR, attributes).orElse("ALL")); break; case DEFAULT: break; case DATA: insideData++; findAttribute(DATA_KEY, attributes).ifPresent(data -> currentDataKey = data); break; default: break; } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { switch (localName) { case GRAPH: insideGraph--; break; case NODE: currentNode = null; insideNode--; break; case EDGE: if (currentEdge != null && currentEdge.getThird() != null) { notifyEdgeAttribute( currentEdge, edgeWeightAttributeName, DefaultAttribute.createAttribute(currentEdge.getThird())); } currentEdge = null; insideEdge--; break; case KEY: registerKey(); currentKey = null; break; case DEFAULT: break; case DATA: if (--insideData == 0) { notifyData(); currentDataValue.setLength(0); currentDataKey = null; } break; default: break; } } @Override public void characters(char ch[], int start, int length) throws SAXException { if (insideData == 1) { currentDataValue.append(ch, start, length); } } @Override public void warning(SAXParseException e) throws SAXException { throw e; } public void error(SAXParseException e) throws SAXException { throw e; } public void fatalError(SAXParseException e) throws SAXException { throw e; } private Optional findAttribute(String localName, Attributes attributes) { for (int i = 0; i < attributes.getLength(); i++) { String attrLocalName = attributes.getLocalName(i); if (attrLocalName.equals(localName)) { return Optional.ofNullable(attributes.getValue(i)); } } return Optional.empty(); } private void notifyData() { if (currentDataKey == null || currentDataValue.length() == 0) { return; } if (currentNode != null) { Key key = nodeValidKeys.get(currentDataKey); if (key != null) { notifyVertexAttribute( currentNode, key.attributeName, new DefaultAttribute<>(currentDataValue.toString(), key.type)); } } if (currentEdge != null) { Key key = edgeValidKeys.get(currentDataKey); if (key != null) { /* * Handle special weight key */ if (key.attributeName.equals(edgeWeightAttributeName)) { try { currentEdge.setThird(Double.parseDouble(currentDataValue.toString())); } catch (NumberFormatException e) { // ignore } } else { notifyEdgeAttribute( currentEdge, key.attributeName, new DefaultAttribute<>(currentDataValue.toString(), key.type)); } } } Key key = graphValidKeys.get(currentDataKey); if (key != null) { notifyGraphAttribute( key.attributeName, new DefaultAttribute<>(currentDataValue.toString(), key.type)); } } private void registerKey() { if (currentKey.isValid()) { switch (currentKey.target) { case NODE: nodeValidKeys.put(currentKey.id, currentKey); break; case EDGE: edgeValidKeys.put(currentKey.id, currentKey); break; case GRAPH: graphValidKeys.put(currentKey.id, currentKey); break; case ALL: nodeValidKeys.put(currentKey.id, currentKey); edgeValidKeys.put(currentKey.id, currentKey); graphValidKeys.put(currentKey.id, currentKey); break; } } } } private static class Key { String id; String attributeName; String target; AttributeType type; public Key(String id, String attributeName, AttributeType type, String target) { this.id = id; this.attributeName = attributeName; this.type = type; this.target = target; } public boolean isValid() { return id != null && attributeName != null && target != null; } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/SimpleGraphMLImporter.java000066400000000000000000000273441402514743400330260ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Imports a graph from a GraphML data source. * *

    * This is a simple implementation with supports only a limited set of features of the GraphML * specification. For a more rigorous parser use {@link GraphMLImporter}. This version is oriented * towards parsing speed. * *

    * The importer uses the graph suppliers ({@link Graph#getVertexSupplier()} and * {@link Graph#getEdgeSupplier()}) in order to create new vertices and edges. Moreover, it notifies * lazily and completely out-of-order for any additional vertex, edge or graph attributes in the * input file. Users can register consumers for vertex, edge and graph attributes after construction * of the importer. Finally, default attribute values are completely ignored. * *

    * For a description of the format see * http://en.wikipedia.org/wiki/ GraphML or the * GraphML Primer. *

    * *

    * Below is small example of a graph in GraphML format. * *

     * {@code
     * 
     * 
     *   
     *   
     *   
     *     
     *       green
     *     
     *     
     *       black
     *          
     *     
     *       blue
     *     
     *     
     *       red
     *     
     *     
     *       white
     *     
     *     
     *       turquoise
     *     
     *     
     *       1.0
     *     
     *     
     *       1.0
     *     
     *     
     *       2.0
     *     
     *     
     *     
     *     
     *     
     *       1.1
     *     
     *   
     * 
     * }
     * 
    * *

    * The importer reads the input into a graph which is provided by the user. In case the graph is * weighted and the corresponding edge key with attr.name="weight" is defined, the importer also * reads edge weights. Otherwise edge weights are ignored. To test whether the graph is weighted, * method {@link Graph#getType()} can be used. * *

    * The provided graph object, where the imported graph will be stored, must be able to support the * features of the graph that is read. For example if the GraphML file contains self-loops then the * graph provided must also support self-loops. The same for multiple edges. Moreover, the parser * completely ignores the attribute "edgedefault" which denotes whether an edge is directed or not. * Whether edges are directed or not depends on the underlying implementation of the user provided * graph object. * *

    * The importer by default validates the input using the 1.0 * GraphML Schema. The user can * (not recommended) disable the validation by calling {@link #setSchemaValidation(boolean)}. * *

    * The graph vertices and edges are build using the corresponding graph suppliers. The id of the * vertices in the original dot file are reported as a vertex attribute named "ID". Thus, in case * vertices in the dot file also contain an "ID" attribute, such an attribute will be reported * multiple times. * *

    * The default behavior of the importer is to use the graph vertex supplier in order to create * vertices. The user can also bypass vertex creation by providing a custom vertex factory method * using {@link #setVertexFactory(Function)}. The factory method is responsible to create a new * graph vertex given the vertex identifier read from file. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class SimpleGraphMLImporter extends BaseEventDrivenImporter implements GraphImporter { /** * Default key used for vertex ID. */ public static final String DEFAULT_VERTEX_ID_KEY = "ID"; private static final String EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME = "weight"; private boolean schemaValidation; private String edgeWeightAttributeName = EDGE_WEIGHT_DEFAULT_ATTRIBUTE_NAME; private Function vertexFactory; /** * Constructs a new importer. */ public SimpleGraphMLImporter() { super(); this.schemaValidation = true; } /** * Get the attribute name for edge weights * * @return the attribute name */ public String getEdgeWeightAttributeName() { return edgeWeightAttributeName; } /** * Set the attribute name to use for edge weights. * * @param edgeWeightAttributeName the attribute name */ public void setEdgeWeightAttributeName(String edgeWeightAttributeName) { this.edgeWeightAttributeName = Objects .requireNonNull(edgeWeightAttributeName, "Edge weight attribute name cannot be null"); } /** * Whether the importer validates the input * * @return true if the importer validates the input */ public boolean isSchemaValidation() { return schemaValidation; } /** * Set whether the importer should validate the input * * @param schemaValidation value for schema validation */ public void setSchemaValidation(boolean schemaValidation) { this.schemaValidation = schemaValidation; } /** * Get the user custom vertex factory. This is null by default and the graph supplier is used * instead. * * @return the user custom vertex factory */ public Function getVertexFactory() { return vertexFactory; } /** * Set the user custom vertex factory. The default behavior is being null in which case the * graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the file. * The method is called with parameter the vertex identifier from the file and should return the * actual graph vertex to add to the graph. * * @param vertexFactory a vertex factory */ public void setVertexFactory(Function vertexFactory) { this.vertexFactory = vertexFactory; } /** * Import a graph. * *

    * The provided graph must be able to support the features of the graph that is read. For * example if the GraphML file contains self-loops then the graph provided must also support * self-loops. The same for multiple edges. * * @param graph the output graph * @param input the input reader * @throws ImportException in case an error occurs, such as I/O or parse error */ @Override public void importGraph(Graph graph, Reader input) { SimpleGraphMLEventDrivenImporter genericImporter = new SimpleGraphMLEventDrivenImporter(); genericImporter.setEdgeWeightAttributeName(edgeWeightAttributeName); genericImporter.setSchemaValidation(schemaValidation); Consumers globalConsumer = new Consumers(graph); genericImporter.addGraphAttributeConsumer(globalConsumer.graphAttributeConsumer); genericImporter.addVertexAttributeConsumer(globalConsumer.vertexAttributeConsumer); genericImporter.addEdgeAttributeConsumer(globalConsumer.edgeAttributeConsumer); genericImporter.addVertexConsumer(globalConsumer.vertexConsumer); genericImporter.addEdgeConsumer(globalConsumer.edgeConsumer); genericImporter.importInput(input); } private class Consumers { private Graph graph; private Map nodesMap; private E lastEdge; private Triple lastTriple; public Consumers(Graph graph) { this.graph = graph; this.nodesMap = new HashMap<>(); this.lastEdge = null; this.lastTriple = null; } public final BiConsumer graphAttributeConsumer = (key, a) -> { notifyGraphAttribute(key, a); }; public final BiConsumer, Attribute> vertexAttributeConsumer = (vertexAndKey, a) -> { notifyVertexAttribute( mapNode(vertexAndKey.getFirst()), vertexAndKey.getSecond(), a); }; public final BiConsumer, String>, Attribute> edgeAttributeConsumer = (edgeAndKey, a) -> { Triple qe = edgeAndKey.getFirst(); if (qe == lastTriple) { if (qe.getThird() != null && edgeWeightAttributeName.equals(edgeAndKey.getSecond()) && graph.getType().isWeighted()) { graph.setEdgeWeight(lastEdge, qe.getThird()); } notifyEdgeAttribute(lastEdge, edgeAndKey.getSecond(), a); } }; public final Consumer vertexConsumer = (vId) -> { V v = mapNode(vId); notifyVertex(v); notifyVertexAttribute(v, DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(vId)); }; public final Consumer> edgeConsumer = (qe) -> { if (lastTriple != qe) { String source = qe.getFirst(); String target = qe.getSecond(); Double weight = qe.getThird(); E e = graph.addEdge(mapNode(source), mapNode(target)); if (weight != null && graph.getType().isWeighted()) { graph.setEdgeWeight(e, weight); } lastEdge = e; lastTriple = qe; notifyEdge(lastEdge); } }; private V mapNode(String vId) { V vertex = nodesMap.get(vId); if (vertex == null) { if (vertexFactory != null) { vertex = vertexFactory.apply(vId); graph.addVertex(vertex); } else { vertex = graph.addVertex(); } nodesMap.put(vId, vertex); } return vertex; } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/graphml/package-info.java000066400000000000000000000001101402514743400311420ustar00rootroot00000000000000/** * GraphML importers/exporters */ package org.jgrapht.nio.graphml; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/json/000077500000000000000000000000001402514743400253025ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONEventDrivenImporter.java000066400000000000000000000376211402514743400326230ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.json; import java.io.IOException; import java.io.Reader; import java.util.ArrayDeque; import java.util.Deque; import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.Map.Entry; import org.antlr.v4.runtime.BaseErrorListener; import org.antlr.v4.runtime.CharStreams; import org.antlr.v4.runtime.CommonTokenStream; import org.antlr.v4.runtime.RecognitionException; import org.antlr.v4.runtime.Recognizer; import org.antlr.v4.runtime.misc.ParseCancellationException; import org.antlr.v4.runtime.tree.ParseTreeWalker; import org.antlr.v4.runtime.tree.TerminalNode; import org.apache.commons.text.StringEscapeUtils; import org.jgrapht.Graph; import org.jgrapht.alg.util.Triple; import org.jgrapht.nio.Attribute; import org.jgrapht.nio.AttributeType; import org.jgrapht.nio.BaseEventDrivenImporter; import org.jgrapht.nio.DefaultAttribute; import org.jgrapht.nio.EventDrivenImporter; import org.jgrapht.nio.ImportEvent; import org.jgrapht.nio.ImportException; import org.jgrapht.nio.json.JsonParser.JsonContext; /** * Imports a graph from a JSON file. * * Below is a small example of a graph in JSON format. * *

     * {
     *   "nodes": [
     *     { "id": "1" },
     *     { "id": "2", "label": "Node 2 label" },
     *     { "id": "3" }
     *   ],
     *   "edges": [
     *     { "source": "1", "target": "2", "weight": 2.0, "label": "Edge between 1 and 2" },
     *     { "source": "2", "target": "3", "weight": 3.0, "label": "Edge between 2 and 3" }
     *   ]
     * }
     * 
    * *

    * In case the graph is weighted then the importer also reads edge weights. Otherwise the default * edge weight is returned. The importer also supports reading additional string attributes such as * label or custom user attributes. * *

    * The parser completely ignores elements from the input that are not related to vertices or edges * of the graph. Moreover, complicated nested structures which are inside vertices or edges are * simply returned as a whole. For example, in the following graph * *

     * {
     *   "nodes": [
     *     { "id": "1" },
     *     { "id": "2" }
     *   ],
     *   "edges": [
     *     { "source": "1", "target": "2", "points": { "x": 1.0, "y": 2.0 } }
     *   ]
     * }
     * 
    * * the points attribute of the edge is returned as a string containing {"x":1.0,"y":2.0}. The same * is done for arrays or any other arbitrary nested structure. * * @author Dimitrios Michail */ public class JSONEventDrivenImporter extends BaseEventDrivenImporter> implements EventDrivenImporter> { private boolean notifyVertexAttributesOutOfOrder; private boolean notifyEdgeAttributesOutOfOrder; /** * Constructs a new importer. */ public JSONEventDrivenImporter() { this(true, true); } /** * Constructs a new importer. * * @param notifyVertexAttributesOutOfOrder whether to notify for vertex attributes out-of-order * even if they appear together in the input * @param notifyEdgeAttributesOutOfOrder whether to notify for edge attributes out-of-order even * if they appear together in the input */ public JSONEventDrivenImporter( boolean notifyVertexAttributesOutOfOrder, boolean notifyEdgeAttributesOutOfOrder) { this.notifyVertexAttributesOutOfOrder = notifyVertexAttributesOutOfOrder; this.notifyEdgeAttributesOutOfOrder = notifyEdgeAttributesOutOfOrder; } @Override public void importInput(Reader input) { try { ThrowingErrorListener errorListener = new ThrowingErrorListener(); // create lexer JsonLexer lexer = new JsonLexer(CharStreams.fromReader(input)); lexer.removeErrorListeners(); lexer.addErrorListener(errorListener); // create parser JsonParser parser = new JsonParser(new CommonTokenStream(lexer)); parser.removeErrorListeners(); parser.addErrorListener(errorListener); // Specify our entry point JsonContext graphContext = parser.json(); // Walk it and attach our listener ParseTreeWalker walker = new ParseTreeWalker(); NotifyJsonListener listener = new NotifyJsonListener(); notifyImportEvent(ImportEvent.START); walker.walk(listener, graphContext); notifyImportEvent(ImportEvent.END); } catch (IOException e) { throw new ImportException("Failed to import json graph: " + e.getMessage(), e); } catch (ParseCancellationException pe) { throw new ImportException("Failed to import json graph: " + pe.getMessage(), pe); } catch (IllegalArgumentException iae) { throw new ImportException("Failed to import json graph: " + iae.getMessage(), iae); } } private class ThrowingErrorListener extends BaseErrorListener { @Override public void syntaxError( Recognizer recognizer, Object offendingSymbol, int line, int charPositionInLine, String msg, RecognitionException e) throws ParseCancellationException { throw new ParseCancellationException( "line " + line + ":" + charPositionInLine + " " + msg); } } // notify about graph from parse tree private class NotifyJsonListener extends JsonBaseListener { private static final String GRAPH = "graph"; private static final String NODES = "nodes"; private static final String EDGES = "edges"; private static final String ID = "id"; private static final String WEIGHT = "weight"; private static final String SOURCE = "source"; private static final String TARGET = "target"; // current state of parser private int objectLevel; private int arrayLevel; private boolean insideNodes; private boolean insideNodesArray; private boolean insideNode; private boolean insideEdges; private boolean insideEdgesArray; private boolean insideEdge; private Deque pairNames; private String nodeId; private String sourceId; private String targetId; private Map attributes; private int singletons; private String singletonsUUID; @Override public void enterJson(JsonParser.JsonContext ctx) { objectLevel = 0; arrayLevel = 0; insideNodes = false; insideNodesArray = false; insideNode = false; insideEdges = false; insideEdgesArray = false; insideEdge = false; singletons = 0; singletonsUUID = UUID.randomUUID().toString(); pairNames = new ArrayDeque(); pairNames.push(GRAPH); } @Override public void enterObj(JsonParser.ObjContext ctx) { objectLevel++; if (objectLevel == 2 && arrayLevel == 1) { if (insideNodesArray) { insideNode = true; nodeId = null; attributes = new HashMap<>(); } else if (insideEdgesArray) { insideEdge = true; sourceId = null; targetId = null; attributes = new HashMap<>(); } } } @Override public void exitObj(JsonParser.ObjContext ctx) { if (objectLevel == 2 && arrayLevel == 1) { if (insideNodesArray) { if (nodeId == null) { nodeId = "Singleton_" + singletonsUUID + "_" + (singletons++); } if (notifyVertexAttributesOutOfOrder) { notifyVertex(nodeId); for (Entry entry : attributes.entrySet()) { notifyVertexAttribute(nodeId, entry.getKey(), entry.getValue()); } } else { notifyVertexWithAttributes(nodeId, attributes); } insideNode = false; attributes = null; } else if (insideEdgesArray) { if (sourceId != null && targetId != null) { Double weight = Graph.DEFAULT_EDGE_WEIGHT; Attribute attributeWeight = attributes.get(WEIGHT); if (attributeWeight != null) { AttributeType type = attributeWeight.getType(); if (type.equals(AttributeType.INT) || type.equals(AttributeType.FLOAT) || type.equals(AttributeType.DOUBLE)) { weight = Double.parseDouble(attributeWeight.getValue()); } } Triple et = Triple.of(sourceId, targetId, weight); if (notifyEdgeAttributesOutOfOrder) { // notify individually notifyEdge(et); for (Entry entry : attributes.entrySet()) { notifyEdgeAttribute(et, entry.getKey(), entry.getValue()); } } else { // notify with all attributes notifyEdgeWithAttributes(et, attributes); } } else if (sourceId == null) { throw new IllegalArgumentException("Edge with missing source detected"); } else { throw new IllegalArgumentException("Edge with missing target detected"); } insideEdge = false; attributes = null; } } objectLevel--; } @Override public void enterArray(JsonParser.ArrayContext ctx) { arrayLevel++; if (insideNodes && objectLevel == 1 && arrayLevel == 1) { insideNodesArray = true; } else if (insideEdges && objectLevel == 1 && arrayLevel == 1) { insideEdgesArray = true; } } @Override public void exitArray(JsonParser.ArrayContext ctx) { if (insideNodes && objectLevel == 1 && arrayLevel == 1) { insideNodesArray = false; } else if (insideEdges && objectLevel == 1 && arrayLevel == 1) { insideEdgesArray = false; } arrayLevel--; } @Override public void enterPair(JsonParser.PairContext ctx) { String name = unquote(ctx.STRING().getText()); if (objectLevel == 1 && arrayLevel == 0) { if (NODES.equals(name)) { insideNodes = true; } else if (EDGES.equals(name)) { insideEdges = true; } } pairNames.push(name); } @Override public void exitPair(JsonParser.PairContext ctx) { String name = unquote(ctx.STRING().getText()); if (objectLevel == 1 && arrayLevel == 0) { if (NODES.equals(name)) { insideNodes = false; } else if (EDGES.equals(name)) { insideEdges = false; } } pairNames.pop(); } @Override public void enterValue(JsonParser.ValueContext ctx) { String name = pairNames.element(); if (objectLevel == 2 && arrayLevel < 2) { if (insideNode) { if (ID.equals(name)) { nodeId = readIdentifier(ctx); } else { attributes.put(name, readAttribute(ctx)); } } else if (insideEdge) { if (SOURCE.equals(name)) { sourceId = readIdentifier(ctx); } else if (TARGET.equals(name)) { targetId = readIdentifier(ctx); } else { attributes.put(name, readAttribute(ctx)); } } } } private Attribute readAttribute(JsonParser.ValueContext ctx) { // string String stringValue = readString(ctx); if (stringValue != null) { return DefaultAttribute.createAttribute(stringValue); } // number TerminalNode tn = ctx.NUMBER(); if (tn != null) { String value = tn.getText(); try { return DefaultAttribute.createAttribute(Integer.parseInt(value, 10)); } catch (NumberFormatException e) { // ignore } try { return DefaultAttribute.createAttribute(Long.parseLong(value, 10)); } catch (NumberFormatException e) { // ignore } try { return DefaultAttribute.createAttribute(Double.parseDouble(value)); } catch (NumberFormatException e) { // ignore } } // other String other = ctx.getText(); if (other != null) { if ("true".equals(other)) { return DefaultAttribute.createAttribute(Boolean.TRUE); } else if ("false".equals(other)) { return DefaultAttribute.createAttribute(Boolean.FALSE); } else if ("null".equals(other)) { return DefaultAttribute.NULL; } else { return new DefaultAttribute<>(other, AttributeType.UNKNOWN); } } return DefaultAttribute.NULL; } private String unquote(String value) { if (value.startsWith("\"") && value.endsWith("\"")) { value = value.substring(1, value.length() - 1); } value = StringEscapeUtils.unescapeJson(value); return value; } private String readString(JsonParser.ValueContext ctx) { TerminalNode tn = ctx.STRING(); if (tn == null) { return null; } return unquote(tn.getText()); } private String readIdentifier(JsonParser.ValueContext ctx) { TerminalNode tn = ctx.STRING(); if (tn != null) { return unquote(tn.getText()); } tn = ctx.NUMBER(); if (tn == null) { return null; } try { return Long.valueOf(tn.getText(), 10).toString(); } catch (NumberFormatException e) { } throw new IllegalArgumentException("Failed to read valid identifier"); } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONExporter.java000066400000000000000000000162171402514743400304560ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.json; import org.apache.commons.text.*; import org.jgrapht.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Exports a graph using JSON. * *

    * The output is one object which contains: *

      *
    • A member named nodes whose value is an array of nodes. *
    • A member named edges whose value is an array of edges. *
    • Two members named creator and version for metadata. *
    * *

    * Each node contains an identifier and possibly other attributes. Similarly each edge contains the * source and target vertices, a possible identifier and possible other attributes. All these can be * adjusted using the setters. The default constructor constructs integer identifiers using an * {@link IntegerIdProvider} for both vertices and edges and does not output any custom attributes. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class JSONExporter extends BaseExporter implements GraphExporter { private static final String CREATOR = "JGraphT JSON Exporter"; private static final String VERSION = "1"; /** * Creates a new exporter with integers for the vertex identifiers. */ public JSONExporter() { this(new IntegerIdProvider<>()); } /** * Creates a new exporter. * * @param vertexIdProvider for generating vertex identifiers. Must not be null. */ public JSONExporter(Function vertexIdProvider) { super(vertexIdProvider); } @Override public void exportGraph(Graph g, Writer writer) { PrintWriter out = new PrintWriter(writer); out.print('{'); /* * Version */ out.print(quoted("creator")); out.print(':'); out.print(quoted(CREATOR)); out.print(','); out.print(quoted("version")); out.print(':'); out.print(quoted(VERSION)); /* * Vertices */ out.print(','); out.print(quoted("nodes")); out.print(':'); out.print('['); boolean printComma = false; for (V v : g.vertexSet()) { if (!printComma) { printComma = true; } else { out.print(','); } exportVertex(out, g, v); } out.print("]"); /* * Edges */ out.print(','); out.print(quoted("edges")); out.print(':'); out.print('['); printComma = false; for (E e : g.edgeSet()) { if (!printComma) { printComma = true; } else { out.print(','); } exportEdge(out, g, e); } out.print("]"); out.print('}'); out.flush(); } private void exportVertex(PrintWriter out, Graph g, V v) { String vertexId = vertexIdProvider.apply(v); out.print('{'); out.print(quoted("id")); out.print(':'); out.print(quoted(vertexId)); exportVertexAttributes(out, g, v); out.print('}'); } private void exportEdge(PrintWriter out, Graph g, E e) { V source = g.getEdgeSource(e); String sourceId = vertexIdProvider.apply(source); V target = g.getEdgeTarget(e); String targetId = vertexIdProvider.apply(target); out.print('{'); edgeIdProvider.ifPresent(p -> { String edgeId = p.apply(e); if (edgeId != null) { out.print(quoted("id")); out.print(':'); out.print(quoted(edgeId)); out.print(','); } }); out.print(quoted("source")); out.print(':'); out.print(quoted(sourceId)); out.print(','); out.print(quoted("target")); out.print(':'); out.print(quoted(targetId)); exportEdgeAttributes(out, g, e); out.print('}'); } private void exportVertexAttributes(PrintWriter out, Graph g, V v) { if (!vertexAttributeProvider.isPresent()) { return; } vertexAttributeProvider .get().apply(v).entrySet().stream().filter(e -> !e.getKey().equals("id")) .forEach(entry -> { out.print(","); out.print(quoted(entry.getKey())); out.print(":"); outputValue(out, entry.getValue()); }); } private void exportEdgeAttributes(PrintWriter out, Graph g, E e) { if (!edgeAttributeProvider.isPresent()) { return; } Set forbidden = Set.of("id", "source", "target"); edgeAttributeProvider .get().apply(e).entrySet().stream().filter(entry -> !forbidden.contains(entry.getKey())) .forEach(entry -> { out.print(","); out.print(quoted(entry.getKey())); out.print(":"); outputValue(out, entry.getValue()); }); } private void outputValue(PrintWriter out, Attribute value) { AttributeType type = value.getType(); if (type.equals(AttributeType.BOOLEAN)) { boolean booleanValue = Boolean.parseBoolean(value.getValue()); out.print(booleanValue ? "true" : "false"); } else if (type.equals(AttributeType.INT)) { out.print(Integer.parseInt(value.getValue())); } else if (type.equals(AttributeType.LONG)) { out.print(Long.parseLong(value.getValue())); } else if (type.equals(AttributeType.FLOAT)) { float floatValue = Float.parseFloat(value.getValue()); if (!Float.isFinite(floatValue)) { throw new IllegalArgumentException("Infinity and NaN not allowed in JSON"); } out.print(floatValue); } else if (type.equals(AttributeType.DOUBLE)) { double doubleValue = Double.parseDouble(value.getValue()); if (!Double.isFinite(doubleValue)) { throw new IllegalArgumentException("Infinity and NaN not allowed in JSON"); } out.print(doubleValue); } else { out.print(quoted(value.toString())); } } private String quoted(final String s) { return "\"" + StringEscapeUtils.escapeJson(s) + "\""; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/json/JSONImporter.java000066400000000000000000000325301402514743400304430ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.json; import java.io.Reader; import java.util.HashMap; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import org.jgrapht.Graph; import org.jgrapht.GraphType; import org.jgrapht.alg.util.Pair; import org.jgrapht.alg.util.Triple; import org.jgrapht.nio.Attribute; import org.jgrapht.nio.BaseEventDrivenImporter; import org.jgrapht.nio.DefaultAttribute; import org.jgrapht.nio.GraphImporter; import org.jgrapht.nio.ImportException; /** * Imports a graph from a JSON file. * * Below is a small example of a graph in JSON format. * *

     * {
     *   "nodes": [
     *     { "id": "1" },
     *     { "id": "2", "label": "Node 2 label" },
     *     { "id": "3" }
     *   ],
     *   "edges": [
     *     { "source": "1", "target": "2", "weight": 2.0, "label": "Edge between 1 and 2" },
     *     { "source": "2", "target": "3", "weight": 3.0, "label": "Edge between 2 and 3" }
     *   ]
     * }
     * 
    * *

    * In case the graph is weighted then the importer also reads edge weights. Otherwise edge weights * are ignored. The importer also supports reading additional string attributes such as label or * custom user attributes. * *

    * The parser completely ignores elements from the input that are not related to vertices or edges * of the graph. Moreover, complicated nested structures which are inside vertices or edges are * simply returned as a whole. For example, in the following graph * *

     * {
     *   "nodes": [
     *     { "id": "1" },
     *     { "id": "2" }
     *   ],
     *   "edges": [
     *     { "source": "1", "target": "2", "points": { "x": 1.0, "y": 2.0 } }
     *   ]
     * }
     * 
    * * the points attribute of the edge is returned as a string containing {"x":1.0,"y":2.0}. The same * is done for arrays or any other arbitrary nested structure. * *

    * The graph vertices and edges are build using the corresponding graph suppliers. The id of the * vertices in the original dot file are reported as a vertex attribute named "ID". Thus, in case * vertices in the dot file also contain an "ID" attribute, such an attribute will be reported * multiple times. * *

    * The default behavior of the importer is to use the graph vertex supplier in order to create * vertices. The user can also bypass vertex creation by providing a custom vertex factory method * using {@link #setVertexFactory(Function)}. The factory method is responsible to create a new * graph vertex given the vertex identifier read from file. Additionally this importer also supports * creating vertices with {@link #setVertexWithAttributesFactory(BiFunction)}. This factory method * is responsible for creating a new graph vertex given the vertex identifier read from file * together with all available attributes of the vertex at the location of the file where the vertex * is first defined. * *

    * The default behavior of the importer is to use the graph edge supplier in order to create edges. * The user can also bypass edge creation by providing a custom edge factory method using * {@link #setEdgeWithAttributesFactory(Function)}. The factory method is responsible to create a * new graph edge given all available attributes of the edge at the location of the file where the * edge is first defined. * * @param the vertex type * @param the edge type * * @author Dimitrios Michail */ public class JSONImporter extends BaseEventDrivenImporter implements GraphImporter { /** * Default key used for vertex ID. */ public static final String DEFAULT_VERTEX_ID_KEY = "ID"; private Function vertexFactory; private BiFunction, V> vertexWithAttributesFactory; private Function, E> edgeWithAttributesFactory; /** * Construct a new importer */ public JSONImporter() { super(); } /** * Import a graph. * *

    * The provided graph must be able to support the features of the graph that is read. For * example if the file contains self-loops then the graph provided must also support self-loops. * The same for multiple edges. * *

    * If the provided graph is a weighted graph, the importer also reads edge weights. Otherwise * edge weights are ignored. * * @param graph the output graph * @param input the input reader * @throws ImportException in case an error occurs, such as I/O or parse error */ @Override public void importGraph(Graph graph, Reader input) { final boolean verticesOutOfOrder = vertexWithAttributesFactory == null; final boolean edgesOutOfOrder = edgeWithAttributesFactory == null; JSONEventDrivenImporter genericImporter = new JSONEventDrivenImporter(verticesOutOfOrder, edgesOutOfOrder); Consumers consumers = new Consumers(graph); if (vertexWithAttributesFactory != null) { genericImporter.addVertexWithAttributesConsumer(consumers.vertexWithAttributesConsumer); } else { genericImporter.addVertexConsumer(consumers.vertexConsumer); } genericImporter.addVertexAttributeConsumer(consumers.vertexAttributeConsumer); if (edgeWithAttributesFactory != null) { genericImporter.addEdgeWithAttributesConsumer(consumers.edgeWithAttributesConsumer); } else { genericImporter.addEdgeConsumer(consumers.edgeConsumer); } genericImporter.addEdgeAttributeConsumer(consumers.edgeAttributeConsumer); genericImporter.importInput(input); } /** * Get the user custom vertex factory. This is null by default and the graph supplier is used * instead. * * @return the user custom vertex factory */ public Function getVertexFactory() { return vertexFactory; } /** * Set the user custom vertex factory. The default behavior is being null in which case the * graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the file. * The method is called with parameter the vertex identifier from the file and should return the * actual graph vertex to add to the graph. * * @param vertexFactory a vertex factory */ public void setVertexFactory(Function vertexFactory) { this.vertexFactory = vertexFactory; } /** * Set the user custom vertex factory with attributes. The default behavior is being null in * which case the graph vertex supplier is used. * * If supplied the vertex factory is called every time a new vertex is encountered in the input. * The method is called with parameter the vertex identifier from the input and a set of * attributes and should return the actual graph vertex to add to the graph. Note that the set * of attributes might not be complete, as only attributes available at the first vertex * definition are collected. * * @param vertexWithAttributesFactory a vertex factory with attributes */ public void setVertexWithAttributesFactory( BiFunction, V> vertexWithAttributesFactory) { this.vertexWithAttributesFactory = vertexWithAttributesFactory; } /** * Get the user custom edges factory with attributes. This is null by default and the graph * supplier is used instead. * * @return the user custom edge factory with attributes. */ public Function, E> getEdgeWithAttributesFactory() { return edgeWithAttributesFactory; } /** * Set the user custom edge factory with attributes. The default behavior is being null in which * case the graph edge supplier is used. * * If supplied the edge factory is called every time a new edge is encountered in the input. The * method is called with parameter the set of attributes and should return the actual graph edge * to add to the graph. Note that the set of attributes might not be complete, as only * attributes available at the first edge definition are collected. * * @param edgeWithAttributesFactory an edge factory with attributes */ public void setEdgeWithAttributesFactory( Function, E> edgeWithAttributesFactory) { this.edgeWithAttributesFactory = edgeWithAttributesFactory; } private class Consumers { private Graph graph; private GraphType graphType; private Map map; private Triple lastTriple; private E lastEdge; public Consumers(Graph graph) { this.graph = graph; this.graphType = graph.getType(); this.map = new HashMap<>(); } public final Consumer vertexConsumer = (t) -> { if (map.containsKey(t)) { throw new ImportException("Node " + t + " already exists"); } V v; if (vertexFactory != null) { v = vertexFactory.apply(t); graph.addVertex(v); } else { v = graph.addVertex(); } map.put(t, v); notifyVertex(v); notifyVertexAttribute(v, DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(t)); }; public final BiConsumer> vertexWithAttributesConsumer = (t, attrs) -> { if (map.containsKey(t)) { throw new ImportException("Node " + t + " already exists"); } V v; if (vertexWithAttributesFactory != null) { v = vertexWithAttributesFactory.apply(t, attrs); graph.addVertex(v); } else { v = graph.addVertex(); } map.put(t, v); // notify with all collected attributes attrs.put(DEFAULT_VERTEX_ID_KEY, DefaultAttribute.createAttribute(t)); notifyVertexWithAttributes(v, attrs); }; public final BiConsumer, Attribute> vertexAttributeConsumer = (p, a) -> { String vertex = p.getFirst(); if (!map.containsKey(vertex)) { throw new ImportException("Node " + vertex + " does not exist"); } notifyVertexAttribute(map.get(vertex), p.getSecond(), a); }; public final Consumer> edgeConsumer = (t) -> { String source = t.getFirst(); V from = map.get(source); if (from == null) { throw new ImportException("Node " + source + " does not exist"); } String target = t.getSecond(); V to = map.get(target); if (to == null) { throw new ImportException("Node " + target + " does not exist"); } E e = graph.addEdge(from, to); if (graphType.isWeighted() && t.getThird() != null) { graph.setEdgeWeight(e, t.getThird()); } notifyEdge(e); lastTriple = t; lastEdge = e; }; public final BiConsumer, Map> edgeWithAttributesConsumer = (t, attrs) -> { String source = t.getFirst(); V from = map.get(source); if (from == null) { throw new ImportException("Node " + source + " does not exist"); } String target = t.getSecond(); V to = map.get(target); if (to == null) { throw new ImportException("Node " + target + " does not exist"); } E e; if (edgeWithAttributesFactory != null) { e = edgeWithAttributesFactory.apply(attrs); graph.addEdge(from, to, e); } else { e = graph.addEdge(from, to); } notifyEdgeWithAttributes(e, attrs); lastTriple = t; lastEdge = e; }; public final BiConsumer, String>, Attribute> edgeAttributeConsumer = (p, a) -> { Triple t = p.getFirst(); if (t == lastTriple) { notifyEdgeAttribute(lastEdge, p.getSecond(), a); } }; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/json/package-info.java000066400000000000000000000001021402514743400304620ustar00rootroot00000000000000/** * Json importers/exporters */ package org.jgrapht.nio.json; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/lemon/000077500000000000000000000000001402514743400254435ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/lemon/LemonExporter.java000066400000000000000000000113751402514743400311200ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.lemon; import org.apache.commons.text.*; import org.jgrapht.*; import org.jgrapht.nio.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Exports a graph into Lemon graph format (LGF). * *

    * This is the custom graph format used in the Lemon graph * library. * * @param the graph vertex type * @param the graph edge type * * @author Dimitrios Michail */ public class LemonExporter extends BaseExporter implements GraphExporter { private static final String CREATOR = "JGraphT Lemon (LGF) Exporter"; private static final String VERSION = "1"; private static final String DELIM = " "; private static final String TAB1 = "\t"; private final Set parameters; /** * Parameters that affect the behavior of the {@link LemonExporter} exporter. */ public enum Parameter { /** * If set the exporter outputs edge weights */ EXPORT_EDGE_WEIGHTS, /** * If set the exporter escapes all strings as Java strings, otherwise no escaping is * performed. */ ESCAPE_STRINGS_AS_JAVA, } /** * Constructs a new exporter. */ public LemonExporter() { this(new IntegerIdProvider<>()); } /** * Constructs a new exporter with a given vertex id provider. * * @param vertexIdProvider for generating vertex IDs. Must not be null. */ public LemonExporter(Function vertexIdProvider) { super(vertexIdProvider); this.parameters = new HashSet<>(); } @Override public void exportGraph(Graph g, Writer writer) { PrintWriter out = new PrintWriter(writer); exportHeader(out); exportVertices(out, g); exportEdges(out, g); out.flush(); } /** * Return if a particular parameter of the exporter is enabled * * @param p the parameter * @return {@code true} if the parameter is set, {@code false} otherwise */ public boolean isParameter(Parameter p) { return parameters.contains(p); } /** * Set the value of a parameter of the exporter * * @param p the parameter * @param value the value to set */ public void setParameter(Parameter p, boolean value) { if (value) { parameters.add(p); } else { parameters.remove(p); } } private String prepareId(final String s) { boolean escapeStringAsJava = parameters.contains(Parameter.ESCAPE_STRINGS_AS_JAVA); if (escapeStringAsJava) { return "\"" + StringEscapeUtils.escapeJava(s) + "\""; } else { return s; } } private void exportHeader(PrintWriter out) { out.println("#Creator:" + DELIM + CREATOR); out.println("#Version:" + DELIM + VERSION); out.println(); } private void exportVertices(PrintWriter out, Graph g) { out.println("@nodes"); out.println("label"); for (V v : g.vertexSet()) { String id = getVertexId(v); String quotedId = prepareId(id); out.println(quotedId); } out.println(); } private void exportEdges(PrintWriter out, Graph g) { boolean exportEdgeWeights = parameters.contains(Parameter.EXPORT_EDGE_WEIGHTS); out.println("@arcs"); out.print(TAB1); out.print(TAB1); if (exportEdgeWeights) { out.println("weight"); } else { out.println("-"); } for (E edge : g.edgeSet()) { String s = getVertexId(g.getEdgeSource(edge)); String t = getVertexId(g.getEdgeTarget(edge)); out.print(prepareId(s)); out.print(TAB1); out.print(prepareId(t)); if (exportEdgeWeights) { out.print(TAB1); out.print(Double.toString(g.getEdgeWeight(edge))); } out.println(); } out.println(); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/lemon/package-info.java000066400000000000000000000000761402514743400306350ustar00rootroot00000000000000/** * Lemon input/output. */ package org.jgrapht.nio.lemon; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/matrix/000077500000000000000000000000001402514743400256355ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/matrix/MatrixExporter.java000066400000000000000000000172661402514743400315110ustar00rootroot00000000000000/* * (C) Copyright 2005-2021, by Charles Fry and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.matrix; import org.jgrapht.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; /** * Exports a graph to a plain text matrix format, which can be processed by matrix manipulation * software, such as MTJ or * MATLAB. * *

    * The exporter supports three different formats, see {@link Format}. *

    * * @see Format * * @param the graph vertex type * @param the graph edge type * * @author Charles Fry * @author Dimitrios Michail */ public class MatrixExporter extends BaseExporter implements GraphExporter { private final String delimiter = " "; private Format format; /** * Formats supported by the {@link MatrixExporter} exporter. */ public enum Format { /** * A sparse representation of the adjacency matrix. This is the default. Exports the * specified graph into a plain text file format containing a sparse representation of the * graph's adjacency matrix. The value stored in each position of the matrix indicates the * number of edges between two vertices. With an undirected graph, the adjacency matrix is * symmetric. */ SPARSE_ADJACENCY_MATRIX, /** * A sparse representation of the Laplacian. */ SPARSE_LAPLACIAN_MATRIX, /** * A sparse representation of the normalized Laplacian. */ SPARSE_NORMALIZED_LAPLACIAN_MATRIX, } /** * Creates a new MatrixExporter with integer name provider for the vertex identifiers and * {@link Format#SPARSE_ADJACENCY_MATRIX} as the default format. */ public MatrixExporter() { this(Format.SPARSE_ADJACENCY_MATRIX, new IntegerIdProvider<>()); } /** * Creates a new MatrixExporter with integer name provider for the vertex identifiers. * * @param format format to use */ public MatrixExporter(Format format) { this(format, new IntegerIdProvider<>()); } /** * Creates a new MatrixExporter. * * @param format format to use * @param vertexIdProvider for generating vertex identifiers. Must not be null. */ public MatrixExporter(Format format, Function vertexIdProvider) { super(vertexIdProvider); this.format = format; } /** * Get the format that the exporter is using. * * @return the output format */ public Format getFormat() { return format; } /** * Set the output format of the exporter * * @param format the format to use */ public void setFormat(Format format) { this.format = format; } @Override public void exportGraph(Graph g, Writer writer) throws ExportException { switch (format) { case SPARSE_ADJACENCY_MATRIX: exportAdjacencyMatrix(g, writer); break; case SPARSE_LAPLACIAN_MATRIX: if (g.getType().isUndirected()) { exportLaplacianMatrix(g, writer); } else { throw new ExportException( "Exporter can only export undirected graphs in this format"); } break; case SPARSE_NORMALIZED_LAPLACIAN_MATRIX: if (g.getType().isUndirected()) { exportNormalizedLaplacianMatrix(g, writer); } else { throw new ExportException( "Exporter can only export undirected graphs in this format"); } break; } } private void exportAdjacencyMatrix(Graph g, Writer writer) { for (V from : g.vertexSet()) { // assign ids in vertex set iteration order getVertexId(from); } PrintWriter out = new PrintWriter(writer); if (g.getType().isDirected()) { for (V from : g.vertexSet()) { exportAdjacencyMatrixVertex(out, from, Graphs.successorListOf(g, from)); } } else { for (V from : g.vertexSet()) { exportAdjacencyMatrixVertex(out, from, Graphs.neighborListOf(g, from)); } } out.flush(); } private void exportAdjacencyMatrixVertex(PrintWriter writer, V from, List neighbors) { String fromName = getVertexId(from); Map counts = new LinkedHashMap<>(); for (V to : neighbors) { String toName = getVertexId(to); ModifiableInteger count = counts.get(toName); if (count == null) { count = new ModifiableInteger(0); counts.put(toName, count); } count.increment(); if (from.equals(to)) { // count loops twice, once for each end count.increment(); } } for (Map.Entry entry : counts.entrySet()) { String toName = entry.getKey(); ModifiableInteger count = entry.getValue(); exportEntry(writer, fromName, toName, count.toString()); } } private void exportEntry(PrintWriter writer, String from, String to, String value) { writer.println(from + delimiter + to + delimiter + value); } private void exportLaplacianMatrix(Graph g, Writer writer) { PrintWriter out = new PrintWriter(writer); for (V from : g.vertexSet()) { // assign ids in vertex set iteration order getVertexId(from); } for (V from : g.vertexSet()) { String fromName = getVertexId(from); List neighbors = Graphs.neighborListOf(g, from); exportEntry(out, fromName, fromName, Integer.toString(neighbors.size())); for (V to : neighbors) { String toName = getVertexId(to); exportEntry(out, fromName, toName, "-1"); } } out.flush(); } private void exportNormalizedLaplacianMatrix(Graph g, Writer writer) { PrintWriter out = new PrintWriter(writer); for (V from : g.vertexSet()) { // assign ids in vertex set iteration order getVertexId(from); } for (V from : g.vertexSet()) { String fromName = getVertexId(from); Set neighbors = new LinkedHashSet<>(Graphs.neighborListOf(g, from)); if (neighbors.isEmpty()) { exportEntry(out, fromName, fromName, "0"); } else { exportEntry(out, fromName, fromName, "1"); for (V to : neighbors) { String toName = getVertexId(to); double value = -1 / Math.sqrt(g.degreeOf(from) * g.degreeOf(to)); exportEntry(out, fromName, toName, Double.toString(value)); } } } out.flush(); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/matrix/package-info.java000066400000000000000000000000771402514743400310300ustar00rootroot00000000000000/** * Matrix input/output */ package org.jgrapht.nio.matrix; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/package-info.java000066400000000000000000000001231402514743400275140ustar00rootroot00000000000000/** * Importers/Exporters for various graph formats. */ package org.jgrapht.nio; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/tsplib/000077500000000000000000000000001402514743400256265ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/tsplib/TSPLIBImporter.java000066400000000000000000001112011402514743400312040ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Hannes Wellmann and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.tsplib; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import java.io.*; import java.util.*; import java.util.function.*; import java.util.stream.*; import static java.util.Arrays.asList; /** * Importer for files in the * TSPLIB95 format. * *

    * This importer reads the nodes of a Symmetric travelling salesman problem instance from a * file and creates a {@link GraphTests#isComplete(Graph) complete graph} and provides further data * from the file and about the imported graph. *

    *

    * This implementation does not cover the full TSPLIB95 standard and only implements the subset of * capabilities required at the time of creation. All keywords of The specification part in * chapter 1.1 of the TSPLIB95 standard are considered. Their values can be obtained from * the corresponding getters of the {@link Specification}. But only the following *

  • EDGE_WEIGHT_TYPE
  • values are supported for a NODE_DATA_SECTION: *
      *
    • EUC_2D
    • *
    • EUC_3D
    • *
    • MAX_2D
    • *
    • MAX_3D
    • *
    • MAN_2D
    • *
    • MAN_3D
    • *
    • CEIL2D
    • *
    • GEO
    • *
    • ATT
    • *
    *

    *

    * The following data sections of The data part in chapter 1.2 of the TSPLIB95 * standard are supported: *

      *
    • NODE_COORD_SECTION
    • *
    • TOUR_SECTION
    • *
    *

    *

    * It was attempted to make the structure of this implementation generic so further keywords from * the specification part or other data sections can be considered if required by broaden this * class. Currently this implementation only reads Symmetric travelling salesman problems * with a NODE_COORD_SECTION and on of the supported EDGE_WEIGHT_TYPE. *

    *

    * The website of the TSPLIB standard already contains a large library of different TSP instances * provided as files in TSPLIB format. The * TSPLIB library of the University of * Waterlo provides more problem instances, among others a World TSP and instances based on * cities of different countries. *

    * * @author Hannes Wellmann * @param the graph vertex type * @param the graph edge type * */ public class TSPLIBImporter implements GraphImporter { private static final String NAME = "NAME"; private static final String TYPE = "TYPE"; private static final String COMMENT = "COMMENT"; private static final String DIMENSION = "DIMENSION"; private static final String CAPACITY = "CAPACITY"; private static final String EDGE_WEIGHT_TYPE = "EDGE_WEIGHT_TYPE"; private static final String EDGE_WEIGHT_FORMAT = "EDGE_WEIGHT_FORMAT"; private static final String EDGE_DATA_FORMAT = "EDGE_DATA_FORMAT"; private static final String NODE_COORD_TYPE = "NODE_COORD_TYPE"; private static final String DISPLAY_DATA_TYPE = "DISPLAY_DATA_TYPE"; private static final String NODE_COORD_SECTION = "NODE_COORD_SECTION"; private static final String TOUR_SECTION = "TOUR_SECTION"; private static final List VALID_TYPES = asList("TSP", "ATSP", "SOP", "HCP", "CVRP", "TOUR"); private static final List VALID_EDGE_WEIGHT_TYPES = asList( "EXPLICIT", "EUC_2D", "EUC_3D", "MAX_2D", "MAX_3D", "MAN_2D", "MAN_3D", "CEIL_2D", "GEO", "ATT", "XRAY1", "XRAY2", "SPECIAL"); private static final List VALID_EDGE_WEIGHT_FORMATS = asList( "FUNCTION", "FULL_MATRIX", "UPPER_ROW", "LOWER_ROW", "UPPER_DIAG_ROW", "LOWER_DIAG_ROW", "UPPER_COL", "LOWER_COL", "UPPER_DIAG_COL", "LOWER_DIAG_COL"); private static final List VALID_EDGE_DATA_FORMATS = asList("EDGE_LIST", "ADJ_LIST"); private static final List VALID_NODE_COORD_TYPES = asList("TWOD_COORDS", "THREED_COORDS", "NO_COORDS"); private static final List VALID_DISPLAY_DATA_TYPE = asList("COORD_DISPLAY", "TWOD_DISPLAY", "NO_DISPLAY"); /** * Container for the entry values read from the specification part of a file in * TSPLIB95 format. * * @author Hannes Wellmann */ public static class Specification { private String name; private String type; private final List comment = new ArrayList<>(); private Integer dimension; private Integer capacity; private String edgeWeightType; private String edgeWeightFormat; private String edgeDataFormat; private String nodeCoordType; private String displayDataType; Specification() { } /** * Returns the value of the NAME keyword in the imported file. * * @return the value of the NAME keyword */ public String getName() { return name; } /** * Returns the value of the TYPE keyword in the imported file. * * @return the value of the TYPE keyword */ public String getType() { return type; } /** * Returns the {@link List} of values for the COMMENT keyword in the imported file. * * @return the value of the COMMENT keyword */ public List getComments() { return Collections.unmodifiableList(comment); } /** * Returns the value of the DIMENSION keyword in the imported file. * * @return the value of the DIMENSION keyword */ public Integer getDimension() { return dimension; } /** * Returns the value of the CAPACITY keyword in the imported file. * * @return the value of the CAPACITY keyword */ public Integer getCapacity() { return capacity; } /** * Returns the value of the EDGE_WEIGHT_TYPE keyword in the imported file. * * @return the value of the EDGE_WEIGHT_TYPE keyword */ public String getEdgeWeightType() { return edgeWeightType; } /** * Returns the value of the EDGE_WEIGHT_FORMAT keyword in the imported file. * * @return the value of the EDGE_WEIGHT_FORMAT keyword */ public String getEdgeWeightFormat() { return edgeWeightFormat; } /** * Returns the value of the EDGE_DATA_FORMAT keyword in the imported file. * * @return the value of the EDGE_DATA_FORMAT keyword */ public String getEdgeDataFormat() { return edgeDataFormat; } /** * Returns the value of the NODE_COORD_TYPE keyword in the imported file. * * @return the value of the NODE_COORD_TYPE keyword */ public String getNodeCoordType() { return nodeCoordType; } /** * Returns the value of the DISPLAY_DATA_TYPE keyword in the imported file. * * @return the value of the DISPLAY_DATA_TYPE keyword */ public String getDisplayDataType() { return displayDataType; } } /** * Container for the meta data of an imported TSPLIB95 file. * * @author Hannes Wellmann * @param the graph vertex type * @param the graph edge type */ public static class Metadata { private final Specification spec = new Specification(); private Map vertex2node; private Graph graph; private List tour; private Boolean hasDistinctLocations; private Boolean hasDistinctNeighborDistances; private Metadata() { } /** * Returns the {@link Specification} instance containing all values from the specification * part of a TSPLIB95 file. * * @return the {@code Specification} of an imported TSPLIB95 file */ public Specification getSpecification() { return spec; } /** * Returns the mapping of vertex to corresponding node imported from the * NODE_COORD_SECTION of a TSPLIB95 file. * * @return the mapping of vertex to corresponding node */ public Map getVertexToNodeMapping() { return vertex2node; } /** * Returns the {@link List} of vertices in the order of the tour defined in an imported * TSPLIB95 file or null if no tour was imported. *

    * Note that a tour can be imported by {@link TSPLIBImporter#importGraph(Graph, Reader)} or * {@link TSPLIBImporter#importTour(Metadata, Reader)} . *

    * * @return the vertex tour from the file or null */ public List getTour() { return tour; } /** * Returns true if for the imported graph all vertices have distinct coordinates and non of * them have {@link Arrays#equals(Object) equal} {@link Node#getCoordinates() coordinate * values} , else false. * * @return true if no equally located nodes were imported from the file, else false * @throws IllegalStateException if no graph was imported */ public boolean hasDistinctNodeLocations() { if (graph == null) { throw new IllegalStateException("No graph imported"); } if (hasDistinctLocations == null) { hasDistinctLocations = Boolean.TRUE; Set> distinctCoordinates = CollectionUtil.newHashSetWithExpectedSize(vertex2node.size()); for (Node node : vertex2node.values()) { double[] coordinates = node.getCoordinates(); // Arrays.equals checks identity. Conversion to a List and use of a // HashSet has linear runtime. Unlike with a TreeSet using a comparator. Double[] coordinateObj = new Double[coordinates.length]; for (int i = 0; i < coordinates.length; i++) { coordinateObj[i] = Double.valueOf(coordinates[i]); } if (!distinctCoordinates.add(Arrays.asList(coordinateObj))) { hasDistinctLocations = Boolean.FALSE; return hasDistinctLocations; } } } return hasDistinctLocations; } /** * Returns true if for the imported graph each vertex all touching edges have different * weights. *

    * If this method returns true this means for the TSP that for each location each other * location has a different distance, so there are no two other locations that have the same * distance from that location. *

    * * @return true if all touching edges of each vertex have different weight, else false * @throws IllegalStateException if no graph was imported */ public boolean hasDistinctNeighborDistances() { if (graph == null) { throw new IllegalStateException("No graph imported"); } if (hasDistinctNeighborDistances == null) { hasDistinctNeighborDistances = Boolean.TRUE; Set vertices = graph.vertexSet(); // each vertex has vertices.size()-1 edges Set weights = CollectionUtil.newHashSetWithExpectedSize(vertices.size() - 1); for (V v : vertices) { weights.clear(); for (E edge : graph.edgesOf(v)) { if (!weights.add(graph.getEdgeWeight(edge))) { hasDistinctNeighborDistances = Boolean.FALSE; return hasDistinctNeighborDistances; } } } } return hasDistinctNeighborDistances; } } /** * A node imported from the NODE_COORD_SECTION of a TSPLIB95-file. * * @author Hannes Wellmann */ public static class Node { /** The one based number of this node. */ private final int number; /** The coordinates of this node. */ private final double[] coordinates; Node(int number, double[] coordinates) { this.number = number; this.coordinates = coordinates; } /** * Returns the number of this node as specified in the source TSPLIB95-file. * * @return the number of this node */ public int getNumber() { return number; } /** * Returns the number of elements the coordinates of this node have (either two or three). * * @return the number of coordinate elements of this node */ public int getCoordinatesLength() { return coordinates.length; } /** * Returns the value of the coordinate element with zero-based index i of this * node. * * @param i the index of the coordinate element * @return the value of the i-th coordinate element */ public double getCoordinateValue(int i) { return coordinates[i]; } /** * Returns a copy of the coordinates of this node. * * @return the coordinates of this node */ public double[] getCoordinates() { return Arrays.copyOf(coordinates, coordinates.length); } @Override public String toString() { return number + " " + Arrays .stream(coordinates).mapToObj(Double::toString).collect(Collectors.joining(" ")); } } private int vectorLength = -1; private Metadata metadata; /** Constructs a new importer. */ public TSPLIBImporter() { // NoOp } /** * Returns the {@link Metadata} of the latest imported file or null, if no import completed yet * or the latest import failed. * * @return {@code TSPLIBFileData} of the latest import */ public Metadata getMetadata() { return metadata; } // read of node data section /** * {@inheritDoc} *

    * The given {@link Graph} must be weighted. Also the graph should be empty, otherwise the * behavior is unspecified which could lead to exceptions. *

    *

    * The source of the given Reader should contain a NODE_COORD_SECTION (if not the graph * is not changed) and can contain a TOUR_SECTION. If a TOUR_SECTION is * present a corresponding NODE_COORD_SECTION is mandatory and the read vertex numbers * are referred to the NODE_COORD_SECTION in the same source. *

    *

    * {@link Metadata} of the import can be obtained with {@link #getMetadata()} after this method * returns. If the readers source contains a TOUR_SECTION the imported tour can be * obtained from {@link Metadata#getTour()}. *

    *

    * This implementation is not thread-safe and must be synchronized externally if called by * concurrent threads. *

    * * @param graph the graph into which this importer writes, must weighted. * @throws IllegalArgumentException if the specified {@code graph} is not weighted */ @Override public void importGraph(Graph graph, Reader in) { metadata = null; try { Iterator lines = getLineIterator(in); metadata = readContentForGraph(lines, graph); } catch (Exception e) { throw getImportException(e, "graph"); } } private Metadata readContentForGraph(Iterator lines, Graph graph) { if (!graph.getType().isWeighted()) { throw new IllegalArgumentException("Graph must be weighted"); } vectorLength = -1; Metadata data = new Metadata<>(); List tour = null; while (lines.hasNext()) { String[] keyValue = lines.next().split(":"); String key = getKey(keyValue); if (readSpecificationSection(key, data.spec, keyValue)) { // some specification element was read. Continue with next line. } else if (NODE_COORD_SECTION.equals(key)) { requireNotSet(data.graph, NODE_COORD_SECTION); data.graph = graph; data.vertex2node = readNodeCoordinateSection(lines, data); } else if (TOUR_SECTION.equals(key)) { requireNotSet(tour, TOUR_SECTION); tour = readTourSection(lines, data.spec.dimension); } } if (tour != null) { data.tour = getVertexTour(tour, data.vertex2node); } return data; } /** * Reads all nodes of the NODE_COORD_SECTION and fills the graph of the data accordingly. * * @return a mapping from created graph {@link V vertex} to corresponding imported {@link Node} */ private Map readNodeCoordinateSection(Iterator lines, Metadata data) { requireSet(data.spec.edgeWeightType, NODE_COORD_SECTION); requireSet(data.spec.dimension, DIMENSION); // DIMENSION specifies the number of nodes ToIntBiFunction edgeWeightFunction = getEdgeWeightFunction(data.spec.edgeWeightType); List nodes = readNodes(lines, data.spec.dimension); // create vertices for all imported nodes Map vertex2node = CollectionUtil.newHashMapWithExpectedSize(nodes.size()); Graph graph = data.graph; for (Node node : nodes) { V v = graph.addVertex(); vertex2node.put(v, node); } // create edges for each possible pair of vertices and compute their weights new CompleteGraphGenerator().generateGraph(graph, null); graph.edgeSet().forEach(e -> { Node s = vertex2node.get(graph.getEdgeSource(e)); Node t = vertex2node.get(graph.getEdgeTarget(e)); double weight = edgeWeightFunction.applyAsInt(s, t); graph.setEdgeWeight(e, weight); }); return Collections.unmodifiableMap(vertex2node); } private ToIntBiFunction getEdgeWeightFunction(String edgeWeightType) { switch (edgeWeightType) { case "EUC_2D": vectorLength = 2; return this::computeEuclideanDistance; case "EUC_3D": vectorLength = 3; return this::computeEuclideanDistance; case "MAX_2D": vectorLength = 2; return this::computeMaximumDistance; case "MAX_3D": vectorLength = 3; return this::computeMaximumDistance; case "MAN_2D": vectorLength = 2; return this::computeManhattanDistance; case "MAN_3D": vectorLength = 3; return this::computeManhattanDistance; case "CEIL_2D": vectorLength = 2; return this::compute2DCeilingEuclideanDistance; case "GEO": vectorLength = 2; return this::compute2DGeographicalDistance; case "ATT": vectorLength = 2; return this::compute2DPseudoEuclideanDistance; default: throw new IllegalStateException( "Unsupported EDGE_WEIGHT_TYPE <" + edgeWeightType + ">"); } } private List readNodes(Iterator lines, int dimension) { List nodes = new ArrayList<>(dimension); for (int i = 0; i < dimension && lines.hasNext(); i++) { String line = lines.next(); Node node = parseNode(line); nodes.add(node); } return nodes; } private Node parseNode(String line) { String[] elements = line.split(" "); if (elements.length != vectorLength + 1) { throw new IllegalArgumentException( "Unexpected number of elements <" + elements.length + "> in line: " + line); } int number = Integer.parseInt(elements[0]); double[] coordinates = Arrays.stream(elements, 1, elements.length).mapToDouble(Double::parseDouble).toArray(); return new Node(number, coordinates); } // read of tour data section /** * Imports a tour described by a {@link List} of {@link V vertices} using the given Reader. *

    * It is the callers responsibility to ensure the {@code Reader} is closed after this method * returned. *

    *

    * The source of the given Reader should contain a TOUR_SECTION (if not null is * returned). The vertices specified by their number in the TOUR_SECTION are referred * to the nodes respectively vertices in the given {@code metadata}. *

    *

    * The {@link Metadata} of the import can be obtained with {@link #getMetadata()} after this * method returns. The {@code Metadata#getVertexToNodeMapping() vertexToNodeMapping} in the * metadata of this import is the same as in the given {@code metadata}. *

    *

    * This implementation is not thread-safe and must be synchronized externally if called by * concurrent threads. *

    * * @param referenceMetadata the {@code Metadata} defining the available vertices and their * {@code Nodes}. * @param in the input reader * @return the imported tour or null, if no tour was imported */ public List importTour(Metadata referenceMetadata, Reader in) { metadata = null; try { Iterator lines = getLineIterator(in); metadata = readContentForTour(lines, referenceMetadata.vertex2node); return metadata.tour; } catch (Exception e) { throw getImportException(e, "tour"); } } private Metadata readContentForTour(Iterator lines, Map vertex2node) { Metadata data = new Metadata<>(); while (lines.hasNext()) { String[] keyValue = lines.next().split(":"); String key = getKey(keyValue); if (readSpecificationSection(key, data.spec, keyValue)) { // some specification element was read. Continue with next line. } else if (TOUR_SECTION.equals(key)) { requireNotSet(data.tour, TOUR_SECTION); List tour = readTourSection(lines, data.spec.dimension); data.tour = getVertexTour(tour, vertex2node); } } data.vertex2node = vertex2node; return data; } /** * Reads a tour of the TOUR_SECTION and returns the List of ordered vertex numbers describing * the tour. * * @return the list of vertex number describing the tour */ private List readTourSection(Iterator lines, Integer dimension) { List tour = dimension != null ? new ArrayList<>(dimension) : new ArrayList<>(); while (lines.hasNext()) { String lineContent = lines.next(); if ("-1".equals(lineContent)) { break; } tour.add(Integer.valueOf(lineContent)); } return tour; } private List getVertexTour(List tour, Map vertex2node) { requireSet(vertex2node, TOUR_SECTION); List orderedVertices = getOrderedVertices(vertex2node); List vertexTour = new ArrayList<>(orderedVertices.size()); for (Integer vertexNumber : tour) { // number may be zero or one based (its more a id) V v = vertexNumber < orderedVertices.size() ? orderedVertices.get(vertexNumber) : null; if (v == null) { throw new IllegalStateException("Missing vertex with number " + vertexNumber); } vertexTour.add(v); } return vertexTour; } private List getOrderedVertices(Map vertex2node) { int maxNumber = vertex2node.values().stream().mapToInt(Node::getNumber).max().getAsInt(); @SuppressWarnings("unchecked") V[] orderedVertices = (V[]) new Object[maxNumber + 1]; vertex2node.forEach((v, n) -> orderedVertices[n.number] = v); return asList(orderedVertices); } // read of specification private boolean readSpecificationSection(String key, Specification spec, String[] lineElements) { // only read value if it is sure that there should be a value switch (key) { case NAME: requireNotSet(spec.name, NAME); spec.name = getValue(lineElements); return true; case TYPE: requireNotSet(spec.type, TYPE); String type = getValue(lineElements); spec.type = requireValidValue(type, VALID_TYPES, TYPE); return true; case COMMENT: String comment = getValue(lineElements); spec.comment.add(comment); return true; case DIMENSION: requireNotSet(spec.dimension, DIMENSION); String dimension = getValue(lineElements); spec.dimension = parseInteger(dimension, DIMENSION); return true; case CAPACITY: requireNotSet(spec.capacity, CAPACITY); String capacity = getValue(lineElements); spec.capacity = parseInteger(capacity, CAPACITY); return true; case EDGE_WEIGHT_TYPE: requireNotSet(spec.edgeWeightType, EDGE_WEIGHT_TYPE); String edgeWeightType = getValue(lineElements); spec.edgeWeightType = requireValidValue(edgeWeightType, VALID_EDGE_WEIGHT_TYPES, EDGE_WEIGHT_TYPE); return true; case EDGE_WEIGHT_FORMAT: requireNotSet(spec.edgeWeightFormat, EDGE_WEIGHT_FORMAT); String edgeWeightFormat = getValue(lineElements); spec.edgeWeightFormat = requireValidValue(edgeWeightFormat, VALID_EDGE_WEIGHT_FORMATS, EDGE_WEIGHT_FORMAT); return true; case EDGE_DATA_FORMAT: requireNotSet(spec.edgeDataFormat, EDGE_DATA_FORMAT); String edgeDataFormat = getValue(lineElements); spec.edgeDataFormat = requireValidValue(edgeDataFormat, VALID_EDGE_DATA_FORMATS, EDGE_DATA_FORMAT); return true; case NODE_COORD_TYPE: requireNotSet(spec.nodeCoordType, NODE_COORD_TYPE); String nodeCoordType = getValue(lineElements); spec.nodeCoordType = requireValidValue(nodeCoordType, VALID_NODE_COORD_TYPES, NODE_COORD_TYPE); return true; case DISPLAY_DATA_TYPE: requireNotSet(spec.displayDataType, DISPLAY_DATA_TYPE); String displayDataType = getValue(lineElements); spec.displayDataType = requireValidValue(displayDataType, VALID_DISPLAY_DATA_TYPE, DISPLAY_DATA_TYPE); return true; default: return false; } } private String requireValidValue(String value, List validValues, String valueType) { for (String validValue : validValues) { if (validValue.equalsIgnoreCase(value)) { return validValue; // always use the upper case version } } throw new IllegalArgumentException("Invalid " + valueType + " value <" + value + ">"); } private Integer parseInteger(String valueStr, String valueType) { try { return Integer.valueOf(valueStr); } catch (NumberFormatException e) { throw new IllegalArgumentException( "Invalid " + valueType + " integer value <" + valueStr + ">", e); } } // read utilities private static Iterator getLineIterator(Reader in) { BufferedReader reader = new BufferedReader(in); return Stream.iterate(readLine(reader), Objects::nonNull, l -> readLine(reader)).iterator(); } private static String readLine(BufferedReader reader) { try { String line = reader.readLine(); if (line != null) { line = line.trim(); return "EOF".equals(line) ? null : line; } return null; } catch (IOException e) { throw new IllegalStateException("I/O exception while reading line of TSPLIB file", e); } } private static String getKey(String[] keyValue) { return keyValue[0].trim().toUpperCase(); } private String getValue(String[] keyValue) { if (keyValue.length < 2) { throw new IllegalStateException("Missing value for key " + getKey(keyValue)); } return keyValue[1].trim(); } private void requireNotSet(Object target, String keyName) { if (target != null) { throw new IllegalStateException("Multiple values for key " + keyName); } } private void requireSet(Object requirement, String target) { if (requirement == null) { throw new IllegalStateException("Missing data to read <" + target + ">"); } } private static ImportException getImportException(Exception e, String target) { return new ImportException( "Failed to import " + target + " from TSPLIB-file: " + e.getMessage(), e); } // distance computations // all of the following methods are implemented in accordance to // section "2. The distance functions" of TSPLIB95 /** * Computes the distance of the two nodes n1 and n2 according to the {@code EUC_2D} or * {@code EUC_3D} metric depending on their dimension. The used metric is also known as L2-norm. * * @param n1 a {@code Node} with two or three dimensional coordinates * @param n2 a {@code Node} with two or three dimensional coordinates * @return the {@code EUC_2D} or {@code EUC_3D} edge weight for nodes n1 and n2 */ int computeEuclideanDistance(Node n1, Node n2) { // according to TSPLIB95 distances are rounded to next integer value return (int) Math.round(getL2Distance(n1, n2)); } /** * Computes the distance of the two nodes n1 and n2 according to the {@code MAX_2D} or * {@code MAX_3D} metric depending on their dimension. The used metric is also known as * L∞-norm. * * @param n1 a {@code Node} with two or three dimensional coordinates * @param n2 a {@code Node} with two or three dimensional coordinates * @return the {@code MAX_2D} or {@code MAX_3D} edge weight for nodes n1 and n2 */ int computeMaximumDistance(Node n1, Node n2) { // according to TSPLIB95 distances are rounded to next integer value return (int) Math.round(getLInfDistance(n1, n2)); } /** * Computes the distance of the two nodes n1 and n2 according to the {@code MAN_2D} or * {@code MAN_3D} metric depending on their dimension. The used metric is also known as L1-norm. * * @param n1 a {@code Node} with two or three dimensional coordinates * @param n2 a {@code Node} with two or three dimensional coordinates * @return the {@code MAN_2D} or {@code MAN_3D} edge weight for nodes n1 and n2 */ int computeManhattanDistance(Node n1, Node n2) { // according to TSPLIB95 distances are rounded to next integer value return (int) Math.round(getL1Distance(n1, n2)); } /** * Computes the distance of the two nodes n1 and n2 according to the {@code CEIL_2D} metric, the * round up version of {@code EUC_2D}. The points must have dimension two. * * @param n1 a {@code Node} with two or three dimensional coordinates * @param n2 a {@code Node} with two or three dimensional coordinates * @return the {@code CEIL_2D} edge weight for nodes n1 and n2 * @see #computeEuclideanDistance(RealVector, RealVector) */ int compute2DCeilingEuclideanDistance(Node n1, Node n2) { return (int) Math.ceil(getL2Distance(n1, n2)); } /** * Computes the distance of the two nodes n1 and n2 according to the {@code GEO} metric. The * used metric computes the distance between two points on a earth-like sphere, while the point * coordinates describe their geographical latitude and longitude. The points must have * dimension two. * * @param n1 a {@code Node} with two or three dimensional coordinates * @param n2 a {@code Node} with two or three dimensional coordinates * @return the {@code GEO} edge weight for nodes n1 and n2 */ int compute2DGeographicalDistance(Node n1, Node n2) { double latitude1 = computeRadiansAngle(n1.getCoordinateValue(0)); double longitude1 = computeRadiansAngle(n1.getCoordinateValue(1)); double latitude2 = computeRadiansAngle(n2.getCoordinateValue(0)); double longitude2 = computeRadiansAngle(n2.getCoordinateValue(1)); double q1 = Math.cos(longitude1 - longitude2); double q2 = Math.cos(latitude1 - latitude2); double q3 = Math.cos(latitude1 + latitude2); return (int) (RRR * Math.acos(0.5 * ((1.0 + q1) * q2 - (1.0 - q1) * q3)) + 1.0); } static final double PI = 3.141592; // constants according to TSPLIB95 static final double RRR = 6378.388; // constants according to TSPLIB95 private static double computeRadiansAngle(double x) { // computation according to TSPLIB95 chapter 2.4 - Geographical distance // First computes decimal angle from degrees and minutes, then converts it into radian double deg = Math.round(x); double min = x - deg; return PI * (deg + 5.0 * min / 3.0) / 180.0; } /** * Computes the distance of two the two nodes n1 and n2 according to the {@code ATT} metric. The * nodes must have two dimensional coordinates. * * @param n1 a {@code Node} with two dimensional coordinates * @param n2 a {@code Node} with two dimensional coordinates * @return the {@code ATT} edge weight for nodes n1 and n2 */ int compute2DPseudoEuclideanDistance(Node n1, Node n2) { double xd = n1.getCoordinateValue(0) - n2.getCoordinateValue(0); double yd = n1.getCoordinateValue(1) - n2.getCoordinateValue(1); double rij = Math.sqrt((xd * xd + yd * yd) / 10.0); double tij = Math.round(rij); if (tij < rij) { return (int) (tij + 1); } else { return (int) tij; } } private double getL1Distance(Node n1, Node n2) { double elementSum = 0; for (int i = 0; i < vectorLength; i++) { double delta = n1.getCoordinateValue(i) - n2.getCoordinateValue(i); elementSum += Math.abs(delta); } return elementSum; } private double getL2Distance(Node n1, Node n2) { double elementSum = 0; for (int i = 0; i < vectorLength; i++) { double delta = n1.getCoordinateValue(i) - n2.getCoordinateValue(i); elementSum += delta * delta; } return Math.sqrt(elementSum); } private double getLInfDistance(Node n1, Node n2) { double maxElement = 0; for (int i = 0; i < vectorLength; i++) { double delta = n1.getCoordinateValue(i) - n2.getCoordinateValue(i); maxElement = Math.max(maxElement, Math.abs(delta)); } return maxElement; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/java/org/jgrapht/nio/tsplib/package-info.java000066400000000000000000000001101402514743400310050ustar00rootroot00000000000000/** * TSPLIB95 importers/exporters */ package org.jgrapht.nio.tsplib; jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/resources/000077500000000000000000000000001402514743400224075ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/resources/gexf.xsd000066400000000000000000000251151402514743400240640ustar00rootroot00000000000000 Tree Datatypes jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/resources/graphml.xsd000066400000000000000000001216001402514743400245610ustar00rootroot00000000000000 This document defines the GraphML language including GraphML attributes and GraphML parseinfo. Complex type definitions for the GraphML structural layer elements: <data>, <default>, <key>, <graphml>, <graph>, <node>, <port>, <edge>, <hyperedge>, <endpoint> and <locator>. The names of the complex types are constructed corresponding to the pattern element_name.type. (The only remaining GraphML structural layer element <desc> is of simple type xs:string.) Extension mechanism for the content of <data> and <default>. The complex type data-extension.type is empty per default. Users may redefine this type in order to add content to the complex types data.type and default.type which are extensions of data-extension.type. Complex type for the <data> element. data.type is mixed, that is, <data> may contain #PCDATA. Content type: extension of data-extension.type which is empty per default. refers to the id attribute of a <key>. identifies this <data>. user defined extra attributes for <data> elements Complex type for the <default> element. default.type is mixed, that is, data may contain #PCDATA. Content type: extension of data-extension.type which is empty per default. user defined extra attributes for <default> elements Simple type for the for attribute of <key>. key.for.type is a restriction of xs:NMTOKEN Allowed values: all, graphml, graph, node, edge, hyperedge, port and endpoint. Complex type for the <key> element. identifies this <key>. describes the domain of definition for the corresponding graph attribute. user defined extra attributes for <key> elements. Complex type for the <graphml> element. user defined extra attributes for <graphml> elements. Simple type for the edgedefault attribute of <graph>. graph.edgedefault.type is a restriction of xs:NMTOKEN Allowed values: directed, undirected. Complex type for the <graph> element. user defined extra attributes for <graph> elements. identifies this graph . describes whether edges of this graph are considered as directed or undirected per default (unless specified by the attribute directed of <edge>). Complex type for the <node> element. user defined extra attributes for <node elements. identifies this node. Complex type for the <port> element. user defined extra attributes for <port> elements. identifies this port, within the node it is contained in. Complex type for the <edge> element. user defined extra attributes for <edge> elements. identifies this edge . overwrites the edgedefault attribute of <graph> . points to the id attribute of the source <node>. points to the id attribute of the target <node>. points to the name attribute of the source <port>. points to the name attribute of the target <port>. Complex type for the <hyperedge> element. user defined extra attributes for <hyperedge> elements. identifies this <hyperedge> . Simple type for the type attribute of <endpoint>. endpoint.type.type is a restriction of xs:NMTOKEN Allowed values: in, out, undir. Complex type for the <endpoint> element. user defined extra attributes for <endpoint> elements. identifies this <endpoint> . points to the name of the port, to which this endpoint is connected . points to the id of the node, to which this endpoint is connected. defines the direction on this endpoint (undirected per default). Complex type for the <locator> element. Content type: (empty) user defined extra attributes for <locator> elements. points to the resource of this locator. type of the hyperlink (fixed as simple). Description: Provides human-readable descriptions for the GraphML element containing this <desc> as its first child. Occurence: <key>, <graphml>, <graph>, <node>, <port>, <edge>, <hyperedge>, and <endpoint>. Description: Graphs and nodes are declared by the elements <graph> and <node>, respectively. The optional <locator>-child of these elements point to their definition. (If there is no <locator>-child the graphs/nodes are defined by their content). Occurence: <graph>, and <node>. Description: In GraphML there may be data-functions attached to graphs, nodes, ports, edges, hyperedges and endpoint and to the whole collection of graphs described by the content of <graphml>. These functions are declared by <key> elements (children of <graphml>) and defined by <data> elements. Occurence: <graphml>, <graph>, <node>, <port>, <edge>, <hyperedge>, and <endpoint>. Ensures: uniqueness of the key attributes of <data> children of this <data> element. Description: In GraphML there may be data-functions attached to graphs, nodes, ports, edges, hyperedges and endpoint and to the whole collection of graphs described by the content of <graphml>. These functions are declared by <key> elements (children of <graphml>) and defined by <data> elements. Occurence: <graphml>. Description: In GraphML there may be data-functions attached to graphs, nodes, ports, edges, hyperedges and endpoint and to the whole collection of graphs described by the content of <graphml>. These functions are declared by <key> elements (children of <graphml>) and defined by <data> elements. The (optional) <default> child of <key> gives the default value for the corresponding function. Occurence: <key>. Description: <graphml> is the root element of each GraphML document. Occurence: root. Ensures: uniqueness of the key attributes of <data> children of this <graphml> element. Ensures: existence and uniqueness of the id attributes of each <key> element in this document. Ensures: uniqueness of the id attributes of each <graph> element in this document. Ensures: for the key attribute of each <data> in this document, the existence of an id attribute of <key> which matches the value of it. Description: Describes one graph in this document. Occurence: <graphml>, <node>, <edge>, <hyperedge>. Ensures: uniqueness of the key attributes of <data> children of this <graph> element. Ensures: existence and uniqueness of the id attributes of each <node> element in this graph. Ensures: uniqueness of the id attributes of each <edge> element in this graph. Ensures: uniqueness of the id attributes of each <hyperedge> element in this graph. Ensures: uniqueness of the id attributes of each <endpoint> element in this graph. Ensures: for the source attribute of each <edge> in this graph, the existence of an id attribute of <node> which matches the value of it. Ensures: for the target attribute of each <edge> in this graph, the existence of an id attribute of <node> which matches the value of it. Ensures: for the node attribute of each <endpoint> in this graph, the existence of an id attribute of <node> which matches the value of it. Description: Describes one node in the <graph> containing this <node>. Occurence: <graph>. Ensures: existence and uniqueness of the name attributes of each <port> element within this <node>. Ensures: uniqueness of the key attributes of <data> children of this <node> element. Description: Nodes may be structured by ports; thus edges are not only attached to a node but to a certain port in this node. Occurence: <node>, <port>. Ensures: uniqueness of the key attributes of <data> children of this <port> element. Description: Describes an edge in the <graph> which contains this <edge>. Occurence: <graph>. Ensures: uniqueness of the key attributes of <data> children of this <edge> element. Description: While edges describe relations between two nodes, a hyperedge describes a relation between an arbitrary number of nodes. Occurence: <graph>. Ensures: uniqueness of the key attributes of <data> children of this <hyperedge> element. Description: The list of <endpoints> within a hyperedge points to the nodes contained in this hyperedge. Occurence: <hyperedge>. Simple type for the attr.name attribute of <key>. key.name.type is final, that is, it may not be extended or restricted. key.name.type is a restriction of xs:NMTOKEN Allowed values: (no restriction) Simple type for the attr.type attribute of <key>. key.type.type is final, that is, it may not be extended or restricted. key.type.type is a restriction of xs:NMTOKEN Allowed values: boolean, int, long, float, double, string. Definition of the attribute group key.attributes.attrib. This group consists of the two optional attributes - attr.name (gives the name for the data function) - attr.type ((declares the range of values for the data function) Simple type definitions for the new graph attributes. Simple type for the parse.order attribute of <graph>. graph.order.type is final, that is, it may not be extended or restricted. graph.order.type is a restriction of xs:NMTOKEN Allowed values: free, nodesfirst, adjacencylist. Simple type for the parse.nodes attribute of <graph>. graph.nodes.type is final, that is, it may not be extended or restricted. graph.nodes.type is a restriction of xs:nonNegativeInteger Allowed values: (no restriction). Simple type for the parse.edges attribute of <graph>. graph.edges.type is final, that is, it may not be extended or restricted. graph.edges.type is a restriction of xs:nonNegativeInteger Allowed values: (no restriction). Simple type for the parse.maxindegree attribute of <graph>. graph.maxindegree.type is final, that is, it may not be extended or restricted. graph.maxindegree.type is a restriction of xs:nonNegativeInteger Allowed values: (no restriction). Simple type for the parse.maxoutdegree attribute of <graph>. graph.maxoutdegree.type is final, that is, it may not be extended or restricted. graph.maxoutdegree.type is a restriction of xs:nonNegativeInteger Allowed values: (no restriction). Simple type for the parse.nodeids attribute of <graph>. graph.nodeids.type is final, that is, it may not be extended or restricted. graph.nodeids.type is a restriction of xs:string Allowed values: (no restriction). Simple type for the parse.edgeids attribute of <graph>. graph.edgeids.type is final, that is, it may not be extended or restricted. graph.edgeids.type is a restriction of xs:string Allowed values: (no restriction). Definition of the attribute group graph.parseinfo.attrib. This group consists of the seven attributes - parse.nodeids (fixed to 'canonical' meaning that the id attribute of <node> follows the pattern 'n[number]), - parse.edgeids (fixed to 'canonical' meaning that the id attribute of <edge> follows the pattern 'e[number]), - parse.order (required; one of the values 'nodesfirst', 'adjacencylist' or 'free'), - parse.nodes (required; number of nodes in this graph), - parse.edges (required; number of edges in this graph), - parse.maxindegree (optional; maximal indegree of a node in this graph), - parse.maxoutdegree (optional; maximal outdegree of a node in this graph) Simple type definitions for the new node attributes. Simple type for the parse.indegree attribute of <node>. node.indegree.type is final, that is, it may not be extended or restricted. node.indegree.type is a restriction of xs:nonNegativeInteger Allowed values: (no restriction). Simple type for the parse.outdegree attribute of <node>. node.outdegree.type is final, that is, it may not be extended or restricted. node.outdegree.type is a restriction of xs:nonNegativeInteger Allowed values: (no restriction). Definition of the attribute group node.parseinfo.attrib. This group consists of two attributes - parse.indegree (optional; indegree of this node), - parse.outdegree (optional; outdegree of this node). jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/resources/viz.xsd000066400000000000000000000132101402514743400237340ustar00rootroot00000000000000 jgrapht-jgrapht-1.5.1/jgrapht-io/src/main/resources/xlink.xsd000066400000000000000000000026731402514743400242640ustar00rootroot00000000000000 jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/000077500000000000000000000000001402514743400204305ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/000077500000000000000000000000001402514743400213515ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/000077500000000000000000000000001402514743400221405ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/000077500000000000000000000000001402514743400235775ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/000077500000000000000000000000001402514743400243645ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/IntegerIdProviderTest.java000066400000000000000000000026261402514743400314620ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Amr ALHOSSARY and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio; import org.junit.*; import static org.junit.Assert.assertEquals; /** * Tests * * @author Amr ALHOSSARY */ public class IntegerIdProviderTest { @Test public void testDefaultConstructor() { IntegerIdProvider provider = new IntegerIdProvider<>(); String id1 = provider.apply(new Object()); assertEquals("1", id1); String id2 = provider.apply(new Object()); assertEquals("2", id2); } @Test public void testConstructorWithParameter() { IntegerIdProvider provider = new IntegerIdProvider<>(0); String id1 = provider.apply(new Object()); assertEquals("0", id1); String id2 = provider.apply(new Object()); assertEquals("1", id2); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/csv/000077500000000000000000000000001402514743400251575ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/csv/CSVExporterTest.java000066400000000000000000000356601402514743400310600ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.csv; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.io.*; import java.util.function.*; import static org.junit.Assert.assertEquals; /** * Tests for {@link CSVExporter}. * * @author Dimitrios Michail */ public class CSVExporterTest { // ~ Static fields/initializers // --------------------------------------------- private static final String NL = System.getProperty("line.separator"); private static final Function NAME_PROVIDER = v -> String.valueOf(v); // @formatter:off private static final String UNDIRECTED_EDGE_LIST = "1;2" + NL + "1;3" + NL + "3;4" + NL + "4;5" + NL + "5;1" + NL; private static final String DIRECTED_EDGE_LIST = "1;2" + NL + "1;3" + NL + "3;1" + NL + "3;4" + NL + "4;5" + NL + "5;1" + NL; private static final String DIRECTED_WEIGHTED_EDGE_LIST = "1;2;2.0" + NL + "1;3;2.0" + NL + "3;1;2.0" + NL + "3;4;2.0" + NL + "4;5;2.0" + NL + "5;1;2.0" + NL; private static final String UNDIRECTED_ADJACENCY_LIST = "1;2;3;3;5" + NL + "2;1;5" + NL + "3;1;1;4;5" + NL + "4;3;5;5" + NL + "5;4;1;2;3;4;5;5" + NL; private static final String DIRECTED_ADJACENCY_LIST = "1;2;3" + NL + "2" + NL + "3;1;4" + NL + "4;5" + NL + "5;1;2;3;4;5;5" + NL; private static final String DIRECTED_WEIGHTED_ADJACENCY_LIST = "1;2;3.3;3;3.3" + NL + "2" + NL + "3;1;3.3;4;3.3" + NL + "4;5;3.3" + NL + "5;1;3.3;2;3.3;3;3.3;4;3.3;5;3.3;5;3.3" + NL; private static final String DIRECTED_MATRIX_NODEID = ";1;2;3;4;5" + NL + "1;;1;1;;" + NL + "2;;;;;" + NL + "3;1;;;1;" + NL + "4;;;;;1" + NL + "5;1;1;1;1;1" + NL; private static final String DIRECTED_MATRIX_NODEID_ZERO_NO_EDGE = ";1;2;3;4;5" + NL + "1;0;1;1;0;0" + NL + "2;0;0;0;0;0" + NL + "3;1;0;0;1;0" + NL + "4;0;0;0;0;1" + NL + "5;1;1;1;1;1" + NL; private static final String DIRECTED_MATRIX_NO_NODEID = ";1;1;;" + NL + ";;;;" + NL + "1;;;1;" + NL + ";;;;1" + NL + "1;1;1;1;1" + NL; private static final String DIRECTED_MATRIX_NO_NODEID_ZERO_NO_EDGE = "0;1;1;0;0" + NL + "0;0;0;0;0" + NL + "1;0;0;1;0" + NL + "0;0;0;0;1" + NL + "1;1;1;1;1" + NL; private static final String DIRECTED_MATRIX_NO_NODEID_ZERO_NO_EDGE_WEIGHTED = "0;1.0;13.0;0;0" + NL + "0;0;0;0;0" + NL + "1.0;0;0;1.0;0" + NL + "0;0;0;0;1.0" + NL + "1.0;1.0;53.0;1.0;1.0" + NL; private static final String DIRECTED_MATRIX_NO_NODEID_WEIGHTED = ";1.0;13.0;;" + NL + ";;;;" + NL + "1.0;;;1.0;" + NL + ";;;;1.0" + NL + "1.0;1.0;53.0;1.0;1.0" + NL; private static final String DIRECTED_EDGE_LIST_ESCAPE = "'john doe';fred" + NL + "fred;\"fred\n\"\"21\"\"\"" + NL + "\"fred\n\"\"21\"\"\";\"who;;\"" +NL + "\"who;;\";'john doe'" + NL; // @formatter:on // ~ Methods // ---------------------------------------------------------------- @Test public void testUndirectedEdgeList() { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.EDGE_LIST, ';'); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(UNDIRECTED_EDGE_LIST, w.toString()); } @Test public void testDirectedEdgeList() { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.EDGE_LIST, ';'); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_EDGE_LIST, w.toString()); } @Test public void testDirectedWeightedEdgeList() { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); g = new AsWeightedGraph<>(g, e -> 2.0, false, false); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.EDGE_LIST, ';'); exporter.setParameter(CSVFormat.Parameter.EDGE_WEIGHTS, true); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_WEIGHTED_EDGE_LIST, w.toString()); } @Test public void testDirectedAdjacencyList() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); g.addEdge(5, 2); g.addEdge(5, 3); g.addEdge(5, 4); g.addEdge(5, 5); g.addEdge(5, 5); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.ADJACENCY_LIST, ';'); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_ADJACENCY_LIST, w.toString()); } @Test public void testDirectedWeightedAdjacencyList() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g = new AsWeightedGraph<>(g, e -> 3.3, false, false); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); g.addEdge(5, 2); g.addEdge(5, 3); g.addEdge(5, 4); g.addEdge(5, 5); g.addEdge(5, 5); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.ADJACENCY_LIST, ';'); StringWriter w = new StringWriter(); exporter.setParameter(CSVFormat.Parameter.EDGE_WEIGHTS, true); exporter.exportGraph(g, w); assertEquals(DIRECTED_WEIGHTED_ADJACENCY_LIST, w.toString()); } @Test public void testUndirectedAdjacencyList() { Graph g = new Pseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); g.addEdge(5, 2); g.addEdge(5, 3); g.addEdge(5, 4); g.addEdge(5, 5); g.addEdge(5, 5); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.ADJACENCY_LIST, ';'); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(UNDIRECTED_ADJACENCY_LIST, w.toString()); } @Test public void testDirectedMatrixNodeId() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); g.addEdge(5, 2); g.addEdge(5, 3); g.addEdge(5, 4); g.addEdge(5, 5); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.MATRIX, ';'); exporter.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_NODEID, true); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_MATRIX_NODEID, w.toString()); } @Test public void testDirectedMatrixNoNodeId() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); g.addEdge(5, 2); g.addEdge(5, 3); g.addEdge(5, 4); g.addEdge(5, 5); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.MATRIX, ';'); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_MATRIX_NO_NODEID, w.toString()); } @Test public void testDirectedMatrixNodeIdZeroMissingEdges() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); g.addEdge(5, 2); g.addEdge(5, 3); g.addEdge(5, 4); g.addEdge(5, 5); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.MATRIX, ';'); exporter.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_NODEID, true); exporter.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_MATRIX_NODEID_ZERO_NO_EDGE, w.toString()); } @Test public void testDirectedMatrixNoNodeIdZeroMissingEdges() { Graph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); g.addEdge(5, 2); g.addEdge(5, 3); g.addEdge(5, 4); g.addEdge(5, 5); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.MATRIX, ';'); exporter.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_MATRIX_NO_NODEID_ZERO_NO_EDGE, w.toString()); } @Test public void testDirectedMatrixNoNodeIdZeroMissingEdgesWeighted() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); g.addEdge(5, 2); g.addEdge(5, 3); g.addEdge(5, 4); g.addEdge(5, 5); g.setEdgeWeight(g.getEdge(1, 3), 13); g.setEdgeWeight(g.getEdge(5, 3), 53); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.MATRIX, ';'); exporter.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); exporter.setParameter(CSVFormat.Parameter.EDGE_WEIGHTS, true); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_MATRIX_NO_NODEID_ZERO_NO_EDGE_WEIGHTED, w.toString()); } @Test public void testDirectedMatrixNoNodeIdWeighted() { DirectedWeightedPseudograph g = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); g.addEdge(1, 2); g.addEdge(1, 3); g.addEdge(3, 1); g.addEdge(3, 4); g.addEdge(4, 5); g.addEdge(5, 1); g.addEdge(5, 2); g.addEdge(5, 3); g.addEdge(5, 4); g.addEdge(5, 5); g.setEdgeWeight(g.getEdge(1, 3), 13); g.setEdgeWeight(g.getEdge(5, 3), 53); CSVExporter exporter = new CSVExporter<>(NAME_PROVIDER, CSVFormat.MATRIX, ';'); exporter.setParameter(CSVFormat.Parameter.EDGE_WEIGHTS, true); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_MATRIX_NO_NODEID_WEIGHTED, w.toString()); } @Test public void testEdgeListWithStringsDirectedUnweightedWithSemicolon() { DirectedPseudograph g = new DirectedPseudograph<>(DefaultEdge.class); g.addVertex("'john doe'"); g.addVertex("fred"); g.addVertex("fred\n\"21\""); g.addVertex("who;;"); g.addEdge("'john doe'", "fred"); g.addEdge("fred", "fred\n\"21\""); g.addEdge("fred\n\"21\"", "who;;"); g.addEdge("who;;", "'john doe'"); CSVExporter exporter = new CSVExporter<>(x -> x, CSVFormat.EDGE_LIST, ';'); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_EDGE_LIST_ESCAPE, w.toString()); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/csv/CSVImporterTest.java000066400000000000000000000636421402514743400310520ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.csv; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.util.*; import static org.junit.Assert.*; /** * Tests * * @author Dimitrios Michail */ public class CSVImporterTest { private static final String NL = System.getProperty("line.separator"); public Graph readGraph( String input, CSVFormat format, Character delimiter, Class edgeClass, boolean directed, boolean weighted) { Graph g; if (directed) { g = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(weighted) .edgeClass(edgeClass).vertexSupplier(SupplierUtil.createStringSupplier(1)) .buildGraph(); } else { g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(weighted) .edgeClass(edgeClass).vertexSupplier(SupplierUtil.createStringSupplier(1)) .buildGraph(); } CSVImporter importer = new CSVImporter<>(format, delimiter); if ((format == CSVFormat.EDGE_LIST || format == CSVFormat.ADJACENCY_LIST) && weighted) { importer.setParameter(CSVFormat.Parameter.EDGE_WEIGHTS, true); } importer.importGraph(g, new StringReader(input)); return g; } @Test public void testEdgeListDirectedUnweighted() { // @formatter:off String input = "1,2\n" + "2,3\n" + "3,4\n" + "4,1\n"; // @formatter:on Graph g = readGraph(input, CSVFormat.EDGE_LIST, ',', DefaultEdge.class, true, false); assertEquals(4, g.vertexSet().size()); assertEquals(4, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("4", "1")); } @Test public void testEdgeListDirectedWeighted() { // @formatter:off String input = "1,2,1.0\n" + "2,3,2.0\n" + "3,4,3.0\n" + "4,1,4.0\n"; // @formatter:on Graph g = readGraph(input, CSVFormat.EDGE_LIST, ',', DefaultEdge.class, true, true); assertEquals(4, g.vertexSet().size()); assertEquals(4, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertTrue(g.containsEdge("2", "3")); assertEquals(2.0, g.getEdgeWeight(g.getEdge("2", "3")), 1e-9); assertTrue(g.containsEdge("3", "4")); assertEquals(3.0, g.getEdgeWeight(g.getEdge("3", "4")), 1e-9); assertTrue(g.containsEdge("4", "1")); assertEquals(4.0, g.getEdgeWeight(g.getEdge("4", "1")), 1e-9); } @Test public void testEdgeListDirectedUnweightedWithSemicolon() { // @formatter:off String input = "1;2\n" + "2;3\n" + "3;4\n" + "4;1\n"; // @formatter:on Graph g = readGraph(input, CSVFormat.EDGE_LIST, ';', DefaultEdge.class, true, false); assertEquals(4, g.vertexSet().size()); assertEquals(4, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("4", "1")); } @Test public void testAdjacencyListDirectedUnweightedWithSemicolon() { // @formatter:off String input = "1;2;3;4\n" + "2;3\n" + "3;4;5;6\n" + "4;1;5;6\n"; // @formatter:on Graph g = readGraph(input, CSVFormat.ADJACENCY_LIST, ';', DefaultEdge.class, true, false); assertEquals(6, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsVertex("6")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("1", "4")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("3", "5")); assertTrue(g.containsEdge("3", "6")); assertTrue(g.containsEdge("4", "1")); assertTrue(g.containsEdge("4", "5")); assertTrue(g.containsEdge("4", "6")); } @Test public void testAdjacencyListDirectedWeightedWithSemicolon() { // @formatter:off String input = "1;2;2.1;3;3.1;4;4.1\n" + "2;3;3.1\n" + "3;4;4.1;5;5.1;6;6.1\n" + "4;1;1.1;5;5.1;6;6.1\n"; // @formatter:on Graph g = readGraph(input, CSVFormat.ADJACENCY_LIST, ';', DefaultEdge.class, true, true); assertEquals(6, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsVertex("6")); assertTrue(g.containsEdge("1", "2")); assertEquals(2.1, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertTrue(g.containsEdge("1", "3")); assertEquals(3.1, g.getEdgeWeight(g.getEdge("1", "3")), 1e-9); assertTrue(g.containsEdge("1", "4")); assertEquals(4.1, g.getEdgeWeight(g.getEdge("1", "4")), 1e-9); assertTrue(g.containsEdge("2", "3")); assertEquals(3.1, g.getEdgeWeight(g.getEdge("2", "3")), 1e-9); assertTrue(g.containsEdge("3", "4")); assertEquals(4.1, g.getEdgeWeight(g.getEdge("3", "4")), 1e-9); assertTrue(g.containsEdge("3", "5")); assertEquals(5.1, g.getEdgeWeight(g.getEdge("3", "5")), 1e-9); assertTrue(g.containsEdge("3", "6")); assertEquals(6.1, g.getEdgeWeight(g.getEdge("3", "6")), 1e-9); assertTrue(g.containsEdge("4", "1")); assertEquals(1.1, g.getEdgeWeight(g.getEdge("4", "1")), 1e-9); assertTrue(g.containsEdge("4", "5")); assertEquals(5.1, g.getEdgeWeight(g.getEdge("4", "5")), 1e-9); assertTrue(g.containsEdge("4", "6")); assertEquals(6.1, g.getEdgeWeight(g.getEdge("4", "6")), 1e-9); } @Test public void testEdgeListWithStringsDirectedUnweightedWithSemicolon() { // @formatter:off String input = "'john doe';fred\n" + "fred;\"fred\n\"\"21\"\"\"\n" + "\"fred\n\"\"21\"\"\";\"who;;\"\n" + "\"who;;\";'john doe'\n"; // @formatter:on Graph g = readGraph(input, CSVFormat.EDGE_LIST, ';', DefaultEdge.class, true, false); assertEquals(4, g.vertexSet().size()); assertEquals(4, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("4", "1")); } @Test public void testDirectedMatrixNoNodeIdZeroNoEdgeWeighted() { // @formatter:off String input = "0;1.0;13.0;0;0" + NL + "0;0;0;0;0" + NL + "1.0;0;0;1.0;0" + NL + "0;0;0;0;1.0" + NL + "1.0;1.0;53.0;1.0;1.0" + NL; // @formatter:on Graph g = new DirectedWeightedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ';'); importer.setParameter(CSVFormat.Parameter.EDGE_WEIGHTS, true); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); importer.importGraph(g, new StringReader(input)); assertEquals(5, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsEdge("1", "2")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("1", "2")), 0.0001); assertTrue(g.containsEdge("1", "3")); assertEquals(13.0, g.getEdgeWeight(g.getEdge("1", "3")), 0.0001); assertTrue(g.containsEdge("3", "1")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("3", "1")), 0.0001); assertTrue(g.containsEdge("3", "4")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("3", "4")), 0.0001); assertTrue(g.containsEdge("4", "5")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("4", "5")), 0.0001); assertTrue(g.containsEdge("5", "1")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("5", "1")), 0.0001); assertTrue(g.containsEdge("5", "2")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("5", "2")), 0.0001); assertTrue(g.containsEdge("5", "3")); assertEquals(53.0, g.getEdgeWeight(g.getEdge("5", "3")), 0.0001); assertTrue(g.containsEdge("5", "4")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("5", "4")), 0.0001); assertTrue(g.containsEdge("5", "5")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("5", "5")), 0.0001); } @Test public void testDirectedMatrixNoNodeIdWeighted() { // @formatter:off String input = ",1.0,13.0,," + NL + ",,,," + NL + "1.0,,,1.0," + NL + ",,,,1.0" + NL + "1.0,1.0,53.0,1.0,1.0" + NL; // @formatter:on Graph g = new DirectedWeightedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ','); importer.setParameter(CSVFormat.Parameter.EDGE_WEIGHTS, true); importer.importGraph(g, new StringReader(input)); assertEquals(5, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsEdge("1", "2")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("1", "2")), 0.0001); assertTrue(g.containsEdge("1", "3")); assertEquals(13.0, g.getEdgeWeight(g.getEdge("1", "3")), 0.0001); assertTrue(g.containsEdge("3", "1")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("3", "1")), 0.0001); assertTrue(g.containsEdge("3", "4")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("3", "4")), 0.0001); assertTrue(g.containsEdge("4", "5")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("4", "5")), 0.0001); assertTrue(g.containsEdge("5", "1")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("5", "1")), 0.0001); assertTrue(g.containsEdge("5", "2")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("5", "2")), 0.0001); assertTrue(g.containsEdge("5", "3")); assertEquals(53.0, g.getEdgeWeight(g.getEdge("5", "3")), 0.0001); assertTrue(g.containsEdge("5", "4")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("5", "4")), 0.0001); assertTrue(g.containsEdge("5", "5")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("5", "5")), 0.0001); } @Test public void testDirectedMatrixNoNodeIdZeroNoEdge() { // @formatter:off String input = "0;1;1;0;0" + NL + "0;0;0;0;0" + NL + "1;0;0;1;0" + NL + "0;0;0;0;1" + NL + "1;1;1;1;1" + NL; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ';'); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); importer.importGraph(g, new StringReader(input)); assertEquals(5, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("3", "1")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("4", "5")); assertTrue(g.containsEdge("5", "1")); assertTrue(g.containsEdge("5", "2")); assertTrue(g.containsEdge("5", "3")); assertTrue(g.containsEdge("5", "4")); assertTrue(g.containsEdge("5", "5")); } @Test public void testDirectedMatrixNoNodeId() { // @formatter:off String input = ";1;1;;" + NL + ";;;;" + NL + "1;;;1;" + NL + ";;;;1" + NL + "1;1;1;1;1" + NL; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ';'); importer.importGraph(g, new StringReader(input)); assertEquals(5, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("3", "1")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("4", "5")); assertTrue(g.containsEdge("5", "1")); assertTrue(g.containsEdge("5", "2")); assertTrue(g.containsEdge("5", "3")); assertTrue(g.containsEdge("5", "4")); assertTrue(g.containsEdge("5", "5")); } @Test public void testDirectedMatrixNodeIdZeroNoEdge() { // @formatter:off String input = ";A;B;C;D;E" + NL + "A;0;1;1;0;0" + NL + "B;0;0;0;0;0" + NL + "C;1;0;0;1;0" + NL + "D;0;0;0;0;1" + NL + "E;1;1;1;1;1" + NL; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ';'); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_NODEID, true); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); importer.importGraph(g, new StringReader(input)); assertEquals(5, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("3", "1")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("4", "5")); assertTrue(g.containsEdge("5", "1")); assertTrue(g.containsEdge("5", "2")); assertTrue(g.containsEdge("5", "3")); assertTrue(g.containsEdge("5", "4")); assertTrue(g.containsEdge("5", "5")); } @Test public void testDirectedMatrixNodeIdZeroNoEdgeShuffled() { // @formatter:off String input = ";A;B;C;D;E" + NL + "C;1;0;0;1;0" + NL + "D;0;0;0;0;1" + NL + "B;0;0;0;0;0" + NL + "A;0;1;1;0;0" + NL + "E;1;1;1;1;1" + NL; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ';'); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_NODEID, true); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); importer.importGraph(g, new StringReader(input)); assertEquals(5, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("3", "1")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("4", "5")); assertTrue(g.containsEdge("5", "1")); assertTrue(g.containsEdge("5", "2")); assertTrue(g.containsEdge("5", "3")); assertTrue(g.containsEdge("5", "4")); assertTrue(g.containsEdge("5", "5")); assertEquals(attrs.get("1").get("ID").getValue(), "A"); assertEquals(attrs.get("2").get("ID").getValue(), "B"); assertEquals(attrs.get("3").get("ID").getValue(), "C"); assertEquals(attrs.get("4").get("ID").getValue(), "D"); assertEquals(attrs.get("5").get("ID").getValue(), "E"); } @Test public void testDirectedMatrixNodeIdZeroNoEdgeWeightedShuffledZeroWeightsAsDouble() { // @formatter:off String input = ";A;B;C;D;E" + NL + "C;1;0;0;1;0" + NL + "D;0;0;0.0;0;1" + NL + "B;0;0;0;0;0" + NL + "A;0;1;1;0;0" + NL + "E;1;1;0;1;1" + NL; // @formatter:on Graph g = new DirectedWeightedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ';'); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_NODEID, true); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); importer.setParameter(CSVFormat.Parameter.EDGE_WEIGHTS, true); importer.importGraph(g, new StringReader(input)); assertEquals(5, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("3", "1")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("4", "3")); assertEquals(0d, g.getEdgeWeight(g.getEdge("4", "3")), 0.0001); assertTrue(g.containsEdge("4", "5")); assertEquals(1d, g.getEdgeWeight(g.getEdge("4", "5")), 0.0001); assertTrue(g.containsEdge("5", "1")); assertTrue(g.containsEdge("5", "2")); assertTrue(g.containsEdge("5", "4")); assertTrue(g.containsEdge("5", "5")); } @Test public void testDoubleOnUnweighted() { // @formatter:off String input = ";A;B;C;D;E" + NL + "C;1;0;0;1;0" + NL + "D;0;0;0.0;0;1" + NL + "B;0;0;0;0;0" + NL + "A;0;1;1;0;0" + NL + "E;1;1;0;1;1" + NL; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ';'); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_NODEID, true); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); try { importer.importGraph(g, new StringReader(input)); fail("No!"); } catch (ImportException e) { // nothing } } @Test public void testWrongHeaderNodeIds() { // @formatter:off String input = ";A;B; ;D;E" + NL + "C;1;0;0;1;0" + NL + "D;0;0;0.0;0;1" + NL + "B;0;0;0;0;0" + NL + "A;0;1;1;0;0" + NL + "E;1;1;0;1;1" + NL; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ';'); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_NODEID, true); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); try { importer.importGraph(g, new StringReader(input)); fail("No!"); } catch (ImportException e) { // nothing } } @Test public void testDirectedMatrixNoNodeIdMissingEntries() { // @formatter:off String input = ";1;1;;" + NL + ";;;;" + NL + "1;;;1;" + NL + ";;;1" + NL + "1;1;1;1;1" + NL; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, ';'); try { importer.importGraph(g, new StringReader(input)); fail("No!"); } catch (ImportException e) { // nothing } } @Test public void testDirectedMatrixNodeIdZeroNoEdgeShuffledAndTabDelimiter() { // @formatter:off String input = "\tA\tB\t\"C\tC\"\tD\tE" + NL + "\"C\tC\"\t1\t0\t0\t1\t0" + NL + "D\t0\t0\t0\t0\t1" + NL + "B\t0\t0\t0\t0\t0" + NL + "A\t0\t1\t1\t0\t0" + NL + "E\t1\t1\t1\t1\t1" + NL; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); CSVImporter importer = new CSVImporter<>(CSVFormat.MATRIX, '\t'); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_NODEID, true); importer.setParameter(CSVFormat.Parameter.MATRIX_FORMAT_ZERO_WHEN_NO_EDGE, true); importer.importGraph(g, new StringReader(input)); assertEquals(5, g.vertexSet().size()); assertEquals(10, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("3", "1")); assertTrue(g.containsEdge("3", "4")); assertTrue(g.containsEdge("4", "5")); assertTrue(g.containsEdge("5", "1")); assertTrue(g.containsEdge("5", "2")); assertTrue(g.containsEdge("5", "3")); assertTrue(g.containsEdge("5", "4")); assertTrue(g.containsEdge("5", "5")); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/csv/DSVUtilsTest.java000066400000000000000000000044201402514743400303370ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.csv; import org.junit.*; import static org.junit.Assert.assertEquals; /** * Tests * * @author Dimitrios Michail */ public class DSVUtilsTest { @Test public void testEscape() { String input1 = "nothing special in here"; assertEquals(input1, DSVUtils.escapeDSV(input1, ';')); assertEquals(input1, DSVUtils.escapeDSV(input1, ',')); String input2 = "foo;;;"; assertEquals(input2, DSVUtils.escapeDSV(input2, ',')); assertEquals("\"foo;;;\"", DSVUtils.escapeDSV(input2, ';')); String input3 = "foo\n"; assertEquals("\"foo\n\"", DSVUtils.escapeDSV(input3, ';')); String input4 = "foo\rfoo"; assertEquals("\"foo\rfoo\"", DSVUtils.escapeDSV(input4, ';')); String input5 = "\"foo\"\n\"foo\""; assertEquals("\"\"\"foo\"\"\n\"\"foo\"\"\"", DSVUtils.escapeDSV(input5, ';')); } @Test public void testUnescape() { String input1 = "nothing special in here"; assertEquals(input1, DSVUtils.unescapeDSV(input1, ';')); assertEquals(input1, DSVUtils.unescapeDSV(input1, ',')); String input2 = "\"foo;;;\""; assertEquals("foo;;;", DSVUtils.unescapeDSV(input2, ';')); assertEquals("\"foo;;;\"", DSVUtils.unescapeDSV(input2, ',')); String input3 = "\"foo\n\""; assertEquals("foo\n", DSVUtils.unescapeDSV(input3, ';')); String input4 = "\"foo\rfoo\""; assertEquals("foo\rfoo", DSVUtils.unescapeDSV(input4, ';')); String input5 = "\"\"\"foo\"\"\n\"\"foo\"\"\""; assertEquals("\"foo\"\n\"foo\"", DSVUtils.unescapeDSV(input5, ';')); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/000077500000000000000000000000001402514743400256245ustar00rootroot00000000000000DIMACSEventDrivenImporterTest.java000066400000000000000000000217271402514743400341150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dimacs; import org.jgrapht.alg.util.*; import org.junit.*; import java.io.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Tests * * @author Dimitrios Michail */ public class DIMACSEventDrivenImporterTest { /** * Read and parse an actual instance */ @Test public void testReadDIMACSInstance() { InputStream fstream = getClass().getClassLoader().getResourceAsStream("myciel3.col"); DIMACSEventDrivenImporter importer = new DIMACSEventDrivenImporter(); importer.addVertexCountConsumer(count -> { assertEquals(count, Integer.valueOf(11)); }); List> collected = new ArrayList<>(); importer.addEdgeConsumer(t -> { assertNull(t.getThird()); collected.add(Pair.of(t.getFirst(), t.getSecond())); }); importer.importInput(fstream); int[][] edges = { { 0, 1 }, { 0, 2 }, { 0, 3 }, { 0, 4 }, { 1, 5 }, { 1, 6 }, { 1, 7 }, { 5, 8 }, { 5, 3 }, { 5, 9 }, { 2, 8 }, { 2, 6 }, { 2, 9 }, { 8, 7 }, { 8, 4 }, { 6, 10 }, { 3, 10 }, { 7, 10 }, { 4, 10 }, { 9, 10 } }; assertEquals(collected.size(), edges.length); for (int i = 0; i < edges.length; i++) { Pair e = collected.get(i); assertEquals(Integer.valueOf(edges[i][0]), e.getFirst()); assertEquals(Integer.valueOf(edges[i][1]), e.getSecond()); } } /** * Read and parse an weighted instance */ @Test public void testReadWeightedDIMACSInstance() { InputStream fstream = getClass().getClassLoader().getResourceAsStream("myciel3_weighted.col"); Map nameMap = new HashMap<>(); nameMap.put(1, 0); nameMap.put(2, 1); nameMap.put(4, 2); nameMap.put(7, 3); nameMap.put(9, 4); nameMap.put(3, 5); nameMap.put(6, 6); nameMap.put(8, 7); nameMap.put(5, 8); nameMap.put(10, 9); nameMap.put(11, 10); DIMACSEventDrivenImporter importer = new DIMACSEventDrivenImporter(); importer.addVertexCountConsumer(count -> { assertEquals(count, Integer.valueOf(11)); }); List> collected = new ArrayList<>(); importer.addEdgeConsumer(t -> { collected.add(t); }); importer.importInput(fstream); int[][] edges = { { 1, 2, 1 }, { 1, 4, 2 }, { 1, 7, 3 }, { 1, 9, 4 }, { 2, 3, 5 }, { 2, 6, 6 }, { 2, 8, 7 }, { 3, 5, 8 }, { 3, 7, 9 }, { 3, 10, 10 }, { 4, 5, 11 }, { 4, 6, 12 }, { 4, 10, 13 }, { 5, 8, 14 }, { 5, 9, 15 }, { 6, 11, 16 }, { 7, 11, 17 }, { 8, 11, 18 }, { 9, 11, 19 }, { 10, 11, 20 } }; int i = 0; for (int[] edge : edges) { Triple e = collected.get(i); assertEquals(nameMap.get(edge[0]), e.getFirst()); assertEquals(nameMap.get(edge[1]), e.getSecond()); assertEquals(Double.valueOf(edge[2]), e.getThird()); i++; } } /** * Read and parse an weighted instance */ @Test public void testReadWeightedWithoutZeroBasedDIMACSInstance() { InputStream fstream = getClass().getClassLoader().getResourceAsStream("myciel3_weighted.col"); Map nameMap = new HashMap<>(); nameMap.put(1, 1); nameMap.put(2, 2); nameMap.put(4, 3); nameMap.put(7, 4); nameMap.put(9, 5); nameMap.put(3, 6); nameMap.put(6, 7); nameMap.put(8, 8); nameMap.put(5, 9); nameMap.put(10, 10); nameMap.put(11, 11); DIMACSEventDrivenImporter importer = new DIMACSEventDrivenImporter().zeroBasedNumbering(false); importer.addVertexCountConsumer(count -> { assertEquals(count, Integer.valueOf(11)); }); List> collected = new ArrayList<>(); importer.addEdgeConsumer(t -> { collected.add(t); }); importer.importInput(fstream); int[][] edges = { { 1, 2, 1 }, { 1, 4, 2 }, { 1, 7, 3 }, { 1, 9, 4 }, { 2, 3, 5 }, { 2, 6, 6 }, { 2, 8, 7 }, { 3, 5, 8 }, { 3, 7, 9 }, { 3, 10, 10 }, { 4, 5, 11 }, { 4, 6, 12 }, { 4, 10, 13 }, { 5, 8, 14 }, { 5, 9, 15 }, { 6, 11, 16 }, { 7, 11, 17 }, { 8, 11, 18 }, { 9, 11, 19 }, { 10, 11, 20 } }; int i = 0; for (int[] edge : edges) { Triple e = collected.get(i); assertEquals(nameMap.get(edge[0]), e.getFirst()); assertEquals(nameMap.get(edge[1]), e.getSecond()); assertEquals(Double.valueOf(edge[2]), e.getThird()); i++; } } /** * Read and parse an weighted instance */ @Test public void testReadWeightedWithoutRenumberingDIMACSInstance() { InputStream fstream = getClass().getClassLoader().getResourceAsStream("myciel3_weighted.col"); Map nameMap = new HashMap<>(); nameMap.put(1, 0); nameMap.put(2, 1); nameMap.put(3, 2); nameMap.put(4, 3); nameMap.put(5, 4); nameMap.put(6, 5); nameMap.put(7, 6); nameMap.put(8, 7); nameMap.put(9, 8); nameMap.put(10, 9); nameMap.put(11, 10); DIMACSEventDrivenImporter importer = new DIMACSEventDrivenImporter(); importer = importer.renumberVertices(false); importer.addVertexCountConsumer(count -> { assertEquals(count, Integer.valueOf(11)); }); List> collected = new ArrayList<>(); importer.addEdgeConsumer(t -> { collected.add(t); }); importer.importInput(fstream); int[][] edges = { { 1, 2, 1 }, { 1, 4, 2 }, { 1, 7, 3 }, { 1, 9, 4 }, { 2, 3, 5 }, { 2, 6, 6 }, { 2, 8, 7 }, { 3, 5, 8 }, { 3, 7, 9 }, { 3, 10, 10 }, { 4, 5, 11 }, { 4, 6, 12 }, { 4, 10, 13 }, { 5, 8, 14 }, { 5, 9, 15 }, { 6, 11, 16 }, { 7, 11, 17 }, { 8, 11, 18 }, { 9, 11, 19 }, { 10, 11, 20 } }; int i = 0; for (int[] edge : edges) { Triple e = collected.get(i); assertEquals(nameMap.get(edge[0]), e.getFirst()); assertEquals(nameMap.get(edge[1]), e.getSecond()); assertEquals(Double.valueOf(edge[2]), e.getThird()); i++; } } /** * Read and parse an weighted instance */ @Test public void testReadWeightedWithoutRenumberingAndWithoutZeroBasedDIMACSInstance() { InputStream fstream = getClass().getClassLoader().getResourceAsStream("myciel3_weighted.col"); Map nameMap = new HashMap<>(); nameMap.put(1, 1); nameMap.put(2, 2); nameMap.put(3, 3); nameMap.put(4, 4); nameMap.put(5, 5); nameMap.put(6, 6); nameMap.put(7, 7); nameMap.put(8, 8); nameMap.put(9, 9); nameMap.put(10, 10); nameMap.put(11, 11); DIMACSEventDrivenImporter importer = new DIMACSEventDrivenImporter(); importer = importer.renumberVertices(false).zeroBasedNumbering(false); importer.addVertexCountConsumer(count -> { assertEquals(count, Integer.valueOf(11)); }); List> collected = new ArrayList<>(); importer.addEdgeConsumer(t -> { collected.add(t); }); importer.importInput(fstream); int[][] edges = { { 1, 2, 1 }, { 1, 4, 2 }, { 1, 7, 3 }, { 1, 9, 4 }, { 2, 3, 5 }, { 2, 6, 6 }, { 2, 8, 7 }, { 3, 5, 8 }, { 3, 7, 9 }, { 3, 10, 10 }, { 4, 5, 11 }, { 4, 6, 12 }, { 4, 10, 13 }, { 5, 8, 14 }, { 5, 9, 15 }, { 6, 11, 16 }, { 7, 11, 17 }, { 8, 11, 18 }, { 9, 11, 19 }, { 10, 11, 20 } }; int i = 0; for (int[] edge : edges) { Triple e = collected.get(i); assertEquals(nameMap.get(edge[0]), e.getFirst()); assertEquals(nameMap.get(edge[1]), e.getSecond()); assertEquals(Double.valueOf(edge[2]), e.getThird()); i++; } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSExporterTest.java000066400000000000000000000201641402514743400320230ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dimacs; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.io.*; import static org.junit.Assert.*; /** * Tests * * @author Dimitrios Michail */ public class DIMACSExporterTest { private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String V4 = "v4"; private static final String V5 = "v5"; private static final String NL = System.getProperty("line.separator"); // @formatter:off private static final String UNDIRECTED = "c" + NL + "c SOURCE: Generated using the JGraphT library" + NL + "c" + NL + "p sp 3 2" + NL + "a 1 2" + NL + "a 3 1" + NL; private static final String UNDIRECTED_WEIGHTED = "c" + NL + "c SOURCE: Generated using the JGraphT library" + NL + "c" + NL + "p sp 3 2" + NL + "a 1 2 2.0" + NL + "a 3 1 5.0" + NL; private static final String UNDIRECTED_AS_UNWEIGHTED = "c" + NL + "c SOURCE: Generated using the JGraphT library" + NL + "c" + NL + "p sp 3 2" + NL + "a 1 2 1.0" + NL + "a 3 1 1.0" + NL; private static final String DIRECTED = "c" + NL + "c SOURCE: Generated using the JGraphT library" + NL + "c" + NL + "p sp 5 5" + NL + "a 1 2" + NL + "a 3 1" + NL + "a 2 3" + NL + "a 3 4" + NL + "a 4 5" + NL; private static final String DIRECTED_MAX_CLIQUE = "c" + NL + "c SOURCE: Generated using the JGraphT library" + NL + "c" + NL + "p edge 5 5" + NL + "e 1 2" + NL + "e 3 1" + NL + "e 2 3" + NL + "e 3 4" + NL + "e 4 5" + NL; private static final String DIRECTED_COLORING = "c" + NL + "c SOURCE: Generated using the JGraphT library" + NL + "c" + NL + "p col 5 5" + NL + "e 1 2" + NL + "e 3 1" + NL + "e 2 3" + NL + "e 3 4" + NL + "e 4 5" + NL; // @formatter:on @Test public void testUndirected() throws UnsupportedEncodingException { Graph g = new SimpleGraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); DIMACSExporter exporter = new DIMACSExporter<>(); exporter.setFormat(DIMACSFormat.SHORTEST_PATH); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED, res); } @Test public void testUnweightedUndirected() throws UnsupportedEncodingException { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); DIMACSExporter exporter = new DIMACSExporter<>(); exporter.setFormat(DIMACSFormat.SHORTEST_PATH); exporter.setParameter(DIMACSExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED_AS_UNWEIGHTED, res); } @Test public void testDirected() throws UnsupportedEncodingException { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); g.addEdge(V1, V2); g.addEdge(V3, V1); g.addEdge(V2, V3); g.addEdge(V3, V4); g.addEdge(V4, V5); DIMACSExporter exporter = new DIMACSExporter<>(); exporter.setFormat(DIMACSFormat.SHORTEST_PATH); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(DIRECTED, res); } @Test public void testWeightedUndirected() throws UnsupportedEncodingException { SimpleGraph g = new SimpleWeightedGraph(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); DefaultWeightedEdge e1 = g.addEdge(V1, V2); g.setEdgeWeight(e1, 2.0); DefaultWeightedEdge e2 = g.addEdge(V3, V1); g.setEdgeWeight(e2, 5.0); DIMACSExporter exporter = new DIMACSExporter<>(); exporter.setFormat(DIMACSFormat.SHORTEST_PATH); exporter.setParameter(DIMACSExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED_WEIGHTED, res); } @Test public void testParameters() { DIMACSExporter exporter = new DIMACSExporter<>(); assertFalse(exporter.isParameter(DIMACSExporter.Parameter.EXPORT_EDGE_WEIGHTS)); exporter.setParameter(DIMACSExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); assertTrue(exporter.isParameter(DIMACSExporter.Parameter.EXPORT_EDGE_WEIGHTS)); exporter.setParameter(DIMACSExporter.Parameter.EXPORT_EDGE_WEIGHTS, false); assertFalse(exporter.isParameter(DIMACSExporter.Parameter.EXPORT_EDGE_WEIGHTS)); } @Test public void testDefaultFormat() { DIMACSExporter exporter = new DIMACSExporter<>(); assertEquals(DIMACSFormat.MAX_CLIQUE, exporter.getFormat()); } @Test public void testDirectedColoring() throws UnsupportedEncodingException { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); g.addEdge(V1, V2); g.addEdge(V3, V1); g.addEdge(V2, V3); g.addEdge(V3, V4); g.addEdge(V4, V5); DIMACSExporter exporter = new DIMACSExporter<>(); exporter.setFormat(DIMACSFormat.COLORING); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(DIRECTED_COLORING, res); } @Test public void testDirectedMaxClique() throws UnsupportedEncodingException { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); g.addEdge(V1, V2); g.addEdge(V3, V1); g.addEdge(V2, V3); g.addEdge(V3, V4); g.addEdge(V4, V5); DIMACSExporter exporter = new DIMACSExporter<>(); exporter.setFormat(DIMACSFormat.MAX_CLIQUE); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(DIRECTED_MAX_CLIQUE, res); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/dimacs/DIMACSImporterTest.java000066400000000000000000000171421402514743400320160ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dimacs; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.nio.charset.*; import static org.junit.Assert.*; /** * Tests * * @author Joris Kinable * @author Dimitrios Michail */ public class DIMACSImporterTest { public Graph readGraph(InputStream in, Class edgeClass, boolean weighted) { Graph g = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(weighted) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(edgeClass).buildGraph(); DIMACSImporter importer = new DIMACSImporter<>(); try { importer.importGraph(g, new InputStreamReader(in, "UTF-8")); } catch (UnsupportedEncodingException e) { // cannot happen } return g; } /** * Read and parse an actual instance */ @Test public void testReadDIMACSInstance() { InputStream fstream = getClass().getClassLoader().getResourceAsStream("myciel3.col"); Graph graph = readGraph(fstream, DefaultEdge.class, false); assertEquals(graph.vertexSet().size(), 11); assertEquals(graph.edgeSet().size(), 20); int[][] edges = { { 1, 2 }, { 1, 4 }, { 1, 7 }, { 1, 9 }, { 2, 3 }, { 2, 6 }, { 2, 8 }, { 3, 5 }, { 3, 7 }, { 3, 10 }, { 4, 5 }, { 4, 6 }, { 4, 10 }, { 5, 8 }, { 5, 9 }, { 6, 11 }, { 7, 11 }, { 8, 11 }, { 9, 11 }, { 10, 11 } }; for (int[] edge : edges) assertTrue(graph.containsEdge(edge[0] - 1, edge[1] - 1)); } /** * Read and parse an weighted instance */ @Test public void testReadWeightedDIMACSInstance() { InputStream fstream = getClass().getClassLoader().getResourceAsStream("myciel3_weighted.col"); Graph graph = readGraph(fstream, DefaultWeightedEdge.class, true); assertEquals(graph.vertexSet().size(), 11); assertEquals(graph.edgeSet().size(), 20); int[][] edges = { { 1, 2, 1 }, { 1, 4, 2 }, { 1, 7, 3 }, { 1, 9, 4 }, { 2, 3, 5 }, { 2, 6, 6 }, { 2, 8, 7 }, { 3, 5, 8 }, { 3, 7, 9 }, { 3, 10, 10 }, { 4, 5, 11 }, { 4, 6, 12 }, { 4, 10, 13 }, { 5, 8, 14 }, { 5, 9, 15 }, { 6, 11, 16 }, { 7, 11, 17 }, { 8, 11, 18 }, { 9, 11, 19 }, { 10, 11, 20 } }; for (int[] edge : edges) { assertTrue(graph.containsEdge(edge[0] - 1, edge[1] - 1)); DefaultWeightedEdge e = graph.getEdge(edge[0] - 1, edge[1] - 1); assertEquals((int) graph.getEdgeWeight(e), edge[2]); } } @Test public void testReadDIMACSShortestPathFormat() { // @formatter:off String input = "p sp 3 3\n" + "a 1 2\n" + "a 2 1\n" + "a 2 3\n"; // @formatter:on Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultWeightedEdge.class, false); assertEquals(3, graph.vertexSet().size()); assertEquals(3, graph.edgeSet().size()); int[][] edges = { { 1, 2, 1 }, { 2, 1, 1 }, { 2, 3, 1 } }; for (int[] edge : edges) { assertTrue(graph.containsEdge(edge[0] - 1, edge[1] - 1)); DefaultWeightedEdge e = graph.getEdge(edge[0] - 1, edge[1] - 1); assertEquals((int) graph.getEdgeWeight(e), edge[2]); } } @Test public void testReadDIMACSShortestPathFormatWithVertexFactory() { // @formatter:off String input = "p sp 3 3\n" + "a 1 2\n" + "a 2 1\n" + "a 2 3\n"; // @formatter:on Graph graph = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.createDefaultWeightedEdgeSupplier()).buildGraph(); DIMACSImporter importer = new DIMACSImporter<>(); importer.setVertexFactory(id -> id + 100); try { importer .importGraph( graph, new InputStreamReader( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), "UTF-8")); } catch (UnsupportedEncodingException e) { // cannot happen } assertEquals(3, graph.vertexSet().size()); assertEquals(3, graph.edgeSet().size()); int[][] edges = { { 101, 102, 1 }, { 102, 101, 1 }, { 102, 103, 1 } }; for (int[] edge : edges) { assertTrue(graph.containsEdge(edge[0], edge[1])); DefaultWeightedEdge e = graph.getEdge(edge[0], edge[1]); assertEquals((int) graph.getEdgeWeight(e), edge[2]); } } @Test public void testWrongDIMACSInstance1() { // @formatter:off String input = "p edge ERROR 5\n" + "e 1 2\n" + "e 1 4\n"; // @formatter:on try { readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); fail("No!"); } catch (ImportException e) { } } @Test public void testWrongDIMACSInstance2() { // @formatter:off String input = "p edge -10 5\n" + "e 1 2\n" + "e 1 4\n"; // @formatter:on try { readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); fail("No!"); } catch (ImportException e) { } } @Test public void testWrongDIMACSInstance3() throws ImportException { // @formatter:off String input = "p edge 2 5\n" + "e 1 2\n" + "e 1 4\n"; // @formatter:on try { readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); fail("No!"); } catch (ImportException e) { } } @Test public void testWrongDIMACSInstance4() throws ImportException { // @formatter:off String input = "p edge 2 2\n" + "e 2\n" + "e 1 2\n"; // @formatter:on try { readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); fail("No!"); } catch (ImportException e) { } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/dot/000077500000000000000000000000001402514743400251525ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTExporterTest.java000066400000000000000000000166771402514743400310550ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Trevor Harmon and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dot; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.nio.*; import org.junit.*; import java.io.*; import java.util.*; import static org.hamcrest.CoreMatchers.containsString; import static org.junit.Assert.*; /** * Tests * * @author Trevor Harmon */ public class DOTExporterTest { // ~ Static fields/initializers --------------------------------------------- private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String NL = System.getProperty("line.separator"); private static final String UNDIRECTED = "graph G {" + NL + " 1 [ label=\"a\" ];" + NL + " 2 [ x=\"y\" ];" + NL + " 3;" + NL + " 1 -- 2;" + NL + " 3 -- 1;" + NL + "}" + NL; // @formatter:off private static final String UNDIRECTED_WITH_GRAPH_ATTRIBUTES = "graph G {" + NL + " overlap=false;" + NL + " splines=true;" + NL + " 1;" + NL + " 2;" + NL + " 3;" + NL + " 1 -- 2;" + NL + " 3 -- 1;" + NL + "}" + NL; // @formatter:on // ~ Methods ---------------------------------------------------------------- @Test public void testUndirected() throws UnsupportedEncodingException, ExportException { testUndirected(new SimpleGraph<>(DefaultEdge.class), true); testUndirected(new Multigraph<>(DefaultEdge.class), false); testUndirectedWithGraphAttributes(new Multigraph<>(DefaultEdge.class), false); } private void testUndirected(Graph g, boolean strict) throws UnsupportedEncodingException, ExportException { g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); DOTExporter exporter = new DOTExporter<>(); exporter.setVertexAttributeProvider((v) -> { Map map = new LinkedHashMap<>(); switch (v) { case V1: map.put("label", DefaultAttribute.createAttribute("a")); break; case V2: map.put("x", DefaultAttribute.createAttribute("y")); break; default: map = null; break; } return map; }); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals((strict) ? "strict " + UNDIRECTED : UNDIRECTED, res); } private void testUndirectedWithGraphAttributes(Graph g, boolean strict) throws UnsupportedEncodingException, ExportException { g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); DOTExporter exporter = new DOTExporter<>(); exporter.setGraphAttributeProvider(() -> { Map map = new LinkedHashMap<>(); map.put("overlap", DefaultAttribute.createAttribute("false")); map.put("splines", DefaultAttribute.createAttribute("true")); return map; }); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals( (strict) ? "strict " + UNDIRECTED_WITH_GRAPH_ATTRIBUTES : UNDIRECTED_WITH_GRAPH_ATTRIBUTES, res); } @Test public void testValidNodeIDs() throws ExportException { DOTExporter exporter = new DOTExporter<>(x -> String.valueOf(x)); List validVertices = Arrays.asList("-9.78", "-.5", "12", "a", "12", "abc_78", "\"--34asdf\""); for (String vertex : validVertices) { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex(vertex); exporter.exportGraph(graph, new ByteArrayOutputStream()); } List invalidVertices = Arrays.asList("2test", "--4", "foo-bar", "", "t:32"); for (String vertex : invalidVertices) { Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex(vertex); try { exporter.exportGraph(graph, new ByteArrayOutputStream()); fail(vertex); } catch (RuntimeException re) { // this is a negative test so exception is expected } } } @Test public void testQuotedNodeIDs() { DOTExporter exporter = new DOTExporter<>(x -> String.valueOf(x)); exporter.setVertexAttributeProvider((v) -> { Map map = new LinkedHashMap<>(); map.put("label", DefaultAttribute.createAttribute(v)); return map; }); StringWriter outputWriter = new StringWriter(); String quotedNodeId = "\"abc\""; Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex(quotedNodeId); exporter.exportGraph(graph, outputWriter); assertThat(outputWriter.toString(), containsString("label=\"\\\"abc\\\"\"")); } @Test public void testNodeHtmlLabelFromAttribute() { DOTExporter exporter = new DOTExporter<>(x -> String.valueOf(x)); exporter.setVertexAttributeProvider((v) -> { Map map = new LinkedHashMap<>(); map.put("label", new DefaultAttribute<>("html label", AttributeType.HTML)); return map; }); StringWriter outputWriter = new StringWriter(); Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); graph.addVertex("myVertex"); exporter.exportGraph(graph, outputWriter); assertThat(outputWriter.toString(), containsString("label=<html label>")); } @Test public void testDifferentGraphID() throws UnsupportedEncodingException, ExportException { Graph g = new SimpleGraph<>(DefaultEdge.class); final String customID = "MyGraph"; DOTExporter exporter = new DOTExporter<>(); exporter.setGraphIdProvider(() -> { return customID; }); final String correctResult = "strict graph " + customID + " {" + NL + "}" + NL; ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(correctResult, res); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTImporter1Test.java000066400000000000000000000531271402514743400311160ustar00rootroot00000000000000/* * (C) Copyright 2015-2021, by Wil Selwood and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dot; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; /** * 1st part of tests for DOTImporter. See also {@link DOTImporter2Test}. */ public class DOTImporter1Test { @Test public void testImportID() throws ImportException { String id = "MyGraph"; String input = "strict graph " + id + " {\n}\n"; Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); DOTImporter importer = new DOTImporter(); importer.addGraphAttributeConsumer((k, a) -> { assertEquals(k, "ID"); assertEquals(a.getValue(), id); }); importer.importGraph(result, new StringReader(input)); } @Test public void testImportWrongID() throws ImportException { String invalidID = "2test"; String input = "graph " + invalidID + " {\n}\n"; Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); try { DOTImporter importer = new DOTImporter(); importer.importGraph(result, new StringReader(input)); fail("Should not get here"); } catch (ImportException e) { assertEquals( "Failed to import DOT graph: line 1:7 extraneous input 'test' expecting '{'", e.getMessage()); } } @Test public void testInvalidHeader() throws ImportException { // testing all cases of missing keywords or wrong order for (String invalidInput : new String[] { " {}", "strict {}", "id {}", "strict id {}", "id strict {}", "id strict graph {}", "graph strict id {}" }) { Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); try { DOTImporter importer = new DOTImporter(); importer.importGraph(result, new StringReader(invalidInput)); fail("Correctly loaded incorrect graph: " + invalidInput); } catch (ImportException e) { // this is the expected exception } catch (Exception e) { fail("Expected ImportException but found " + e.getClass().getSimpleName()); } } } @Test public void testImportOnlyGraphKeyword() throws ImportException { String input = "graph {\n}\n"; Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); DOTImporter importer = new DOTImporter(); importer.addGraphAttributeConsumer((k, a) -> { fail("Id should not be consumed"); }); importer.importGraph(result, new StringReader(input)); } @Test public void testImportNoID() throws ImportException { String input = "strict graph {\n}\n"; Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); DOTImporter importer = new DOTImporter(); importer.addGraphAttributeConsumer((k, a) -> { fail("Id should not be consumed"); }); importer.importGraph(result, new StringReader(input)); } @Test public void testUndirectedWithLabels() throws ImportException { String input = "graph G {\n" + " 1 [ \"label\"=\"abc123\" ];\n" + " 2 [ label=\"fred\" ];\n" + " 1 -- 2;\n" + "}"; Multigraph expected = new Multigraph<>(DefaultEdge.class); expected.addVertex("0"); expected.addVertex("1"); expected.addEdge("0", "1"); DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(result, new StringReader(input)); assertEquals(expected.toString(), result.toString()); assertEquals(2, result.vertexSet().size()); assertEquals(1, result.edgeSet().size()); assertEquals(attrs.get("0").get("label").getValue(), "abc123"); assertEquals(attrs.get("0").get("ID").getValue(), "1"); assertEquals(attrs.get("1").get("label").getValue(), "fred"); assertEquals(attrs.get("1").get("ID").getValue(), "2"); } @Test public void testDirectedNoLabels() throws ImportException { String input = "digraph graphname {\r\n" + " a -> b -> c;\r\n" + " b -> d;\r\n" + " }"; DirectedMultigraph expected = new DirectedMultigraph<>(DefaultEdge.class); expected.addVertex("0"); expected.addVertex("1"); expected.addVertex("2"); expected.addVertex("3"); expected.addEdge("0", "1"); expected.addEdge("1", "2"); expected.addEdge("1", "3"); GraphImporter importer = new DOTImporter<>(); DirectedMultigraph result = new DirectedMultigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(result, new StringReader(input)); assertEquals(expected.toString(), result.toString()); assertEquals(4, result.vertexSet().size()); assertEquals(3, result.edgeSet().size()); } @Test public void testDirectedSameLabels() throws ImportException { String input = "digraph sample {\n" + " a -> b;" + " b -> c;\n" + " a [ label=\"Test\"];\n" + " b [ label=\"Test\"];\n" + " c [ label=\"Test\"];\n" + "}"; DirectedMultigraph expected = new DirectedMultigraph<>(DefaultEdge.class); expected.addVertex("0"); expected.addVertex("1"); expected.addVertex("2"); expected.addEdge("0", "1"); expected.addEdge("1", "2"); Map> attrs = new HashMap<>(); DOTImporter importer = new DOTImporter<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); DirectedMultigraph result = new DirectedMultigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(result, new StringReader(input)); assertEquals(expected.toString(), result.toString()); assertEquals(attrs.get("0").get("label").getValue(), "Test"); assertEquals(attrs.get("0").get("ID").getValue(), "a"); assertEquals(attrs.get("1").get("label").getValue(), "Test"); assertEquals(attrs.get("1").get("ID").getValue(), "b"); assertEquals(attrs.get("2").get("label").getValue(), "Test"); assertEquals(attrs.get("2").get("ID").getValue(), "c"); } @Test public void testMultiLinksUndirected() throws ImportException { String input = "graph G {\n" + " 1 [ label=\"bob\" ];\n" + " 2 [ label=\"fred\" ];\n" // the extra label will be ignored but not cause any problems. + " 1 -- 2 [ label=\"friend\"];\n" + " 1 -- 2;\n" + "}"; Multigraph expected = new Multigraph<>(DefaultEdge.class); expected.addVertex("0"); expected.addVertex("1"); expected.addEdge("0", "1", new DefaultEdge()); expected.addEdge("0", "1", new DefaultEdge()); Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphImporter importer = new DOTImporter<>(); importer.importGraph(result, new StringReader(input)); assertEquals(expected.toString(), result.toString()); assertEquals(2, result.vertexSet().size()); assertEquals(2, result.edgeSet().size()); } @Test public void testExportImportLoop() throws ImportException, ExportException, UnsupportedEncodingException { DirectedMultigraph start = new DirectedMultigraph<>(DefaultEdge.class); start.addVertex("0"); start.addVertex("1"); start.addVertex("2"); start.addVertex("3"); start.addEdge("0", "1"); start.addEdge("1", "2"); start.addEdge("1", "3"); DOTExporter exporter = new DOTExporter<>(v -> v); GraphImporter importer = new DOTImporter<>(); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(start, os); String output = new String(os.toByteArray(), "UTF-8"); DirectedMultigraph result = new DirectedMultigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(result, new StringReader(output)); assertEquals(start.toString(), result.toString()); assertEquals(4, result.vertexSet().size()); assertEquals(3, result.edgeSet().size()); } @Test public void testDashLabelVertex() throws ImportException { String input = "graph G {\n" + "a [label=\"------this------contains-------dashes------\"]\n" + "}"; Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Map> attrs = new HashMap<>(); DOTImporter importer = new DOTImporter<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.importGraph(result, new StringReader(input)); assertEquals(1, result.vertexSet().size()); String v = result.vertexSet().stream().findFirst().get(); assertEquals("0", v); assertEquals("a", attrs.get("0").get("ID").getValue()); assertEquals( "------this------contains-------dashes------", attrs.get("0").get("label").getValue()); } @Test public void testAttributesWithNoQuotes() throws ImportException { String input = "graph G {\n" + " 1 [ label = \"bob\" \"foo\"=bar ];\n" + " 2 [ label = \"fred\" ];\n" // the extra label will be ignored but not cause any problems. + " 1 -- 2 [ label = \"friend\" \"foo\" = wibble];\n" + "}"; Map> attrs = new HashMap<>(); Map> edgeAttrs = new HashMap<>(); DOTImporter importer = new DOTImporter<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addEdgeAttributeConsumer((p, a) -> { Map map = edgeAttrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); edgeAttrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(result, new StringReader(input)); assertEquals("wrong size of vertexSet", 2, result.vertexSet().size()); assertEquals("wrong size of edgeSet", 1, result.edgeSet().size()); assertEquals(attrs.get("0").get("ID").getValue(), "1"); assertEquals(attrs.get("0").get("label").getValue(), "bob"); assertEquals(attrs.get("0").get("foo").getValue(), "bar"); assertEquals(attrs.get("1").get("ID").getValue(), "2"); assertEquals(attrs.get("1").get("label").getValue(), "fred"); DefaultEdge edge = result.getEdge("0", "1"); assertEquals(edgeAttrs.get(edge).get("label").getValue(), "friend"); assertEquals(edgeAttrs.get(edge).get("foo").getValue(), "wibble"); } @Test public void testEmptyString() { testGarbage( "", "Failed to import DOT graph: line 1:0 mismatched input '' expecting {STRICT, GRAPH, DIGRAPH}"); } @Test public void testGarbageStringEnoughLines() { String input = "jsfhg kjdsf hgkfds\n" + "fdsgfdsgfd\n" + "gfdgfdsgfdsg\n" + "jdhgkjfdshgsjkhl\n"; testGarbage( input, "Failed to import DOT graph: line 1:0 mismatched input 'jsfhg' expecting {STRICT, GRAPH, DIGRAPH}"); } @Test public void testGarbageStringInvalidFirstLine() { String input = "jsfhgkjdsfhgkfds\n" + "fdsgfdsgfd\n"; testGarbage( input, "Failed to import DOT graph: line 1:0 mismatched input 'jsfhgkjdsfhgkfds' expecting {STRICT, GRAPH, DIGRAPH}"); } @Test public void testGarbageStringNotEnoughLines() { String input = "jsfhgkjdsfhgkfds\n"; testGarbage( input, "Failed to import DOT graph: line 1:0 mismatched input 'jsfhgkjdsfhgkfds' expecting {STRICT, GRAPH, DIGRAPH}"); } @Test public void testAttributesWithNoValues() throws ImportException { String input = "graph G {\n" + " 1 [ label = \"bob\" \"foo\" ];\n" + " 2 [ label = \"fred\" ];\n" // the extra label will be ignored but not cause any problems. + " 1 -- 2 [ label = friend foo];\n" + "}"; Multigraph graph = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); DOTImporter importer = new DOTImporter<>(); try { importer.importGraph(graph, new StringReader(input)); fail("Failed to import DOT graph: line 2:26 mismatched input ']' expecting '='"); } catch (ImportException e) { } } @Test public void testUpdatingVertex() throws ImportException { String input = "graph G {\n" + "a -- b;\n" + "a [foo=\"bar\"];\n" + "}"; DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(result, new StringReader(input)); assertEquals("wrong size of vertexSet", 2, result.vertexSet().size()); assertEquals("wrong size of edgeSet", 1, result.edgeSet().size()); for (String v : result.vertexSet()) { if ("0".equals(v)) { assertEquals("wrong number of attributes", 2, attrs.get(v).size()); } else { assertEquals("attributes are populated", 1, attrs.get(v).size()); } } assertEquals(attrs.get("0").get("foo").getValue(), "bar"); assertEquals(attrs.get("0").get("ID").getValue(), "a"); assertEquals(attrs.get("1").get("ID").getValue(), "b"); } @Test public void testParametersWithSemicolons() throws ImportException { String input = "graph G {\n 1 [ label=\"this label; contains a semi colon\" ];\n}\n"; Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); DOTImporter importer = new DOTImporter<>(); importer.importGraph(result, new StringReader(input)); assertEquals("wrong size of vertexSet", 1, result.vertexSet().size()); assertEquals("wrong size of edgeSet", 0, result.edgeSet().size()); } @Test public void testLabelsWithEscapedSemicolons() throws ImportException { String escapedLabel = "this \\\"label; \\\"contains an escaped semi colon"; String input = "graph G {\n node [ label=\"" + escapedLabel + "\" ];\n node0 }\n"; Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.importGraph(result, new StringReader(input)); assertEquals("wrong size of vertexSet", 1, result.vertexSet().size()); assertEquals("wrong size of edgeSet", 0, result.edgeSet().size()); assertEquals(attrs.get("0").get("ID").getValue(), "node0"); assertEquals( attrs.get("0").get("label").getValue(), "this \"label; \"contains an escaped semi colon"); } @Test public void testNoLineEndBetweenNodes() throws ImportException { String input = "graph G {\n 1 [ label=\"this label; contains a semi colon\" ]; 2 [ label=\"wibble\" ] \n}\n"; Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); DOTImporter importer = new DOTImporter<>(); importer.importGraph(result, new StringReader(input)); assertEquals("wrong size of vertexSet", 2, result.vertexSet().size()); assertEquals("wrong size of edgeSet", 0, result.edgeSet().size()); } @Test public void testWithReader() throws ImportException { String input = "graph G {\n" + " 1 [ \"label\"=\"abc123\" ];\n" + " 2 [ label=\"fred\" ];\n" + " 1 -- 2;\n" + "}"; Multigraph expected = new Multigraph<>(DefaultEdge.class); expected.addVertex("0"); expected.addVertex("1"); expected.addEdge("0", "1"); Multigraph result = new Multigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphImporter importer = new DOTImporter<>(); importer.importGraph(result, new StringReader(input)); assertEquals(expected.toString(), result.toString()); assertEquals(2, result.vertexSet().size()); assertEquals(1, result.edgeSet().size()); } private void testGarbage(String input, String expected) { DirectedMultigraph result = new DirectedMultigraph<>(DefaultEdge.class); testGarbageGraph(input, expected, result); } private void testGarbageGraph(String input, String expected, Graph graph) { GraphImporter importer = new DOTImporter<>(); try { importer.importGraph(graph, new StringReader(input)); fail("Should not get here"); } catch (ImportException e) { assertEquals(expected, e.getMessage()); } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTImporter2Test.java000066400000000000000000001165361402514743400311230ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dot; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; /** * 2nd part of tests for DOTImporter. See also {@link DOTImporter1Test}. */ public class DOTImporter2Test { private static final String NL = "\n"; @Test public void testDOT1() throws ImportException { // @formatter:off String input = "digraph {" + NL + " a -- b -- c;" + NL + " k:1 -- q:a:3 -- d:3;" + NL + "}"; // @formatter:on GraphImporter importer = new DOTImporter<>(); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Graphs.addAllVertices(expected, Arrays.asList("0", "1", "2", "3", "4", "5")); expected.addEdge("0", "1"); expected.addEdge("1", "2"); expected.addEdge("3", "4"); expected.addEdge("4", "5"); assertEquals(expected.toString(), graph.toString()); } @Test public void testDOT2() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " subgraph cluster0 { " + NL + " a0 -> a1 -> a2 -> a3;" + NL + " }" + NL + " subgraph cluster1 { " + NL + " b0 -> b1 -> b2 -> b3;" + NL + " }" + NL + " start -> a0;" + NL + " start -> b0;" + NL + " a1 -> b3;" + NL + " b2 -> a3;" + NL + " a3 -> a0;" + NL + " a3 -> end;" + NL + " b3 -> end;" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Graphs .addAllVertices( expected, Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7", "8", "9")); expected.addEdge("0", "1"); expected.addEdge("1", "2"); expected.addEdge("2", "3"); expected.addEdge("4", "5"); expected.addEdge("5", "6"); expected.addEdge("6", "7"); expected.addEdge("8", "0"); expected.addEdge("8", "4"); expected.addEdge("1", "7"); expected.addEdge("6", "3"); expected.addEdge("3", "0"); expected.addEdge("3", "9"); expected.addEdge("7", "9"); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "a0"); assertEquals(attrs.get("1").get("ID").getValue(), "a1"); assertEquals(attrs.get("2").get("ID").getValue(), "a2"); assertEquals(attrs.get("3").get("ID").getValue(), "a3"); assertEquals(attrs.get("4").get("ID").getValue(), "b0"); assertEquals(attrs.get("5").get("ID").getValue(), "b1"); assertEquals(attrs.get("6").get("ID").getValue(), "b2"); assertEquals(attrs.get("7").get("ID").getValue(), "b3"); assertEquals(attrs.get("8").get("ID").getValue(), "start"); assertEquals(attrs.get("9").get("ID").getValue(), "end"); } @Test public void testDOT3() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " subgraph { " + NL + " a0 -> a1;" + NL + " }" + "->" + " subgraph { " + NL + " b0 -> b1;" + NL + " }" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0", "1", "2", "3")); expected.addEdge("0", "1"); expected.addEdge("2", "3"); expected.addEdge("0", "2"); expected.addEdge("0", "3"); expected.addEdge("1", "2"); expected.addEdge("1", "3"); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "a0"); assertEquals(attrs.get("1").get("ID").getValue(), "a1"); assertEquals(attrs.get("2").get("ID").getValue(), "b0"); assertEquals(attrs.get("3").get("ID").getValue(), "b1"); } @Test public void testDOT4() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " subgraph { " + NL + " a0 -> a1;" + NL + " subgraph { " + NL + " a00 -> a11" + NL + " }" + NL + " }" + "->" + " subgraph { " + NL + " b0 -> b1;" + NL + " subgraph { " + NL + " b00 -> b11" + NL + " }" + NL + " }" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0", "1", "2", "3", "4", "5", "6", "7")); expected.addEdge("0", "1"); expected.addEdge("2", "3"); expected.addEdge("4", "5"); expected.addEdge("6", "7"); expected.addEdge("0", "4"); expected.addEdge("0", "5"); expected.addEdge("0", "6"); expected.addEdge("0", "7"); expected.addEdge("1", "4"); expected.addEdge("1", "5"); expected.addEdge("1", "6"); expected.addEdge("1", "7"); expected.addEdge("2", "4"); expected.addEdge("2", "5"); expected.addEdge("2", "6"); expected.addEdge("2", "7"); expected.addEdge("3", "4"); expected.addEdge("3", "5"); expected.addEdge("3", "6"); expected.addEdge("3", "7"); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "a0"); assertEquals(attrs.get("1").get("ID").getValue(), "a1"); assertEquals(attrs.get("2").get("ID").getValue(), "a00"); assertEquals(attrs.get("3").get("ID").getValue(), "a11"); assertEquals(attrs.get("4").get("ID").getValue(), "b0"); assertEquals(attrs.get("5").get("ID").getValue(), "b1"); assertEquals(attrs.get("6").get("ID").getValue(), "b00"); assertEquals(attrs.get("7").get("ID").getValue(), "b11"); } @Test public void testDOT5() throws ImportException { // @formatter:off String input = "digraph {" + NL + " a -- b -- c;" + NL + " k:1 -- q:a:3 -- d:3;" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); importer.setVertexFactory(id -> id); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Graphs.addAllVertices(expected, Arrays.asList("a", "b", "c", "k", "q", "d")); expected.addEdge("a", "b"); expected.addEdge("b", "c"); expected.addEdge("k", "q"); expected.addEdge("q", "d"); assertEquals(expected.toString(), graph.toString()); } @Test public void testNodeSubgraphEdges() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " a0 -> { a00 -> a11 } -> b0 -> { b00 -> b11 }" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0", "1", "2", "3", "4", "5")); expected.addEdge("1", "2"); expected.addEdge("4", "5"); expected.addEdge("0", "1"); expected.addEdge("0", "2"); expected.addEdge("1", "3"); expected.addEdge("2", "3"); expected.addEdge("3", "4"); expected.addEdge("3", "5"); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "a0"); assertEquals(attrs.get("1").get("ID").getValue(), "a00"); assertEquals(attrs.get("2").get("ID").getValue(), "a11"); assertEquals(attrs.get("3").get("ID").getValue(), "b0"); assertEquals(attrs.get("4").get("ID").getValue(), "b00"); assertEquals(attrs.get("5").get("ID").getValue(), "b11"); } @Test public void testNodeSubgraphEdgesUndirected() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " a0 -> { a00 -- a11 } -> b0 -- { b00 -- b11 }" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0", "1", "2", "3", "4", "5")); expected.addEdge("1", "2"); expected.addEdge("4", "5"); expected.addEdge("0", "1"); expected.addEdge("0", "2"); expected.addEdge("1", "3"); expected.addEdge("2", "3"); expected.addEdge("3", "4"); expected.addEdge("3", "5"); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "a0"); assertEquals(attrs.get("1").get("ID").getValue(), "a00"); assertEquals(attrs.get("2").get("ID").getValue(), "a11"); assertEquals(attrs.get("3").get("ID").getValue(), "b0"); assertEquals(attrs.get("4").get("ID").getValue(), "b00"); assertEquals(attrs.get("5").get("ID").getValue(), "b11"); } @Test public void testNestedSubgraphs() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " a0 -> { { { { a00 -> a11 } } } } -> b0 -> { { b00 -> b11 } }" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0", "1", "2", "3", "4", "5")); expected.addEdge("1", "2"); expected.addEdge("4", "5"); expected.addEdge("0", "1"); expected.addEdge("0", "2"); expected.addEdge("1", "3"); expected.addEdge("2", "3"); expected.addEdge("3", "4"); expected.addEdge("3", "5"); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "a0"); assertEquals(attrs.get("1").get("ID").getValue(), "a00"); assertEquals(attrs.get("2").get("ID").getValue(), "a11"); assertEquals(attrs.get("3").get("ID").getValue(), "b0"); assertEquals(attrs.get("4").get("ID").getValue(), "b00"); assertEquals(attrs.get("5").get("ID").getValue(), "b11"); } @Test public void testEdgeAttributes() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " edge [weight=5.0];" + NL + " a0 -> a1;" + NL + " subgraph {" + NL + " edge [weight=2.0];" + NL + " a2 -> a3;" + NL + " };" + NL + " a4 -> a5 [weight=15.0];" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); Map> edgeAttrs = new HashMap<>(); importer.addEdgeAttributeConsumer((p, a) -> { Map map = edgeAttrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); edgeAttrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0", "1", "2", "3", "4", "5")); expected.addEdge("0", "1"); expected.addEdge("2", "3"); expected.addEdge("4", "5"); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "a0"); assertEquals(attrs.get("1").get("ID").getValue(), "a1"); assertEquals(attrs.get("2").get("ID").getValue(), "a2"); assertEquals(attrs.get("3").get("ID").getValue(), "a3"); assertEquals(attrs.get("4").get("ID").getValue(), "a4"); assertEquals(attrs.get("5").get("ID").getValue(), "a5"); assertEquals(edgeAttrs.get(graph.getEdge("0", "1")).get("weight").getValue(), "5.0"); assertEquals(edgeAttrs.get(graph.getEdge("2", "3")).get("weight").getValue(), "2.0"); assertEquals(edgeAttrs.get(graph.getEdge("4", "5")).get("weight").getValue(), "15.0"); } @Test public void testComments() throws ImportException { // @formatter:off String input = "digraph G { // ignore" + NL + " /* ignore */ a0 -> a1; /* ignore */" + NL + "# ignore" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0", "1")); expected.addEdge("0", "1"); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "a0"); assertEquals(attrs.get("1").get("ID").getValue(), "a1"); } @Test public void testNodeAttributes() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " node [color=gray];" + NL + " a0 -> a1;" + NL + " subgraph {" + NL + " node [color=black];" + NL + " a2 -> a3;" + NL + " };" + NL + " a4 [color=white];" + NL + " a4 -> a5;" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0", "1", "2", "3", "4", "5")); expected.addEdge("0", "1"); expected.addEdge("2", "3"); expected.addEdge("4", "5"); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "a0"); assertEquals(attrs.get("1").get("ID").getValue(), "a1"); assertEquals(attrs.get("2").get("ID").getValue(), "a2"); assertEquals(attrs.get("3").get("ID").getValue(), "a3"); assertEquals(attrs.get("4").get("ID").getValue(), "a4"); assertEquals(attrs.get("5").get("ID").getValue(), "a5"); assertEquals("gray", attrs.get("0").get("color").getValue()); assertEquals("gray", attrs.get("1").get("color").getValue()); assertEquals("black", attrs.get("2").get("color").getValue()); assertEquals("black", attrs.get("3").get("color").getValue()); assertEquals("white", attrs.get("4").get("color").getValue()); assertEquals("gray", attrs.get("5").get("color").getValue()); } @Test public void testUnescape() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " a0 [name=\"myname\"];" + NL + " a1 [name=\"name with ; semicolon\"];" + NL + " a2 [name=myname];" + NL + " a3 [name=\"name with \\\"internal\\\" quotes\"];" + NL + " a4 [name=\"my\nname\"];" + NL + " a5 [name=<>];" + NL + " a6 [name=<>];" + NL + " a7 [name=<

    name ✅

    >];" + NL + " a8 [name=\"two\\\nlines\"];" + NL + " a9 [name=\"\"];" + NL + " a10 [name=\"\\\\\\\\\\\\\\\\\"];" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); assertEquals("myname", attrs.get("0").get("name").getValue()); assertEquals("name with ; semicolon", attrs.get("1").get("name").getValue()); assertEquals("myname", attrs.get("2").get("name").getValue()); assertEquals("name with \"internal\" quotes", attrs.get("3").get("name").getValue()); assertEquals("my\nname", attrs.get("4").get("name").getValue()); assertEquals( "
    ", attrs.get("5").get("name").getValue()); assertEquals( "", attrs.get("6").get("name").getValue()); assertEquals("

    name \u2705

    ", attrs.get("7").get("name").getValue()); assertEquals("two\nlines", attrs.get("8").get("name").getValue()); assertEquals("", attrs.get("9").get("name").getValue()); assertEquals("\\\\\\\\", attrs.get("10").get("name").getValue()); } @Test public void testNonValidHtmlString() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " a0 [name=< >> >];" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); try { importer.importGraph(graph, new StringReader(input)); fail("No!"); } catch (ImportException e) { } } @Test public void testValidHtmlString() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " a0 [name=<

    >];" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); Attribute attr = attrs.get("0").get("name"); assertEquals("

    ", attr.getValue()); assertEquals(AttributeType.STRING, attr.getType()); } @Test public void testLoopError() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " a0 -> a0;" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedMultigraph graph = new DirectedMultigraph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); try { importer.importGraph(graph, new StringReader(input)); fail("No!"); } catch (ImportException e) { } } @Test public void testExampleDOT1() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " subgraph cluster0 {" + NL + " node [style=filled,color=white];" + NL+ " style=filled;" + NL+ " color=lightgrey;" + NL+ " a0->a1->a2->a3;" + NL+ " label=\"process #1\";" + NL+ " }"+NL+ " subgraph cluster1 {" + NL+ " node [style=filled];"+NL+ " b0->b1->b2->b3;" + NL+ " label=\"process #2\";" + NL+ " color=blue"+NL+ " }"+NL+ " start -> a0;"+NL+ " start -> b0;"+NL+ " a1 -> b3;"+NL+ " b2 -> a3;"+NL+ " a3 -> a0;"+NL+ " a3 -> end;"+NL+ " b3 -> end;"+NL+ " start [shape=Mdiamond];"+NL+ " end [shape=Msquare];" + NL+ "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "G"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); } @Test public void testShapes1() throws ImportException { // @formatter:off // input www.graphviz.org/doc/info/html3.gv String input = "digraph structs {" + NL + " node [shape=plaintext];" + NL + " struct1 [label=<" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + "
    line 1line2line3line4" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + "
    Mixedfonts
    " + NL + "
    >];" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { assertEquals(p, "ID"); assertEquals(a.getValue(), "structs"); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0")); assertEquals(expected.toString(), graph.toString()); } @Test public void testEmptyList() throws Exception { // @formatter:off String input = "digraph g {" + NL + " name=\"TEST\";" + NL + " graph []" + NL + " anyNodeWithAttributes [color=\"red\"];" + NL + " anyNodeWithoutAttributes;" + NL + " anyNodeWithEmptyAttributes [];" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.addGraphAttributeConsumer((p, a) -> { if ("ID".equals(p)) { assertEquals("g", a.getValue()); } if ("name".equals(p)) { assertEquals("TEST", a.getValue()); } }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); DirectedPseudograph expected = new DirectedPseudograph<>(DefaultEdge.class); Graphs.addAllVertices(expected, Arrays.asList("0", "1", "2")); assertEquals(expected.toString(), graph.toString()); assertEquals(attrs.get("0").get("ID").getValue(), "anyNodeWithAttributes"); assertEquals(attrs.get("1").get("ID").getValue(), "anyNodeWithoutAttributes"); assertEquals(attrs.get("2").get("ID").getValue(), "anyNodeWithEmptyAttributes"); } @Test public void testCreateVerticesWithAttributes() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " a0 [color=gray];" + NL + " a1 [color=green];" + NL + " a0 -> a1;" + NL + " a2 [color=white];" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); importer.setVertexWithAttributesFactory((id, attrs) -> { return id + "-" + attrs.get("color").getValue(); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); assertTrue(graph.containsVertex("a0-gray")); assertTrue(graph.containsVertex("a1-green")); assertTrue(graph.containsVertex("a2-white")); } @Test public void testCreateEdgesWithAttributes() throws ImportException { // @formatter:off String input = "digraph G {" + NL + " a0 [color=gray];" + NL + " a1 [color=green];" + NL + " a2 [color=white];" + NL + " a0 -> a1 [label=\"e1\"];" + NL + " a1 -> a2 [label=\"e2\"];" + NL + "}"; // @formatter:on DOTImporter importer = new DOTImporter<>(); importer.setVertexWithAttributesFactory((id, attrs) -> { return id + "-" + attrs.get("color").getValue(); }); importer.setEdgeWithAttributesFactory((attrs) -> { return attrs.get("label").getValue(); }); DirectedPseudograph graph = new DirectedPseudograph<>(SupplierUtil.createStringSupplier(), null, false); importer.importGraph(graph, new StringReader(input)); assertTrue(graph.containsEdge("e1")); assertTrue(graph.containsEdge("e2")); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/dot/DOTUtilsTest.java000066400000000000000000000026521402514743400303310ustar00rootroot00000000000000/* * (C) Copyright 2018, by Mariusz Smykula and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.dot; import org.junit.*; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class DOTUtilsTest { @Test public void shouldAcceptIdWithDigits() { String idWithDigit = "id3"; boolean isValid = DOTUtils.isValidID(idWithDigit); assertTrue(isValid); } @Test public void shouldRejectIdThatStartsWithDigit() { String idThatStartsWithDigit = "3id"; boolean isValid = DOTUtils.isValidID(idThatStartsWithDigit); assertFalse(isValid); } @Test public void shouldAcceptIdThatStartWithUnderscore() { String idThatStartsWithUnderscore = "_id3"; boolean isValid = DOTUtils.isValidID(idThatStartsWithUnderscore); assertTrue(isValid); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/000077500000000000000000000000001402514743400253155ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/GEXFExporterTest.java000066400000000000000000000254061402514743400313110ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gexf; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.nio.gexf.GEXFExporter.*; import org.jgrapht.util.*; import org.junit.*; import org.xmlunit.builder.*; import org.xmlunit.diff.*; import java.io.*; import java.util.*; import static org.junit.Assert.assertFalse; /** * Tests * * @author Dimitrios Michail */ public class GEXFExporterTest { private static final String NL = System.getProperty("line.separator"); // ~ Methods // ---------------------------------------------------------------- @Test public void testDirected() throws UnsupportedEncodingException { String output = // @formatter:off "" + NL + "" + NL + "" + NL + " The JGraphT Library" + NL + "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " ski|dance|photo" + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + "" + NL + "" + NL; // @formatter:on Graph graph = GraphTypeBuilder .directed().edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createStringSupplier()).allowingMultipleEdges(true) .allowingSelfLoops(true).buildGraph(); graph.addVertex("v1"); graph.addVertex("v2"); DefaultEdge e_1_2 = graph.addEdge("v1", "v2"); graph.addVertex("v3"); graph.addEdge("v3", "v1"); GEXFExporter exporter = new GEXFExporter<>(); exporter.setParameter(GEXFExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); exporter.setParameter(GEXFExporter.Parameter.EXPORT_EDGE_TYPES, true); exporter.setParameter(GEXFExporter.Parameter.EXPORT_EDGE_LABELS, true); exporter.registerAttribute("color", AttributeCategory.NODE, GEXFAttributeType.STRING, null); exporter.registerAttribute("city", AttributeCategory.NODE, GEXFAttributeType.STRING, null); exporter .registerAttribute( "hobby", AttributeCategory.NODE, GEXFAttributeType.STRING, null, "ski|dance|photo"); exporter.setVertexAttributeProvider(v -> { Map map = new HashMap(); if ("v1".equals(v)) { map.put("color", DefaultAttribute.createAttribute("Red")); map.put("city", DefaultAttribute.createAttribute("Paris")); } return map; }); exporter .registerAttribute("length", AttributeCategory.EDGE, GEXFAttributeType.DOUBLE, null); exporter.setEdgeAttributeProvider(e -> { Map map = new HashMap(); if (e == e_1_2) { map.put("label", DefaultAttribute.createAttribute("Edge from node 1 to node 2")); map.put("length", DefaultAttribute.createAttribute("100.0")); } return map; }); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(graph, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testUndirected() throws UnsupportedEncodingException { String output = // @formatter:off "" + NL + "" + NL + "" + NL + " The JGraphT Library" + NL + " Test" + NL + "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + "" + NL + "" + NL; // @formatter:on Graph graph = GraphTypeBuilder .undirected().weighted(true).edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createStringSupplier()).allowingMultipleEdges(true) .allowingSelfLoops(true).buildGraph(); graph.addVertex("v1"); graph.addVertex("v2"); DefaultEdge e_1_2 = graph.addEdge("v1", "v2"); graph.addVertex("v3"); DefaultEdge e_3_1 = graph.addEdge("v3", "v1"); graph.setEdgeWeight(e_3_1, 13.5d); GEXFExporter exporter = new GEXFExporter<>(); exporter.setParameter(GEXFExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); exporter.setParameter(GEXFExporter.Parameter.EXPORT_EDGE_TYPES, true); exporter.setDescription("Test"); exporter.registerAttribute("color", AttributeCategory.NODE, GEXFAttributeType.STRING, null); exporter.registerAttribute("city", AttributeCategory.NODE, GEXFAttributeType.STRING, null); exporter.setVertexAttributeProvider(v -> { Map map = new HashMap(); if ("v1".equals(v)) { map.put("color", DefaultAttribute.createAttribute("Red")); map.put("city", DefaultAttribute.createAttribute("Paris")); } return map; }); exporter .registerAttribute("length", AttributeCategory.EDGE, GEXFAttributeType.DOUBLE, null); exporter.setEdgeAttributeProvider(e -> { Map map = new HashMap(); if (e == e_1_2) { map.put("label", DefaultAttribute.createAttribute("Edge from node 1 to node 2")); map.put("length", DefaultAttribute.createAttribute("100.0")); } if (e == e_3_1) { map.put("length", DefaultAttribute.createAttribute("30.0")); } return map; }); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(graph, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } } SimpleGEXFEventDrivenImporterTest.java000066400000000000000000000117101402514743400345400ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gexf; import org.jgrapht.alg.util.*; import org.junit.*; import java.io.*; import java.nio.charset.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Tests * * @author Dimitrios Michail */ public class SimpleGEXFEventDrivenImporterTest { private static final String NL = System.getProperty("line.separator"); @Test public void testUndirectedUnweighted() { // @formatter:off String input = "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + ""; // @formatter:on SimpleGEXFEventDrivenImporter importer = new SimpleGEXFEventDrivenImporter(); List> collected = new ArrayList<>(); importer.addEdgeConsumer(q -> { assertNull(q.getThird()); collected.add(Pair.of(Integer.valueOf(q.getFirst()), Integer.valueOf(q.getSecond()))); }); importer.importInput(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); int[][] edges = { { 2, 3 }, { 1, 2 }, { 3, 1 } }; int i = 0; for (int[] edge : edges) { Pair e = collected.get(i); assertEquals(Integer.valueOf(edge[0]), e.getFirst()); assertEquals(Integer.valueOf(edge[1]), e.getSecond()); i++; } } @Test public void testWithAttributesWeightedGraphs() { // @formatter:off String input = "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + ""; // @formatter:on SimpleGEXFEventDrivenImporter importer = new SimpleGEXFEventDrivenImporter(); List> collected = new ArrayList<>(); importer.addEdgeConsumer(q -> { collected.add(q); }); importer.importInput(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(collected.get(0).getFirst(), "n0"); assertEquals(collected.get(0).getSecond(), "n2"); assertEquals(collected.get(0).getThird(), 2.0, 1e-9); assertEquals(collected.get(1).getFirst(), "n0"); assertEquals(collected.get(1).getSecond(), "n1"); assertEquals(collected.get(1).getThird(), 3.0, 1e-9); assertEquals(collected.get(2).getFirst(), "n1"); assertEquals(collected.get(2).getSecond(), "n2"); assertNull(collected.get(2).getThird()); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/gexf/SimpleGEXFImporterTest.java000066400000000000000000000503771402514743400324610ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gexf; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.nio.charset.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests * * @author Dimitrios Michail */ public class SimpleGEXFImporterTest { private static final String NL = System.getProperty("line.separator"); @Test public void testUndirectedUnweighted() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); new SimpleGEXFImporter() .importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("0")); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("0", "1")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "0")); } @Test public void testUndirectedUnweightedWithMeta() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + " " + NL + " JGraphT" + NL + " JGraphT test" + NL + " graph, jgrapht" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); new SimpleGEXFImporter() .importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("0")); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("0", "1")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "0")); } @Test public void testVertexFactory() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); SimpleGEXFImporter importer = new SimpleGEXFImporter(); importer.setVertexFactory(id -> String.valueOf("node" + id)); importer.importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("node1")); assertTrue(g.containsVertex("node2")); assertTrue(g.containsVertex("node3")); assertTrue(g.containsEdge("node1", "node2")); assertTrue(g.containsEdge("node2", "node3")); assertTrue(g.containsEdge("node3", "node1")); } @Test public void testUndirectedUnweightedWithConsumers() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); Map, Attribute> vertexAttrs = new HashMap<>(); Map, Attribute> edgeAttrs = new HashMap<>(); Map graphAttrs = new HashMap<>(); SimpleGEXFImporter importer = new SimpleGEXFImporter(); importer.addVertexAttributeConsumer((k, v) -> vertexAttrs.put(k, v)); importer.addEdgeAttributeConsumer((k, v) -> edgeAttrs.put(k, v)); importer.addGraphAttributeConsumer((k, v) -> graphAttrs.put(k, v)); importer.importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); // check graph assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("0")); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("0", "1")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "0")); // check collected attributes assertEquals(vertexAttrs.get(Pair.of("0", "ID")), DefaultAttribute.createAttribute("v")); assertEquals( vertexAttrs.get(Pair.of("0", "label")), DefaultAttribute.createAttribute("node-v")); assertEquals(vertexAttrs.get(Pair.of("1", "ID")), DefaultAttribute.createAttribute("x")); assertEquals( vertexAttrs.get(Pair.of("1", "label")), DefaultAttribute.createAttribute("node-x")); assertEquals(vertexAttrs.get(Pair.of("2", "ID")), DefaultAttribute.createAttribute("u")); assertEquals( vertexAttrs.get(Pair.of("2", "label")), DefaultAttribute.createAttribute("node-u")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("0", "1"), "source")), DefaultAttribute.createAttribute("v")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("0", "1"), "target")), DefaultAttribute.createAttribute("x")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("1", "2"), "source")), DefaultAttribute.createAttribute("x")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("1", "2"), "target")), DefaultAttribute.createAttribute("u")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("2", "0"), "source")), DefaultAttribute.createAttribute("u")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("2", "0"), "target")), DefaultAttribute.createAttribute("v")); } @Test public void testWithAttributesWeightedGraphs() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .directed().weighted(true).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); Map, Attribute> vertexAttrs = new HashMap<>(); Map, Attribute> edgeAttrs = new HashMap<>(); Map graphAttrs = new HashMap<>(); SimpleGEXFImporter importer = new SimpleGEXFImporter(); importer.addVertexAttributeConsumer((k, v) -> vertexAttrs.put(k, v)); importer.addEdgeAttributeConsumer((k, v) -> edgeAttrs.put(k, v)); importer.addGraphAttributeConsumer((k, v) -> graphAttrs.put(k, v)); importer.importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("0")); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("0", "1")); assertTrue(g.containsEdge("2", "0")); assertEquals(1.2, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(0.1, g.getEdgeWeight(g.getEdge("0", "1")), 1e-9); assertEquals(2.0, g.getEdgeWeight(g.getEdge("2", "0")), 1e-9); assertEquals(vertexAttrs.get(Pair.of("0", "ID")), DefaultAttribute.createAttribute("1")); assertEquals( vertexAttrs.get(Pair.of("0", "color")), DefaultAttribute.createAttribute("Red")); assertEquals( vertexAttrs.get(Pair.of("0", "city")), DefaultAttribute.createAttribute("Paris")); assertEquals( vertexAttrs.get(Pair.of("0", "weight")), new DefaultAttribute<>("100.0", AttributeType.DOUBLE)); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("0", "1"), "length")), new DefaultAttribute<>("333.0", AttributeType.DOUBLE)); } @Test(expected = ImportException.class) public void testValidate() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); new SimpleGEXFImporter() .importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); } @Test public void testIgnoringNested() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + " " + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); SimpleGEXFImporter simpleGEXFImporter = new SimpleGEXFImporter(); simpleGEXFImporter.setVertexFactory(id -> id); simpleGEXFImporter .importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("3", "1")); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/gml/000077500000000000000000000000001402514743400251435ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/gml/GmlExporterTest.java000066400000000000000000000525711402514743400311300ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by John V Sichi and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gml; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.nio.*; import org.junit.*; import java.io.*; import java.util.*; import static org.junit.Assert.*; /** * Tests * * @author John V. Sichi * @author Dimitrios Michail */ public class GmlExporterTest { // ~ Static fields/initializers // --------------------------------------------- private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String V4 = "v4"; private static final String V5 = "v5"; private static final String NL = System.getProperty("line.separator"); // @formatter:off private static final String UNDIRECTED = "Creator \"JGraphT GML Exporter\"" + NL + "Version 1" + NL + "graph" + NL + "[" + NL + "\tlabel \"\"" + NL + "\tdirected 0" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 3" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t\tsource 1" + NL + "\t\ttarget 2" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t\tsource 3" + NL + "\t\ttarget 1" + NL + "\t]" + NL + "]" + NL; private static final String UNDIRECTED_WEIGHTED = "Creator \"JGraphT GML Exporter\"" + NL + "Version 1" + NL + "graph" + NL + "[" + NL + "\tlabel \"\"" + NL + "\tdirected 0" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 3" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t\tsource 1" + NL + "\t\ttarget 2" + NL + "\t\tweight 2.0" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t\tsource 3" + NL + "\t\ttarget 1" + NL + "\t\tweight 5.0" + NL + "\t]" + NL + "]" + NL; private static final String UNDIRECTED_WEIGHTED_WITH_EDGE_LABELS = "Creator \"JGraphT GML Exporter\"" + NL + "Version 1" + NL + "graph" + NL + "[" + NL + "\tlabel \"\"" + NL + "\tdirected 0" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 3" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t\tsource 1" + NL + "\t\ttarget 2" + NL + "\t\tlabel \"(v1 : v2)\"" + NL + "\t\tweight 2.0" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t\tsource 3" + NL + "\t\ttarget 1" + NL + "\t\tlabel \"(v3 : v1)\"" + NL + "\t\tweight 5.0" + NL + "\t]" + NL + "]" + NL; private static final String UNDIRECTED_WITH_VERTEX_LABELS = "Creator \"JGraphT GML Exporter\"" + NL + "Version 1" + NL + "graph" + NL + "[" + NL + "\tlabel \"\"" + NL + "\tdirected 0" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t\tlabel \"v1\"" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t\tlabel \"v2\"" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 3" + NL + "\t\tlabel \"v3\"" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t\tsource 1" + NL + "\t\ttarget 2" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t\tsource 3" + NL + "\t\ttarget 1" + NL + "\t]" + NL + "]" + NL; private static final String UNDIRECTED_WITH_VERTEX_LABELS_AND_CUSTOM_ATTRIBUTES = "Creator \"JGraphT GML Exporter\"" + NL + "Version 1" + NL + "graph" + NL + "[" + NL + "\tlabel \"\"" + NL + "\tdirected 0" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t\tlabel \"v1\"" + NL + "\t\tcolor \"red\"" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t\tlabel \"v2\"" + NL + "\t\tcolor \"black\"" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 3" + NL + "\t\tlabel \"v3\"" + NL + "\t\tcost 5.5" + NL + "\t\tlength 100" + NL + "\t\tvisited \"false\"" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t\tsource 1" + NL + "\t\ttarget 2" + NL + "\t\tname \"first edge\"" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t\tsource 3" + NL + "\t\ttarget 1" + NL + "\t]" + NL + "]" + NL; private static final String DIRECTED = "Creator \"JGraphT GML Exporter\"" + NL + "Version 1" + NL + "graph" + NL + "[" + NL + "\tlabel \"\"" + NL + "\tdirected 1" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 3" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 4" + NL + "\t]" + NL + "\tnode" + NL + "\t[" + NL + "\t\tid 5" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 1" + NL + "\t\tsource 1" + NL + "\t\ttarget 2" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 2" + NL + "\t\tsource 3" + NL + "\t\ttarget 1" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 3" + NL + "\t\tsource 2" + NL + "\t\ttarget 3" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 4" + NL + "\t\tsource 3" + NL + "\t\ttarget 4" + NL + "\t]" + NL + "\tedge" + NL + "\t[" + NL + "\t\tid 5" + NL + "\t\tsource 4" + NL + "\t\ttarget 5" + NL + "\t]" + NL + "]" + NL; // @formatter:on // ~ Methods // ---------------------------------------------------------------- @Test public void testUndirected() throws UnsupportedEncodingException, ExportException { Graph g = new SimpleGraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); GmlExporter exporter = new GmlExporter(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED, res); } @Test public void testUnweightedUndirected() throws UnsupportedEncodingException, ExportException { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setParameter(GmlExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED, res); } @Test public void testDirected() throws UnsupportedEncodingException, ExportException { Graph g = new SimpleDirectedGraph<>(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); g.addVertex(V4); g.addVertex(V5); g.addEdge(V1, V2); g.addEdge(V3, V1); g.addEdge(V2, V3); g.addEdge(V3, V4); g.addEdge(V4, V5); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(DIRECTED, res); } @Test public void testWeightedUndirected() throws UnsupportedEncodingException, ExportException { SimpleGraph g = new SimpleWeightedGraph(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); DefaultWeightedEdge e1 = g.addEdge(V1, V2); g.setEdgeWeight(e1, 2.0); DefaultWeightedEdge e2 = g.addEdge(V3, V1); g.setEdgeWeight(e2, 5.0); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setParameter(GmlExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED_WEIGHTED, res); } @Test public void testWeightedUndirectedWithEdgeLabels() throws UnsupportedEncodingException, ExportException { SimpleGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); DefaultWeightedEdge e1 = g.addEdge(V1, V2); g.setEdgeWeight(e1, 2.0); DefaultWeightedEdge e2 = g.addEdge(V3, V1); g.setEdgeWeight(e2, 5.0); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setParameter(GmlExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); exporter.setParameter(GmlExporter.Parameter.EXPORT_EDGE_LABELS, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED_WEIGHTED_WITH_EDGE_LABELS, res); } @Test public void testUndirectedWithVertexLabels() throws UnsupportedEncodingException, ExportException { SimpleGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); DefaultWeightedEdge e1 = g.addEdge(V1, V2); g.setEdgeWeight(e1, 2.0); DefaultWeightedEdge e2 = g.addEdge(V3, V1); g.setEdgeWeight(e2, 5.0); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setParameter(GmlExporter.Parameter.EXPORT_VERTEX_LABELS, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED_WITH_VERTEX_LABELS, res); } @Test public void testParameters() { GmlExporter exporter = new GmlExporter(); assertFalse(exporter.isParameter(GmlExporter.Parameter.EXPORT_VERTEX_LABELS)); assertFalse(exporter.isParameter(GmlExporter.Parameter.EXPORT_EDGE_LABELS)); assertFalse(exporter.isParameter(GmlExporter.Parameter.EXPORT_EDGE_WEIGHTS)); exporter.setParameter(GmlExporter.Parameter.EXPORT_VERTEX_LABELS, true); assertTrue(exporter.isParameter(GmlExporter.Parameter.EXPORT_VERTEX_LABELS)); exporter.setParameter(GmlExporter.Parameter.EXPORT_VERTEX_LABELS, false); assertFalse(exporter.isParameter(GmlExporter.Parameter.EXPORT_VERTEX_LABELS)); exporter.setParameter(GmlExporter.Parameter.EXPORT_EDGE_LABELS, true); assertTrue(exporter.isParameter(GmlExporter.Parameter.EXPORT_EDGE_LABELS)); exporter.setParameter(GmlExporter.Parameter.EXPORT_EDGE_LABELS, false); assertFalse(exporter.isParameter(GmlExporter.Parameter.EXPORT_EDGE_LABELS)); exporter.setParameter(GmlExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); assertTrue(exporter.isParameter(GmlExporter.Parameter.EXPORT_EDGE_WEIGHTS)); exporter.setParameter(GmlExporter.Parameter.EXPORT_EDGE_WEIGHTS, false); assertFalse(exporter.isParameter(GmlExporter.Parameter.EXPORT_EDGE_WEIGHTS)); } @Test public void testUndirectedWithCustomVertexAttributesAndVertexLabels() throws UnsupportedEncodingException, ExportException { SimpleGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); DefaultWeightedEdge e1 = g.addEdge(V1, V2); g.setEdgeWeight(e1, 2.0); DefaultWeightedEdge e2 = g.addEdge(V3, V1); g.setEdgeWeight(e2, 5.0); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setParameter(GmlExporter.Parameter.EXPORT_VERTEX_LABELS, true); exporter.setParameter(GmlExporter.Parameter.EXPORT_CUSTOM_VERTEX_ATTRIBUTES, true); exporter.setParameter(GmlExporter.Parameter.EXPORT_CUSTOM_EDGE_ATTRIBUTES, true); exporter.setVertexAttributeProvider(v -> { Map map = new HashMap<>(); if (v.equals(V1)) { map.put("color", DefaultAttribute.createAttribute("red")); } if (v.equals(V2)) { map.put("color", DefaultAttribute.createAttribute("black")); } if (v.equals(V3)) { map.put("cost", DefaultAttribute.createAttribute(5.5d)); map.put("length", DefaultAttribute.createAttribute(100L)); map.put("visited", DefaultAttribute.createAttribute(false)); } return map; }); exporter.setEdgeAttributeProvider(e -> { Map map = new HashMap<>(); if (e.equals(e1)) { map.put("name", DefaultAttribute.createAttribute("first edge")); } return map; }); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED_WITH_VERTEX_LABELS_AND_CUSTOM_ATTRIBUTES, res); } @Test(expected = IllegalArgumentException.class) public void testBadCustomVertexAttributes() throws UnsupportedEncodingException { SimpleGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setParameter(GmlExporter.Parameter.EXPORT_CUSTOM_VERTEX_ATTRIBUTES, true); exporter.setVertexAttributeProvider(v -> { Map map = new HashMap<>(); if (v.equals(V1)) { map.put("id", DefaultAttribute.createAttribute("custom-id")); } return map; }); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); } @Test(expected = IllegalArgumentException.class) public void testBadCustomEdgeAttributeId() throws UnsupportedEncodingException { SimpleGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); DefaultWeightedEdge e1 = g.addEdge(V1, V2); g.setEdgeWeight(e1, 2.0); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setParameter(GmlExporter.Parameter.EXPORT_CUSTOM_EDGE_ATTRIBUTES, true); exporter.setEdgeAttributeProvider(e -> { Map map = new HashMap<>(); if (e.equals(e1)) { map.put("id", DefaultAttribute.createAttribute("id")); } return map; }); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); } @Test(expected = IllegalArgumentException.class) public void testBadCustomEdgeAttributeSource() throws UnsupportedEncodingException { SimpleGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); DefaultWeightedEdge e1 = g.addEdge(V1, V2); g.setEdgeWeight(e1, 2.0); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setParameter(GmlExporter.Parameter.EXPORT_CUSTOM_EDGE_ATTRIBUTES, true); exporter.setEdgeAttributeProvider(e -> { Map map = new HashMap<>(); if (e.equals(e1)) { map.put("source", DefaultAttribute.createAttribute("source")); } return map; }); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); } @Test(expected = IllegalArgumentException.class) public void testBadCustomEdgeAttributeTarget() throws UnsupportedEncodingException { SimpleGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); DefaultWeightedEdge e1 = g.addEdge(V1, V2); g.setEdgeWeight(e1, 2.0); GmlExporter exporter = new GmlExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setParameter(GmlExporter.Parameter.EXPORT_CUSTOM_EDGE_ATTRIBUTES, true); exporter.setEdgeAttributeProvider(e -> { Map map = new HashMap<>(); if (e.equals(e1)) { map.put("target", DefaultAttribute.createAttribute("target")); } return map; }); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/gml/GmlImporterTest.java000066400000000000000000001055141402514743400311150ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.gml; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.util.*; import static org.junit.Assert.*; /** * Tests for {@link GmlImporter}. * * @author Dimitrios Michail */ public class GmlImporterTest { @Test public void testUndirectedUnweighted() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + " node [\n" + " id 3\n" + " ]\n" + " node [\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 2\n" + " ]\n" + " edge [\n" + " source 2\n" + " target 3\n" + " ]\n" + " edge [\n" + " source 3\n" + " target 1\n" + " ]\n" + "]"; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(4, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); } @Test public void testIgnoreWeightsUndirectedUnweighted() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 2\n" + " weight 2.0\n" + " ]\n" + " edge [\n" + " source 2.0\n" + " target 3\n" + " ]\n" + " edge [\n" + " source 3.3\n" + " target 1\n" + " ]\n" + "]"; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(2, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("1", "2")); } @Test public void testNoGraph() throws ImportException { // @formatter:off String input = "GRAPH [\n" + "]"; // @formatter:on Graph g1 = readGraph(input, DefaultEdge.class, false, false); assertEquals(0, g1.vertexSet().size()); assertEquals(0, g1.edgeSet().size()); Graph g2 = readGraph(input.toLowerCase(), DefaultEdge.class, false, false); assertEquals(0, g2.vertexSet().size()); assertEquals(0, g2.edgeSet().size()); } @Test public void testVertexFactory() throws ImportException { // @formatter:off String input = "graph [\n" + " directed 1\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 8\n" + " ]\n" + "]"; // @formatter:on Graph g; g = GraphTypeBuilder .directed().weighted(false).allowingSelfLoops(true).allowingMultipleEdges(true) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER) .vertexSupplier(SupplierUtil.createStringSupplier(1)).buildGraph(); GmlImporter importer = new GmlImporter<>(); importer.setVertexFactory(id -> String.valueOf(id + 100)); importer.importGraph(g, new StringReader(input)); assertEquals(2, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); assertTrue(g.containsVertex("101")); assertTrue(g.containsVertex("108")); } @Test public void testIgnore() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " ignore [\n" + " node [\n" + " id 5\n" + " ]" + " ]\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + " node [\n" + " id 3\n" + " label \"3\"" + " ]\n" + " node [\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 2\n" + " ]\n" + " ignore [\n" + " edge [\n" + " source 5\n" + " target 1\n" + " label \"edge51\"" + " ]" + " ]\n" + " edge [\n" + " source 2\n" + " target 3\n" + " label \"23\"" + " ]\n" + " edge [\n" + " source 3\n" + " target 1\n" + " ]\n" + "]" + "node [\n" + " id 6\n" + "]\n" ; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(4, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); } @Test public void testUndirectedUnweightedWrongOrder() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " edge [\n" + " source 1\n" + " target 2\n" + " ]\n" + " edge [\n" + " source 3\n" + " target 1\n" + " ]\n" + " edge [\n" + " source 2\n" + " target 3\n" + " ]\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + " node [\n" + " id 3\n" + " ]\n" + " node [\n" + " ]\n" + "]"; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(4, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); } @Test public void testDirectedPseudographUnweighted() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + " node [\n" + " id 3\n" + " ]\n" + " node [\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 2\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 2\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 2\n" + " ]\n" + " edge [\n" + " source 2\n" + " target 3\n" + " ]\n" + " edge [\n" + " source 3\n" + " target 1\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 1\n" + " ]\n" + " edge [\n" + " source 2\n" + " target 2\n" + " ]\n" + "]"; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, true, false); assertEquals(4, g.vertexSet().size()); assertEquals(7, g.edgeSet().size()); } @Test public void testDirectedWeighted() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + " node [\n" + " id 3\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 2\n" + " weight 2.0\n" + " ]\n" + " edge [\n" + " source 3\n" + " target 1\n" + " weight 3.0\n" + " ]\n" + "]"; // @formatter:on Graph g = readGraph(input, DefaultWeightedEdge.class, true, true); assertEquals(3, g.vertexSet().size()); assertEquals(2, g.edgeSet().size()); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("3", "1")); assertEquals(2.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(g.getEdge("3", "1")), 1e-9); } @Test public void testDirectedWeightedWithComments() throws ImportException { // @formatter:off String input = "# A comment line\n" + "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + "# Another comment line\n" + " node [\n" + " id 3\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 2\n" + " weight 2.0\n" + " ]\n" + " edge [\n" + " source 3\n" + " target 1\n" + " weight 3.0\n" + " ]\n" + "]"; // @formatter:on Graph g = readGraph(input, DefaultWeightedEdge.class, true, true); assertEquals(3, g.vertexSet().size()); assertEquals(2, g.edgeSet().size()); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("3", "1")); assertEquals(2.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(g.getEdge("3", "1")), 1e-9); } @Test public void testDirectedWeightedSingleLine() throws ImportException { // @formatter:off String input = "graph [ node [ id 1 ] node [ id 2 ] node [ id 3 ] " + "edge [ source 1 target 2 weight 2.0 ] " + "edge [ source 3 target 1 weight 3.0 ] ]"; // @formatter:on Graph g = readGraph(input, DefaultWeightedEdge.class, true, true); assertEquals(3, g.vertexSet().size()); assertEquals(2, g.edgeSet().size()); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("3", "1")); assertEquals(2.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(g.getEdge("3", "1")), 1e-9); } @Test public void testParserError() { // @formatter:off String input = "graph [ [ node ] ]"; // @formatter:on try { readGraph(input, DefaultEdge.class, false, false); fail("Managed to parse wrong input"); } catch (ImportException e) { } } @Test public void testExportImport() throws ImportException, ExportException, UnsupportedEncodingException { DirectedWeightedPseudograph g1 = new DirectedWeightedPseudograph<>(DefaultWeightedEdge.class); g1.addVertex("1"); g1.addVertex("2"); g1.addVertex("3"); g1.setEdgeWeight(g1.addEdge("1", "2"), 2.0); g1.setEdgeWeight(g1.addEdge("2", "3"), 3.0); g1.setEdgeWeight(g1.addEdge("3", "3"), 5.0); GmlExporter exporter = new GmlExporter<>(); exporter.setParameter(GmlExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g1, os); String output = new String(os.toByteArray(), "UTF-8"); Graph g2 = readGraph(output, DefaultWeightedEdge.class, true, true); assertEquals(3, g2.vertexSet().size()); assertEquals(3, g2.edgeSet().size()); assertTrue(g2.containsEdge("1", "2")); assertTrue(g2.containsEdge("2", "3")); assertTrue(g2.containsEdge("3", "3")); assertEquals(2.0, g2.getEdgeWeight(g2.getEdge("1", "2")), 1e-9); assertEquals(3.0, g2.getEdgeWeight(g2.getEdge("2", "3")), 1e-9); assertEquals(5.0, g2.getEdgeWeight(g2.getEdge("3", "3")), 1e-9); } @Test public void testNotSupportedGraph() { // @formatter:off String input = "graph [ node [ id 1 ] " + "edge [ source 1 target 1 ] ]"; // @formatter:on Graph g = new SimpleGraph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); try { GmlImporter importer = new GmlImporter<>(); importer.importGraph(g, new StringReader(input)); fail("No!"); } catch (ImportException e) { } } @Test(expected = ImportException.class) public void testNodeIdBadType() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " node [\n" + " id \"1\"\n" + " ]\n" + "]"; // @formatter:on readGraph(input, DefaultEdge.class, false, false); } @Test(expected = ImportException.class) public void testEdgeSourceBadType() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + " edge [\n" + " source \"1\"\n" + " target 2\n" + " ]\n" + "]"; // @formatter:on readGraph(input, DefaultEdge.class, false, false); } @Test(expected = ImportException.class) public void testEdgeTargetBadType() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + " edge [\n" + " source 1\n" + " target \"2\"\n" + " ]\n" + "]"; // @formatter:on readGraph(input, DefaultEdge.class, false, false); } @Test(expected = ImportException.class) public void testEdgeWeightBadType() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " node [\n" + " id 1\n" + " ]\n" + " node [\n" + " id 2\n" + " ]\n" + " edge [\n" + " source 1\n" + " target 2\n" + " weight \"2.0\"\n" + " ]\n" + "]"; // @formatter:on readGraph(input, DefaultEdge.class, false, false); } @Test public void testAttributesSupport() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " edge [\n" + " source 1\n" + " target 2\n" + " label \"Edge 1-2\"" + " name \"Name 12\"" + " ]\n" + " edge [\n" + " source 3\n" + " target 1\n" + " label \"Edge 3-1\"" + " name \"Name 31\"" + " ]\n" + " edge [\n" + " source 2\n" + " target 3\n" + " label \"Edge 2-3\"" + " name \"Name 23\"" + " ]\n" + " node [\n" + " id 1\n" + " label \"Node\t\t1\"" + " ]\n" + " node [\n" + " id 2\n" + " label \"Node\t\t2\"" + " ]\n" + " node [\n" + " id 3\n" + " label \"Node\t\t3\"" + " ]\n" + " node [\n" + " label \"Node\t\t?\"" + " ]\n" + " node [\n" + " label \"Node\t\t?\"" + " ]\n" + "]"; // @formatter:on Graph g = new DirectedWeightedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER); GmlImporter importer = new GmlImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); Map> edgeAttrs = new HashMap<>(); importer.addEdgeAttributeConsumer((p, a) -> { Map map = edgeAttrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); edgeAttrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.importGraph(g, new StringReader(input)); assertEquals(5, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); assertEquals(attrs.get("1").get("ID").getValue(), "1"); assertEquals(attrs.get("1").get("label").getValue(), "Node\t\t1"); assertEquals(attrs.get("2").get("ID").getValue(), "2"); assertEquals(attrs.get("2").get("label").getValue(), "Node\t\t2"); assertEquals(attrs.get("3").get("ID").getValue(), "3"); assertEquals(attrs.get("3").get("label").getValue(), "Node\t\t3"); assertEquals(attrs.get("4").get("ID").getValue(), "4"); assertEquals(attrs.get("4").get("label").getValue(), "Node\t\t?"); assertEquals(attrs.get("5").get("ID").getValue(), "5"); assertEquals(attrs.get("5").get("label").getValue(), "Node\t\t?"); assertEquals(edgeAttrs.get(g.getEdge("1", "2")).get("label").getValue(), "Edge 1-2"); assertEquals(edgeAttrs.get(g.getEdge("1", "2")).get("name").getValue(), "Name 12"); assertEquals(edgeAttrs.get(g.getEdge("3", "1")).get("label").getValue(), "Edge 3-1"); assertEquals(edgeAttrs.get(g.getEdge("3", "1")).get("name").getValue(), "Name 31"); assertEquals(edgeAttrs.get(g.getEdge("2", "3")).get("label").getValue(), "Edge 2-3"); assertEquals(edgeAttrs.get(g.getEdge("2", "3")).get("name").getValue(), "Name 23"); } @Test public void testCustomNumberAttributesSupport() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " edge [\n" + " source 1\n" + " target 2\n" + " frequency 6\n" + " customweight 7.5\n" + " ]\n" + " node [\n" + " id 1\n" + " frequency 2\n" + " customweight 1.2\n" + " ]\n" + " node [\n" + " id 2\n" + " frequency 3\n" + " customweight 2.1\n" + " ]\n" + " node [\n" + " frequency 5\n" + " customweight 5.5\n" + " ]\n" + "]"; // @formatter:on Graph g = new DirectedWeightedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER); GmlImporter importer = new GmlImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); Map> edgeAttrs = new HashMap<>(); importer.addEdgeAttributeConsumer((p, a) -> { Map map = edgeAttrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); edgeAttrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.importGraph(g, new StringReader(input)); assertEquals(3, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("1", "2")); assertEquals(attrs.get("1").get("ID").getValue(), "1"); assertEquals(attrs.get("1").get("frequency").getValue(), "2"); assertEquals(attrs.get("1").get("frequency").getType(), AttributeType.INT); assertEquals(attrs.get("1").get("customweight").getValue(), "1.2"); assertEquals(attrs.get("1").get("customweight").getType(), AttributeType.DOUBLE); assertEquals(attrs.get("2").get("ID").getValue(), "2"); assertEquals(attrs.get("2").get("frequency").getValue(), "3"); assertEquals(attrs.get("2").get("frequency").getType(), AttributeType.INT); assertEquals(attrs.get("2").get("customweight").getValue(), "2.1"); assertEquals(attrs.get("2").get("customweight").getType(), AttributeType.DOUBLE); assertEquals(attrs.get("3").get("ID").getValue(), "3"); assertEquals(attrs.get("3").get("frequency").getValue(), "5"); assertEquals(attrs.get("3").get("frequency").getType(), AttributeType.INT); assertEquals(attrs.get("3").get("customweight").getValue(), "5.5"); assertEquals(attrs.get("3").get("customweight").getType(), AttributeType.DOUBLE); assertEquals(edgeAttrs.get(g.getEdge("1", "2")).get("frequency").getValue(), "6"); assertEquals( edgeAttrs.get(g.getEdge("1", "2")).get("frequency").getType(), AttributeType.INT); assertEquals(edgeAttrs.get(g.getEdge("1", "2")).get("customweight").getValue(), "7.5"); assertEquals( edgeAttrs.get(g.getEdge("1", "2")).get("customweight").getType(), AttributeType.DOUBLE); } @Test public void testNestedStructure() throws ImportException { // @formatter:off String input = "graph [\n" + " comment \"Sample Graph\"\n" + " directed 1\n" + " edge [\n" + " source 1\n" + " target 2\n" + " frequency 6\n" + " customweight 7.5\n" + " points [ x 1.0 y 2.0 ]\n" + " deep [ one [ one 1 two 2 ] two [ one 1 two 2 ] ]\n" + " ]\n" + " node [\n" + " id 1\n" + " frequency 2\n" + " customweight 1.2\n" + " deep [ one [ one 1.0 two 2.0 ] two [ one \"1\" two \"2\" ] ]\n" + " ]\n" + " node [\n" + " id 2\n" + " frequency 3\n" + " customweight 2.1\n" + " points [ x 1.0 y 2.0 z 3.0 ]\n" + " deep [ one [ one 1 two 2 ] two [ one 1 two 2 ] ]\n" + " ]\n" + " node [\n" + " frequency 5\n" + " customweight 5.5\n" + " ]\n" + " deepignored [\n" + " deep1 [ deep2 [ deep3 [ deep4 [ deep 5 ] ] ] ]\n" + " ]\n" + "]\n" + "deepignored [\n" + " deep1 [ deep2 [ deep3 [ deep4 [ deep 5 ] ] ] ]\n" + "]\n"; // @formatter:on Graph g = new DirectedWeightedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER); GmlImporter importer = new GmlImporter<>(); Map> attrs = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map map = attrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); attrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); Map> edgeAttrs = new HashMap<>(); importer.addEdgeAttributeConsumer((p, a) -> { Map map = edgeAttrs.get(p.getFirst()); if (map == null) { map = new HashMap<>(); edgeAttrs.put(p.getFirst(), map); } map.put(p.getSecond(), a); }); importer.importGraph(g, new StringReader(input)); assertEquals(3, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("1", "2")); assertEquals(attrs.get("1").get("ID").getValue(), "1"); assertEquals(attrs.get("1").get("frequency").getValue(), "2"); assertEquals(attrs.get("1").get("frequency").getType(), AttributeType.INT); assertEquals(attrs.get("1").get("customweight").getValue(), "1.2"); assertEquals(attrs.get("1").get("customweight").getType(), AttributeType.DOUBLE); assertEquals( attrs.get("1").get("deep").getValue(), "[ one [ one 1.0 two 2.0 ] two [ one \"1\" two \"2\" ] ]"); assertEquals(attrs.get("1").get("deep").getType(), AttributeType.UNKNOWN); assertEquals(attrs.get("2").get("ID").getValue(), "2"); assertEquals(attrs.get("2").get("frequency").getValue(), "3"); assertEquals(attrs.get("2").get("frequency").getType(), AttributeType.INT); assertEquals(attrs.get("2").get("customweight").getValue(), "2.1"); assertEquals(attrs.get("2").get("customweight").getType(), AttributeType.DOUBLE); assertEquals( attrs.get("2").get("deep").getValue(), "[ one [ one 1 two 2 ] two [ one 1 two 2 ] ]"); assertEquals(attrs.get("2").get("deep").getType(), AttributeType.UNKNOWN); assertEquals(attrs.get("2").get("points").getValue(), "[ x 1.0 y 2.0 z 3.0 ]"); assertEquals(attrs.get("2").get("points").getType(), AttributeType.UNKNOWN); assertEquals(attrs.get("3").get("ID").getValue(), "3"); assertEquals(attrs.get("3").get("frequency").getValue(), "5"); assertEquals(attrs.get("3").get("frequency").getType(), AttributeType.INT); assertEquals(attrs.get("3").get("customweight").getValue(), "5.5"); assertEquals(attrs.get("3").get("customweight").getType(), AttributeType.DOUBLE); assertEquals(edgeAttrs.get(g.getEdge("1", "2")).get("frequency").getValue(), "6"); assertEquals( edgeAttrs.get(g.getEdge("1", "2")).get("frequency").getType(), AttributeType.INT); assertEquals(edgeAttrs.get(g.getEdge("1", "2")).get("customweight").getValue(), "7.5"); assertEquals( edgeAttrs.get(g.getEdge("1", "2")).get("customweight").getType(), AttributeType.DOUBLE); assertEquals( edgeAttrs.get(g.getEdge("1", "2")).get("deep").getValue(), "[ one [ one 1 two 2 ] two [ one 1 two 2 ] ]"); assertEquals( edgeAttrs.get(g.getEdge("1", "2")).get("deep").getType(), AttributeType.UNKNOWN); assertEquals( edgeAttrs.get(g.getEdge("1", "2")).get("points").getValue(), "[ x 1.0 y 2.0 ]"); assertEquals( edgeAttrs.get(g.getEdge("1", "2")).get("points").getType(), AttributeType.UNKNOWN); } // ~ Private Methods ------------------------------------------------------ private Graph readGraph( String input, Class edgeClass, boolean directed, boolean weighted) throws ImportException { Graph g; if (directed) { g = GraphTypeBuilder .directed().weighted(weighted).allowingSelfLoops(true).allowingMultipleEdges(true) .edgeClass(edgeClass).vertexSupplier(SupplierUtil.createStringSupplier(1)) .buildGraph(); } else { g = GraphTypeBuilder .undirected().weighted(weighted).allowingSelfLoops(true).allowingMultipleEdges(true) .edgeClass(edgeClass).vertexSupplier(SupplierUtil.createStringSupplier(1)) .buildGraph(); } GmlImporter importer = new GmlImporter<>(); importer.importGraph(g, new StringReader(input)); return g; } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/graph6/000077500000000000000000000000001402514743400255535ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/graph6/Graph6Sparse6ExporterTest.java000066400000000000000000000213151402514743400334040ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graph6; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.nio.charset.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for Graph6Sparse6Exporter * * @author Joris Kinable */ public class Graph6Sparse6ExporterTest { // -------------------Sparse6 tests-------------------- @Test public void testEmptyGraph() throws UnsupportedEncodingException, ExportException { Graph orig = new SimpleGraph<>(DefaultEdge.class); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.SPARSE6); assertEquals(":?", res); } @Test public void testGraphWithoutEdges() throws UnsupportedEncodingException, ExportException { Graph orig = new SimpleGraph<>(DefaultEdge.class); orig.addVertex(0); orig.addVertex(1); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.SPARSE6); assertEquals(":A", res); } @Test public void testExampleGraph() throws UnsupportedEncodingException, ExportException { Graph orig = new SimpleGraph<>(DefaultEdge.class); Graphs.addAllVertices(orig, Arrays.asList(0, 1, 2, 3, 4, 5, 6)); orig.addEdge(0, 1); orig.addEdge(0, 2); orig.addEdge(1, 2); orig.addEdge(5, 6); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.SPARSE6); assertEquals(":Fa@x^", res); } @Test public void testGraph1a() throws UnsupportedEncodingException, ExportException, ImportException { Graph orig = NamedGraphGenerator.petersenGraph(); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.SPARSE6); Graph g = importGraph(res); this.compare(orig, g); } @Test public void testGraph2a() throws UnsupportedEncodingException, ExportException, ImportException { Graph orig = NamedGraphGenerator.ellinghamHorton78Graph(); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.SPARSE6); Graph g = importGraph(res); this.compare(orig, g); } @Test public void testGraph3a() throws UnsupportedEncodingException, ExportException, ImportException { Graph orig = NamedGraphGenerator.klein3RegularGraph(); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.SPARSE6); Graph g = importGraph(res); this.compare(orig, g); } @Test public void testPseudoGraph() throws UnsupportedEncodingException, ExportException, ImportException { Graph orig = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(orig, Arrays.asList(0, 1, 2)); orig.addEdge(0, 1); orig.addEdge(0, 1); orig.addEdge(1, 2); orig.addEdge(2, 0); orig.addEdge(2, 2); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.SPARSE6); Graph g = importGraph(res); this.compare(orig, g); } @Test public void testRandomGraphsS6() throws UnsupportedEncodingException, ExportException, ImportException { GnpRandomGraphGenerator gnp = new GnpRandomGraphGenerator<>(40, .55, 0, true); for (int i = 0; i < 20; i++) { Graph orig = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gnp.generateGraph(orig); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.SPARSE6); Graph g = importGraph(res); this.compare(orig, g); } } // -------------------Graph6 tests-------------------- @Test public void testGraph1b() throws UnsupportedEncodingException, ExportException, ImportException { Graph orig = NamedGraphGenerator.petersenGraph(); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.GRAPH6); Graph g = importGraph(res); this.compare(orig, g); } @Test public void testGraph2b() throws UnsupportedEncodingException, ExportException, ImportException { Graph orig = NamedGraphGenerator.ellinghamHorton78Graph(); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.GRAPH6); Graph g = importGraph(res); this.compare(orig, g); } @Test public void testGraph3b() throws UnsupportedEncodingException, ExportException, ImportException { Graph orig = NamedGraphGenerator.klein3RegularGraph(); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.GRAPH6); Graph g = importGraph(res); this.compare(orig, g); } @Test public void testRandomGraphsG6() throws UnsupportedEncodingException, ExportException, ImportException { GnpRandomGraphGenerator gnp = new GnpRandomGraphGenerator<>(40, .55, 0); for (int i = 0; i < 20; i++) { Graph orig = new SimpleGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), false); gnp.generateGraph(orig); String res = exportGraph(orig, Graph6Sparse6Exporter.Format.GRAPH6); Graph g = importGraph(res); this.compare(orig, g); } } // -------------------helper methods-------------------- private String exportGraph(Graph g, Graph6Sparse6Exporter.Format format) throws UnsupportedEncodingException, ExportException { Graph6Sparse6Exporter exporter = new Graph6Sparse6Exporter<>(format); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); return new String(os.toByteArray(), "UTF-8"); } private Graph importGraph(String g6) throws ImportException { Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Graph6Sparse6Importer importer = new Graph6Sparse6Importer<>(); importer.importGraph(g, new ByteArrayInputStream(g6.getBytes(StandardCharsets.UTF_8))); return g; } private void compare(Graph orig, Graph g) { assertEquals(orig.vertexSet().size(), g.vertexSet().size()); assertEquals(orig.edgeSet().size(), g.edgeSet().size()); // The original and output graph cannot be compared 1:1 since sparse6/graph6 encodings do // not preserve vertex labels // Testing for graph isomorphism is hard, so we compare characteristics. int[] degeeVectorOrig = new int[orig.edgeSet().size()]; for (V v : orig.vertexSet()) degeeVectorOrig[orig.degreeOf(v)]++; int[] degeeVectorG = new int[g.edgeSet().size()]; for (V v : g.vertexSet()) degeeVectorG[g.degreeOf(v)]++; assertTrue(Arrays.equals(degeeVectorOrig, degeeVectorG)); assertEquals(GraphMetrics.getRadius(orig), GraphMetrics.getRadius(g), 0.00000001); assertEquals(GraphMetrics.getDiameter(orig), GraphMetrics.getDiameter(g), 0.00000001); assertEquals(GraphMetrics.getGirth(orig), GraphMetrics.getGirth(g), 0.00000001); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/graph6/Graph6Sparse6ImporterTest.java000066400000000000000000000320501402514743400333730ustar00rootroot00000000000000/* * (C) Copyright 2017-2021, by Joris Kinable and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graph6; import org.jgrapht.*; import org.jgrapht.generate.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.nio.charset.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests for Graph6Sparse6Importer Sparse6/Graph6 strings are generated with Sage Math engine * * @author Joris Kinable */ public class Graph6Sparse6ImporterTest { public Graph readGraph(InputStream in, Class edgeClass, boolean weighted) throws ImportException { Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(weighted) .vertexSupplier(SupplierUtil.createIntegerSupplier()).edgeClass(edgeClass).buildGraph(); Graph6Sparse6Importer importer = new Graph6Sparse6Importer<>(); try { importer.importGraph(g, new InputStreamReader(in, "UTF-8")); } catch (UnsupportedEncodingException e) { // cannot happen } return g; } @Test public void testExampleGraph() throws ImportException { String input = ":Fa@x^\n"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); assertEquals(7, graph.vertexSet().size()); assertEquals(4, graph.edgeSet().size()); int[][] edges = { { 0, 1 }, { 0, 2 }, { 1, 2 }, { 5, 6 } }; for (int[] edge : edges) assertTrue(graph.containsEdge(edge[0], edge[1])); } @Test public void pseudoGraph() throws ImportException { // Klein7RegularGraph String input = ":B_`V"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); Graph orig = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(orig, Arrays.asList(0, 1, 2)); orig.addEdge(0, 1); orig.addEdge(0, 1); orig.addEdge(1, 2); orig.addEdge(2, 2); orig.addEdge(2, 0); this.compare(orig, graph); } @Test public void testVertexFactory() throws ImportException { // Klein7RegularGraph String input = ":B_`V"; Graph graph = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); Graph6Sparse6Importer importer = new Graph6Sparse6Importer<>(); importer.setVertexFactory(id -> String.valueOf("node" + id)); try { importer .importGraph( graph, new InputStreamReader( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), "UTF-8")); } catch (UnsupportedEncodingException e) { // cannot happen } Graph orig = new Pseudograph<>(DefaultEdge.class); Graphs.addAllVertices(orig, Arrays.asList("node0", "node1", "node2")); orig.addEdge("node0", "node1"); orig.addEdge("node0", "node1"); orig.addEdge("node0", "node2"); orig.addEdge("node1", "node2"); orig.addEdge("node2", "node2"); this.compare(orig, graph); assertEquals(orig.toString(), graph.toString()); } @Test public void testNumberVertices1() throws ImportException { String input = "~??~?????_@?CG??B??@OG?C?G???GO??W@a???CO???OACC?OA?P@G??O??????G??C????c?G?CC?_?@???C_??_?C????PO?C_??AA?OOAHCA___?CC?A?CAOGO??????A??G?GR?C?_o`???g???A_C?OG??O?G_IA????_QO@EG???O??C?_?C@?G???@?_??AC?AO?a???O?????A?_Dw?H???__O@AAOAACd?_C??G?G@??GO?_???O@?_O??W??@P???AG??B?????G??GG???A??@?aC_G@A??O??_?A?????O@Z?_@M????GQ@_G@?C?\n"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); assertEquals(63, graph.vertexSet().size()); } @Test public void testNumberVertices2() throws ImportException { String input = "_???C?@AA?_?A?O?C??S??O?q_?P?CHD??@?C?GC???C??GG?C_??O?COG????I?J??Q??O?_@@??@??????\n"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); assertEquals(32, graph.vertexSet().size()); } @Test public void testGraph6a() throws ImportException { // Klein7RegularGraph String input = "WzK[WgIOT@Wq_A?NALPAq?{GDASCCXO?l?OJAGOY_D@__wb"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.klein7RegularGraph(), graph); } @Test public void testGraph6b() throws ImportException { // ellinghamHorton78Graph String input = "~?@MhEGHC?AG?_PO@?Ga?GA???C??G??G??C??P???G@?G_??????P????_??AG??O@???@C??A?G?????????C????@?????G?????_????P?????@?????G????????????P??????C?????AG????A?G?????_???????H???????G???????_??????@???????@????????_??????AG???????@?????_?@C????????????????AG????????C????????P???????A?G????????G_?????C??G_???????????????????_?????????G?????C???@??????????_?????@????G?????A???????????????_??????????@????@?????AG??????????C????G?????G@?AG@????????????????@??o??????CW????????????C?W?????????????I???????????c?G"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.ellinghamHorton78Graph(), graph); } @Test public void testGraph6c() throws ImportException { // goldnerHararyGraph String input = "JntIBcPEA~_"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.goldnerHararyGraph(), graph); } @Test public void testGraph6d() throws ImportException { // buckyBallGraph String input = "{R??OKGPG??@AA??_???@@?GO?G?????CAGA?OGO??????@???O??C@_??O??G?@?????????W???D????OS??????????????O@????@BG???????????_???_??????@B??@???_??O???g?????????????C????C???????C?W?A????C??_????D_???????????????_????C????????_?@??????O?g??????@@O?A?????????????C?C?_??????A????????OQ????????@O????????B"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.buckyBallGraph(), graph); } @Test public void testGraph6f() throws ImportException { // heawoodGraph String input = "MhEGHC@AI?_PC@_G_"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.heawoodGraph(), graph); } @Test public void testSparse6a() throws ImportException { // Klein7RegularGraph String input = ":W__@`AaBbC_CDbDcE`F_AG_@DEH_IgHIJbFGIKaFHILeFGHMdFKN_EKOPaCNPQ`HOQRcGLRS`BKMSTdJKLPTU\n"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.klein7RegularGraph(), graph); } @Test public void testSparse6b() throws ImportException { // ellinghamHorton78Graph String input = ":~?@M_GEA_w?C`WGEaOOGaWWI_OmGBGKL`w}OcXINCxQGCPUWCp]WdPeOEh[Zc`q^Fh}_gXwagyAfGaYfhAa^IYEgIyqlji}ojREqfa{rlbCtljKvjbatMYWv_Jq|hBy{hSAdn{M\\OCRAeRtEa_wVlSHBhagjkBgzpCY}OSr"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.ellinghamHorton78Graph(), graph); } @Test public void testSparse6c() throws ImportException { // goldnerHararyGraph String input = ":J`E?POAMHGpCKsrrHCXAeM`N"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.goldnerHararyGraph(), graph); } @Test public void testSparse6d() throws ImportException { // buckyBallGraph String input = ":{`?GGIKCa`gcCIGdag_iXNFPPsK`RHP`PIMMHtqtM]VKShXiyZMUBTWw]pDcDpAa`XI}@IeghHyXPjTV[IlXLTQtay@ooWUUT_qtkU[vSucLmJ]Aw_MVV"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.buckyBallGraph(), graph); } @Test public void testSparse6f() throws ImportException { // heawoodGraph String input = ":M`ESwCjGtyGaeqhj_`f"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.heawoodGraph(), graph); } @Test public void testSparse6g() throws ImportException { // ellinghamHorton78Graph generated by mathematica 10.0 String input = ">>sparse6<<:~?@M__EC?GEA_wQD`g]DAGOH`oiEAwqLbg}?CGCP_`IBCxCSc@URDhGV_ocXaG?IEgkZfXuWgiA^GQMaHIEhHA]eII[igAabIYaoJAuqJi}pizIrlJUrLjGvlRasMZiznJumNi{~kSAoOZ|AncN@PK@DkRXEls]wQCmnMSf~~~~~"; Graph graph = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false); this.compare(NamedGraphGenerator.ellinghamHorton78Graph(), graph); } @Test public void testFromFile() throws ImportException, IOException { InputStream fstream = getClass().getClassLoader().getResourceAsStream("ellinghamHorton78Graph.s6"); Graph g = new Pseudograph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); Graph6Sparse6Importer importer = new Graph6Sparse6Importer<>(); importer.importGraph(g, fstream); this.compare(NamedGraphGenerator.ellinghamHorton78Graph(), g); } private void compare(Graph orig, Graph g) { assertEquals(orig.vertexSet().size(), g.vertexSet().size()); assertEquals(orig.edgeSet().size(), g.edgeSet().size()); // The original and output graph cannot be compared 1:1 since sparse6/graph6 encodings do // not preserve vertex labels // Testing for graph isomorphism is hard, so we compare characteristics. int[] degeeVectorOrig = new int[orig.edgeSet().size()]; for (V v : orig.vertexSet()) degeeVectorOrig[orig.degreeOf(v)]++; int[] degeeVectorG = new int[g.edgeSet().size()]; for (V v : g.vertexSet()) degeeVectorG[g.degreeOf(v)]++; assertTrue(Arrays.equals(degeeVectorOrig, degeeVectorG)); assertEquals(GraphMetrics.getRadius(orig), GraphMetrics.getRadius(g), 0.00000001); assertEquals(GraphMetrics.getDiameter(orig), GraphMetrics.getDiameter(g), 0.00000001); assertEquals(GraphMetrics.getGirth(orig), GraphMetrics.getGirth(g), 0.00000001); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/000077500000000000000000000000001402514743400260165ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/GraphMLExporterTest.java000066400000000000000000000700521402514743400325500ustar00rootroot00000000000000/* * (C) Copyright 2006-2021, by Trevor Harmon and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.nio.*; import org.jgrapht.nio.graphml.GraphMLExporter.*; import org.junit.*; import org.xmlunit.builder.*; import org.xmlunit.diff.*; import java.io.*; import java.util.*; import static org.junit.Assert.assertFalse; import static org.junit.Assert.fail; /** * Tests * * @author Trevor Harmon * @author Dimitrios Michail */ public class GraphMLExporterTest { // ~ Static fields/initializers // --------------------------------------------- private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String NL = System.getProperty("line.separator"); // ~ Methods // ---------------------------------------------------------------- @Test public void testUndirected() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on Graph g = new SimpleGraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testUndirectedWeighted() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on Graph g = new SimpleGraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setExportEdgeWeights(true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testDirected() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on Graph g = new SimpleDirectedGraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testUndirectedUnweightedWithWeights() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on Graph g = new SimpleGraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setExportEdgeWeights(true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testUndirectedWeightedWithWeights() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "3.0" + NL + "" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on SimpleWeightedGraph g = new SimpleWeightedGraph(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); g.setEdgeWeight(g.getEdge(V1, V2), 3.0); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setExportEdgeWeights(true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testUndirectedWeightedWithCustomNameWeights() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "3.0" + NL + "" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on SimpleWeightedGraph g = new SimpleWeightedGraph(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); g.setEdgeWeight(g.getEdge(V1, V2), 3.0); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setExportEdgeWeights(true); exporter.setEdgeWeightAttributeName("value"); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testNoRegisterWeightAttribute() throws Exception { try { GraphMLExporter exporter = new GraphMLExporter(); exporter.registerAttribute("weight", AttributeCategory.ALL, AttributeType.STRING); fail("Registered reserved attribute"); } catch (IllegalArgumentException e) { } } @Test public void testRegisterWeightAttribute() throws Exception { try { GraphMLExporter exporter = new GraphMLExporter(); exporter.setEdgeWeightAttributeName("anothername"); exporter.registerAttribute("weight", AttributeCategory.ALL, AttributeType.STRING); } catch (IllegalArgumentException e) { fail("No!"); } } @Test public void testNoAlreadyRegisteredAttributeAsWeightName() throws Exception { try { GraphMLExporter exporter = new GraphMLExporter(); exporter.registerAttribute("length", AttributeCategory.EDGE, AttributeType.STRING); exporter.setEdgeWeightAttributeName("length"); fail("Registered reserved attribute"); } catch (IllegalArgumentException e) { } } @Test public void testUndirectedWeightedWithWeightsAndLabels() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + "v1" + NL + "" + NL + "" + NL + "v2" + NL + "" + NL + "" + NL + "v3" + NL + "" + NL + "" + NL + "(v1 : v2)" + NL + "3.0" + NL + "" + NL + "" + NL + "(v3 : v1)"+ NL + "15.0" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on SimpleWeightedGraph g = new SimpleWeightedGraph(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); g.setEdgeWeight(g.getEdge(V1, V2), 3.0); g.setEdgeWeight(g.getEdge(V3, V1), 15.0); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setExportEdgeWeights(true); exporter.setExportVertexLabels(true); exporter.setExportEdgeLabels(true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testUndirectedWeightedWithWeightsAndLabelsAndCustomNames() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + "myvertex-v1" + NL + "" + NL + "" + NL + "myvertex-v2" + NL + "" + NL + "" + NL + "myvertex-v3" + NL + "" + NL + "" + NL + "myedge-(v1 : v2)" + NL + "3.0" + NL + "" + NL + "" + NL + "myedge-(v3 : v1)"+ NL + "15.0" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); g.setEdgeWeight(g.getEdge(V1, V2), 3.0); g.setEdgeWeight(g.getEdge(V3, V1), 15.0); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setVertexAttributeProvider((v) -> { Map map = new LinkedHashMap<>(); map .put( "custom_vertex_label", DefaultAttribute.createAttribute("myvertex-" + String.valueOf(v))); return map; }); exporter.setVertexLabelAttributeName("custom_vertex_label"); exporter.setEdgeAttributeProvider((e) -> { Map map = new LinkedHashMap<>(); map .put( "custom_edge_label", DefaultAttribute.createAttribute("myedge-" + String.valueOf(e))); return map; }); exporter.setEdgeLabelAttributeName("custom_edge_label"); exporter.setExportEdgeLabels(true); exporter.setExportVertexLabels(true); exporter.setExportEdgeWeights(true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testUndirectedWeightedWithWeightsAndColor() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "yellow" + NL + "" + NL + "" + NL + "johndoe" + NL + "" + NL + "" + NL + "" + NL + "V1" + NL + "" + NL + "" + NL + "red" + NL + "V2" + NL + "" + NL + "" + NL + "V3" + NL + "" + NL + "" + NL + "3.0" + NL + "e12" + NL + "" + NL + "" + NL + "e31" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); g.setEdgeWeight(g.getEdge(V1, V2), 3.0); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setVertexAttributeProvider((v) -> { Map map = new LinkedHashMap<>(); switch (v) { case V1: map.put("color", DefaultAttribute.createAttribute("yellow")); map.put("name", DefaultAttribute.createAttribute("V1")); break; case V2: map.put("color", DefaultAttribute.createAttribute("red")); map.put("name", DefaultAttribute.createAttribute("V2")); break; case V3: map.put("name", DefaultAttribute.createAttribute("V3")); break; default: break; } return map; }); exporter.setEdgeAttributeProvider((e) -> { Map map = new LinkedHashMap<>(); if (e.equals(g.getEdge(V1, V2))) { map.put("color", DefaultAttribute.createAttribute("what?")); map.put("name", DefaultAttribute.createAttribute("e12")); } else if (e.equals(g.getEdge(V3, V1))) { map.put("color", DefaultAttribute.createAttribute("I have no color!")); map.put("name", DefaultAttribute.createAttribute("e31")); } return map; }); exporter.setExportEdgeWeights(true); exporter .registerAttribute( "color", GraphMLExporter.AttributeCategory.NODE, AttributeType.STRING, "yellow"); exporter .registerAttribute( "name", GraphMLExporter.AttributeCategory.ALL, AttributeType.STRING, "johndoe"); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } @Test public void testUndirectedWeightedWithNullComponentProvider() throws Exception { String output = // @formatter:off "" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "yellow" + NL + "" + NL + "" + NL + "johndoe" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "3.0" + NL + "" + NL + "" + NL + "" + NL + "" + NL; // @formatter:on SimpleWeightedGraph g = new SimpleWeightedGraph<>(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); g.setEdgeWeight(g.getEdge(V1, V2), 3.0); GraphMLExporter exporter = new GraphMLExporter<>(); exporter.setEdgeIdProvider(new IntegerIdProvider<>()); exporter.setExportEdgeWeights(true); exporter .registerAttribute( "color", GraphMLExporter.AttributeCategory.NODE, AttributeType.STRING, "yellow"); exporter .registerAttribute( "name", GraphMLExporter.AttributeCategory.ALL, AttributeType.STRING, "johndoe"); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); Diff diff = DiffBuilder .compare(res).withTest(output).ignoreWhitespace().checkForIdentical().build(); assertFalse("XML identical " + diff.toString(), diff.hasDifferences()); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/GraphMLImporterTest.java000066400000000000000000001403731402514743400325450ustar00rootroot00000000000000/* * (C) Copyright 2016-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.nio.charset.*; import java.util.*; import static org.junit.Assert.*; /** * Tests * * @author Dimitrios Michail */ public class GraphMLImporterTest { private static final String NL = System.getProperty("line.separator"); @Test public void testUndirectedUnweighted() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); } @Test public void testVertexFactory() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); GraphMLImporter importer = new GraphMLImporter<>(); importer.setVertexFactory(id -> String.valueOf("node" + id)); importer.importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("node1")); assertTrue(g.containsVertex("node2")); assertTrue(g.containsVertex("node3")); assertTrue(g.containsEdge("node1", "node2")); assertTrue(g.containsEdge("node2", "node3")); assertTrue(g.containsEdge("node3", "node1")); } @Test public void testUndirectedUnweightedFromInputStream() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = readGraph( new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)), DefaultEdge.class, false, false); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); } @Test public void testUndirectedUnweightedPseudoGraph() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + ""+ NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(3, g.vertexSet().size()); assertEquals(5, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); assertTrue(g.containsEdge("1", "1")); } @Test public void testUndirectedUnweightedStringKeys() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(3, g.vertexSet().size()); assertEquals(4, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); assertTrue(g.containsEdge("1", "1")); } @Test public void testUndirectedUnweightedWrongOrder() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); } @Test public void testDirectedUnweighted() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, true, false); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "2")); assertFalse(g.containsEdge("2", "1")); assertTrue(g.containsEdge("2", "3")); assertFalse(g.containsEdge("3", "2")); assertTrue(g.containsEdge("3", "1")); assertFalse(g.containsEdge("1", "3")); } @Test public void testUndirectedUnweightedPrefix() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); } @Test public void testWithAttributes() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "yellow" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + "" + NL + "blue" + NL + "" + NL+ "" + NL + "2.0" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); } @Test public void testWithMapAttributes() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "yellow" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + "" + NL + "blue" + NL + "" + NL+ "" + NL + "2.0" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on Map> vAttributes = new HashMap<>(); Map> eAttributes = new HashMap<>(); Graph g = readGraph(input, DefaultEdge.class, false, false, vAttributes, eAttributes); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertEquals("green", vAttributes.get("1").get("color").getValue()); assertEquals(AttributeType.STRING, vAttributes.get("1").get("color").getType()); assertEquals("yellow", vAttributes.get("2").get("color").getValue()); assertEquals(AttributeType.STRING, vAttributes.get("2").get("color").getType()); assertEquals("blue", vAttributes.get("3").get("color").getValue()); assertEquals(AttributeType.STRING, vAttributes.get("3").get("color").getType()); assertEquals("2.0", eAttributes.get(g.getEdge("1", "3")).get("weight").getValue()); assertEquals( AttributeType.DOUBLE, eAttributes.get(g.getEdge("1", "3")).get("weight").getType()); assertEquals("1.0", eAttributes.get(g.getEdge("1", "2")).get("weight").getValue()); assertEquals( AttributeType.DOUBLE, eAttributes.get(g.getEdge("1", "2")).get("weight").getType()); assertNull(eAttributes.get(g.getEdge("2", "3"))); } @Test public void testWithAttributesWeightedGraphs() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "yellow" + NL + "" + NL + "" + NL + "3.0" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + "" + NL + "blue" + NL + "" + NL+ "" + NL + "2.0" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultWeightedEdge.class, true, true); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertEquals(2.0, g.getEdgeWeight(g.getEdge("1", "3")), 1e-9); assertEquals(1.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(g.getEdge("2", "3")), 1e-9); } @Test public void testWithAttributesCustomNamedWeightedGraphs() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "yellow" + NL + "" + NL + "" + NL + "3.0" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + "" + NL + "blue" + NL + "" + NL+ "" + NL + "2.0" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = new DirectedWeightedPseudograph<>( SupplierUtil.createStringSupplier(0), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); Map> vAttributes = new HashMap<>(); Map> eAttributes = new HashMap<>(); GraphMLImporter importer = createGraphImporter(vAttributes, eAttributes); importer.setEdgeWeightAttributeName("myvalue"); importer.importGraph(g, new StringReader(input)); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("0")); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("0", "2")); assertTrue(g.containsEdge("0", "1")); assertTrue(g.containsEdge("1", "2")); assertEquals(2.0, g.getEdgeWeight(g.getEdge("0", "2")), 1e-9); assertEquals(1.0, g.getEdgeWeight(g.getEdge("0", "1")), 1e-9); assertEquals(3.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(vAttributes.get("0").get("ID").getValue(), "n0"); } @Test public void testWithAttributesCustomNamedWeightedForAllGraphs() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "yellow" + NL + "" + NL + "" + NL + "3.0" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + "value1" + NL + "" + NL + "" + NL + "blue" + NL + "" + NL+ "" + NL + "2.0" + NL + "" + NL + "" + NL + "1.0" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = new DirectedWeightedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER); Map> vAttributes = new HashMap<>(); Map> eAttributes = new HashMap<>(); GraphMLImporter importer = createGraphImporter(vAttributes, eAttributes); importer.setEdgeWeightAttributeName("myvalue"); importer.importGraph(g, new StringReader(input)); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertEquals(2.0, g.getEdgeWeight(g.getEdge("1", "3")), 1e-9); assertEquals(1.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(g.getEdge("2", "3")), 1e-9); assertEquals("3.0", vAttributes.get("1").get("myvalue").getValue()); assertEquals("n0", vAttributes.get("1").get("ID").getValue()); assertEquals("3.0", vAttributes.get("2").get("myvalue").getValue()); assertEquals("n1", vAttributes.get("2").get("ID").getValue()); assertEquals("3.0", vAttributes.get("3").get("myvalue").getValue()); assertEquals("n2", vAttributes.get("3").get("ID").getValue()); assertFalse(vAttributes.get("1").containsKey("onemore")); assertEquals("value1", vAttributes.get("2").get("onemore").getValue()); assertFalse(vAttributes.get("3").containsKey("onemore")); assertFalse(eAttributes.get(g.getEdge("1", "3")).containsKey("onemore")); assertFalse(eAttributes.get(g.getEdge("1", "2")).containsKey("onemore")); assertFalse(eAttributes.get(g.getEdge("2", "3")).containsKey("onemore")); } @Test public void testWithHyperEdges() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + " " + NL + " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " " + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(4, g.vertexSet().size()); assertEquals(2, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "4")); } @Test public void testValidate() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on try { readGraph(input, DefaultEdge.class, false, false); fail("No!"); } catch (ImportException e) { } } @Test public void testValidateNoNodeId() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on try { readGraph(input, DefaultEdge.class, false, false); fail("No!"); } catch (ImportException e) { } } @Test public void testValidateDuplicateNode() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on try { readGraph(input, DefaultEdge.class, false, false); fail("No!"); } catch (ImportException e) { } } @Test public void testValidWithXLinkNodeAttrib() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("3", "1")); } @Test public void testExportImport() throws Exception { DirectedPseudograph g1 = new DirectedPseudograph<>(DefaultEdge.class); g1.addVertex("1"); g1.addVertex("2"); g1.addVertex("3"); g1.addEdge("1", "2"); g1.addEdge("2", "3"); g1.addEdge("3", "3"); GraphMLExporter exporter = new GraphMLExporter<>(); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g1, os); String output = new String(os.toByteArray(), "UTF-8"); Graph g2 = readGraph(output, DefaultEdge.class, true, false); assertEquals(3, g2.vertexSet().size()); assertEquals(3, g2.edgeSet().size()); assertTrue(g2.containsEdge("1", "2")); assertTrue(g2.containsEdge("2", "3")); assertTrue(g2.containsEdge("3", "3")); } @Test public void testWithAttributesAtGraphLevel() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(2, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("1", "2")); } @Test public void testWithAttributesAtGraphMLLevel() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(2, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("1", "2")); } @Test public void testNestedGraphs() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " green" + NL + " " + NL + " " + NL + "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " green" + NL + " " + NL + " " + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "green" + NL + ""; // @formatter:on Graph g = readGraph(input, DefaultEdge.class, false, false); assertEquals(7, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsVertex("5")); assertTrue(g.containsVertex("6")); assertTrue(g.containsVertex("7")); assertTrue(g.containsEdge("2", "3")); assertTrue(g.containsEdge("5", "6")); assertTrue(g.containsEdge("4", "7")); } @Test public void testUnsupportedGraph() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + ""+ NL + ""+ NL + "" + NL + ""; // @formatter:on final Graph g = new SimpleGraph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); try { GraphMLImporter importer = new GraphMLImporter<>(); importer.importGraph(g, new StringReader(input)); fail("No!"); } catch (Exception e) { // nothing } } @Test public void testNonValidApoc() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ":Person:ENTITY" + NL + "Person1" + NL + "1" + NL + "" + NL + "" + NL + ":Person:ENTITY" + NL + "Person2" + NL + "2" + NL + "" + NL + "" + NL + "daughter" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); HashMap> vertexAttributes = new HashMap<>(); HashMap> edgeAttributes = new HashMap<>(); GraphMLImporter importer = new GraphMLImporter<>(); importer.addVertexAttributeConsumer((k, a) -> { String vertex = k.getFirst(); Map attrs = vertexAttributes.get(vertex); if (attrs == null) { attrs = new HashMap<>(); vertexAttributes.put(vertex, attrs); } attrs.put(k.getSecond(), a); }); importer.addEdgeAttributeConsumer((k, a) -> { DefaultEdge edge = k.getFirst(); Map attrs = edgeAttributes.get(edge); if (attrs == null) { attrs = new HashMap<>(); edgeAttributes.put(edge, attrs); } attrs.put(k.getSecond(), a); }); importer.setSchemaValidation(false); importer.importGraph(g, new StringReader(input)); assertEquals(2, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); for (Map va : vertexAttributes.values()) { assertTrue(va.containsKey("name")); assertTrue(va.containsKey("id")); assertFalse(va.containsKey("labels")); } for (Map ea : edgeAttributes.values()) { assertTrue(ea.isEmpty()); } } @Test(expected = ImportException.class) public void testNonValidNoVertexId() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(1), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); GraphMLImporter importer = new GraphMLImporter<>(); importer.setSchemaValidation(false); importer.importGraph(g, new StringReader(input)); } public Graph readGraph( String input, Class edgeClass, boolean directed, boolean weighted) throws ImportException { return readGraph( input, edgeClass, directed, weighted, new HashMap<>(), new HashMap>()); } public Graph readGraph( InputStream input, Class edgeClass, boolean directed, boolean weighted) throws ImportException { return readGraph( input, edgeClass, directed, weighted, new HashMap<>(), new HashMap>()); } public Graph readGraph( String input, Class edgeClass, boolean directed, boolean weighted, Map> vertexAttributes, Map> edgeAttributes) throws ImportException { return readGraph( new ByteArrayInputStream(input.getBytes()), edgeClass, directed, weighted, vertexAttributes, edgeAttributes); } public Graph readGraph( InputStream input, Class edgeClass, boolean directed, boolean weighted, Map> vertexAttributes, Map> edgeAttributes) throws ImportException { Graph g; if (directed) { g = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(weighted) .vertexSupplier(SupplierUtil.createStringSupplier(1)).edgeClass(edgeClass) .buildGraph(); } else { g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(weighted) .vertexSupplier(SupplierUtil.createStringSupplier(1)).edgeClass(edgeClass) .buildGraph(); } GraphMLImporter importer = new GraphMLImporter<>(); importer.addVertexAttributeConsumer((k, a) -> { String vertex = k.getFirst(); Map attrs = vertexAttributes.get(vertex); if (attrs == null) { attrs = new HashMap<>(); vertexAttributes.put(vertex, attrs); } attrs.put(k.getSecond(), a); }); importer.addEdgeAttributeConsumer((k, a) -> { E edge = k.getFirst(); Map attrs = edgeAttributes.get(edge); if (attrs == null) { attrs = new HashMap<>(); edgeAttributes.put(edge, attrs); } attrs.put(k.getSecond(), a); }); importer.importGraph(g, input); return g; } public GraphMLImporter createGraphImporter( Map> vertexAttributes, Map> edgeAttributes) { GraphMLImporter importer = new GraphMLImporter<>(); importer.addVertexAttributeConsumer((k, a) -> { String vertex = k.getFirst(); Map attrs = vertexAttributes.get(vertex); if (attrs == null) { attrs = new HashMap<>(); vertexAttributes.put(vertex, attrs); } attrs.put(k.getSecond(), a); }); importer.addEdgeAttributeConsumer((k, a) -> { E edge = k.getFirst(); Map attrs = edgeAttributes.get(edge); if (attrs == null) { attrs = new HashMap<>(); edgeAttributes.put(edge, attrs); } attrs.put(k.getSecond(), a); }); return importer; } } SimpleGraphMLEdgeListImporterTest.java000066400000000000000000000122011402514743400352450ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.alg.util.*; import org.jgrapht.nio.*; import org.junit.*; import java.io.*; import java.nio.charset.*; import java.util.*; import static org.junit.Assert.*; /** * Tests * * @author Dimitrios Michail */ public class SimpleGraphMLEdgeListImporterTest { private static final String NL = System.getProperty("line.separator"); @Test public void testUndirectedUnweighted() { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on SimpleGraphMLEdgeListImporter importer = new SimpleGraphMLEdgeListImporter(); List> collected = new ArrayList<>(); importer.addEdgeConsumer(t -> { assertNull(t.getThird()); collected.add(Pair.of(t.getFirst(), t.getSecond())); }); importer.importInput(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); Map nameMap = new HashMap<>(); nameMap.put(2, 0); nameMap.put(3, 1); nameMap.put(1, 2); int[][] edges = { { 2, 3 }, { 1, 2 }, { 3, 1 } }; int i = 0; for (int[] edge : edges) { Pair e = collected.get(i); assertEquals(nameMap.get(edge[0]), e.getFirst()); assertEquals(nameMap.get(edge[1]), e.getSecond()); i++; } } @Test public void testWithAttributesWeightedGraphs() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + "" + NL + "blue" + NL + "" + NL+ "" + NL + "2.0" + NL + "" + NL + "" + NL + "3.0" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on SimpleGraphMLEdgeListImporter importer = new SimpleGraphMLEdgeListImporter(); List> collected = new ArrayList<>(); importer.addEdgeConsumer(t -> { collected.add(t); }); importer.importInput(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); Integer[][] edges = { { 0, 2, 2 }, { 0, 1, 3 }, { 1, 2, 1 } }; assertTrue(collected.size() == 3); int i = 0; for (Integer[] edge : edges) { Triple e = collected.get(i); assertEquals(Integer.valueOf(edge[0]), e.getFirst()); assertEquals(Integer.valueOf(edge[1]), e.getSecond()); if (i < 2) { assertEquals(Double.valueOf(edge[2]), collected.get(i).getThird()); } else { assertNull(e.getThird()); } i++; } } } SimpleGraphMLEventDrivenImporterTest.java000066400000000000000000000125511402514743400360060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.alg.util.*; import org.junit.*; import java.io.*; import java.nio.charset.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; /** * Tests * * @author Dimitrios Michail */ public class SimpleGraphMLEventDrivenImporterTest { private static final String NL = System.getProperty("line.separator"); @Test public void testUndirectedUnweighted() { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on SimpleGraphMLEventDrivenImporter importer = new SimpleGraphMLEventDrivenImporter(); List> collected = new ArrayList<>(); importer.addEdgeConsumer(q -> { assertNull(q.getThird()); collected.add(Pair.of(Integer.valueOf(q.getFirst()), Integer.valueOf(q.getSecond()))); }); importer.importInput(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); int[][] edges = { { 2, 3 }, { 1, 2 }, { 3, 1 } }; int i = 0; for (int[] edge : edges) { Pair e = collected.get(i); assertEquals(Integer.valueOf(edge[0]), e.getFirst()); assertEquals(Integer.valueOf(edge[1]), e.getSecond()); i++; } } @Test public void testWithAttributesWeightedGraphs() { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + "" + NL + "blue" + NL + "" + NL+ "" + NL + "2.0" + NL + "" + NL + "" + NL + "3.0" + NL + "13.0" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on SimpleGraphMLEventDrivenImporter importer = new SimpleGraphMLEventDrivenImporter(); importer.addEdgeAttributeConsumer((p, a) -> { String key = p.getSecond(); String value = a.getValue(); if (key.equals("cost")) { assertEquals(value, "13.0"); } }); List> collected = new ArrayList<>(); importer.addEdgeConsumer(q -> { collected.add(q); }); importer.importInput(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(collected.get(0).getFirst(), "n0"); assertEquals(collected.get(0).getSecond(), "n2"); assertEquals(collected.get(0).getThird(), 2.0, 1e-9); assertEquals(collected.get(1).getFirst(), "n0"); assertEquals(collected.get(1).getSecond(), "n1"); assertEquals(collected.get(1).getThird(), 3.0, 1e-9); assertEquals(collected.get(2).getFirst(), "n1"); assertEquals(collected.get(2).getSecond(), "n2"); assertNull(collected.get(2).getThird()); } } SimpleGraphMLImporterTest.java000066400000000000000000000443401402514743400336350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/graphml/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.graphml; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.nio.charset.*; import java.util.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Tests * * @author Dimitrios Michail */ public class SimpleGraphMLImporterTest { private static final String NL = System.getProperty("line.separator"); @Test public void testUndirectedUnweighted() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); new SimpleGraphMLImporter() .importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("0")); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("0", "1")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "0")); } @Test public void testVertexFactory() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); SimpleGraphMLImporter importer = new SimpleGraphMLImporter(); importer.setVertexFactory(id -> String.valueOf("node" + id)); importer.importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("node1")); assertTrue(g.containsVertex("node2")); assertTrue(g.containsVertex("node3")); assertTrue(g.containsEdge("node1", "node2")); assertTrue(g.containsEdge("node2", "node3")); assertTrue(g.containsEdge("node3", "node1")); } @Test public void testUndirectedUnweightedWithConsumers() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""+ NL + "" + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); Map, Attribute> vertexAttrs = new HashMap<>(); Map, Attribute> edgeAttrs = new HashMap<>(); Map graphAttrs = new HashMap<>(); SimpleGraphMLImporter importer = new SimpleGraphMLImporter(); importer.addVertexAttributeConsumer((k, v) -> vertexAttrs.put(k, v)); importer.addEdgeAttributeConsumer((k, v) -> edgeAttrs.put(k, v)); importer.addGraphAttributeConsumer((k, v) -> graphAttrs.put(k, v)); importer.importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); // check graph assertEquals(3, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("0")); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsEdge("0", "1")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("2", "0")); // check collected attributes assertEquals(vertexAttrs.get(Pair.of("0", "id")), DefaultAttribute.createAttribute("v")); assertEquals(vertexAttrs.get(Pair.of("1", "id")), DefaultAttribute.createAttribute("x")); assertEquals(vertexAttrs.get(Pair.of("2", "id")), DefaultAttribute.createAttribute("u")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("0", "1"), "source")), DefaultAttribute.createAttribute("v")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("0", "1"), "target")), DefaultAttribute.createAttribute("x")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("1", "2"), "source")), DefaultAttribute.createAttribute("x")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("1", "2"), "target")), DefaultAttribute.createAttribute("u")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("2", "0"), "source")), DefaultAttribute.createAttribute("u")); assertEquals( edgeAttrs.get(Pair.of(g.getEdge("2", "0"), "target")), DefaultAttribute.createAttribute("v")); } @Test public void testWithAttributesWeightedGraphs() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + "" + NL + "blue" + NL + "" + NL+ "" + NL + "" + NL + "2.0" + NL + "" + NL + "" + NL + "3.0" + NL + "" + NL + "" + NL + "" + NL + "99.0" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(true).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); Map, Attribute> vertexAttrs = new HashMap<>(); Map, Attribute> edgeAttrs = new HashMap<>(); Map graphAttrs = new HashMap<>(); SimpleGraphMLImporter importer = new SimpleGraphMLImporter(); importer.addVertexAttributeConsumer((k, v) -> vertexAttrs.put(k, v)); importer.addEdgeAttributeConsumer((k, v) -> edgeAttrs.put(k, v)); importer.addGraphAttributeConsumer((k, v) -> graphAttrs.put(k, v)); importer.importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(4, g.vertexSet().size()); assertEquals(4, g.edgeSet().size()); assertTrue(g.containsVertex("0")); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsEdge("0", "2")); assertTrue(g.containsEdge("0", "1")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("0", "3")); assertEquals(2.0, g.getEdgeWeight(g.getEdge("0", "2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(g.getEdge("0", "1")), 1e-9); assertEquals(1.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(99.0, g.getEdgeWeight(g.getEdge("0", "3")), 1e-9); } @Test(expected = ImportException.class) public void testValidate() throws ImportException { // @formatter:off String input = "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); new SimpleGraphMLImporter() .importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); } @Test(expected = ImportException.class) public void testNestedGraphs() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "green" + NL + "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " green" + NL + " " + NL + " " + NL + "" + NL + "" + NL + " " + NL + " " + NL + " " + NL + " green" + NL + " " + NL + " " + NL + "" + NL + "" + NL + "green" + NL + "" + NL + "green" + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); new SimpleGraphMLImporter() .importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); } @Test public void testWithAttributesAtGraphMLLevel() throws ImportException { // @formatter:off String input = " " + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "" + NL + "green" + NL + ""; // @formatter:on Graph g = GraphTypeBuilder .undirected().weighted(false).allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.createDefaultEdgeSupplier()).buildGraph(); Map, Attribute> vertexAttrs = new HashMap<>(); Map, Attribute> edgeAttrs = new HashMap<>(); Map graphAttrs = new HashMap<>(); SimpleGraphMLImporter importer = new SimpleGraphMLImporter(); importer.addVertexAttributeConsumer((k, v) -> vertexAttrs.put(k, v)); importer.addEdgeAttributeConsumer((k, v) -> edgeAttrs.put(k, v)); importer.addGraphAttributeConsumer((k, v) -> graphAttrs.put(k, v)); importer.importGraph(g, new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8))); assertEquals(2, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); assertTrue(g.containsVertex("0")); assertTrue(g.containsVertex("1")); assertTrue(g.containsEdge("0", "1")); assertEquals(DefaultAttribute.createAttribute("green"), graphAttrs.get("color")); assertEquals(DefaultAttribute.createAttribute("undirected"), graphAttrs.get("edgedefault")); assertEquals(DefaultAttribute.createAttribute("n0"), vertexAttrs.get(Pair.of("0", "id"))); assertEquals(DefaultAttribute.createAttribute("n1"), vertexAttrs.get(Pair.of("1", "id"))); assertEquals( DefaultAttribute.createAttribute("e1"), edgeAttrs.get(Pair.of(g.getEdge("0", "1"), "id"))); assertEquals( DefaultAttribute.createAttribute("n0"), edgeAttrs.get(Pair.of(g.getEdge("0", "1"), "source"))); assertEquals( DefaultAttribute.createAttribute("n1"), edgeAttrs.get(Pair.of(g.getEdge("0", "1"), "target"))); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/json/000077500000000000000000000000001402514743400253355ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/json/JSONExporterTest.java000066400000000000000000000341621402514743400313500ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.json; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.nio.*; import org.jgrapht.util.*; import org.junit.*; import java.io.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * Test JSONExporter */ public class JSONExporterTest { @Test public void testBasic() throws UnsupportedEncodingException { String expected = "{\"creator\":\"JGraphT JSON Exporter\",\"version\":\"1\",\"nodes\":[{\"id\":\"1\"},{\"id\":\"2\"},{\"id\":\"3\"},{\"id\":\"4\"}],\"edges\":[{\"id\":\"1\",\"source\":\"1\",\"target\":\"2\"},{\"id\":\"2\",\"source\":\"2\",\"target\":\"3\"},{\"id\":\"3\",\"source\":\"3\",\"target\":\"4\"},{\"id\":\"4\",\"source\":\"1\",\"target\":\"4\"}]}"; Graph graph = GraphTypeBuilder .directed().edgeClass(DefaultEdge.class) .vertexSupplier(SupplierUtil.createIntegerSupplier()).allowingMultipleEdges(false) .allowingSelfLoops(false).buildGraph(); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); graph.addVertex(4); graph.addEdge(1, 2); graph.addEdge(2, 3); graph.addEdge(3, 4); graph.addEdge(1, 4); JSONExporter exporter = new JSONExporter<>(v -> String.valueOf(v)); exporter.setEdgeIdProvider(new IntegerIdProvider<>(1)); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(graph, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(expected, res); } @Test public void testUndirectedWeightedWithWeightsAndColor() throws Exception { String expected = "{\"creator\":\"JGraphT JSON Exporter\",\"version\":\"1\",\"nodes\":[{\"id\":\"1\",\"color\":\"yellow\",\"label\":\"V1\"},{\"id\":\"2\",\"color\":\"red\",\"label\":\"V2\"},{\"id\":\"3\",\"label\":\"V3\"}],\"edges\":[{\"id\":\"1\",\"source\":\"1\",\"target\":\"2\",\"color\":\"what?\",\"label\":\"e12\",\"weight\":1.0},{\"id\":\"2\",\"source\":\"1\",\"target\":\"3\",\"color\":\"I have no color!\",\"label\":\"e13\",\"weight\":1.0},{\"id\":\"3\",\"source\":\"2\",\"target\":\"3\",\"color\":\"I have no color!\",\"label\":\"e13\",\"weight\":100.0}]}"; Graph graph = GraphTypeBuilder .directed().weighted(true).edgeClass(DefaultWeightedEdge.class) .vertexSupplier(SupplierUtil.createIntegerSupplier()).allowingMultipleEdges(false) .allowingSelfLoops(false).buildGraph(); graph.addVertex(1); graph.addVertex(2); graph.addVertex(3); DefaultWeightedEdge e12 = graph.addEdge(1, 2); DefaultWeightedEdge e13 = graph.addEdge(1, 3); DefaultWeightedEdge e23 = graph.addEdge(2, 3); graph.setEdgeWeight(e23, 100d); Function> vertexAttributeProvider = v -> { Map map = new LinkedHashMap<>(); switch (v) { case 1: map.put("color", DefaultAttribute.createAttribute("yellow")); map.put("label", DefaultAttribute.createAttribute("V1")); break; case 2: map.put("color", DefaultAttribute.createAttribute("red")); map.put("label", DefaultAttribute.createAttribute("V2")); break; case 3: map.put("label", DefaultAttribute.createAttribute("V3")); break; default: break; } return map; }; Function> edgeAttributeProvider = e -> { Map map = new LinkedHashMap<>(); if (e.equals(e12)) { map.put("color", DefaultAttribute.createAttribute("what?")); map.put("label", DefaultAttribute.createAttribute("e12")); map.put("weight", DefaultAttribute.createAttribute(graph.getEdgeWeight(e))); } else if (e.equals(e13)) { map.put("color", DefaultAttribute.createAttribute("I have no color!")); map.put("label", DefaultAttribute.createAttribute("e13")); map.put("weight", DefaultAttribute.createAttribute(graph.getEdgeWeight(e))); } else if (e.equals(e23)) { map.put("color", DefaultAttribute.createAttribute("I have no color!")); map.put("label", DefaultAttribute.createAttribute("e13")); map.put("weight", DefaultAttribute.createAttribute(graph.getEdgeWeight(e))); } return map; }; JSONExporter exporter = new JSONExporter<>(new IntegerIdProvider<>(1)); exporter.setEdgeIdProvider(new IntegerIdProvider<>(1)); exporter.setVertexAttributeProvider(vertexAttributeProvider); exporter.setEdgeAttributeProvider(edgeAttributeProvider); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(graph, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(expected, res); } @Test public void testAttributeTypes() throws Exception { String expected = "{\"creator\":\"JGraphT JSON Exporter\",\"version\":\"1\",\"nodes\":[{\"id\":\"1\",\"stringAttribute\":\"yellow\",\"doubleAttribute\":3.4,\"intAttribute\":3,\"floatAttribute\":3.4,\"longAttribute\":3,\"booleanAttribute\":true},{\"id\":\"2\"}],\"edges\":[{\"id\":\"1\",\"source\":\"1\",\"target\":\"2\",\"color\":\"what?\",\"label\":\"e12\",\"weight\":100.0}]}"; Graph graph = GraphTypeBuilder .directed().weighted(true).edgeClass(DefaultWeightedEdge.class) .vertexSupplier(SupplierUtil.createIntegerSupplier()).allowingMultipleEdges(false) .allowingSelfLoops(false).buildGraph(); graph.addVertex(1); graph.addVertex(2); DefaultWeightedEdge e12 = graph.addEdge(1, 2); graph.setEdgeWeight(e12, 100d); Function> vertexAttributeProvider = v -> { Map map = new LinkedHashMap<>(); switch (v) { case 1: map.put("stringAttribute", DefaultAttribute.createAttribute("yellow")); map.put("doubleAttribute", DefaultAttribute.createAttribute(3.4d)); map.put("intAttribute", DefaultAttribute.createAttribute(3)); map.put("floatAttribute", DefaultAttribute.createAttribute(3.4f)); map.put("longAttribute", DefaultAttribute.createAttribute(3l)); map.put("booleanAttribute", DefaultAttribute.createAttribute(true)); break; default: break; } return map; }; Function> edgeAttributeProvider = e -> { Map map = new LinkedHashMap<>(); if (e.equals(e12)) { map.put("color", DefaultAttribute.createAttribute("what?")); map.put("label", DefaultAttribute.createAttribute("e12")); map.put("weight", DefaultAttribute.createAttribute(graph.getEdgeWeight(e))); } return map; }; JSONExporter exporter = new JSONExporter<>(new IntegerIdProvider<>(1)); exporter.setEdgeIdProvider(new IntegerIdProvider<>(1)); exporter.setVertexAttributeProvider(vertexAttributeProvider); exporter.setEdgeAttributeProvider(edgeAttributeProvider); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(graph, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(expected, res); } @Test(expected = IllegalArgumentException.class) public void testNotAllowedNanDouble() throws Exception { Graph graph = GraphTypeBuilder .directed().weighted(true).edgeClass(DefaultWeightedEdge.class) .vertexSupplier(SupplierUtil.createIntegerSupplier()).allowingMultipleEdges(false) .allowingSelfLoops(false).buildGraph(); graph.addVertex(1); Function> vertexAttributeProvider = v -> { Map map = new LinkedHashMap<>(); switch (v) { case 1: map.put("NaNAttribute", DefaultAttribute.createAttribute(Double.NaN)); break; default: break; } return map; }; JSONExporter exporter = new JSONExporter<>(new IntegerIdProvider<>(1)); exporter.setEdgeIdProvider(new IntegerIdProvider<>(1)); exporter.setVertexAttributeProvider(vertexAttributeProvider); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(graph, os); } @Test public void testExportAndImport() throws ExportException, ImportException { Graph graph1 = GraphTypeBuilder .directed().weighted(true).edgeClass(DefaultWeightedEdge.class) .vertexSupplier(SupplierUtil.createIntegerSupplier()).allowingMultipleEdges(true) .allowingSelfLoops(true).buildGraph(); graph1.addVertex(1); graph1.addVertex(2); graph1.addVertex(3); graph1.addVertex(4); graph1.addVertex(5); graph1.addEdge(1, 2); graph1.addEdge(1, 3); graph1.addEdge(1, 4); graph1.addEdge(1, 4); graph1.addEdge(1, 4); graph1.addEdge(4, 4); JSONExporter exporter = new JSONExporter<>(x -> String.valueOf(x)); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(graph1, os); String output1 = os.toString(); Graph graph2 = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createIntegerSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(graph2, new StringReader(output1)); assertEquals(5, graph2.vertexSet().size()); assertEquals(6, graph2.edgeSet().size()); assertTrue(graph2.containsVertex(1)); assertTrue(graph2.containsVertex(2)); assertTrue(graph2.containsVertex(3)); assertTrue(graph2.containsVertex(4)); assertTrue(graph2.containsVertex(5)); assertTrue(graph2.containsEdge(1, 2)); assertTrue(graph2.containsEdge(1, 3)); assertTrue(graph2.containsEdge(1, 4)); assertTrue(graph2.containsEdge(4, 4)); assertEquals(3, graph2.getAllEdges(1, 4).size()); } @Test public void testExportAndImportWithEscape() throws ExportException, ImportException { Graph graph1 = GraphTypeBuilder .directed().weighted(true).edgeClass(DefaultWeightedEdge.class) .vertexSupplier(SupplierUtil.createStringSupplier()).allowingMultipleEdges(true) .allowingSelfLoops(true).buildGraph(); String difficultId = "I have \"\" in my id"; graph1.addVertex("1"); graph1.addVertex(difficultId); graph1.addVertex("3"); graph1.addVertex("4"); graph1.addVertex("5"); graph1.addEdge("1", difficultId); graph1.addEdge("1", "3"); graph1.addEdge("1", "4"); graph1.addEdge("1", "4"); graph1.addEdge("1", "4"); graph1.addEdge("4", "4"); JSONExporter exporter = new JSONExporter<>(x -> String.valueOf(x)); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(graph1, os); String output1 = os.toString(); Graph graph2 = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_WEIGHTED_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.setVertexFactory(x -> x); importer.importGraph(graph2, new StringReader(output1)); assertEquals(5, graph2.vertexSet().size()); assertEquals(6, graph2.edgeSet().size()); assertTrue(graph2.containsVertex("1")); assertTrue(graph2.containsVertex(difficultId)); assertTrue(graph2.containsVertex("3")); assertTrue(graph2.containsVertex("4")); assertTrue(graph2.containsVertex("5")); assertTrue(graph2.containsEdge("1", difficultId)); assertTrue(graph2.containsEdge("1", "3")); assertTrue(graph2.containsEdge("1", "4")); assertTrue(graph2.containsEdge("4", "4")); assertEquals(3, graph2.getAllEdges("1", "4").size()); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/json/JSONImporterTest.java000066400000000000000000000626461402514743400313510ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.json; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.StringReader; import java.util.HashMap; import java.util.Map; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DirectedPseudograph; import org.jgrapht.graph.builder.GraphTypeBuilder; import org.jgrapht.nio.Attribute; import org.jgrapht.nio.AttributeType; import org.jgrapht.nio.ImportException; import org.jgrapht.util.SupplierUtil; import org.junit.Test; /** * Tests for {@link JsonImporter}. * * @author Dimitrios Michail */ public class JSONImporterTest { @Test public void testUndirectedUnweighted() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"1\" },\n" + " { \"id\":\"2\" },\n" + " { \"id\":\"3\" },\n" + " { \"id\":\"4\" }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\":\"2\" },\n" + " { \"source\":\"1\", \"target\":\"3\" }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(g, new StringReader(input)); assertEquals(4, g.vertexSet().size()); assertEquals(2, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "3")); } @Test public void testVertexFactory() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"1\" },\n" + " { \"id\":\"2\" },\n" + " { \"id\":\"3\" },\n" + " { \"id\":\"4\" }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\":\"2\" },\n" + " { \"source\":\"1\", \"target\":\"3\" }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.setVertexFactory(id -> String.valueOf("node" + id)); importer.importGraph(g, new StringReader(input)); assertEquals(4, g.vertexSet().size()); assertEquals(2, g.edgeSet().size()); assertTrue(g.containsVertex("node1")); assertTrue(g.containsVertex("node2")); assertTrue(g.containsVertex("node3")); assertTrue(g.containsVertex("node4")); assertTrue(g.containsEdge("node1", "node2")); assertTrue(g.containsEdge("node1", "node3")); } @Test public void testMixedStringAndIntegerIds() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":1 },\n" + " { \"id\":\"2\" },\n" + " { \"id\":\"3\" },\n" + " { \"id\":4 }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":1, \"target\":\"2\" },\n" + " { \"source\":1, \"target\":3 }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(g, new StringReader(input)); assertEquals(4, g.vertexSet().size()); assertEquals(2, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertTrue(g.containsEdge("1", "2")); assertTrue(g.containsEdge("1", "3")); } @Test(expected = ImportException.class) public void testDuplicateNodeIds() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":1 },\n" + " { \"id\":\"2\" },\n" + " { \"id\":1 }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\":\"2\" }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(g, new StringReader(input)); } @Test(expected = ImportException.class) public void testMissingSourceOnEdge() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":1 },\n" + " { \"id\":\"2\" },\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\":\"2\" },\n" + " { \"target\":\"2\" },\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(g, new StringReader(input)); } @Test(expected = ImportException.class) public void testMissingTargetOnEdge() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":1 },\n" + " { \"id\":\"2\" },\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\" },\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(g, new StringReader(input)); } @Test public void testWeightsOnWeighted() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"1\" },\n" + " { \"id\":\"2\" },\n" + " { \"id\":\"3\" },\n" + " { \"id\":\"4\" }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\":\"2\", \"weight\": 2.0 },\n" + " { \"source\":\"1\", \"target\":\"3\", \"weight\": 3.0 },\n" + " { \"source\":\"2\", \"target\":\"3\" }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(g, new StringReader(input)); assertEquals(4, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertEquals(2.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(3.0, g.getEdgeWeight(g.getEdge("1", "3")), 1e-9); assertEquals(1.0, g.getEdgeWeight(g.getEdge("2", "3")), 1e-9); } @Test public void testWeightsOnUnweighted() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"1\" },\n" + " { \"id\":\"2\" },\n" + " { \"id\":\"3\" },\n" + " { \"id\":\"4\" }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\":\"2\", \"weight\": 2.0 },\n" + " { \"source\":\"1\", \"target\":\"3\", \"weight\": 3.0 },\n" + " { \"source\":\"2\", \"target\":\"3\" }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(g, new StringReader(input)); assertEquals(4, g.vertexSet().size()); assertEquals(3, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertTrue(g.containsVertex("3")); assertTrue(g.containsVertex("4")); assertEquals(1.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); assertEquals(1.0, g.getEdgeWeight(g.getEdge("1", "3")), 1e-9); assertEquals(1.0, g.getEdgeWeight(g.getEdge("2", "3")), 1e-9); } @Test public void testNodeAttributes() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"1\", \"label\": \"Label\", \"int\": 4, \"double\": 0.5, \"boolean\": true, \"boolean1\": false, \"novalue\": null }\n" + " ],\n" + " \"edges\": null" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); Map> vertexAttributes = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map attrs = vertexAttributes.get(p.getFirst()); if (attrs == null) { attrs = new HashMap<>(); vertexAttributes.put(p.getFirst(), attrs); } attrs.put(p.getSecond(), a); }); importer.importGraph(g, new StringReader(input)); assertEquals(1, g.vertexSet().size()); assertEquals(0, g.edgeSet().size()); assertTrue(g.containsVertex("1")); Map attributes = vertexAttributes.get("1"); assertNotNull(attributes); assertTrue(attributes.get("label").getType().equals(AttributeType.STRING)); assertTrue(attributes.get("label").getValue().equals("Label")); assertTrue(attributes.get("int").getType().equals(AttributeType.INT)); assertTrue(attributes.get("int").getValue().equals("4")); assertTrue(attributes.get("double").getType().equals(AttributeType.DOUBLE)); assertTrue(attributes.get("double").getValue().equals("0.5")); assertTrue(attributes.get("boolean").getType().equals(AttributeType.BOOLEAN)); assertTrue(attributes.get("boolean").getValue().equals("true")); assertTrue(attributes.get("boolean1").getType().equals(AttributeType.BOOLEAN)); assertTrue(attributes.get("boolean1").getValue().equals("false")); assertTrue(attributes.get("novalue").getType().equals(AttributeType.NULL)); assertTrue(attributes.get("novalue").getValue().equals("null")); } @Test public void testEdgeAttributes() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"1\" }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\": \"1\", \"label\": \"Label\", \"int\": 4, \"double\": 0.5, \"boolean\": true, \"boolean1\": false, \"novalue\": null }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); Map> edgeAttributes = new HashMap<>(); importer.addEdgeAttributeConsumer((p, a) -> { Map attrs = edgeAttributes.get(p.getFirst()); if (attrs == null) { attrs = new HashMap<>(); edgeAttributes.put(p.getFirst(), attrs); } attrs.put(p.getSecond(), a); }); importer.importGraph(g, new StringReader(input)); assertEquals(1, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); assertTrue(g.containsVertex("1")); DefaultEdge edge = g.getEdge("1", "1"); assertNotNull(edge); Map attributes = edgeAttributes.get(edge); assertNotNull(attributes); assertTrue(attributes.get("label").getType().equals(AttributeType.STRING)); assertTrue(attributes.get("label").getValue().equals("Label")); assertTrue(attributes.get("int").getType().equals(AttributeType.INT)); assertTrue(attributes.get("int").getValue().equals("4")); assertTrue(attributes.get("double").getType().equals(AttributeType.DOUBLE)); assertTrue(attributes.get("double").getValue().equals("0.5")); assertTrue(attributes.get("boolean").getType().equals(AttributeType.BOOLEAN)); assertTrue(attributes.get("boolean").getValue().equals("true")); assertTrue(attributes.get("boolean1").getType().equals(AttributeType.BOOLEAN)); assertTrue(attributes.get("boolean1").getValue().equals("false")); assertTrue(attributes.get("novalue").getType().equals(AttributeType.NULL)); assertTrue(attributes.get("novalue").getValue().equals("null")); } @Test public void testNestedAttributes() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"1\", \"custom\": { \"pi\": 3.14 } },\n" + " { \"id\":\"2\", \"array\": [ { \"obj\": 3.14 } ] }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\": \"2\", \"array\": [ { \"key1\": 1 }, { \"key2\": 2 } ] },\n" + " { \"source\":\"2\", \"target\": \"1\", \"obj\": { \"key1\": [ { \"key1\": 1 }, { \"key2\": 2 } ] } }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(false) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); Map> vertexAttributes = new HashMap<>(); importer.addVertexAttributeConsumer((p, a) -> { Map attrs = vertexAttributes.get(p.getFirst()); if (attrs == null) { attrs = new HashMap<>(); vertexAttributes.put(p.getFirst(), attrs); } attrs.put(p.getSecond(), a); }); Map> edgeAttributes = new HashMap<>(); importer.addEdgeAttributeConsumer((p, a) -> { Map attrs = edgeAttributes.get(p.getFirst()); if (attrs == null) { attrs = new HashMap<>(); edgeAttributes.put(p.getFirst(), attrs); } attrs.put(p.getSecond(), a); }); importer.importGraph(g, new StringReader(input)); assertEquals(2, g.vertexSet().size()); assertEquals(2, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); Map attributes = vertexAttributes.get("1"); assertNotNull(attributes); assertTrue(attributes.get("custom").getType().equals(AttributeType.UNKNOWN)); assertTrue(attributes.get("custom").getValue().equals("{\"pi\":3.14}")); attributes = vertexAttributes.get("2"); assertNotNull(attributes); assertTrue(attributes.get("array").getType().equals(AttributeType.UNKNOWN)); assertTrue(attributes.get("array").getValue().equals("[{\"obj\":3.14}]")); DefaultEdge edge = g.getEdge("1", "2"); assertNotNull(edge); attributes = edgeAttributes.get(edge); assertNotNull(attributes); assertTrue(attributes.get("array").getType().equals(AttributeType.UNKNOWN)); assertTrue(attributes.get("array").getValue().equals("[{\"key1\":1},{\"key2\":2}]")); edge = g.getEdge("2", "1"); assertNotNull(edge); attributes = edgeAttributes.get(edge); assertNotNull(attributes); assertTrue(attributes.get("obj").getType().equals(AttributeType.UNKNOWN)); assertTrue( attributes.get("obj").getValue().equals("{\"key1\":[{\"key1\":1},{\"key2\":2}]}")); } @Test public void testSingletons() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"1\" },\n" + " { \"id\":\"2\" },\n" + " { },\n" + " { }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\":\"2\" }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createStringSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(g, new StringReader(input)); assertEquals(4, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); } @Test public void testNegativeIntegerWeights() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"1\" },\n" + " { \"id\":\"2\" }\n" + " ],\n" + " \"edges\": [\n" + " { \"source\":\"1\", \"target\":\"2\", \"weight\": -2 }\n" + " ]\n" + "}"; // @formatter:on Graph g = GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true).weighted(true) .vertexSupplier(SupplierUtil.createStringSupplier(1)) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph(); JSONImporter importer = new JSONImporter<>(); importer.importGraph(g, new StringReader(input)); assertEquals(2, g.vertexSet().size()); assertEquals(1, g.edgeSet().size()); assertTrue(g.containsVertex("1")); assertTrue(g.containsVertex("2")); assertEquals(-2.0, g.getEdgeWeight(g.getEdge("1", "2")), 1e-9); } @Test public void testCreateVerticesWithAttributes() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"a0\", \"color\":\"gray\" },\n" + " { \"id\":\"a1\", \"color\":\"green\" },\n" + " { \"id\":\"a2\", \"color\":\"white\" }\n" + " ]," + " \"edges\": [\n" + " { \"source\":\"a0\", \"target\":\"a1\" },\n" + " { \"source\":\"a0\", \"target\":\"a2\" }\n" + " ]\n" + "}"; // @formatter:on JSONImporter importer = new JSONImporter<>(); importer.setVertexWithAttributesFactory((id, attrs) -> { return id + "-" + attrs.get("color").getValue(); }); DirectedPseudograph graph = new DirectedPseudograph<>( SupplierUtil.createStringSupplier(), SupplierUtil.DEFAULT_EDGE_SUPPLIER, false); importer.importGraph(graph, new StringReader(input)); assertTrue(graph.containsVertex("a0-gray")); assertTrue(graph.containsVertex("a1-green")); assertTrue(graph.containsVertex("a2-white")); } @Test public void testCreateEdgesWithAttributes() throws ImportException { // @formatter:off String input = "{\n" + " \"nodes\": [\n" + " { \"id\":\"a0\", \"color\":\"gray\" },\n" + " { \"id\":\"a1\", \"color\":\"green\" },\n" + " { \"id\":\"a2\", \"color\":\"white\" }\n" + " ]," + " \"edges\": [\n" + " { \"source\":\"a0\", \"target\":\"a1\", \"label\":\"e1\" },\n" + " { \"source\":\"a0\", \"target\":\"a2\", \"label\":\"e2\" }\n" + " ]\n" + "}"; // @formatter:on JSONImporter importer = new JSONImporter<>(); importer.setVertexWithAttributesFactory((id, attrs) -> { return id + "-" + attrs.get("color").getValue(); }); importer.setEdgeWithAttributesFactory((attrs) -> { return attrs.get("label").getValue(); }); DirectedPseudograph graph = new DirectedPseudograph<>(SupplierUtil.createStringSupplier(), null, false); importer.importGraph(graph, new StringReader(input)); assertTrue(graph.containsEdge("e1")); assertTrue(graph.containsEdge("e2")); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/lemon/000077500000000000000000000000001402514743400254765ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/lemon/LemonExporterTest.java000066400000000000000000000154441402514743400320140ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.lemon; import org.jgrapht.*; import org.jgrapht.graph.*; import org.junit.*; import java.io.*; import static org.junit.Assert.*; /** * Tests for {@link LemonExporter} * * @author Dimitrios Michail */ public class LemonExporterTest { private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String NL = System.getProperty("line.separator"); // @formatter:off private static final String UNDIRECTED = "#Creator: JGraphT Lemon (LGF) Exporter" + NL + "#Version: 1" + NL + NL + "@nodes" + NL + "label" + NL + "1" + NL + "2" + NL + "3" + NL + NL + "@arcs" + NL + "\t\t-" + NL + "1\t2" + NL + "3\t1" + NL + NL; private static final String UNDIRECTED_DEFAULT_WEIGHTS = "#Creator: JGraphT Lemon (LGF) Exporter" + NL + "#Version: 1" + NL + NL + "@nodes" + NL + "label" + NL + "1" + NL + "2" + NL + "3" + NL + NL + "@arcs" + NL + "\t\tweight" + NL + "1\t2\t1.0" + NL + "3\t1\t1.0" + NL + NL; private static final String UNDIRECTED_WEIGHTED = "#Creator: JGraphT Lemon (LGF) Exporter" + NL + "#Version: 1" + NL + NL + "@nodes" + NL + "label" + NL + "1" + NL + "2" + NL + "3" + NL + NL + "@arcs" + NL + "\t\tweight" + NL + "1\t2\t2.0" + NL + "3\t1\t5.0" + NL + NL; private static final String UNDIRECTED_WITH_ESCAPE = "#Creator: JGraphT Lemon (LGF) Exporter" + NL + "#Version: 1" + NL + NL + "@nodes" + NL + "label" + NL + "\"1\"" + NL + "\"2\"" + NL + "\"3\"" + NL + NL + "@arcs" + NL + "\t\t-" + NL + "\"1\"\t\"2\"" + NL + "\"3\"\t\"1\"" + NL + NL; @Test public void testUndirected() throws UnsupportedEncodingException { Graph g = new SimpleGraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); LemonExporter exporter = new LemonExporter(); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED, res); } @Test public void testUnweightedUndirected() throws UnsupportedEncodingException { Graph g = new SimpleGraph<>(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); LemonExporter exporter = new LemonExporter<>(); exporter.setParameter(LemonExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED_DEFAULT_WEIGHTS, res); } @Test public void testWeightedUndirected() throws UnsupportedEncodingException { SimpleGraph g = new SimpleWeightedGraph(DefaultWeightedEdge.class); g.addVertex(V1); g.addVertex(V2); g.addVertex(V3); DefaultWeightedEdge e1 = g.addEdge(V1, V2); g.setEdgeWeight(e1, 2.0); DefaultWeightedEdge e2 = g.addEdge(V3, V1); g.setEdgeWeight(e2, 5.0); LemonExporter exporter = new LemonExporter<>(); exporter.setParameter(LemonExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED_WEIGHTED, res); } @Test public void testUndirectedWithEscape() throws UnsupportedEncodingException { Graph g = new SimpleGraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); LemonExporter exporter = new LemonExporter(); exporter.setParameter(LemonExporter.Parameter.ESCAPE_STRINGS_AS_JAVA, true); ByteArrayOutputStream os = new ByteArrayOutputStream(); exporter.exportGraph(g, os); String res = new String(os.toByteArray(), "UTF-8"); assertEquals(UNDIRECTED_WITH_ESCAPE, res); } @Test public void testParameters() { LemonExporter exporter = new LemonExporter(); assertFalse(exporter.isParameter(LemonExporter.Parameter.EXPORT_EDGE_WEIGHTS)); exporter.setParameter(LemonExporter.Parameter.EXPORT_EDGE_WEIGHTS, true); assertTrue(exporter.isParameter(LemonExporter.Parameter.EXPORT_EDGE_WEIGHTS)); exporter.setParameter(LemonExporter.Parameter.EXPORT_EDGE_WEIGHTS, false); assertFalse(exporter.isParameter(LemonExporter.Parameter.EXPORT_EDGE_WEIGHTS)); assertFalse(exporter.isParameter(LemonExporter.Parameter.ESCAPE_STRINGS_AS_JAVA)); exporter.setParameter(LemonExporter.Parameter.ESCAPE_STRINGS_AS_JAVA, true); assertTrue(exporter.isParameter(LemonExporter.Parameter.ESCAPE_STRINGS_AS_JAVA)); exporter.setParameter(LemonExporter.Parameter.ESCAPE_STRINGS_AS_JAVA, false); assertFalse(exporter.isParameter(LemonExporter.Parameter.ESCAPE_STRINGS_AS_JAVA)); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/matrix/000077500000000000000000000000001402514743400256705ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/matrix/MatrixExporterTest.java000066400000000000000000000074721402514743400324020ustar00rootroot00000000000000/* * (C) Copyright 2003-2021, by Charles Fry and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.matrix; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.nio.*; import org.junit.*; import java.io.*; import static org.junit.Assert.assertEquals; /** * Tests * * @author Charles Fry */ public class MatrixExporterTest { // ~ Static fields/initializers --------------------------------------------- private static final String V1 = "v1"; private static final String V2 = "v2"; private static final String V3 = "v3"; private static final String NL = System.getProperty("line.separator"); private static final String LAPLACIAN = "1 1 2" + NL + "1 2 -1" + NL + "1 3 -1" + NL + "2 2 1" + NL + "2 1 -1" + NL + "3 3 1" + NL + "3 1 -1" + NL; private static final String NORMALIZED_LAPLACIAN = "1 1 1" + NL + "1 2 -0.7071067811865475" + NL + "1 3 -0.7071067811865475" + NL + "2 2 1" + NL + "2 1 -0.7071067811865475" + NL + "3 3 1" + NL + "3 1 -0.7071067811865475" + NL; private static final String UNDIRECTED_ADJACENCY = "1 2 1" + NL + "1 3 1" + NL + "1 1 2" + NL + "2 1 1" + NL + "3 1 1" + NL; private static final String DIRECTED_ADJACENCY = "1 2 1" + NL + "3 1 2" + NL; // ~ Methods ---------------------------------------------------------------- @Test public void testLaplacian() { Graph g = new SimpleGraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); GraphExporter exporter1 = new MatrixExporter<>(MatrixExporter.Format.SPARSE_LAPLACIAN_MATRIX); StringWriter w1 = new StringWriter(); exporter1.exportGraph(g, w1); assertEquals(LAPLACIAN, w1.toString()); GraphExporter exporter2 = new MatrixExporter<>(MatrixExporter.Format.SPARSE_NORMALIZED_LAPLACIAN_MATRIX); StringWriter w2 = new StringWriter(); exporter2.exportGraph(g, w2); assertEquals(NORMALIZED_LAPLACIAN, w2.toString()); } @Test public void testAdjacencyUndirected() throws ExportException { Graph g = new Pseudograph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); g.addEdge(V1, V1); GraphExporter exporter = new MatrixExporter<>(); StringWriter w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(UNDIRECTED_ADJACENCY, w.toString()); } @Test public void testAdjacencyDirected() throws ExportException { Graph g = new DirectedMultigraph(DefaultEdge.class); g.addVertex(V1); g.addVertex(V2); g.addEdge(V1, V2); g.addVertex(V3); g.addEdge(V3, V1); g.addEdge(V3, V1); GraphExporter exporter = new MatrixExporter<>(); Writer w = new StringWriter(); exporter.exportGraph(g, w); assertEquals(DIRECTED_ADJACENCY, w.toString()); } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/tsplib/000077500000000000000000000000001402514743400256615ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/java/org/jgrapht/nio/tsplib/TSPLIBImporterTest.java000066400000000000000000000757071402514743400321230ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Hannes Wellmann and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.nio.tsplib; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.jgrapht.graph.*; import org.jgrapht.nio.*; import org.jgrapht.nio.tsplib.TSPLIBImporter.Node; import org.jgrapht.nio.tsplib.TSPLIBImporter.*; import org.junit.*; import java.io.*; import java.text.*; import java.util.*; import java.util.stream.*; import static org.junit.Assert.*; public class TSPLIBImporterTest { private static class TestVector { private final int index; private final double[] elements; public TestVector(int index, double... elements) { this.index = index; this.elements = elements; } public int getIndex() { return index; } public double[] getElementValues() { return Arrays.copyOf(elements, elements.length); } private static DecimalFormat indexFormat = new DecimalFormat("0000"); private static DecimalFormat coordinateFormat = new DecimalFormat("0.00", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); @Override public String toString() { String indexStr = index >= 0 ? indexFormat.format(index) + " " : ""; return indexStr + Arrays .stream(elements).mapToObj(coordinateFormat::format) .collect(Collectors.joining(" ")); } } private static StringJoiner get3DPointsFileContent(String edgeWeightType) { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("TYPE : TSP"); fileContent.add("DIMENSION : 4"); fileContent.add("EDGE_WEIGHT_TYPE : " + edgeWeightType); fileContent.add("NODE_COORD_SECTION"); fileContent.add("1 10.0 15.0 3.7"); fileContent.add("2 14.0 15.0 3.7"); fileContent.add("3 14.0 20.0 3.7"); fileContent.add("4 14.0 20.0 3.7"); return fileContent; } private static List getExpected3DPoints() { return Arrays .asList( new TestVector(1, 10.0, 15.0, 3.7), new TestVector(2, 14.0, 15.0, 3.7), new TestVector(3, 14.0, 20.0, 3.7), new TestVector(4, 14.0, 20.0, 3.7)); } private static StringJoiner get2DPointsFileContent(String edgeWeightType) { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("TYPE : TSP"); fileContent.add("DIMENSION : 4"); fileContent.add("EDGE_WEIGHT_TYPE : " + edgeWeightType); fileContent.add("NODE_COORD_SECTION"); fileContent.add("1 10.2 15.0"); fileContent.add("2 14.2 15.0"); fileContent.add("3 14.8 20.0"); fileContent.add("4 10.8 20.0"); fileContent.add("EOF"); return fileContent; } private static List getExpected2DPoints() { return Arrays .asList( new TestVector(1, 10.2, 15.0), new TestVector(2, 14.2, 15.0), new TestVector(3, 14.8, 20.0), new TestVector(4, 10.8, 20.0)); } // ---------------------------------------------------------------------- // tests @Test public void testMetaDataValues() { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("NAME : theNameOfThisFile"); fileContent.add("COMMENT : The first line of the comment"); fileContent.add("COMMENT : A second line"); fileContent.add("TYPE : TSP"); fileContent.add("DIMENSION : 4"); fileContent.add("EDGE_WEIGHT_TYPE : EUC_2D"); fileContent.add("NODE_COORD_TYPE: THREED_COORDS"); fileContent.add("CAPACITY : 7"); fileContent.add("EDGE_WEIGHT_FORMAT: FULL_MATRIX"); fileContent.add("EDGE_DATA_FORMAT: ADJ_LIST"); fileContent.add("DISPLAY_DATA_TYPE : TWOD_DISPLAY"); fileContent.add("NODE_COORD_SECTION"); fileContent.add("1 10.2 15.0"); fileContent.add("EOF"); Metadata metaData = importGraphFromFile(fileContent).getSecond(); Specification spec = metaData.getSpecification(); assertEquals("theNameOfThisFile", spec.getName()); assertEquals("TSP", spec.getType()); assertEquals( Arrays.asList("The first line of the comment", "A second line"), spec.getComments()); assertEquals(Integer.valueOf(4), spec.getDimension()); assertEquals(Integer.valueOf(7), spec.getCapacity()); assertEquals("EUC_2D", spec.getEdgeWeightType()); assertEquals("FULL_MATRIX", spec.getEdgeWeightFormat()); assertEquals("ADJ_LIST", spec.getEdgeDataFormat()); assertEquals("THREED_COORDS", spec.getNodeCoordType()); assertEquals("TWOD_DISPLAY", spec.getDisplayDataType()); assertTrue(metaData.hasDistinctNodeLocations()); assertTrue(metaData.hasDistinctNeighborDistances()); } @Test public void testImportGraph_withNodeCoordSection() { StringJoiner fileContent = get2DPointsFileContent("EUC_2D"); Graph expectedGraph = getExpectedGraph(getExpected2DPoints()); Pair, Metadata> importFile = importGraphFromFile(fileContent); Graph graph = importFile.getFirst(); Metadata metaData = importFile.getSecond(); assertGraphVertexNodes(expectedGraph, graph, metaData); assertTrue(metaData.hasDistinctNodeLocations()); assertTrue(metaData.hasDistinctNeighborDistances()); } @Test public void testImportGraph_withNodeCoordSectionAndTourSection() { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("TYPE : TSP"); fileContent.add("DIMENSION : 4"); fileContent.add("EDGE_WEIGHT_TYPE : EUC_2D"); fileContent.add("NODE_COORD_SECTION"); fileContent.add("7 10.2 15.0"); fileContent.add("2 14.2 15.0"); fileContent.add("9 14.8 20.0"); fileContent.add("4 10.8 20.0"); fileContent.add("TOUR_SECTION"); fileContent.add("9"); fileContent.add("4"); fileContent.add("7"); fileContent.add("2"); fileContent.add("-1"); fileContent.add("EOF"); List expectedVectors = new ArrayList<>(); expectedVectors.add(new TestVector(7, 10.2, 15.0)); expectedVectors.add(new TestVector(2, 14.2, 15.0)); expectedVectors.add(new TestVector(9, 14.8, 20.0)); expectedVectors.add(new TestVector(4, 10.8, 20.0)); Graph expectedGraph = getExpectedGraph(expectedVectors); List expectedTour = Arrays.asList(9, 4, 7, 2); Pair, Metadata> importData = importGraphFromFile(fileContent); Graph graph = importData.getFirst(); Metadata metaData = importData.getSecond(); assertGraphVertexNodes(expectedGraph, graph, metaData); assertTrue(metaData.hasDistinctNodeLocations()); assertTrue(metaData.hasDistinctNeighborDistances()); assertTour(metaData.getTour(), expectedTour, metaData.getVertexToNodeMapping()); } @Test public void testImportTour_onlyWithTourSection() { StringJoiner otherFileContent = new StringJoiner(System.lineSeparator()); otherFileContent.add("DIMENSION : 4"); otherFileContent.add("EDGE_WEIGHT_TYPE : EUC_2D"); otherFileContent.add("NODE_COORD_SECTION"); otherFileContent.add("7 10.2 15.0"); otherFileContent.add("2 14.2 15.0"); otherFileContent.add("9 14.8 20.0"); otherFileContent.add("4 10.8 20.0"); Metadata otherFilesMetaData = importGraphFromFile(otherFileContent).getSecond(); StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("TYPE : TSP"); fileContent.add("DIMENSION : 4"); fileContent.add("EDGE_WEIGHT_TYPE : EUC_2D"); fileContent.add("TOUR_SECTION"); fileContent.add("9"); fileContent.add("4"); fileContent.add("7"); fileContent.add("2"); fileContent.add("-1"); fileContent.add("EOF"); List expectedTour = Arrays.asList(9, 4, 7, 2); TSPLIBImporter importer = new TSPLIBImporter<>(); StringReader reader = new StringReader(fileContent.toString()); List tour = importer.importTour(otherFilesMetaData, reader); assertTour(tour, expectedTour, otherFilesMetaData.getVertexToNodeMapping()); } private static < T> void assertTour(List vertexTour, List expectedTour, Map vertex2node) { List integerTour = vertexTour .stream().map(vertex2node::get).map(Node::getNumber).collect(Collectors.toList()); assertEquals(expectedTour, integerTour); } // edge weight functions tests @Test public void testImportGraph_EdgeWeightTypeEUC2D() { List vertices = getExpected2DPoints(); Graph expectedGraph = getExpectedGraph(vertices); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(1), 4.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(2), 7.); // round sqrt(46.16) Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(3), 5.); // round sqrt(25.36) Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(2), 5.); // round sqrt(25.36) Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(3), 6.); // round sqrt(36.56) Graphs.addEdge(expectedGraph, vertices.get(2), vertices.get(3), 4.); Pair, Metadata> graphData = importGraphFromFile(get2DPointsFileContent("EUC_2D")); // Check coordinates, weights and complete connection assertEqualGraphData(expectedGraph, graphData); assertTrue(graphData.getSecond().hasDistinctNodeLocations()); assertTrue(graphData.getSecond().hasDistinctNeighborDistances()); } @Test public void testImportGraph_EdgeWeightTypeEUC3D() { List vertices = getExpected3DPoints(); Graph expectedGraph = getExpectedGraph(vertices); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(1), 4.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(2), 6.); // round sqrt(41) Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(3), 6.); // round sqrt(41) Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(2), 5.); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(3), 5.); Graphs.addEdge(expectedGraph, vertices.get(2), vertices.get(3), 0.); Pair, Metadata> graphData = importGraphFromFile(get3DPointsFileContent("EUC_3D")); // Check coordinates, weights and complete connection assertEqualGraphData(expectedGraph, graphData); assertFalse(graphData.getSecond().hasDistinctNodeLocations()); assertFalse(graphData.getSecond().hasDistinctNeighborDistances()); } @Test public void testImportGraph_EdgeWeightTypeMAX2D() { List vertices = getExpected2DPoints(); Graph expectedGraph = getExpectedGraph(vertices); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(1), 4.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(2), 5.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(3), 5.); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(2), 5.); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(3), 5.); Graphs.addEdge(expectedGraph, vertices.get(2), vertices.get(3), 4.); Pair, Metadata> graphData = importGraphFromFile(get2DPointsFileContent("MAX_2D")); // Check coordinates, weights and complete connection assertEqualGraphData(expectedGraph, graphData); assertTrue(graphData.getSecond().hasDistinctNodeLocations()); assertFalse(graphData.getSecond().hasDistinctNeighborDistances()); } @Test public void testImportGraph_EdgeWeightTypeMAX3D() { List vertices = getExpected3DPoints(); Graph expectedGraph = getExpectedGraph(vertices); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(1), 4.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(2), 5.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(3), 5.); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(2), 5.); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(3), 5.); Graphs.addEdge(expectedGraph, vertices.get(2), vertices.get(3), 0.); Pair, Metadata> graphData = importGraphFromFile(get3DPointsFileContent("MAX_3D")); // Check coordinates, weights and complete connection assertEqualGraphData(expectedGraph, graphData); assertFalse(graphData.getSecond().hasDistinctNodeLocations()); assertFalse(graphData.getSecond().hasDistinctNeighborDistances()); } @Test public void testImportGraph_EdgeWeightTypeMAN2D() { List vertices = getExpected2DPoints(); Graph expectedGraph = getExpectedGraph(vertices); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(1), 4.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(2), 10.0); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(3), 6.); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(2), 6.); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(3), 8.); Graphs.addEdge(expectedGraph, vertices.get(2), vertices.get(3), 4.); Pair, Metadata> graphData = importGraphFromFile(get2DPointsFileContent("MAN_2D")); // Check coordinates, weights and complete connection assertEqualGraphData(expectedGraph, graphData); assertTrue(graphData.getSecond().hasDistinctNodeLocations()); assertTrue(graphData.getSecond().hasDistinctNeighborDistances()); } @Test public void testImportGraph_EdgeWeightTypeMAN3D() { List vertices = getExpected3DPoints(); Graph expectedGraph = getExpectedGraph(vertices); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(1), 4.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(2), 9.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(3), 9.); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(2), 5.); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(3), 5.); Graphs.addEdge(expectedGraph, vertices.get(2), vertices.get(3), 0.); Pair, Metadata> graphData = importGraphFromFile(get3DPointsFileContent("MAN_3D")); // Check coordinates, weights and complete connection assertEqualGraphData(expectedGraph, graphData); assertFalse(graphData.getSecond().hasDistinctNodeLocations()); assertFalse(graphData.getSecond().hasDistinctNeighborDistances()); } @Test public void testImportGraph_EdgeWeightTypeCEIL2D() { List vertices = getExpected2DPoints(); Graph expectedGraph = getExpectedGraph(vertices); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(1), 4.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(2), 7.); // round sqrt(46.16) Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(3), 6.); // round sqrt(25.36) Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(2), 6.); // round sqrt(25.36) Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(3), 7.); // round sqrt(36.56) Graphs.addEdge(expectedGraph, vertices.get(2), vertices.get(3), 4.); Pair, Metadata> graphData = importGraphFromFile(get2DPointsFileContent("CEIL_2D")); // Check coordinates, weights and complete connection assertEqualGraphData(expectedGraph, graphData); assertTrue(graphData.getSecond().hasDistinctNodeLocations()); assertTrue(graphData.getSecond().hasDistinctNeighborDistances()); } @Test public void testImportGraph_EdgeWeightTypeGEO() { List vertices = getExpected2DPoints(); Graph expectedGraph = getExpectedGraph(vertices); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(1), 446); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(2), 727); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(3), 549); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(2), 541); Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(3), 680); Graphs.addEdge(expectedGraph, vertices.get(2), vertices.get(3), 446); Pair, Metadata> graphData = importGraphFromFile(get2DPointsFileContent("GEO")); // Check coordinates, weights and complete connection assertEqualGraphData(expectedGraph, graphData); assertTrue(graphData.getSecond().hasDistinctNodeLocations()); assertTrue(graphData.getSecond().hasDistinctNeighborDistances()); } @Test public void testCompute2DGeographicalDistance() { TSPLIBImporter importer = new TSPLIBImporter<>(); int halfCircleCircumfence = (int) (TSPLIBImporter.PI * TSPLIBImporter.RRR); int quarterCircleCircumfence = (int) (TSPLIBImporter.PI * TSPLIBImporter.RRR / 2); int d0 = importer.compute2DGeographicalDistance(node(0.0, 0.0), node(0.0, 90.0)); assertEquals(quarterCircleCircumfence, d0, 1.0); int d1 = importer.compute2DGeographicalDistance(node(23.0, 15.0), node(-23.0, 105.0)); assertEquals(10997, d1, 1.0); int d2 = importer.compute2DGeographicalDistance(node(0.0, -90.2), node(0.0, 89.8)); assertEquals(halfCircleCircumfence, d2, 1.0); int d3 = importer.compute2DGeographicalDistance(node(20.0, -90.7), node(-20.0, 89.3)); assertEquals(halfCircleCircumfence, d3, 1.0); int d4 = importer.compute2DGeographicalDistance(node(20.0, -70.0), node(-20.0, 110.0)); assertEquals(halfCircleCircumfence, d4, 1.0); int d5 = importer.compute2DGeographicalDistance(node(40.48, -74.0), node(52.3, 13.24)); assertEquals(6386, d5, 1.0); int d6 = importer.compute2DGeographicalDistance(node(1.48, 113.24), node(-6.36, -65.24)); assertEquals(19488, d6, 1.0); } private static Node node(double... elements) { return new Node(-1, elements); } @Test public void testImportGraph_EdgeWeightTypeATT() { List vertices = getExpected2DPoints(); Graph expectedGraph = getExpectedGraph(vertices); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(1), 2.); Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(2), 3.); // round sqrt(46.16) Graphs.addEdge(expectedGraph, vertices.get(0), vertices.get(3), 2.); // round sqrt(25.36) Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(2), 2.); // round sqrt(25.36) Graphs.addEdge(expectedGraph, vertices.get(1), vertices.get(3), 2.); // round sqrt(36.56) Graphs.addEdge(expectedGraph, vertices.get(2), vertices.get(3), 2.); Pair, Metadata> graphData = importGraphFromFile(get2DPointsFileContent("ATT")); assertEqualGraphData(expectedGraph, graphData); assertTrue(graphData.getSecond().hasDistinctNodeLocations()); assertFalse(graphData.getSecond().hasDistinctNeighborDistances()); } // exception tests @Test public void testImportGraph_ProvideNotWeightedGraph_ImportException() { Graph graph = new SimpleGraph<>(null, DefaultWeightedEdge::new, false); RuntimeException expectedCause = new IllegalArgumentException("Graph must be weighted"); TSPLIBImporter importer = new TSPLIBImporter<>(); expectGraphImportFailedException( () -> importer.importGraph(graph, new StringReader("")), expectedCause); } @Test public void testImportGraph_MissingValue_ImportException() { String fileContent = "NAME : "; RuntimeException expectedCause = new IllegalStateException("Missing value for key NAME"); expectGraphImportFailedException(() -> importGraphFromFile(fileContent), expectedCause); } @Test public void testImportGraph_MultipleValues_ImportException() { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("TYPE : TSP"); fileContent.add("TYPE : TSP"); RuntimeException expectedCause = new IllegalStateException("Multiple values for key TYPE"); expectGraphImportFailedException(() -> importGraphFromFile(fileContent), expectedCause); } @Test public void testImportGraph_invalidSpecificationValue_ImportException() { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("EDGE_WEIGHT_FORMAT : some String"); RuntimeException expectedCause = new IllegalArgumentException("Invalid EDGE_WEIGHT_FORMAT value "); expectGraphImportFailedException(() -> importGraphFromFile(fileContent), expectedCause); } @Test public void testImportGraph_nonNumberDimension_ImportException() { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("DIMENSION : A_STRING"); RuntimeException expectedCause = new IllegalArgumentException("Invalid DIMENSION integer value "); expectGraphImportFailedException(() -> importGraphFromFile(fileContent), expectedCause); } @Test public void testImportGraph_NotSupportedEdgeWeightType_ImportException() { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("EDGE_WEIGHT_TYPE : XRAY1"); fileContent.add("DIMENSION:1"); fileContent.add("NODE_COORD_SECTION"); fileContent.add("1 10.2 15.0"); RuntimeException expectedCause = new IllegalStateException("Unsupported EDGE_WEIGHT_TYPE "); expectGraphImportFailedException(() -> importGraphFromFile(fileContent), expectedCause); } @Test public void testImportGraph_OnlyNodeCoordSection_ImportException() { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("NODE_COORD_SECTION"); fileContent.add("1 10.2 15.0"); fileContent.add("2 14.2 15.0"); RuntimeException expectedCause = new IllegalStateException("Missing data to read "); expectGraphImportFailedException(() -> importGraphFromFile(fileContent), expectedCause); } @Test public void testImportGraph_WrongNodeCoordinateElementCount_ImportException() { StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("EDGE_WEIGHT_TYPE : EUC_3D"); fileContent.add("DIMENSION: 1"); fileContent.add("NODE_COORD_SECTION"); fileContent.add("1 10.2 15.0"); RuntimeException expectedCause = new IllegalArgumentException("Unexpected number of elements <3> in line: 1 10.2 15.0"); expectGraphImportFailedException(() -> importGraphFromFile(fileContent), expectedCause); } @Test public void testImportTour_missingVertexInTour_ImportException() { Metadata otherMetaData = importGraphFromFile(get2DPointsFileContent("EUC_2D")).getSecond(); StringJoiner fileContent = new StringJoiner(System.lineSeparator()); fileContent.add("DIMENSION: 1"); fileContent.add("TOUR_SECTION"); fileContent.add("8"); fileContent.add("-1"); RuntimeException expectedCause = new IllegalStateException("Missing vertex with number 8"); TSPLIBImporter importer = new TSPLIBImporter<>(); expectTourImportFailedException( () -> importer.importTour(otherMetaData, new StringReader(fileContent.toString())), expectedCause); } // utility methods private static Pair, Metadata> importGraphFromFile(StringJoiner fileContent) { return importGraphFromFile(fileContent.toString()); } private static Pair, Metadata> importGraphFromFile(String fileContent) { Graph graph = new SimpleWeightedGraph<>(Object::new, DefaultWeightedEdge::new); TSPLIBImporter importer = new TSPLIBImporter<>(); importer.importGraph(graph, new StringReader(fileContent)); return Pair.of(graph, importer.getMetadata()); } private static Graph getExpectedGraph( List vertices) { Graph expectedGraph = new SimpleWeightedGraph<>(null, DefaultWeightedEdge::new); Graphs.addAllVertices(expectedGraph, vertices); return expectedGraph; } // assertions /** Check coordinates, weights and complete connection. */ private static void assertEqualGraphData( Graph expectedGraph, Pair, Metadata> actualData) { Graph graph = actualData.getFirst(); Metadata metadata = actualData.getSecond(); // assert if the read numbers are as expected assertGraphVertexNodes(expectedGraph, graph, metadata); // assert if the computed edge weights/ distances are as expected Set expectedEdgeSet = expectedGraph.edgeSet(); assertEquals("Unequal edgeSet size", expectedEdgeSet.size(), graph.edgeSet().size()); Map number2vertex = new HashMap<>(); metadata.getVertexToNodeMapping().forEach((v, n) -> number2vertex.put(n.getNumber(), v)); for (DefaultWeightedEdge expectedEdge : expectedEdgeSet) { int sourceNumber = expectedGraph.getEdgeSource(expectedEdge).getIndex(); int targetNumber = expectedGraph.getEdgeTarget(expectedEdge).getIndex(); V source = number2vertex.get(sourceNumber); V target = number2vertex.get(targetNumber); DefaultWeightedEdge actualEdge = graph.getEdge(source, target); assertTrue(actualEdge != null); assertEquals( expectedGraph.getEdgeWeight(expectedEdge), graph.getEdgeWeight(actualEdge), 1e-5); } } private static void assertGraphVertexNodes( Graph expectedGraph, Graph graph, Metadata metadata) { Map vertex2node = metadata.getVertexToNodeMapping(); List sortedVertexNodes = graph.vertexSet().stream().map(vertex2node::get).collect(Collectors.toList()); sortedVertexNodes.sort((n1, n2) -> Integer.compare(n1.getNumber(), n2.getNumber())); List expectedSortedVectors = new ArrayList<>(expectedGraph.vertexSet()); expectedSortedVectors.sort((v1, v2) -> Integer.compare(v1.getIndex(), v2.getIndex())); // assert if the read coordinates are as expected assertEquals(expectedSortedVectors.size(), sortedVertexNodes.size()); for (int i = 0; i < expectedSortedVectors.size(); i++) { TestVector expectedVector = expectedSortedVectors.get(i); Node actualNode = sortedVertexNodes.get(i); assertEquals(expectedVector.getIndex(), actualNode.getNumber()); assertEqualElements(expectedVector.getElementValues(), actualNode.getCoordinates()); } } private static void assertEqualElements(double[] expected, double[] actual) { if (!Arrays.equals(actual, expected)) { fail( "Expected is " + Arrays.toString(expected) + " but was " + Arrays.toString(actual)); } } private void expectGraphImportFailedException(Runnable action, RuntimeException expectedCause) { expectImportFailedException(action, expectedCause, "graph"); } private void expectTourImportFailedException(Runnable action, RuntimeException expectedCause) { expectImportFailedException(action, expectedCause, "tour"); } private void expectImportFailedException( Runnable action, RuntimeException expectedCause, String importTarget) { try { action.run(); fail("Expected exception: " + ImportException.class.getName()); } catch (ImportException e) { Throwable cause = e.getCause(); assertEquals( "Failed to import " + importTarget + " from TSPLIB-file: " + cause.getMessage(), e.getMessage()); assertEquals(expectedCause.getClass(), cause.getClass()); assertEquals(expectedCause.getMessage(), cause.getMessage()); } } } jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/resources/000077500000000000000000000000001402514743400224425ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/resources/ellinghamHorton78Graph.s6000066400000000000000000000002701402514743400272060ustar00rootroot00000000000000>>sparse6<<:~?@M__EC?GEA_wQD`g]DAGOH`oiEAwqLbg}?CGCP_`IBCxCSc@URDhGV_ocXaG?IEgkZfXuWgiA^GQMaHIEhHA]eII[igAabIYaoJAuqJi}pizIrlJUrLjGvlRasMZiznJumNi{~kSAoOZ|AncN@PK@DkRXEls]wQCmnMSf~~~~~jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/resources/myciel3.col000066400000000000000000000005371402514743400245130ustar00rootroot00000000000000c FILE: myciel3.col c SOURCE: Michael Trick (trick@cmu.edu) c DESCRIPTION: Graph based on Mycielski transformation. c Triangle free (clique number 2) but increasing c coloring number p edge 11 20 e 1 2 e 1 4 e 1 7 e 1 9 e 2 3 e 2 6 e 2 8 e 3 5 e 3 7 e 3 10 e 4 5 e 4 6 e 4 10 e 5 8 e 5 9 e 6 11 e 7 11 e 8 11 e 9 11 e 10 11 jgrapht-jgrapht-1.5.1/jgrapht-io/src/test/resources/myciel3_weighted.col000066400000000000000000000006221402514743400263660ustar00rootroot00000000000000c FILE: myciel3.col c SOURCE: Michael Trick (trick@cmu.edu) c DESCRIPTION: Graph based on Mycielski transformation. c Triangle free (clique number 2) but increasing c coloring number p edge 11 20 e 1 2 1 e 1 4 2 e 1 7 3 e 1 9 4 e 2 3 5 e 2 6 6 e 2 8 7 e 3 5 8 e 3 7 9 e 3 10 10 e 4 5 11 e 4 6 12 e 4 10 13 e 5 8 14 e 5 9 15 e 6 11 16 e 7 11 17 e 8 11 18 e 9 11 19 e 10 11 20 jgrapht-jgrapht-1.5.1/jgrapht-opt/000077500000000000000000000000001402514743400170555ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/pom.xml000066400000000000000000000044641402514743400204020ustar00rootroot00000000000000 4.0.0 org.jgrapht jgrapht 1.5.1 jgrapht-opt JGraphT - Optimized Graphs ${project.parent.basedir} GNU Lesser General Public License Version 2.1, February 1999 http://jgrapht.sourceforge.net/LGPL.html repo Eclipse Public License (EPL) 2.0 http://www.eclipse.org/legal/epl-v20.html repo org.apache.maven.plugins maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-resources-plugin org.apache.felix maven-bundle-plugin ${project.groupId} jgrapht-core it.unimi.dsi fastutil 8.5.2 junit junit test jgrapht-jgrapht-1.5.1/jgrapht-opt/src/000077500000000000000000000000001402514743400176445ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/000077500000000000000000000000001402514743400205705ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/000077500000000000000000000000001402514743400215115ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/module-info.java000066400000000000000000000003131402514743400245670ustar00rootroot00000000000000module org.jgrapht.opt { exports org.jgrapht.opt.graph.fastutil; exports org.jgrapht.opt.graph.sparse; requires transitive org.jgrapht.core; requires transitive it.unimi.dsi.fastutil; } jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/000077500000000000000000000000001402514743400223005ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/000077500000000000000000000000001402514743400237375ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/000077500000000000000000000000001402514743400245415ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/000077500000000000000000000000001402514743400256425ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/000077500000000000000000000000001402514743400274755ustar00rootroot00000000000000FastutilFastLookupGSS.java000066400000000000000000000054651402514743400344530ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.fastutil; import it.unimi.dsi.fastutil.objects.*; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.specifics.*; import java.io.*; import java.util.function.*; /** * The fast lookup specifics strategy implementation using fastutil maps for storage.. * *

    * Graphs constructed using this strategy use additional data structures to improve the performance * of methods which depend on edge retrievals, e.g. getEdge(V u, V v), containsEdge(V u, V * v),addEdge(V u, V v). A disadvantage is an increase in memory consumption. If memory utilization * is an issue, use the {@link FastutilGSS} instead. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class FastutilFastLookupGSS implements GraphSpecificsStrategy { private static final long serialVersionUID = -1335362823522091418L; @Override public BiFunction, GraphType, Specifics> getSpecificsFactory() { return (BiFunction, GraphType, Specifics> & Serializable) (graph, type) -> { if (type.isDirected()) { return new FastLookupDirectedSpecifics<>( graph, new Object2ObjectLinkedOpenHashMap<>(), new Object2ObjectOpenHashMap<>(), getEdgeSetFactory()); } else { return new FastLookupUndirectedSpecifics<>( graph, new Object2ObjectLinkedOpenHashMap<>(), new Object2ObjectOpenHashMap<>(), getEdgeSetFactory()); } }; } @Override public Function> getIntrusiveEdgesSpecificsFactory() { return (Function> & Serializable) (type) -> { if (type.isWeighted()) { return new WeightedIntrusiveEdgesSpecifics( new Object2ObjectLinkedOpenHashMap<>()); } else { return new UniformIntrusiveEdgesSpecifics<>(new Object2ObjectLinkedOpenHashMap<>()); } }; } } FastutilFastLookupIntVertexGSS.java000066400000000000000000000056211402514743400363160ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.fastutil; import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.*; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.specifics.*; import java.io.*; import java.util.function.*; /** * A specifics strategy implementation using fastutil maps for storage specialized for integer * vertices. * *

    * Graphs constructed using this strategy use additional data structures to improve the performance * of methods which depend on edge retrievals, e.g. getEdge(V u, V v), containsEdge(V u, V * v),addEdge(V u, V v). A disadvantage is an increase in memory consumption. If memory utilization * is an issue, use the {@link FastutilIntVertexGSS} instead. * * @author Dimitrios Michail * * @param the graph edge type */ public class FastutilFastLookupIntVertexGSS implements GraphSpecificsStrategy { private static final long serialVersionUID = 6098261533235930603L; @Override public BiFunction, GraphType, Specifics> getSpecificsFactory() { return (BiFunction, GraphType, Specifics> & Serializable) (graph, type) -> { if (type.isDirected()) { return new FastLookupDirectedSpecifics<>( graph, new Int2ReferenceLinkedOpenHashMap<>(), new Object2ObjectOpenHashMap<>(), getEdgeSetFactory()); } else { return new FastLookupUndirectedSpecifics<>( graph, new Int2ReferenceLinkedOpenHashMap<>(), new Object2ObjectOpenHashMap<>(), getEdgeSetFactory()); } }; } @Override public Function> getIntrusiveEdgesSpecificsFactory() { return (Function> & Serializable) (type) -> { if (type.isWeighted()) { return new WeightedIntrusiveEdgesSpecifics( new Object2ObjectLinkedOpenHashMap<>()); } else { return new UniformIntrusiveEdgesSpecifics<>(new Object2ObjectLinkedOpenHashMap<>()); } }; } } jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilGSS.java000066400000000000000000000052771402514743400325230ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.fastutil; import it.unimi.dsi.fastutil.objects.*; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.specifics.*; import java.io.*; import java.util.function.*; /** * A specifics strategy implementation using fastutil maps for storage. * *

    * Graphs constructed using this strategy require the least amount of memory, at the expense of slow * edge retrievals. Methods which depend on edge retrievals, e.g. getEdge(V u, V v), containsEdge(V * u, V v), addEdge(V u, V v), etc may be relatively slow when the average degree of a vertex is * high (dense graphs). For a fast implementation, use * {@link FastutilFastLookupGSS}. * * @author Dimitrios Michail * * @param the graph vertex type * @param the graph edge type */ public class FastutilGSS implements GraphSpecificsStrategy { private static final long serialVersionUID = -4319431062943632549L; @Override public BiFunction, GraphType, Specifics> getSpecificsFactory() { return (BiFunction, GraphType, Specifics> & Serializable) (graph, type) -> { if (type.isDirected()) { return new DirectedSpecifics<>( graph, new Object2ObjectLinkedOpenHashMap<>(), getEdgeSetFactory()); } else { return new UndirectedSpecifics<>( graph, new Object2ObjectLinkedOpenHashMap<>(), getEdgeSetFactory()); } }; } @Override public Function> getIntrusiveEdgesSpecificsFactory() { return (Function> & Serializable) (type) -> { if (type.isWeighted()) { return new WeightedIntrusiveEdgesSpecifics( new Object2ObjectLinkedOpenHashMap<>()); } else { return new UniformIntrusiveEdgesSpecifics<>(new Object2ObjectLinkedOpenHashMap<>()); } }; } } FastutilIntVertexGSS.java000066400000000000000000000046221402514743400343060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.fastutil; import it.unimi.dsi.fastutil.ints.*; import it.unimi.dsi.fastutil.objects.*; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.specifics.*; import java.io.*; import java.util.function.*; /** * A specifics strategy implementation using fastutil maps for storage specialized for integer * vertices. * * @author Dimitrios Michail * * @param the graph edge type */ public class FastutilIntVertexGSS implements GraphSpecificsStrategy { private static final long serialVersionUID = 803286406699705306L; @Override public BiFunction, GraphType, Specifics> getSpecificsFactory() { return (BiFunction, GraphType, Specifics> & Serializable) (graph, type) -> { if (type.isDirected()) { return new DirectedSpecifics<>( graph, new Int2ReferenceLinkedOpenHashMap<>(), getEdgeSetFactory()); } else { return new UndirectedSpecifics<>( graph, new Int2ReferenceLinkedOpenHashMap<>(), getEdgeSetFactory()); } }; } @Override public Function> getIntrusiveEdgesSpecificsFactory() { return (Function> & Serializable) (type) -> { if (type.isWeighted()) { return new WeightedIntrusiveEdgesSpecifics( new Object2ObjectLinkedOpenHashMap<>()); } else { return new UniformIntrusiveEdgesSpecifics<>(new Object2ObjectLinkedOpenHashMap<>()); } }; } } jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/FastutilMapGraph.java000066400000000000000000000055611402514743400335620ustar00rootroot00000000000000/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.fastutil; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.function.*; /** * A graph implementation using fastutil's map implementations for storage. * *

    The following example creates a simple undirected weighted graph:

    * *
     * Graph<String,
     *     DefaultWeightedEdge> g = new FastutilMapGraph<>(
     *         SupplierUtil.createStringSupplier(), SupplierUtil.createDefaultWeightedEdgeSupplier(),
     *         DefaultGraphType.simple().asWeighted());
     * 
    * *
    * *

    In case you have integer vertices, consider using the {@link FastutilMapIntVertexGraph}. * * @param the graph vertex type * @param the graph edge type * * @see FastutilMapIntVertexGraph * * @author Dimitrios Michail */ public class FastutilMapGraph extends AbstractBaseGraph { private static final long serialVersionUID = -2261627370606792673L; /** * Construct a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param type the graph type * @param fastLookups whether to index vertex pairs to allow (expected) constant time edge * lookups (by vertex endpoints) * @throws IllegalArgumentException if the graph type is not supported by this implementation */ public FastutilMapGraph( Supplier vertexSupplier, Supplier edgeSupplier, GraphType type, boolean fastLookups) { super( vertexSupplier, edgeSupplier, type, fastLookups ? new FastutilFastLookupGSS<>() : new FastutilGSS<>()); } /** * Construct a new graph. * *

    By default we index vertex pairs to allow (expected) constant time edge lookups. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param type the graph type * @throws IllegalArgumentException if the graph type is not supported by this implementation */ public FastutilMapGraph(Supplier vertexSupplier, Supplier edgeSupplier, GraphType type) { this(vertexSupplier, edgeSupplier, type, true); } } FastutilMapIntVertexGraph.java000066400000000000000000000055551402514743400353570ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.fastutil; import org.jgrapht.*; import org.jgrapht.graph.*; import java.util.function.*; /** * A graph implementation using fastutil's map implementations for storage specialized * for integer vertices. Edges can be of any object type. * *

    The following example creates a simple undirected weighted graph:

    * *
     * Graph<Integer,
     *     DefaultWeightedEdge> g = new FastutilMapIntVertexGraph<>(
     *         SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultWeightedEdgeSupplier(),
     *         DefaultGraphType.simple().asWeighted());
     * 
    * *
    * * @param the graph edge type * * @see FastutilMapGraph * * @author Dimitrios Michail */ public class FastutilMapIntVertexGraph extends AbstractBaseGraph { private static final long serialVersionUID = 6432747838839788559L; /** * Construct a new graph. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param type the graph type * @param fastLookups whether to index vertex pairs to allow (expected) constant time edge * lookups (by vertex endpoints) * @throws IllegalArgumentException if the graph type is not supported by this implementation */ public FastutilMapIntVertexGraph( Supplier vertexSupplier, Supplier edgeSupplier, GraphType type, boolean fastLookups) { super( vertexSupplier, edgeSupplier, type, fastLookups ? new FastutilFastLookupIntVertexGSS<>() : new FastutilIntVertexGSS<>()); } /** * Construct a new graph. * *

    By default we index vertex pairs to allow (expected) constant time edge lookups. * * @param vertexSupplier the vertex supplier, can be null * @param edgeSupplier the edge supplier, can be null * @param type the graph type * @throws IllegalArgumentException if the graph type is not supported by this implementation */ public FastutilMapIntVertexGraph(Supplier vertexSupplier, Supplier edgeSupplier, GraphType type) { this(vertexSupplier, edgeSupplier, type, true); } } jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/fastutil/package-info.java000066400000000000000000000001611402514743400326620ustar00rootroot00000000000000/** * Specialized graph implementations using the FastUtil library */ package org.jgrapht.opt.graph.fastutil; jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/000077500000000000000000000000001402514743400271375ustar00rootroot00000000000000IncomingEdgesSupport.java000066400000000000000000000024401402514743400340330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/* * (C) Copyright 2021-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse; /** * Enumeration for different kind of incoming edges support. Several algorithms do not require the * use of incoming edges. In such cases either no support or lazy support of incoming edges may save * considerable amount of space. * * @author Dimitrios Michail */ public enum IncomingEdgesSupport { /** * No support for incoming edges */ NO_INCOMING_EDGES, /** * Support incoming edges only if explicitly requested by the user using a method call which * requires incoming edges. */ LAZY_INCOMING_EDGES, /** * Support incoming edges. */ FULL_INCOMING_EDGES, } SparseIntDirectedGraph.java000066400000000000000000000106631402514743400342670ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; import org.jgrapht.alg.util.Pair; import org.jgrapht.opt.graph.sparse.specifics.AbstractSparseSpecificsGraph; import org.jgrapht.opt.graph.sparse.specifics.IncomingNoReindexSparseDirectedSpecifics; import org.jgrapht.opt.graph.sparse.specifics.NoIncomingNoReindexSparseDirectedSpecifics; import org.jgrapht.opt.graph.sparse.specifics.SparseGraphSpecifics; /** * A sparse directed graph. * *

    * Assuming the graph has $n$ vertices, the vertices are numbered from $0$ to $n-1$. Similarly, * edges are numbered from $0$ to $m-1$ where $m$ is the total number of edges. * *

    * It stores two boolean incidence matrix of the graph (rows are vertices and columns are edges) as * Compressed Sparse Rows (CSR). Constant time source and target lookups are provided by storing the * edge lists in arrays. This is a classic format for write-once read-many use cases. Thus, the * graph is unmodifiable. * *

    * The question of whether a sparse or dense representation is more appropriate is highly dependent * on various factors such as the graph, the machine running the algorithm and the algorithm itself. * Wilkinson defined a matrix as "sparse" if it has enough zeros that it pays to take advantage of * them. For more details see *

      *
    • Wilkinson, J. H. 1971. Linear algebra; part II: the algebraic eigenvalue problem. In Handbook * for Automatic Computation, J. H. Wilkinson and C. Reinsch, Eds. Vol. 2. Springer-Verlag, Berlin, * New York.
    • *
    * * Additional information about sparse representations can be found in the * wikipedia. * * @author Dimitrios Michail */ public class SparseIntDirectedGraph extends AbstractSparseSpecificsGraph { protected static final String UNMODIFIABLE = "this graph is unmodifiable"; /** * Create a new graph from an edge list. * * @param numVertices the number of vertices * @param edges the edge list */ public SparseIntDirectedGraph(int numVertices, List> edges) { this( numVertices, edges.size(), () -> edges.stream(), IncomingEdgesSupport.FULL_INCOMING_EDGES); } /** * Create a new graph from an edge list. * * @param numVertices the number of vertices * @param edges the edge list * @param incomingEdgesSupport whether to support incoming edges or not */ public SparseIntDirectedGraph( int numVertices, List> edges, IncomingEdgesSupport incomingEdgesSupport) { this(numVertices, edges.size(), () -> edges.stream(), incomingEdgesSupport); } /** * Create a new graph from an edge stream. * * @param numVertices the number of vertices * @param numEdges the number of edges * @param edges the edge stream * @param incomingEdgesSupport whether to support incoming edges or not */ public SparseIntDirectedGraph( int numVertices, int numEdges, Supplier>> edges, IncomingEdgesSupport incomingEdgesSupport) { super(() -> { switch (incomingEdgesSupport) { case FULL_INCOMING_EDGES: return new IncomingNoReindexSparseDirectedSpecifics( numVertices, numEdges, edges, false); case LAZY_INCOMING_EDGES: return new IncomingNoReindexSparseDirectedSpecifics( numVertices, numEdges, edges, true); case NO_INCOMING_EDGES: default: return new NoIncomingNoReindexSparseDirectedSpecifics(numVertices, numEdges, edges); } }); } } SparseIntDirectedWeightedGraph.java000066400000000000000000000122501402514743400357420ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse; import java.io.Serializable; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; import org.jgrapht.Graph; import org.jgrapht.GraphType; import org.jgrapht.alg.util.Pair; import org.jgrapht.alg.util.Triple; /** * Sparse directed weighted graph. * *

    * Assuming the graph has $n$ vertices, the vertices are numbered from $0$ to $n-1$. Similarly, * edges are numbered from $0$ to $m-1$ where $m$ is the total number of edges. * *

    * It stores two boolean incidence matrix of the graph (rows are vertices and columns are edges) as * Compressed Sparse Rows (CSR). Constant time source and target lookups are provided by storing the * edge lists in arrays. This is a classic format for write-once read-many use cases. Thus, the * graph is unmodifiable. The edge weights are maintained in an array indexed by the edge * identifier. If the user does not require support for incoming edges, then the second incidence * matrix can be either completely avoided or constructed lazily. See * {@link #SparseIntDirectedWeightedGraph(int, List, IncomingEdgesSupport)} for more details. * *

    * The graph is weighted. While unmodifiable with respect to the structure of the graph, the edge * weights can be changed even after the graph is constructed. * *

    * The question of whether a sparse or dense representation is more appropriate is highly dependent * on various factors such as the graph, the machine running the algorithm and the algorithm itself. * Wilkinson defined a matrix as "sparse" if it has enough zeros that it pays to take advantage of * them. For more details see *

      *
    • Wilkinson, J. H. 1971. Linear algebra; part II: the algebraic eigenvalue problem. In Handbook * for Automatic Computation, J. H. Wilkinson and C. Reinsch, Eds. Vol. 2. Springer-Verlag, Berlin, * New York.
    • *
    * * Additional information about sparse representations can be found in the * wikipedia. * * @author Dimitrios Michail */ public class SparseIntDirectedWeightedGraph extends SparseIntDirectedGraph implements Serializable { private static final long serialVersionUID = -7601401110000642281L; /** * The edge weights */ protected double[] weights; /** * Create a new graph from an edge list. * * @param numVertices the number of vertices * @param edges the edge list with additional weights */ public SparseIntDirectedWeightedGraph( int numVertices, List> edges) { this( numVertices, edges.size(), () -> edges.stream(), IncomingEdgesSupport.FULL_INCOMING_EDGES); } /** * Create a new graph from an edge list. * * @param numVertices the number of vertices * @param edges the edge list with additional weights * @param incomingEdgeSupport the kind of incoming edges support needed */ public SparseIntDirectedWeightedGraph( int numVertices, List> edges, IncomingEdgesSupport incomingEdgeSupport) { this(numVertices, edges.size(), () -> edges.stream(), incomingEdgeSupport); } /** * Create a new graph from an edge stream. * * @param numVertices the number of vertices * @param numEdges the number of edges * @param edges a supplier of an edge stream with additional weights * @param incomingEdgeSupport the kind of incoming edges support needed */ public SparseIntDirectedWeightedGraph( int numVertices, int numEdges, Supplier>> edges, IncomingEdgesSupport incomingEdgeSupport) { super( numVertices, numEdges, () -> edges.get().map(e -> Pair.of(e.getFirst(), e.getSecond())), incomingEdgeSupport); this.weights = new double[numEdges]; int[] eIndex = new int[1]; edges.get().forEach(e -> { double edgeWeight = e.getThird() != null ? e.getThird() : Graph.DEFAULT_EDGE_WEIGHT; weights[eIndex[0]++] = edgeWeight; }); } @Override public GraphType getType() { return super.getType().asWeighted(); } @Override public double getEdgeWeight(Integer e) { specifics.assertEdgeExist(e); return weights[e]; } @Override public void setEdgeWeight(Integer e, double weight) { specifics.assertEdgeExist(e); weights[e] = weight; } } SparseIntUndirectedGraph.java000066400000000000000000000063541402514743400346340ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; import org.jgrapht.alg.util.Pair; import org.jgrapht.opt.graph.sparse.specifics.AbstractSparseSpecificsGraph; import org.jgrapht.opt.graph.sparse.specifics.IncidenceMatrixSparseUndirectedSpecifics; import org.jgrapht.opt.graph.sparse.specifics.SparseGraphSpecifics; /** * Sparse undirected graph. * *

    * Assuming the graph has $n$ vertices, the vertices are numbered from $0$ to $n-1$. Similarly, * edges are numbered from $0$ to $m-1$ where $m$ is the total number of edges. * *

    * It stores the boolean incidence matrix of the graph (rows are vertices and columns are edges) as * Compressed Sparse Rows (CSR). In order to also support constant time source and target lookups * from an edge identifier we also store the transposed of the incidence matrix again in compressed * sparse rows format. This is a classic format for write-once read-many use cases. Thus, the graph * is unmodifiable. * * *

    * The question of whether a sparse or dense representation is more appropriate is highly dependent * on various factors such as the graph, the machine running the algorithm and the algorithm itself. * Wilkinson defined a matrix as "sparse" if it has enough zeros that it pays to take advantage of * them. For more details see *

      *
    • Wilkinson, J. H. 1971. Linear algebra; part II: the algebraic eigenvalue problem. In Handbook * for Automatic Computation, J. H. Wilkinson and C. Reinsch, Eds. Vol. 2. Springer-Verlag, Berlin, * New York.
    • *
    * * Additional information about sparse representations can be found in the * wikipedia. * * @author Dimitrios Michail */ public class SparseIntUndirectedGraph extends AbstractSparseSpecificsGraph { /** * Create a new graph from an edge list * * @param numVertices number of vertices * @param edges edge list */ public SparseIntUndirectedGraph(int numVertices, List> edges) { this(numVertices, edges.size(), () -> edges.stream()); } /** * Create a new graph from an edge stream * * @param numVertices number of vertices * @param numEdges number of edges * @param edges supplier of an edge stream */ public SparseIntUndirectedGraph( int numVertices, int numEdges, Supplier>> edges) { super(() -> new IncidenceMatrixSparseUndirectedSpecifics(numVertices, numEdges, edges)); } } SparseIntUndirectedWeightedGraph.java000066400000000000000000000104301402514743400363030ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse; import java.io.Serializable; import java.util.List; import java.util.function.Supplier; import java.util.stream.Stream; import org.jgrapht.Graph; import org.jgrapht.GraphType; import org.jgrapht.alg.util.Pair; import org.jgrapht.alg.util.Triple; /** * Sparse undirected weighted graph. * *

    * Assuming the graph has $n$ vertices, the vertices are numbered from $0$ to $n-1$. Similarly, * edges are numbered from $0$ to $m-1$ where $m$ is the total number of edges. * *

    * It stores the boolean incidence matrix of the graph (rows are vertices and columns are edges) as * Compressed Sparse Rows (CSR). In order to also support constant time source and target lookups * from an edge identifier we also store the transposed of the incidence matrix again in compressed * sparse row format. This is a classic format for write-once read-many use cases. Thus, the graph * is unmodifiable. The edge weights are maintained in an array indexed by the edge identifier. * *

    * The graph is weighted. While unmodifiable with respect to the structure of the graph, the edge * weights can be changed even after the graph is constructed. * *

    * The question of whether a sparse or dense representation is more appropriate is highly dependent * on various factors such as the graph, the machine running the algorithm and the algorithm itself. * Wilkinson defined a matrix as "sparse" if it has enough zeros that it pays to take advantage of * them. For more details see *

      *
    • Wilkinson, J. H. 1971. Linear algebra; part II: the algebraic eigenvalue problem. In Handbook * for Automatic Computation, J. H. Wilkinson and C. Reinsch, Eds. Vol. 2. Springer-Verlag, Berlin, * New York.
    • *
    * * Additional information about sparse representations can be found in the * wikipedia. * * @author Dimitrios Michail */ public class SparseIntUndirectedWeightedGraph extends SparseIntUndirectedGraph implements Serializable { private static final long serialVersionUID = -5410680356868181247L; /** * The edge weights */ protected double[] weights; /** * Create a new graph from an edge list * * @param numVertices number of vertices * @param edges edge list with weights */ public SparseIntUndirectedWeightedGraph( int numVertices, List> edges) { this(numVertices, edges.size(), () -> edges.stream()); } /** * Create a new graph from an edge stream * * @param numVertices number of vertices * @param numEdges number of edges * @param edges a supplier of an edge stream with weights */ public SparseIntUndirectedWeightedGraph( int numVertices, int numEdges, Supplier>> edges) { super( numVertices, numEdges, () -> edges.get().map(e -> Pair.of(e.getFirst(), e.getSecond()))); this.weights = new double[numEdges]; int[] eIndex = new int[1]; edges.get().forEach(e -> { double edgeWeight = e.getThird() != null ? e.getThird() : Graph.DEFAULT_EDGE_WEIGHT; weights[eIndex[0]++] = edgeWeight; }); } @Override public GraphType getType() { return super.getType().asWeighted(); } @Override public double getEdgeWeight(Integer e) { specifics.assertEdgeExist(e); return weights[e]; } @Override public void setEdgeWeight(Integer e, double weight) { specifics.assertEdgeExist(e); weights[e] = weight; } } jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/package-info.java000066400000000000000000000001701402514743400323240ustar00rootroot00000000000000/** * Specialized graph implementations using sparse matrix representations. */ package org.jgrapht.opt.graph.sparse; jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/000077500000000000000000000000001402514743400311075ustar00rootroot00000000000000AbstractSparseSpecificsGraph.java000066400000000000000000000112631402514743400374320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/* * (C) Copyright 2021-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse.specifics; import java.util.Objects; import java.util.Set; import java.util.function.Supplier; import org.jgrapht.GraphType; import org.jgrapht.graph.AbstractGraph; /** * Helper class to ease the implementation of different sparse graphs with different backends. * * @author Dimitrios Michail * * @param the type of the graph specifics */ public class AbstractSparseSpecificsGraph extends AbstractGraph { protected static final String UNMODIFIABLE = "this graph is unmodifiable"; protected S specifics; /** * Constructor * * @param specificsSupplier a specifics supplier */ public AbstractSparseSpecificsGraph(Supplier specificsSupplier) { this.specifics = Objects.requireNonNull(specificsSupplier.get()); } @Override public Supplier getVertexSupplier() { return null; } @Override public Supplier getEdgeSupplier() { return null; } @Override public Integer addEdge(Integer sourceVertex, Integer targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public boolean addEdge(Integer sourceVertex, Integer targetVertex, Integer e) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public Integer addVertex() { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public boolean addVertex(Integer v) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public boolean containsEdge(Integer e) { return specifics.containsEdge(e); } @Override public boolean containsVertex(Integer v) { return specifics.containsVertex(v); } @Override public Set edgeSet() { return specifics.edgeSet(); } @Override public int degreeOf(Integer vertex) { return (int) specifics.degreeOf(vertex); } @Override public Set edgesOf(Integer vertex) { return specifics.edgesOf(vertex); } @Override public int inDegreeOf(Integer vertex) { return (int) specifics.inDegreeOf(vertex); } @Override public Set incomingEdgesOf(Integer vertex) { return specifics.incomingEdgesOf(vertex); } @Override public int outDegreeOf(Integer vertex) { return (int) specifics.outDegreeOf(vertex); } @Override public Set outgoingEdgesOf(Integer vertex) { return specifics.outgoingEdgesOf(vertex); } @Override public Integer removeEdge(Integer sourceVertex, Integer targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public boolean removeEdge(Integer e) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public boolean removeVertex(Integer v) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public Set vertexSet() { return specifics.vertexSet(); } @Override public Integer getEdgeSource(Integer e) { return specifics.getEdgeSource(e); } @Override public Integer getEdgeTarget(Integer e) { return specifics.getEdgeTarget(e); } @Override public GraphType getType() { return specifics.getType(); } @Override public double getEdgeWeight(Integer e) { return specifics.getEdgeWeight(e); } @Override public void setEdgeWeight(Integer e, double weight) { specifics.setEdgeWeight(e, weight); } @Override public Integer getEdge(Integer sourceVertex, Integer targetVertex) { return specifics.getEdge(sourceVertex, targetVertex); } @Override public Set getAllEdges(Integer sourceVertex, Integer targetVertex) { return specifics.getAllEdges(sourceVertex, targetVertex); } } CSRBooleanMatrix.java000066400000000000000000000121571402514743400350150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse.specifics; import org.jgrapht.alg.util.*; import java.io.*; import java.util.*; /** * A sparse boolean matrix in Compressed Sparse Row (CSR) format. * *

    * This is a helper class for graph representation and thus does not provide a fully fledged matrix. * * @author Dimitrios Michail */ class CSRBooleanMatrix implements Serializable { private static final long serialVersionUID = -8639339411487665967L; private static final Comparator> INTEGER_PAIR_LEX_COMPARATOR = (o1, o2) -> { if (o1.getFirst() < o2.getFirst()) { return -1; } else if (o1.getFirst() > o2.getFirst()) { return 1; } else if (o1.getSecond() < o2.getSecond()) { return -1; } else if (o1.getSecond() > o2.getSecond()) { return 1; } return 0; }; private int columns; private int[] rowOffsets; private int[] columnIndices; /** * Create a new CSR boolean matrix * * @param rows the number of rows * @param columns the number of columns * @param entries the position of the entries of the matrix */ public CSRBooleanMatrix(int rows, int columns, List> entries) { if (rows < 1) { throw new IllegalArgumentException("Rows must be positive"); } if (columns < 1) { throw new IllegalArgumentException("Columns must be positive"); } if (entries == null) { throw new IllegalArgumentException("Entries cannot be null"); } this.columns = columns; this.rowOffsets = new int[rows + 1]; this.columnIndices = new int[entries.size()]; Iterator> it = entries.stream().sorted(INTEGER_PAIR_LEX_COMPARATOR).iterator(); int cIndex = 0; while (it.hasNext()) { Pair e = it.next(); // add column index int column = e.getSecond(); if (column < 0 || column >= columns) { throw new IllegalArgumentException("Entry at invalid column: " + column); } columnIndices[cIndex++] = column; // count non-zero per row int row = e.getFirst(); rowOffsets[row + 1]++; } // prefix sum Arrays.parallelPrefix(rowOffsets, (x, y) -> x + y); } /** * Get the number of columns of the matrix. * * @return the number of columns */ public int columns() { return columns; } /** * Get the number of rows of the matrix. * * @return the number of rows */ public int rows() { return rowOffsets.length - 1; } /** * Get the number of non-zero entries of a row. * * @param row the row * @return the number of non-zero entries of a row */ public int nonZeros(int row) { assert row >= 0 && row < rowOffsets.length; return rowOffsets[row + 1] - rowOffsets[row]; } /** * Get an iterator over the non-zero entries of a row. * * @param row the row * @return an iterator over the non-zero entries of a row */ public Iterator nonZerosPositionIterator(int row) { assert row >= 0 && row < rowOffsets.length; return new NonZerosIterator(row); } /** * Get the position of non-zero entries of a row as a set. * * @param row the row * @return the position of non-zero entries of a row as a set. */ public Set nonZerosSet(int row) { assert row >= 0 && row < rowOffsets.length; Set nonZeros = new LinkedHashSet<>(); new NonZerosIterator(row).forEachRemaining(nonZeros::add); return nonZeros; } private class NonZerosIterator implements Iterator { private int curPos; private int toPos; public NonZerosIterator(int row) { this.curPos = rowOffsets[row]; this.toPos = rowOffsets[row + 1]; } @Override public boolean hasNext() { return (curPos < toPos); } @Override public Integer next() { if (!hasNext()) { throw new NoSuchElementException(); } return columnIndices[curPos++]; } } } CompleteIntegerSet.java000066400000000000000000000027101402514743400354350ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse.specifics; import java.util.*; import java.util.stream.*; /** * An integer set containing all numbers from 0 to n-1. * * @author Dimitrios Michail */ class CompleteIntegerSet extends AbstractSet { private int n; /** * Create an integer set from 0 to n-1. * * @param n the number n */ public CompleteIntegerSet(int n) { this.n = n; } @Override public Iterator iterator() { return IntStream.range(0, n).iterator(); } @Override public boolean contains(Object o) { if (o instanceof Integer) { Integer x = (Integer) o; return x >= 0 && x < n; } return false; } @Override public int size() { return n; } } IncidenceMatrixSparseUndirectedSpecifics.java000066400000000000000000000133621402514743400417640ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse.specifics; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; import org.jgrapht.GraphType; import org.jgrapht.alg.util.Pair; import org.jgrapht.graph.DefaultGraphType; /** * Specifics for a sparse undirected graph using an incidence matrix representation. * * @author Dimitrios Michail */ public class IncidenceMatrixSparseUndirectedSpecifics implements SparseGraphSpecifics { protected static final String UNMODIFIABLE = "this graph is unmodifiable"; protected CSRBooleanMatrix incidenceMatrix; protected int[] source; protected int[] target; /** * Create a new graph from an edge stream * * @param numVertices number of vertices * @param numEdges number of edges * @param edges a supplier of an edge stream */ public IncidenceMatrixSparseUndirectedSpecifics( int numVertices, int numEdges, Supplier>> edges) { final int m = numEdges; source = new int[m]; target = new int[m]; List> nonZeros = new ArrayList<>(m); int[] eIndex = new int[1]; edges.get().forEach(e -> { nonZeros.add(Pair.of(e.getFirst(), eIndex[0])); nonZeros.add(Pair.of(e.getSecond(), eIndex[0])); source[eIndex[0]] = e.getFirst(); target[eIndex[0]] = e.getSecond(); eIndex[0]++; }); incidenceMatrix = new CSRBooleanMatrix(numVertices, m, nonZeros); } @Override public long edgesCount() { return incidenceMatrix.columns(); } @Override public long verticesCount() { return incidenceMatrix.rows(); } @Override public long degreeOf(Integer vertex) { assertVertexExist(vertex); return incidenceMatrix.nonZeros(vertex); } @Override public Set edgesOf(Integer vertex) { assertVertexExist(vertex); return incidenceMatrix.nonZerosSet(vertex); } @Override public long inDegreeOf(Integer vertex) { assertVertexExist(vertex); return incidenceMatrix.nonZeros(vertex); } @Override public Set incomingEdgesOf(Integer vertex) { assertVertexExist(vertex); return incidenceMatrix.nonZerosSet(vertex); } @Override public long outDegreeOf(Integer vertex) { assertVertexExist(vertex); return incidenceMatrix.nonZeros(vertex); } @Override public Set outgoingEdgesOf(Integer vertex) { assertVertexExist(vertex); return incidenceMatrix.nonZerosSet(vertex); } @Override public GraphType getType() { return new DefaultGraphType.Builder() .undirected().weighted(false).modifiable(false).allowMultipleEdges(true) .allowSelfLoops(true).build(); } @Override public Integer getEdgeSource(Integer e) { assertEdgeExist(e); return source[e]; } @Override public Integer getEdgeTarget(Integer e) { assertEdgeExist(e); return target[e]; } /** * {@inheritDoc} * * This operation costs $O(d)$ where $d$ is the degree of the source vertex. */ @Override public Integer getEdge(Integer sourceVertex, Integer targetVertex) { if (sourceVertex < 0 || sourceVertex >= incidenceMatrix.rows()) { return null; } if (targetVertex < 0 || targetVertex >= incidenceMatrix.rows()) { return null; } Iterator it = incidenceMatrix.nonZerosPositionIterator(sourceVertex); while (it.hasNext()) { int eId = it.next(); int v = getEdgeSource(eId); int u = getEdgeTarget(eId); if (v == sourceVertex.intValue() && u == targetVertex.intValue() || v == targetVertex.intValue() && u == sourceVertex.intValue()) { return eId; } } return null; } /** * {@inheritDoc} * * This operation costs $O(d)$ where $d$ is the degree of the source vertex. */ @Override public Set getAllEdges(Integer sourceVertex, Integer targetVertex) { if (sourceVertex < 0 || sourceVertex >= incidenceMatrix.rows()) { return null; } if (targetVertex < 0 || targetVertex >= incidenceMatrix.rows()) { return null; } Set result = new LinkedHashSet<>(); Iterator it = incidenceMatrix.nonZerosPositionIterator(sourceVertex); while (it.hasNext()) { int eId = it.next(); int v = getEdgeSource(eId); int u = getEdgeTarget(eId); if (v == sourceVertex.intValue() && u == targetVertex.intValue() || v == targetVertex.intValue() && u == sourceVertex.intValue()) { result.add(eId); } } return result; } } IncomingNoReindexSparseDirectedSpecifics.java000066400000000000000000000072041402514743400417300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse.specifics; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; import org.jgrapht.alg.util.Pair; import org.jgrapht.util.UnmodifiableUnionSet; /** * Specifics for a sparse directed graph which does not re-index the edges and supports incoming * edges. No reindexing means that the edges are numbered in increasing order using the order that * the user loads the edges. Support for incoming edges is provided but is initialized lazily the * first time the user accessed a corresponding method. * * @author Dimitrios Michail */ public class IncomingNoReindexSparseDirectedSpecifics extends NoIncomingNoReindexSparseDirectedSpecifics { /** * Incidence matrix with incoming edges */ protected CSRBooleanMatrix inIncidenceMatrix; /** * Create a new graph from an edge list. * * @param numVertices the number of vertices * @param numEdges the number of edges * @param edges a supplier of an edge stream * @param lazyIncomingEdges whether to lazily support incoming edge traversals, only if actually * needed by the user */ public IncomingNoReindexSparseDirectedSpecifics( int numVertices, int numEdges, Supplier>> edges, boolean lazyIncomingEdges) { super(numVertices, numEdges, edges); if (!lazyIncomingEdges) { indexIncomingEdges(); } } @Override public long degreeOf(Integer vertex) { assertVertexExist(vertex); if (inIncidenceMatrix == null) { indexIncomingEdges(); } return outIncidenceMatrix.nonZeros(vertex) + inIncidenceMatrix.nonZeros(vertex); } @Override public Set edgesOf(Integer vertex) { assertVertexExist(vertex); if (inIncidenceMatrix == null) { indexIncomingEdges(); } return new UnmodifiableUnionSet<>( outIncidenceMatrix.nonZerosSet(vertex), inIncidenceMatrix.nonZerosSet(vertex)); } @Override public long inDegreeOf(Integer vertex) { assertVertexExist(vertex); if (inIncidenceMatrix == null) { indexIncomingEdges(); } return inIncidenceMatrix.nonZeros(vertex); } @Override public Set incomingEdgesOf(Integer vertex) { assertVertexExist(vertex); if (inIncidenceMatrix == null) { indexIncomingEdges(); } return inIncidenceMatrix.nonZerosSet(vertex); } /** * Build the index for the incoming edges. */ protected void indexIncomingEdges() { int n = outIncidenceMatrix.rows(); int m = source.length; List> incoming = new ArrayList<>(m); for (int i = 0; i < m; i++) { incoming.add(Pair.of(target[i], i)); } inIncidenceMatrix = new CSRBooleanMatrix(n, m, incoming); } } NoIncomingNoReindexSparseDirectedSpecifics.java000066400000000000000000000134011402514743400422210ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/* * (C) Copyright 2020-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse.specifics; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; import org.jgrapht.GraphType; import org.jgrapht.alg.util.Pair; import org.jgrapht.graph.DefaultGraphType; /** * Specifics for a sparse directed graph which does not re-index the edges and does not support * incoming edges. No reindexing means that the edges are numbered in increasing order using the * order that the user loads the edges. Support for incoming edges is not provided. All methods that * need access to incoming edges throw an exception. * * @author Dimitrios Michail */ public class NoIncomingNoReindexSparseDirectedSpecifics implements SparseGraphSpecifics { protected static final String UNMODIFIABLE = "this graph is unmodifiable"; protected static final String NO_INCOMING = "this graph does not support incoming edges"; /** * Source vertex of edge */ protected int[] source; /** * Target vertex of edge */ protected int[] target; /** * Incidence matrix with outgoing edges */ protected CSRBooleanMatrix outIncidenceMatrix; /** * Create a new graph from an edge list. * * @param numVertices the number of vertices * @param numEdges the number of edges * @param edges a supplier of an edge list */ public NoIncomingNoReindexSparseDirectedSpecifics( int numVertices, int numEdges, Supplier>> edges) { final int m = numEdges; source = new int[m]; target = new int[m]; List> outgoing = new ArrayList<>(m); int[] eIndex = new int[1]; edges.get().forEach(e -> { source[eIndex[0]] = e.getFirst(); target[eIndex[0]] = e.getSecond(); outgoing.add(Pair.of(e.getFirst(), eIndex[0])); eIndex[0]++; }); outIncidenceMatrix = new CSRBooleanMatrix(numVertices, m, outgoing); } @Override public long edgesCount() { return outIncidenceMatrix.columns(); } @Override public long degreeOf(Integer vertex) { throw new UnsupportedOperationException(NO_INCOMING); } @Override public Set edgesOf(Integer vertex) { throw new UnsupportedOperationException(NO_INCOMING); } @Override public long inDegreeOf(Integer vertex) { throw new UnsupportedOperationException(NO_INCOMING); } @Override public Set incomingEdgesOf(Integer vertex) { throw new UnsupportedOperationException(NO_INCOMING); } @Override public long outDegreeOf(Integer vertex) { assertVertexExist(vertex); return outIncidenceMatrix.nonZeros(vertex); } @Override public Set outgoingEdgesOf(Integer vertex) { assertVertexExist(vertex); return outIncidenceMatrix.nonZerosSet(vertex); } @Override public long verticesCount() { return outIncidenceMatrix.rows(); } @Override public Integer getEdgeSource(Integer e) { assertEdgeExist(e); return source[e]; } @Override public Integer getEdgeTarget(Integer e) { assertEdgeExist(e); return target[e]; } @Override public GraphType getType() { return new DefaultGraphType.Builder() .directed().weighted(false).modifiable(false).allowMultipleEdges(true) .allowSelfLoops(true).build(); } /** * {@inheritDoc} * * This operation costs $O(d)$ where $d$ is the out-degree of the source vertex. */ @Override public Integer getEdge(Integer sourceVertex, Integer targetVertex) { if (sourceVertex < 0 || sourceVertex >= outIncidenceMatrix.rows()) { return null; } if (targetVertex < 0 || targetVertex >= outIncidenceMatrix.rows()) { return null; } Iterator it = outIncidenceMatrix.nonZerosPositionIterator(sourceVertex); while (it.hasNext()) { int eId = it.next(); if (getEdgeTarget(eId).equals(targetVertex)) { return eId; } } return null; } /** * {@inheritDoc} * * This operation costs $O(d)$ where $d$ is the out-degree of the source vertex. */ @Override public Set getAllEdges(Integer sourceVertex, Integer targetVertex) { if (sourceVertex < 0 || sourceVertex >= outIncidenceMatrix.rows()) { return null; } if (targetVertex < 0 || targetVertex >= outIncidenceMatrix.rows()) { return null; } Set result = new LinkedHashSet<>(); Iterator it = outIncidenceMatrix.nonZerosPositionIterator(sourceVertex); while (it.hasNext()) { int eId = it.next(); if (getEdgeTarget(eId).equals(targetVertex)) { result.add(eId); } } return result; } } SparseGraphSpecifics.java000066400000000000000000000310701402514743400357440ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/* * (C) Copyright 2021-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse.specifics; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.GraphType; /** * Specifics which provide a sparse graph implementation. * * @author Dimitrios Michail */ public interface SparseGraphSpecifics { /** * Get the number of edges. * * @return the number of edges */ long edgesCount(); /** * Get the number of vertices * * @return the number of vertices */ long verticesCount(); /** * Returns true if this graph contains the specified edge. More formally, returns * true if and only if this graph contains an edge e2 such that * e.equals(e2). If the specified edge is null returns * false. * * @param e edge whose presence in this graph is to be tested. * * @return true if this graph contains the specified edge. */ default boolean containsEdge(Integer e) { return e >= 0 && e < edgesCount(); } /** * Returns true if this graph contains the specified vertex. More formally, returns * true if and only if this graph contains a vertex u such that * u.equals(v). If the specified vertex is null returns * false. * * @param v vertex whose presence in this graph is to be tested. * * @return true if this graph contains the specified vertex. */ default boolean containsVertex(Integer v) { return v >= 0 && v < verticesCount(); } /** * Returns a set of the edges contained in this graph. The set is backed by the graph, so * changes to the graph are reflected in the set. If the graph is modified while an iteration * over the set is in progress, the results of the iteration are undefined. * *

    * The graph implementation may maintain a particular set ordering (e.g. via * {@link java.util.LinkedHashSet}) for deterministic iteration, but this is not required. It is * the responsibility of callers who rely on this behavior to only use graph implementations * which support it. *

    * * @return a set of the edges contained in this graph. */ default Set edgeSet() { Long edgesCount = edgesCount(); if (edgesCount > Integer.MAX_VALUE) { throw new ArithmeticException("integer overflow"); } return new CompleteIntegerSet(edgesCount.intValue()); } /** * Returns the degree of the specified vertex. * *

    * A degree of a vertex in an undirected graph is the number of edges touching that vertex. * Edges with same source and target vertices (self-loops) are counted twice. * *

    * In directed graphs this method returns the sum of the "in degree" and the "out degree". * * @param vertex vertex whose degree is to be calculated. * @return the degree of the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. * @throws ArithmeticException if the result overflows an int */ long degreeOf(Integer vertex); /** * Returns a set of all edges touching the specified vertex. If no edges are touching the * specified vertex returns an empty set. * * @param vertex the vertex for which a set of touching edges is to be returned. * @return a set of all edges touching the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ Set edgesOf(Integer vertex); /** * Returns the "in degree" of the specified vertex. * *

    * The "in degree" of a vertex in a directed graph is the number of inward directed edges from * that vertex. See * http://mathworld.wolfram.com/Indegree.html. * *

    * In the case of undirected graphs this method returns the number of edges touching the vertex. * Edges with same source and target vertices (self-loops) are counted twice. * * @param vertex vertex whose degree is to be calculated. * @return the degree of the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. * @throws ArithmeticException if the result overflows an int */ long inDegreeOf(Integer vertex); /** * Returns a set of all edges incoming into the specified vertex. * *

    * In the case of undirected graphs this method returns all edges touching the vertex, thus, * some of the returned edges may have their source and target vertices in the opposite order. * * @param vertex the vertex for which the list of incoming edges to be returned. * @return a set of all edges incoming into the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ Set incomingEdgesOf(Integer vertex); /** * Returns the "out degree" of the specified vertex. * *

    * The "out degree" of a vertex in a directed graph is the number of outward directed edges from * that vertex. See * http://mathworld.wolfram.com/Outdegree.html. * *

    * In the case of undirected graphs this method returns the number of edges touching the vertex. * Edges with same source and target vertices (self-loops) are counted twice. * * @param vertex vertex whose degree is to be calculated. * @return the degree of the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. * @throws ArithmeticException if the result overflows an int */ long outDegreeOf(Integer vertex); /** * Returns a set of all edges outgoing from the specified vertex. * *

    * In the case of undirected graphs this method returns all edges touching the vertex, thus, * some of the returned edges may have their source and target vertices in the opposite order. * * @param vertex the vertex for which the list of outgoing edges to be returned. * @return a set of all edges outgoing from the specified vertex. * * @throws IllegalArgumentException if vertex is not found in the graph. * @throws NullPointerException if vertex is null. */ Set outgoingEdgesOf(Integer vertex); /** * Returns a set of the vertices contained in this graph. The set is backed by the graph, so * changes to the graph are reflected in the set. If the graph is modified while an iteration * over the set is in progress, the results of the iteration are undefined. * *

    * The graph implementation may maintain a particular set ordering (e.g. via * {@link java.util.LinkedHashSet}) for deterministic iteration, but this is not required. It is * the responsibility of callers who rely on this behavior to only use graph implementations * which support it. *

    * * @return a set view of the vertices contained in this graph. */ default Set vertexSet() { Long verticesCount = verticesCount(); if (verticesCount > Integer.MAX_VALUE) { throw new ArithmeticException("integer overflow"); } return new CompleteIntegerSet(verticesCount.intValue()); } /** * Returns the source vertex of an edge. For an undirected graph, source and target are * distinguishable designations (but without any mathematical meaning). * * @param e edge of interest * * @return source vertex */ Integer getEdgeSource(Integer e); /** * Returns the target vertex of an edge. For an undirected graph, source and target are * distinguishable designations (but without any mathematical meaning). * * @param e edge of interest * * @return target vertex */ Integer getEdgeTarget(Integer e); /** * Get the graph type. The graph type can be used to query for additional metadata such as * whether the graph supports directed or undirected edges, self-loops, multiple (parallel) * edges, weights, etc. * * @return the graph type */ GraphType getType(); /** * Returns the weight assigned to a given edge. Unweighted graphs return 1.0 (as defined by * {@link #DEFAULT_EDGE_WEIGHT}), allowing weighted-graph algorithms to apply to them when * meaningful. * * @param e edge of interest * @return edge weight */ default double getEdgeWeight(Integer e) { return Graph.DEFAULT_EDGE_WEIGHT; } /** * Assigns a weight to an edge. * * @param e edge on which to set weight * @param weight new weight for edge * @throws UnsupportedOperationException if the graph does not support weights */ default public void setEdgeWeight(Integer e, double weight) { throw new UnsupportedOperationException("this graph is unmodifiable"); } /** * Returns an edge connecting source vertex to target vertex if such vertices and such edge * exist in this graph. Otherwise returns * null. If any of the specified vertices is null returns null * *

    * In undirected graphs, the returned edge may have its source and target vertices in the * opposite order. *

    * * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return an edge connecting source vertex to target vertex. */ Integer getEdge(Integer sourceVertex, Integer targetVertex); /** * Returns a set of all edges connecting source vertex to target vertex if such vertices exist * in this graph. If any of the vertices does not exist or is null, returns * null. If both vertices exist but no edges found, returns an empty set. * *

    * In undirected graphs, some of the returned edges may have their source and target vertices in * the opposite order. In simple graphs the returned set is either singleton set or empty set. *

    * * @param sourceVertex source vertex of the edge. * @param targetVertex target vertex of the edge. * * @return a set of all edges connecting source vertex to target vertex. */ Set getAllEdges(Integer sourceVertex, Integer targetVertex); /** * Ensures that the specified vertex exists in this graph, or else throws exception. * * @param v vertex * @return true if this assertion holds. * @throws IllegalArgumentException if specified vertex does not exist in this graph. */ default boolean assertVertexExist(Integer v) { if (v >= 0 && v < verticesCount()) { return true; } else { throw new IllegalArgumentException("no such vertex in graph: " + v.toString()); } } /** * Ensures that the specified edge exists in this graph, or else throws exception. * * @param e edge * @return true if this assertion holds. * @throws IllegalArgumentException if specified edge does not exist in this graph. */ default boolean assertEdgeExist(Integer e) { if (e >= 0 && e < edgesCount()) { return true; } else { throw new IllegalArgumentException("no such edge in graph: " + e.toString()); } } } package-info.java000066400000000000000000000002001402514743400342070ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/main/java/org/jgrapht/opt/graph/sparse/specifics/** * Implementations of different sparse graphs with different tradeoffs. */ package org.jgrapht.opt.graph.sparse.specifics; jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/000077500000000000000000000000001402514743400206235ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/000077500000000000000000000000001402514743400215445ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/000077500000000000000000000000001402514743400223335ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/jgrapht/000077500000000000000000000000001402514743400237725ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/jgrapht/opt/000077500000000000000000000000001402514743400245745ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/000077500000000000000000000000001402514743400256755ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/000077500000000000000000000000001402514743400275305ustar00rootroot00000000000000FastUtilMapGraphTest.java000066400000000000000000000031131402514743400343250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.fastutil; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; /** * Tests for {@link FastutilMapGraph}. * * @author Dimitrios Michail */ public class FastUtilMapGraphTest { /** * Test in-out edges of directed graph */ @Test public void testDirectedGraph() { IncomingOutgoingEdgesTest.testDirectedGraph( () -> new FastutilMapGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), DefaultGraphType.directedPseudograph())); } /** * Test in-out edges of undirected graph */ @Test public void testUndirectedGraph() { IncomingOutgoingEdgesTest.testUndirectedGraph( () -> new FastutilMapGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), DefaultGraphType.pseudograph())); } } FastUtilMapIntVertexGraphTest.java000066400000000000000000000031571402514743400362060ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/* * (C) Copyright 2018-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.fastutil; import org.jgrapht.graph.*; import org.jgrapht.util.*; import org.junit.*; /** * Tests for {@link FastutilMapIntVertexGraph}. * * @author Dimitrios Michail */ public class FastUtilMapIntVertexGraphTest { /** * Test in-out edges of directed graph */ @Test public void testDirectedGraph() { IncomingOutgoingEdgesTest.testDirectedGraph( () -> new FastutilMapIntVertexGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), DefaultGraphType.directedPseudograph())); } /** * Test in-out edges of undirected graph */ @Test public void testUndirectedGraph() { IncomingOutgoingEdgesTest.testUndirectedGraph( () -> new FastutilMapIntVertexGraph<>( SupplierUtil.createIntegerSupplier(), SupplierUtil.createDefaultEdgeSupplier(), DefaultGraphType.pseudograph())); } } IncomingOutgoingEdgesTest.java000066400000000000000000000272441402514743400354140ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/fastutil/* * (C) Copyright 2017-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.fastutil; import org.jgrapht.*; import org.jgrapht.graph.*; import org.jgrapht.graph.builder.*; import org.jgrapht.util.*; import org.junit.*; import java.util.*; import java.util.function.*; import static org.junit.Assert.*; /** * Check Incoming/Outgoing edges in directed and undirected graphs. * * @author Dimitrios Michail */ public class IncomingOutgoingEdgesTest { public static void testAddDuplicateEdgeDirectedGraph( Supplier> graphSupplier) { Graph g = graphSupplier.get(); assertTrue(g.getType().isDirected()); g.addVertex(1); g.addVertex(2); g.addVertex(3); DefaultEdge e = new DefaultEdge(); g.addEdge(1, 2, e); assertTrue(g.edgeSet().size() == 1); assertEquals(Collections.emptySet(), g.incomingEdgesOf(1)); assertEquals(0, g.inDegreeOf(1)); assertEquals(Set.of(e), g.outgoingEdgesOf(1)); assertEquals(1, g.outDegreeOf(1)); assertEquals(Set.of(e), g.incomingEdgesOf(2)); assertEquals(1, g.inDegreeOf(2)); assertEquals(Collections.emptySet(), g.outgoingEdgesOf(2)); assertEquals(0, g.outDegreeOf(2)); assertEquals(Collections.emptySet(), g.incomingEdgesOf(3)); assertEquals(0, g.inDegreeOf(3)); assertEquals(Collections.emptySet(), g.outgoingEdgesOf(3)); assertEquals(0, g.outDegreeOf(3)); assertThrows(IntrusiveEdgeException.class, () -> g.addEdge(1, 3, e)); assertTrue(g.edgeSet().size() == 1); assertEquals(Collections.emptySet(), g.incomingEdgesOf(1)); assertEquals(0, g.inDegreeOf(1)); assertEquals(Set.of(e), g.outgoingEdgesOf(1)); assertEquals(1, g.outDegreeOf(1)); assertEquals(Set.of(e), g.incomingEdgesOf(2)); assertEquals(1, g.inDegreeOf(2)); assertEquals(Collections.emptySet(), g.outgoingEdgesOf(2)); assertEquals(0, g.outDegreeOf(2)); assertEquals(Collections.emptySet(), g.incomingEdgesOf(3)); assertEquals(0, g.inDegreeOf(3)); assertEquals(Collections.emptySet(), g.outgoingEdgesOf(3)); assertEquals(0, g.outDegreeOf(3)); } public static void testDirectedGraph(Supplier> graphSupplier) { Graph g = graphSupplier.get(); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); assertEquals(5, g.vertexSet().size()); assertTrue(g.containsVertex(1)); assertTrue(g.containsVertex(2)); assertTrue(g.containsVertex(3)); assertTrue(g.containsVertex(4)); assertTrue(g.containsVertex(5)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e23_1 = g.addEdge(2, 3); DefaultEdge e23_2 = g.addEdge(2, 3); DefaultEdge e24 = g.addEdge(2, 4); DefaultEdge e44 = g.addEdge(4, 4); DefaultEdge e55_1 = g.addEdge(5, 5); DefaultEdge e52 = g.addEdge(5, 2); DefaultEdge e55_2 = g.addEdge(5, 5); assertEquals(1, g.degreeOf(1)); assertEquals(5, g.degreeOf(2)); assertEquals(2, g.degreeOf(3)); assertEquals(3, g.degreeOf(4)); assertEquals(5, g.degreeOf(5)); assertEquals(Set.of(e12), g.edgesOf(1)); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.edgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.edgesOf(3)); assertEquals(Set.of(e24, e44), g.edgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.edgesOf(5)); assertEquals(0, g.inDegreeOf(1)); assertEquals(2, g.inDegreeOf(2)); assertEquals(2, g.inDegreeOf(3)); assertEquals(2, g.inDegreeOf(4)); assertEquals(2, g.inDegreeOf(5)); assertEquals(Set.of(), g.incomingEdgesOf(1)); assertEquals(Set.of(e12, e52), g.incomingEdgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.incomingEdgesOf(3)); assertEquals(Set.of(e24, e44), g.incomingEdgesOf(4)); assertEquals(Set.of(e55_1, e55_2), g.incomingEdgesOf(5)); assertEquals(1, g.outDegreeOf(1)); assertEquals(3, g.outDegreeOf(2)); assertEquals(0, g.outDegreeOf(3)); assertEquals(1, g.outDegreeOf(4)); assertEquals(3, g.outDegreeOf(5)); assertEquals(Set.of(e12), g.outgoingEdgesOf(1)); assertEquals(Set.of(e23_1, e23_2, e24), g.outgoingEdgesOf(2)); assertEquals(Set.of(), g.outgoingEdgesOf(3)); assertEquals(Set.of(e44), g.outgoingEdgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.outgoingEdgesOf(5)); } /** * Test the most general version of the directed graph. */ @Test public void testDirectedGraph() { testDirectedGraph(() -> new DirectedPseudograph<>(DefaultEdge.class)); testAddDuplicateEdgeDirectedGraph( () -> GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeDirectedGraph( () -> GraphTypeBuilder .directed().allowingMultipleEdges(true).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeDirectedGraph( () -> GraphTypeBuilder .directed().allowingMultipleEdges(false).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeDirectedGraph( () -> GraphTypeBuilder .directed().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); } public static void testAddDuplicateEdgeUndirectedGraph( Supplier> graphSupplier) { Graph g = graphSupplier.get(); assertTrue(g.getType().isUndirected()); g.addVertex(1); g.addVertex(2); g.addVertex(3); DefaultEdge e = new DefaultEdge(); g.addEdge(1, 2, e); assertTrue(g.edgeSet().size() == 1); assertEquals(Set.of(e), g.edgesOf(1)); assertEquals(1, g.degreeOf(1)); assertEquals(Set.of(e), g.edgesOf(2)); assertEquals(1, g.degreeOf(2)); assertEquals(Collections.emptySet(), g.edgesOf(3)); assertEquals(0, g.degreeOf(3)); assertThrows(IntrusiveEdgeException.class, () -> g.addEdge(1, 3, e)); assertTrue(g.edgeSet().size() == 1); assertEquals(Set.of(e), g.edgesOf(1)); assertEquals(1, g.degreeOf(1)); assertEquals(Set.of(e), g.edgesOf(2)); assertEquals(1, g.degreeOf(2)); assertEquals(Collections.emptySet(), g.edgesOf(3)); assertEquals(0, g.degreeOf(3)); } /** * Test the most general version of the undirected graph. */ public static void testUndirectedGraph(Supplier> graphSupplier) { Graph g = graphSupplier.get(); g.addVertex(1); g.addVertex(2); g.addVertex(3); g.addVertex(4); g.addVertex(5); assertEquals(5, g.vertexSet().size()); assertTrue(g.containsVertex(1)); assertTrue(g.containsVertex(2)); assertTrue(g.containsVertex(3)); assertTrue(g.containsVertex(4)); assertTrue(g.containsVertex(5)); DefaultEdge e12 = g.addEdge(1, 2); DefaultEdge e23_1 = g.addEdge(2, 3); DefaultEdge e23_2 = g.addEdge(2, 3); DefaultEdge e24 = g.addEdge(2, 4); DefaultEdge e44 = g.addEdge(4, 4); DefaultEdge e55_1 = g.addEdge(5, 5); DefaultEdge e52 = g.addEdge(5, 2); DefaultEdge e55_2 = g.addEdge(5, 5); assertEquals(1, g.degreeOf(1)); assertEquals(5, g.degreeOf(2)); assertEquals(2, g.degreeOf(3)); assertEquals(3, g.degreeOf(4)); assertEquals(5, g.degreeOf(5)); assertEquals(Set.of(e12), g.edgesOf(1)); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.edgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.edgesOf(3)); assertEquals(Set.of(e24, e44), g.edgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.edgesOf(5)); assertEquals(1, g.inDegreeOf(1)); assertEquals(5, g.inDegreeOf(2)); assertEquals(2, g.inDegreeOf(3)); assertEquals(3, g.inDegreeOf(4)); assertEquals(5, g.inDegreeOf(5)); assertEquals(Set.of(e12), g.incomingEdgesOf(1)); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.incomingEdgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.incomingEdgesOf(3)); assertEquals(Set.of(e24, e44), g.incomingEdgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.incomingEdgesOf(5)); assertEquals(1, g.outDegreeOf(1)); assertEquals(5, g.outDegreeOf(2)); assertEquals(2, g.outDegreeOf(3)); assertEquals(3, g.outDegreeOf(4)); assertEquals(5, g.outDegreeOf(5)); assertEquals(Set.of(e12), g.outgoingEdgesOf(1)); assertEquals(Set.of(e12, e23_1, e23_2, e24, e52), g.outgoingEdgesOf(2)); assertEquals(Set.of(e23_1, e23_2), g.outgoingEdgesOf(3)); assertEquals(Set.of(e24, e44), g.outgoingEdgesOf(4)); assertEquals(Set.of(e52, e55_1, e55_2), g.outgoingEdgesOf(5)); } /** * Test the most general version of the undirected graph. */ @Test public void testUndirectedGraph() { testUndirectedGraph(() -> new Pseudograph<>(DefaultEdge.class)); testAddDuplicateEdgeUndirectedGraph( () -> GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeUndirectedGraph( () -> GraphTypeBuilder .undirected().allowingMultipleEdges(true).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeUndirectedGraph( () -> GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(true) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); testAddDuplicateEdgeUndirectedGraph( () -> GraphTypeBuilder .undirected().allowingMultipleEdges(false).allowingSelfLoops(false) .vertexSupplier(SupplierUtil.createIntegerSupplier()) .edgeSupplier(SupplierUtil.DEFAULT_EDGE_SUPPLIER).buildGraph()); } } jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/sparse/000077500000000000000000000000001402514743400271725ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-opt/src/test/java/org/jgrapht/opt/graph/sparse/SparseIntGraphTest.java000066400000000000000000001055111402514743400335720ustar00rootroot00000000000000/* * (C) Copyright 2019-2021, by Dimitrios Michail and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.opt.graph.sparse; import org.jgrapht.*; import org.jgrapht.alg.util.*; import org.junit.*; import java.util.*; import java.util.function.*; import java.util.stream.*; import static org.junit.Assert.*; /** * Tests * * @author Dimitrios Michail */ public class SparseIntGraphTest { @Test public void testUndirected() { testUndirected((vc, edges) -> new SparseIntUndirectedGraph(vc, edges)); } @Test public void testUndirectedWithLoops() { testUndirectedWithLoops((vc, edges) -> new SparseIntUndirectedGraph(vc, edges)); } @Test public void testUndirectedWeighted() { testUndirectedWeighted((vc, edges) -> new SparseIntUndirectedWeightedGraph(vc, edges)); } @Test public void testDirected() { testDirected((vc, edges) -> new SparseIntDirectedGraph(vc, edges)); } @Test public void testDirectedLazyIncoming() { testDirected((vc, edges) -> new SparseIntDirectedGraph(vc, edges, IncomingEdgesSupport.LAZY_INCOMING_EDGES)); } @Test public void testDirectedLazyNoIncoming() { testDirectedNoIncoming((vc, edges) -> new SparseIntDirectedGraph(vc, edges, IncomingEdgesSupport.NO_INCOMING_EDGES)); } @Test(expected = UnsupportedOperationException.class) public void testDirectedLazyNoIncomingFail() { testDirected((vc, edges) -> new SparseIntDirectedGraph(vc, edges, IncomingEdgesSupport.NO_INCOMING_EDGES)); } @Test public void testDirectedWeighted() { testDirectedWeighted((vc, edges) -> new SparseIntDirectedWeightedGraph(vc, edges)); } public static void testUndirected( BiFunction>, Graph> graphSupplier) { final Integer vertexCount = 6; List> edges = Arrays .asList( Pair.of(0, 5), Pair.of(0, 2), Pair.of(3, 4), Pair.of(1, 4), Pair.of(0, 1), Pair.of(3, 1), Pair.of(2, 4)); Graph g = graphSupplier.apply(vertexCount, edges); assertEquals(vertexCount.intValue(), g.vertexSet().size()); assertTrue(g.containsVertex(0)); assertTrue(g.containsVertex(1)); assertTrue(g.containsVertex(2)); assertTrue(g.containsVertex(3)); assertTrue(g.containsVertex(4)); assertTrue(g.containsVertex(5)); assertEquals(3, g.degreeOf(0)); assertEquals(3, g.inDegreeOf(0)); assertEquals(3, g.outDegreeOf(0)); assertEquals(Set.of(0, 1, 4), g.edgesOf(0)); assertEquals(Set.of(0, 1, 4), g.incomingEdgesOf(0)); assertEquals(Set.of(0, 1, 4), g.outgoingEdgesOf(0)); assertEquals(3, g.degreeOf(1)); assertEquals(3, g.inDegreeOf(1)); assertEquals(3, g.outDegreeOf(1)); assertEquals(Set.of(3, 4, 5), g.edgesOf(1)); assertEquals(Set.of(3, 4, 5), g.incomingEdgesOf(1)); assertEquals(Set.of(3, 4, 5), g.outgoingEdgesOf(1)); assertEquals(2, g.degreeOf(2)); assertEquals(2, g.inDegreeOf(2)); assertEquals(2, g.outDegreeOf(2)); assertEquals(Set.of(1, 6), g.edgesOf(2)); assertEquals(Set.of(1, 6), g.incomingEdgesOf(2)); assertEquals(Set.of(1, 6), g.outgoingEdgesOf(2)); assertEquals(2, g.degreeOf(3)); assertEquals(2, g.inDegreeOf(3)); assertEquals(2, g.outDegreeOf(3)); assertEquals(Set.of(2, 5), g.edgesOf(3)); assertEquals(Set.of(2, 5), g.incomingEdgesOf(3)); assertEquals(Set.of(2, 5), g.outgoingEdgesOf(3)); assertEquals(3, g.degreeOf(4)); assertEquals(3, g.inDegreeOf(4)); assertEquals(3, g.outDegreeOf(4)); assertEquals(Set.of(2, 3, 6), g.edgesOf(4)); assertEquals(Set.of(2, 3, 6), g.incomingEdgesOf(4)); assertEquals(Set.of(2, 3, 6), g.outgoingEdgesOf(4)); assertEquals(1, g.degreeOf(5)); assertEquals(1, g.inDegreeOf(5)); assertEquals(1, g.outDegreeOf(5)); assertEquals(Set.of(0), g.edgesOf(5)); assertEquals(Set.of(0), g.incomingEdgesOf(5)); assertEquals(Set.of(0), g.outgoingEdgesOf(5)); assertEquals(Integer.valueOf(0), g.getEdgeSource(0)); assertEquals(Integer.valueOf(5), g.getEdgeTarget(0)); assertEquals(Integer.valueOf(0), g.getEdgeSource(1)); assertEquals(Integer.valueOf(2), g.getEdgeTarget(1)); assertEquals(Integer.valueOf(3), g.getEdgeSource(2)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(2)); assertEquals(Integer.valueOf(1), g.getEdgeSource(3)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(3)); assertEquals(Integer.valueOf(0), g.getEdgeSource(4)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(4)); assertEquals(Integer.valueOf(3), g.getEdgeSource(5)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(5)); assertEquals(Integer.valueOf(2), g.getEdgeSource(6)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(6)); assertEquals( IntStream.range(0, edges.size()).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.edgeSet()); assertEquals( IntStream.range(0, 6).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.vertexSet()); GraphType type = g.getType(); assertTrue(type.isAllowingCycles()); assertTrue(type.isAllowingMultipleEdges()); assertTrue(type.isAllowingSelfLoops()); assertTrue(type.isUndirected()); assertFalse(type.isModifiable()); assertFalse(type.isDirected()); assertFalse(type.isMixed()); assertFalse(type.isWeighted()); int i = 0; for (Pair p : edges) { assertEquals(Integer.valueOf(i), g.getEdge(p.getFirst(), p.getSecond())); i++; } int j = 0; for (Pair p : edges) { Set edgeSet = Collections.singleton(Integer.valueOf(j)); assertEquals(edgeSet, g.getAllEdges(p.getFirst(), p.getSecond())); j++; } } public static void testUndirectedWithLoops( BiFunction>, Graph> graphSupplier) { final int vertexCount = 4; List> edges = Arrays .asList( Pair.of(0, 0), Pair.of(0, 1), Pair.of(0, 2), Pair.of(0, 0), Pair.of(0, 1), Pair.of(1, 1), Pair.of(1, 2)); Graph g = graphSupplier.apply(vertexCount, edges); assertEquals(4, g.vertexSet().size()); assertTrue(g.containsVertex(0)); assertTrue(g.containsVertex(1)); assertTrue(g.containsVertex(2)); assertTrue(g.containsVertex(3)); assertEquals(7, g.degreeOf(0)); assertEquals(7, g.inDegreeOf(0)); assertEquals(7, g.outDegreeOf(0)); assertEquals(Set.of(0, 3, 1, 4, 2), g.edgesOf(0)); assertEquals(Set.of(0, 3, 1, 4, 2), g.incomingEdgesOf(0)); assertEquals(Set.of(0, 3, 1, 4, 2), g.outgoingEdgesOf(0)); assertEquals(5, g.degreeOf(1)); assertEquals(5, g.inDegreeOf(1)); assertEquals(5, g.outDegreeOf(1)); assertEquals(Set.of(1, 4, 5, 6), g.edgesOf(1)); assertEquals(Set.of(1, 4, 5, 6), g.incomingEdgesOf(1)); assertEquals(Set.of(1, 4, 5, 6), g.outgoingEdgesOf(1)); assertEquals(2, g.degreeOf(2)); assertEquals(2, g.inDegreeOf(2)); assertEquals(2, g.outDegreeOf(2)); assertEquals(Set.of(2, 6), g.edgesOf(2)); assertEquals(Set.of(2, 6), g.incomingEdgesOf(2)); assertEquals(Set.of(2, 6), g.outgoingEdgesOf(2)); assertEquals(0, g.degreeOf(3)); assertEquals(0, g.inDegreeOf(3)); assertEquals(0, g.outDegreeOf(3)); assertEquals(Set.of(), g.edgesOf(3)); assertEquals(Set.of(), g.incomingEdgesOf(3)); assertEquals(Set.of(), g.outgoingEdgesOf(3)); assertEquals(Integer.valueOf(0), g.getEdgeSource(0)); assertEquals(Integer.valueOf(0), g.getEdgeTarget(0)); assertEquals(Integer.valueOf(0), g.getEdgeSource(1)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(1)); assertEquals(Integer.valueOf(0), g.getEdgeSource(2)); assertEquals(Integer.valueOf(2), g.getEdgeTarget(2)); assertEquals(Integer.valueOf(0), g.getEdgeSource(3)); assertEquals(Integer.valueOf(0), g.getEdgeTarget(3)); assertEquals(Integer.valueOf(0), g.getEdgeSource(4)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(4)); assertEquals(Integer.valueOf(1), g.getEdgeSource(5)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(5)); assertEquals(Integer.valueOf(1), g.getEdgeSource(6)); assertEquals(Integer.valueOf(2), g.getEdgeTarget(6)); assertEquals( IntStream.range(0, edges.size()).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.edgeSet()); assertEquals( IntStream.range(0, 4).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.vertexSet()); GraphType type = g.getType(); assertTrue(type.isAllowingCycles()); assertTrue(type.isAllowingMultipleEdges()); assertTrue(type.isAllowingSelfLoops()); assertTrue(type.isUndirected()); assertFalse(type.isModifiable()); assertFalse(type.isDirected()); assertFalse(type.isMixed()); assertFalse(type.isWeighted()); } public static void testUndirectedWeighted( BiFunction>, Graph> graphSupplier) { final Integer vertexCount = 6; List> edges = Arrays .asList( Triple.of(0, 5, 1d), Triple.of(0, 2, 2d), Triple.of(3, 4, 3d), Triple.of(1, 4, 4d), Triple.of(0, 1, 5d), Triple.of(3, 1, 6d), Triple.of(2, 4, 7d)); Graph g = graphSupplier.apply(vertexCount, edges); assertEquals(6, g.vertexSet().size()); assertTrue(g.containsVertex(0)); assertTrue(g.containsVertex(1)); assertTrue(g.containsVertex(2)); assertTrue(g.containsVertex(3)); assertTrue(g.containsVertex(4)); assertTrue(g.containsVertex(5)); assertEquals(3, g.degreeOf(0)); assertEquals(3, g.inDegreeOf(0)); assertEquals(3, g.outDegreeOf(0)); assertEquals(Set.of(0, 1, 4), g.edgesOf(0)); assertEquals(Set.of(0, 1, 4), g.incomingEdgesOf(0)); assertEquals(Set.of(0, 1, 4), g.outgoingEdgesOf(0)); assertEquals(3, g.degreeOf(1)); assertEquals(3, g.inDegreeOf(1)); assertEquals(3, g.outDegreeOf(1)); assertEquals(Set.of(3, 4, 5), g.edgesOf(1)); assertEquals(Set.of(3, 4, 5), g.incomingEdgesOf(1)); assertEquals(Set.of(3, 4, 5), g.outgoingEdgesOf(1)); assertEquals(2, g.degreeOf(2)); assertEquals(2, g.inDegreeOf(2)); assertEquals(2, g.outDegreeOf(2)); assertEquals(Set.of(1, 6), g.edgesOf(2)); assertEquals(Set.of(1, 6), g.incomingEdgesOf(2)); assertEquals(Set.of(1, 6), g.outgoingEdgesOf(2)); assertEquals(2, g.degreeOf(3)); assertEquals(2, g.inDegreeOf(3)); assertEquals(2, g.outDegreeOf(3)); assertEquals(Set.of(2, 5), g.edgesOf(3)); assertEquals(Set.of(2, 5), g.incomingEdgesOf(3)); assertEquals(Set.of(2, 5), g.outgoingEdgesOf(3)); assertEquals(3, g.degreeOf(4)); assertEquals(3, g.inDegreeOf(4)); assertEquals(3, g.outDegreeOf(4)); assertEquals(Set.of(2, 3, 6), g.edgesOf(4)); assertEquals(Set.of(2, 3, 6), g.incomingEdgesOf(4)); assertEquals(Set.of(2, 3, 6), g.outgoingEdgesOf(4)); assertEquals(1, g.degreeOf(5)); assertEquals(1, g.inDegreeOf(5)); assertEquals(1, g.outDegreeOf(5)); assertEquals(Set.of(0), g.edgesOf(5)); assertEquals(Set.of(0), g.incomingEdgesOf(5)); assertEquals(Set.of(0), g.outgoingEdgesOf(5)); assertEquals(Integer.valueOf(0), g.getEdgeSource(0)); assertEquals(Integer.valueOf(5), g.getEdgeTarget(0)); assertEquals(1d, g.getEdgeWeight(0), 1e-16); assertEquals(Integer.valueOf(0), g.getEdgeSource(1)); assertEquals(Integer.valueOf(2), g.getEdgeTarget(1)); assertEquals(2d, g.getEdgeWeight(1), 1e-16); assertEquals(Integer.valueOf(3), g.getEdgeSource(2)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(2)); assertEquals(3d, g.getEdgeWeight(2), 1e-16); assertEquals(Integer.valueOf(1), g.getEdgeSource(3)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(3)); assertEquals(4d, g.getEdgeWeight(3), 1e-16); assertEquals(Integer.valueOf(0), g.getEdgeSource(4)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(4)); assertEquals(5d, g.getEdgeWeight(4), 1e-16); assertEquals(Integer.valueOf(3), g.getEdgeSource(5)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(5)); assertEquals(6d, g.getEdgeWeight(5), 1e-16); assertEquals(Integer.valueOf(2), g.getEdgeSource(6)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(6)); assertEquals(7d, g.getEdgeWeight(6), 1e-16); assertEquals(5d, g.getEdgeWeight(4), 1e-16); g.setEdgeWeight(4, 14d); assertEquals(14d, g.getEdgeWeight(4), 1e-16); assertEquals( IntStream.range(0, edges.size()).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.edgeSet()); assertEquals( IntStream.range(0, 6).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.vertexSet()); GraphType type = g.getType(); assertTrue(type.isAllowingCycles()); assertTrue(type.isAllowingMultipleEdges()); assertTrue(type.isAllowingSelfLoops()); assertTrue(type.isUndirected()); assertFalse(type.isModifiable()); assertFalse(type.isDirected()); assertFalse(type.isMixed()); assertTrue(type.isWeighted()); int i = 0; for (Triple p : edges) { assertEquals(Integer.valueOf(i), g.getEdge(p.getFirst(), p.getSecond())); i++; } int j = 0; for (Triple p : edges) { Set edgeSet = Collections.singleton(Integer.valueOf(j)); assertEquals(edgeSet, g.getAllEdges(p.getFirst(), p.getSecond())); j++; } } public static void testDirected( BiFunction>, Graph> graphSupplier) { final Integer vertexCount = 8; List> edges = Arrays .asList( Pair.of(0, 1), Pair.of(1, 0), Pair.of(1, 4), Pair.of(1, 5), Pair.of(1, 6), Pair.of(2, 4), Pair.of(2, 4), Pair.of(2, 4), Pair.of(3, 4), Pair.of(4, 5), Pair.of(5, 6), Pair.of(7, 6), Pair.of(7, 7)); Graph g = graphSupplier.apply(vertexCount, edges); assertEquals(vertexCount.intValue(), g.vertexSet().size()); assertEquals(edges.size(), g.edgeSet().size()); assertEquals( IntStream.range(0, edges.size()).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.edgeSet()); assertEquals( IntStream.range(0, vertexCount).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.vertexSet()); for (int i = 0; i < vertexCount; i++) { assertTrue(g.containsVertex(i)); } assertEquals(2, g.degreeOf(0)); assertEquals(1, g.inDegreeOf(0)); assertEquals(1, g.outDegreeOf(0)); assertEquals(Set.of(0, 1), g.edgesOf(0)); assertEquals(Set.of(1), g.incomingEdgesOf(0)); assertEquals(Set.of(0), g.outgoingEdgesOf(0)); assertEquals(5, g.degreeOf(1)); assertEquals(1, g.inDegreeOf(1)); assertEquals(4, g.outDegreeOf(1)); assertEquals(Set.of(0, 1, 2, 3, 4), g.edgesOf(1)); assertEquals(Set.of(0), g.incomingEdgesOf(1)); assertEquals(Set.of(1, 2, 3, 4), g.outgoingEdgesOf(1)); assertEquals(3, g.degreeOf(2)); assertEquals(0, g.inDegreeOf(2)); assertEquals(3, g.outDegreeOf(2)); assertEquals(Set.of(5, 6, 7), g.edgesOf(2)); assertEquals(Set.of(), g.incomingEdgesOf(2)); assertEquals(Set.of(5, 6, 7), g.outgoingEdgesOf(2)); assertEquals(1, g.degreeOf(3)); assertEquals(0, g.inDegreeOf(3)); assertEquals(1, g.outDegreeOf(3)); assertEquals(Set.of(8), g.edgesOf(3)); assertEquals(Set.of(), g.incomingEdgesOf(3)); assertEquals(Set.of(8), g.outgoingEdgesOf(3)); assertEquals(6, g.degreeOf(4)); assertEquals(5, g.inDegreeOf(4)); assertEquals(1, g.outDegreeOf(4)); assertEquals(Set.of(2, 5, 6, 7, 8, 9), g.edgesOf(4)); assertEquals(Set.of(2, 5, 6, 7, 8), g.incomingEdgesOf(4)); assertEquals(Set.of(9), g.outgoingEdgesOf(4)); assertEquals(3, g.degreeOf(5)); assertEquals(2, g.inDegreeOf(5)); assertEquals(1, g.outDegreeOf(5)); assertEquals(Set.of(3, 9, 10), g.edgesOf(5)); assertEquals(Set.of(3, 9), g.incomingEdgesOf(5)); assertEquals(Set.of(10), g.outgoingEdgesOf(5)); assertEquals(3, g.degreeOf(6)); assertEquals(3, g.inDegreeOf(6)); assertEquals(0, g.outDegreeOf(6)); assertEquals(Set.of(4, 10, 11), g.edgesOf(6)); assertEquals(Set.of(4, 10, 11), g.incomingEdgesOf(6)); assertEquals(Set.of(), g.outgoingEdgesOf(6)); assertEquals(3, g.degreeOf(7)); assertEquals(1, g.inDegreeOf(7)); assertEquals(2, g.outDegreeOf(7)); assertEquals(Set.of(11, 12), g.edgesOf(7)); assertEquals(Set.of(12), g.incomingEdgesOf(7)); assertEquals(Set.of(11, 12), g.outgoingEdgesOf(7)); assertEquals(Integer.valueOf(0), g.getEdgeSource(0)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(0)); assertEquals(Integer.valueOf(1), g.getEdgeSource(1)); assertEquals(Integer.valueOf(0), g.getEdgeTarget(1)); assertEquals(Integer.valueOf(1), g.getEdgeSource(2)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(2)); assertEquals(Integer.valueOf(1), g.getEdgeSource(3)); assertEquals(Integer.valueOf(5), g.getEdgeTarget(3)); assertEquals(Integer.valueOf(1), g.getEdgeSource(4)); assertEquals(Integer.valueOf(6), g.getEdgeTarget(4)); assertEquals(Integer.valueOf(2), g.getEdgeSource(5)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(5)); assertEquals(Integer.valueOf(2), g.getEdgeSource(6)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(6)); assertEquals(Integer.valueOf(2), g.getEdgeSource(7)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(7)); assertEquals(Integer.valueOf(3), g.getEdgeSource(8)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(8)); assertEquals(Integer.valueOf(4), g.getEdgeSource(9)); assertEquals(Integer.valueOf(5), g.getEdgeTarget(9)); assertEquals(Integer.valueOf(5), g.getEdgeSource(10)); assertEquals(Integer.valueOf(6), g.getEdgeTarget(10)); assertEquals(Integer.valueOf(7), g.getEdgeSource(11)); assertEquals(Integer.valueOf(6), g.getEdgeTarget(11)); assertEquals(Integer.valueOf(7), g.getEdgeSource(12)); assertEquals(Integer.valueOf(7), g.getEdgeTarget(12)); GraphType type = g.getType(); assertTrue(type.isAllowingCycles()); assertTrue(type.isAllowingMultipleEdges()); assertTrue(type.isAllowingSelfLoops()); assertTrue(type.isDirected()); assertFalse(type.isModifiable()); assertFalse(type.isUndirected()); assertFalse(type.isMixed()); assertFalse(type.isWeighted()); assertEquals(Integer.valueOf(0), g.getEdge(0, 1)); assertEquals(Set.of(0), g.getAllEdges(0, 1)); assertEquals(Integer.valueOf(1), g.getEdge(1, 0)); assertEquals(Set.of(1), g.getAllEdges(1, 0)); assertEquals(Integer.valueOf(2), g.getEdge(1, 4)); assertEquals(Set.of(2), g.getAllEdges(1, 4)); assertEquals(Integer.valueOf(3), g.getEdge(1, 5)); assertEquals(Set.of(3), g.getAllEdges(1, 5)); assertEquals(Integer.valueOf(4), g.getEdge(1, 6)); assertEquals(Set.of(4), g.getAllEdges(1, 6)); assertEquals(Integer.valueOf(5), g.getEdge(2, 4)); assertEquals(Set.of(5, 6, 7), g.getAllEdges(2, 4)); assertEquals(Integer.valueOf(8), g.getEdge(3, 4)); assertEquals(Set.of(8), g.getAllEdges(3, 4)); assertEquals(Integer.valueOf(9), g.getEdge(4, 5)); assertEquals(Set.of(9), g.getAllEdges(4, 5)); assertEquals(Integer.valueOf(10), g.getEdge(5, 6)); assertEquals(Set.of(10), g.getAllEdges(5, 6)); assertEquals(Integer.valueOf(11), g.getEdge(7, 6)); assertEquals(Set.of(11), g.getAllEdges(7, 6)); assertEquals(Integer.valueOf(12), g.getEdge(7, 7)); assertEquals(Set.of(12), g.getAllEdges(7, 7)); } public static void testDirectedNoIncoming( BiFunction>, Graph> graphSupplier) { final Integer vertexCount = 8; List> edges = Arrays .asList( Pair.of(0, 1), Pair.of(1, 0), Pair.of(1, 4), Pair.of(1, 5), Pair.of(1, 6), Pair.of(2, 4), Pair.of(2, 4), Pair.of(2, 4), Pair.of(3, 4), Pair.of(4, 5), Pair.of(5, 6), Pair.of(7, 6), Pair.of(7, 7)); Graph g = graphSupplier.apply(vertexCount, edges); assertEquals(vertexCount.intValue(), g.vertexSet().size()); assertEquals(edges.size(), g.edgeSet().size()); assertEquals( IntStream.range(0, edges.size()).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.edgeSet()); assertEquals( IntStream.range(0, vertexCount).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.vertexSet()); for (int i = 0; i < vertexCount; i++) { assertTrue(g.containsVertex(i)); } assertEquals(1, g.outDegreeOf(0)); assertEquals(new HashSet<>(Arrays.asList(0)), g.outgoingEdgesOf(0)); assertEquals(4, g.outDegreeOf(1)); assertEquals(new HashSet<>(Arrays.asList(1, 2, 3, 4)), g.outgoingEdgesOf(1)); assertEquals(3, g.outDegreeOf(2)); assertEquals(new HashSet<>(Arrays.asList(5, 6, 7)), g.outgoingEdgesOf(2)); assertEquals(1, g.outDegreeOf(3)); assertEquals(new HashSet<>(Arrays.asList(8)), g.outgoingEdgesOf(3)); assertEquals(1, g.outDegreeOf(4)); assertEquals(new HashSet<>(Arrays.asList(9)), g.outgoingEdgesOf(4)); assertEquals(1, g.outDegreeOf(5)); assertEquals(new HashSet<>(Arrays.asList(10)), g.outgoingEdgesOf(5)); assertEquals(0, g.outDegreeOf(6)); assertEquals(new HashSet<>(), g.outgoingEdgesOf(6)); assertEquals(2, g.outDegreeOf(7)); assertEquals(new HashSet<>(Arrays.asList(11, 12)), g.outgoingEdgesOf(7)); assertEquals(Integer.valueOf(0), g.getEdgeSource(0)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(0)); assertEquals(Integer.valueOf(1), g.getEdgeSource(1)); assertEquals(Integer.valueOf(0), g.getEdgeTarget(1)); assertEquals(Integer.valueOf(1), g.getEdgeSource(2)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(2)); assertEquals(Integer.valueOf(1), g.getEdgeSource(3)); assertEquals(Integer.valueOf(5), g.getEdgeTarget(3)); assertEquals(Integer.valueOf(1), g.getEdgeSource(4)); assertEquals(Integer.valueOf(6), g.getEdgeTarget(4)); assertEquals(Integer.valueOf(2), g.getEdgeSource(5)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(5)); assertEquals(Integer.valueOf(2), g.getEdgeSource(6)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(6)); assertEquals(Integer.valueOf(2), g.getEdgeSource(7)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(7)); assertEquals(Integer.valueOf(3), g.getEdgeSource(8)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(8)); assertEquals(Integer.valueOf(4), g.getEdgeSource(9)); assertEquals(Integer.valueOf(5), g.getEdgeTarget(9)); assertEquals(Integer.valueOf(5), g.getEdgeSource(10)); assertEquals(Integer.valueOf(6), g.getEdgeTarget(10)); assertEquals(Integer.valueOf(7), g.getEdgeSource(11)); assertEquals(Integer.valueOf(6), g.getEdgeTarget(11)); assertEquals(Integer.valueOf(7), g.getEdgeSource(12)); assertEquals(Integer.valueOf(7), g.getEdgeTarget(12)); GraphType type = g.getType(); assertTrue(type.isAllowingCycles()); assertTrue(type.isAllowingMultipleEdges()); assertTrue(type.isAllowingSelfLoops()); assertTrue(type.isDirected()); assertFalse(type.isModifiable()); assertFalse(type.isUndirected()); assertFalse(type.isMixed()); assertFalse(type.isWeighted()); assertEquals(Integer.valueOf(0), g.getEdge(0, 1)); assertEquals(Collections.singleton(Integer.valueOf(0)), g.getAllEdges(0, 1)); assertEquals(Integer.valueOf(1), g.getEdge(1, 0)); assertEquals(Collections.singleton(Integer.valueOf(1)), g.getAllEdges(1, 0)); assertEquals(Integer.valueOf(2), g.getEdge(1, 4)); assertEquals(Collections.singleton(Integer.valueOf(2)), g.getAllEdges(1, 4)); assertEquals(Integer.valueOf(3), g.getEdge(1, 5)); assertEquals(Collections.singleton(Integer.valueOf(3)), g.getAllEdges(1, 5)); assertEquals(Integer.valueOf(4), g.getEdge(1, 6)); assertEquals(Collections.singleton(Integer.valueOf(4)), g.getAllEdges(1, 6)); assertEquals(Integer.valueOf(5), g.getEdge(2, 4)); assertEquals(new HashSet<>(Arrays.asList(5, 6, 7)), g.getAllEdges(2, 4)); assertEquals(Integer.valueOf(8), g.getEdge(3, 4)); assertEquals(Collections.singleton(Integer.valueOf(8)), g.getAllEdges(3, 4)); assertEquals(Integer.valueOf(9), g.getEdge(4, 5)); assertEquals(Collections.singleton(Integer.valueOf(9)), g.getAllEdges(4, 5)); assertEquals(Integer.valueOf(10), g.getEdge(5, 6)); assertEquals(Collections.singleton(Integer.valueOf(10)), g.getAllEdges(5, 6)); assertEquals(Integer.valueOf(11), g.getEdge(7, 6)); assertEquals(Collections.singleton(Integer.valueOf(11)), g.getAllEdges(7, 6)); assertEquals(Integer.valueOf(12), g.getEdge(7, 7)); assertEquals(Collections.singleton(Integer.valueOf(12)), g.getAllEdges(7, 7)); } public static void testDirectedWeighted( BiFunction>, Graph> graphSupplier) { List> edges = Arrays .asList( Triple.of(0, 1, 0d), Triple.of(1, 0, 1d), Triple.of(1, 4, 2d), Triple.of(1, 5, 3d), Triple.of(1, 6, 4d), Triple.of(2, 4, 5d), Triple.of(2, 4, 6d), Triple.of(2, 4, 7d), Triple.of(3, 4, 8d), Triple.of(4, 5, 9d), Triple.of(5, 6, 10d), Triple.of(7, 6, 11d), Triple.of(7, 7, 12d)); int vertices = 8; Graph g = graphSupplier.apply(vertices, edges); assertEquals(vertices, g.vertexSet().size()); assertEquals(edges.size(), g.edgeSet().size()); assertEquals( IntStream.range(0, edges.size()).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.edgeSet()); assertEquals( IntStream.range(0, vertices).mapToObj(Integer::valueOf).collect(Collectors.toSet()), g.vertexSet()); for (int i = 0; i < vertices; i++) { assertTrue(g.containsVertex(i)); } assertEquals(2, g.degreeOf(0)); assertEquals(1, g.inDegreeOf(0)); assertEquals(1, g.outDegreeOf(0)); assertEquals(Set.of(0, 1), g.edgesOf(0)); assertEquals(Set.of(1), g.incomingEdgesOf(0)); assertEquals(Set.of(0), g.outgoingEdgesOf(0)); assertEquals(5, g.degreeOf(1)); assertEquals(1, g.inDegreeOf(1)); assertEquals(4, g.outDegreeOf(1)); assertEquals(Set.of(0, 1, 2, 3, 4), g.edgesOf(1)); assertEquals(Set.of(0), g.incomingEdgesOf(1)); assertEquals(Set.of(1, 2, 3, 4), g.outgoingEdgesOf(1)); assertEquals(3, g.degreeOf(2)); assertEquals(0, g.inDegreeOf(2)); assertEquals(3, g.outDegreeOf(2)); assertEquals(Set.of(5, 6, 7), g.edgesOf(2)); assertEquals(Set.of(), g.incomingEdgesOf(2)); assertEquals(Set.of(5, 6, 7), g.outgoingEdgesOf(2)); assertEquals(1, g.degreeOf(3)); assertEquals(0, g.inDegreeOf(3)); assertEquals(1, g.outDegreeOf(3)); assertEquals(Set.of(8), g.edgesOf(3)); assertEquals(Set.of(), g.incomingEdgesOf(3)); assertEquals(Set.of(8), g.outgoingEdgesOf(3)); assertEquals(6, g.degreeOf(4)); assertEquals(5, g.inDegreeOf(4)); assertEquals(1, g.outDegreeOf(4)); assertEquals(Set.of(2, 5, 6, 7, 8, 9), g.edgesOf(4)); assertEquals(Set.of(2, 5, 6, 7, 8), g.incomingEdgesOf(4)); assertEquals(Set.of(9), g.outgoingEdgesOf(4)); assertEquals(3, g.degreeOf(5)); assertEquals(2, g.inDegreeOf(5)); assertEquals(1, g.outDegreeOf(5)); assertEquals(Set.of(3, 9, 10), g.edgesOf(5)); assertEquals(Set.of(3, 9), g.incomingEdgesOf(5)); assertEquals(Set.of(10), g.outgoingEdgesOf(5)); assertEquals(3, g.degreeOf(6)); assertEquals(3, g.inDegreeOf(6)); assertEquals(0, g.outDegreeOf(6)); assertEquals(Set.of(4, 10, 11), g.edgesOf(6)); assertEquals(Set.of(4, 10, 11), g.incomingEdgesOf(6)); assertEquals(Set.of(), g.outgoingEdgesOf(6)); assertEquals(3, g.degreeOf(7)); assertEquals(1, g.inDegreeOf(7)); assertEquals(2, g.outDegreeOf(7)); assertEquals(Set.of(11, 12), g.edgesOf(7)); assertEquals(Set.of(12), g.incomingEdgesOf(7)); assertEquals(Set.of(11, 12), g.outgoingEdgesOf(7)); assertEquals(Integer.valueOf(0), g.getEdgeSource(0)); assertEquals(Integer.valueOf(1), g.getEdgeTarget(0)); assertEquals(0d, g.getEdgeWeight(0), 1e-16); assertEquals(Integer.valueOf(1), g.getEdgeSource(1)); assertEquals(Integer.valueOf(0), g.getEdgeTarget(1)); assertEquals(1d, g.getEdgeWeight(1), 1e-16); assertEquals(Integer.valueOf(1), g.getEdgeSource(2)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(2)); assertEquals(2d, g.getEdgeWeight(2), 1e-16); assertEquals(Integer.valueOf(1), g.getEdgeSource(3)); assertEquals(Integer.valueOf(5), g.getEdgeTarget(3)); assertEquals(3d, g.getEdgeWeight(3), 1e-16); assertEquals(Integer.valueOf(1), g.getEdgeSource(4)); assertEquals(Integer.valueOf(6), g.getEdgeTarget(4)); assertEquals(4d, g.getEdgeWeight(4), 1e-16); assertEquals(Integer.valueOf(2), g.getEdgeSource(5)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(5)); assertEquals(5d, g.getEdgeWeight(5), 1e-16); assertEquals(Integer.valueOf(2), g.getEdgeSource(6)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(6)); assertEquals(6d, g.getEdgeWeight(6), 1e-16); assertEquals(Integer.valueOf(2), g.getEdgeSource(7)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(7)); assertEquals(7d, g.getEdgeWeight(7), 1e-16); assertEquals(Integer.valueOf(3), g.getEdgeSource(8)); assertEquals(Integer.valueOf(4), g.getEdgeTarget(8)); assertEquals(8d, g.getEdgeWeight(8), 1e-16); assertEquals(Integer.valueOf(4), g.getEdgeSource(9)); assertEquals(Integer.valueOf(5), g.getEdgeTarget(9)); assertEquals(9d, g.getEdgeWeight(9), 1e-16); assertEquals(Integer.valueOf(5), g.getEdgeSource(10)); assertEquals(Integer.valueOf(6), g.getEdgeTarget(10)); assertEquals(10d, g.getEdgeWeight(10), 1e-16); assertEquals(Integer.valueOf(7), g.getEdgeSource(11)); assertEquals(Integer.valueOf(6), g.getEdgeTarget(11)); assertEquals(11d, g.getEdgeWeight(11), 1e-16); assertEquals(Integer.valueOf(7), g.getEdgeSource(12)); assertEquals(Integer.valueOf(7), g.getEdgeTarget(12)); assertEquals(12d, g.getEdgeWeight(12), 1e-16); for (int i = 0; i < edges.size(); i++) { assertEquals(Double.valueOf(i), g.getEdgeWeight(i), 1e-16); g.setEdgeWeight(i, 100 + g.getEdgeWeight(i)); assertEquals(Double.valueOf(i) + 100, g.getEdgeWeight(i), 1e-16); } GraphType type = g.getType(); assertTrue(type.isAllowingCycles()); assertTrue(type.isAllowingMultipleEdges()); assertTrue(type.isAllowingSelfLoops()); assertTrue(type.isDirected()); assertTrue(type.isWeighted()); assertFalse(type.isModifiable()); assertFalse(type.isUndirected()); assertFalse(type.isMixed()); assertEquals(Integer.valueOf(0), g.getEdge(0, 1)); assertEquals(Set.of(0), g.getAllEdges(0, 1)); assertEquals(Integer.valueOf(1), g.getEdge(1, 0)); assertEquals(Set.of(1), g.getAllEdges(1, 0)); assertEquals(Integer.valueOf(2), g.getEdge(1, 4)); assertEquals(Set.of(2), g.getAllEdges(1, 4)); assertEquals(Integer.valueOf(3), g.getEdge(1, 5)); assertEquals(Set.of(3), g.getAllEdges(1, 5)); assertEquals(Integer.valueOf(4), g.getEdge(1, 6)); assertEquals(Set.of(4), g.getAllEdges(1, 6)); assertEquals(Integer.valueOf(5), g.getEdge(2, 4)); assertEquals(Set.of(5, 6, 7), g.getAllEdges(2, 4)); assertEquals(Integer.valueOf(8), g.getEdge(3, 4)); assertEquals(Set.of(8), g.getAllEdges(3, 4)); assertEquals(Integer.valueOf(9), g.getEdge(4, 5)); assertEquals(Set.of(9), g.getAllEdges(4, 5)); assertEquals(Integer.valueOf(10), g.getEdge(5, 6)); assertEquals(Set.of(10), g.getAllEdges(5, 6)); assertEquals(Integer.valueOf(11), g.getEdge(7, 6)); assertEquals(Set.of(11), g.getAllEdges(7, 6)); assertEquals(Integer.valueOf(12), g.getEdge(7, 7)); assertEquals(Set.of(12), g.getAllEdges(7, 7)); } } jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/000077500000000000000000000000001402514743400201515ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/pom.xml000066400000000000000000000151521402514743400214720ustar00rootroot00000000000000 4.0.0 org.jgrapht jgrapht 1.5.1 jgrapht-unimi-dsi JGraphT - Unimi dsi ${project.parent.basedir} GNU Lesser General Public License Version 2.1, February 1999 http://jgrapht.sourceforge.net/LGPL.html repo Eclipse Public License (EPL) 2.0 http://www.eclipse.org/legal/epl-v20.html repo org.apache.maven.plugins maven-jar-plugin ${project.build.outputDirectory}/META-INF/MANIFEST.MF org.apache.maven.plugins maven-resources-plugin org.apache.felix maven-bundle-plugin ${project.groupId} jgrapht-core ${project.groupId} jgrapht-opt it.unimi.dsi fastutil 8.5.2 it.unimi.dsi dsiutils 2.6.17 org.apache.commons commons-configuration2 org.apache.commons commons-text commons-logging commons-logging org.apache.commons commons-lang3 it.unimi.dsi sux4j 5.2.3 org.apache.commons commons-configuration2 org.apache.commons commons-text commons-logging commons-logging org.apache.commons commons-lang3 org.apache.commons commons-collections4 it.unimi.dsi webgraph 3.6.10 net.sf.jung * org.apache.commons commons-configuration2 org.apache.commons commons-text commons-logging commons-logging org.apache.commons commons-lang3 it.unimi.dsi webgraph-big 3.6.6 net.sf.jung * org.apache.commons commons-configuration2 org.apache.commons commons-text commons-logging commons-logging org.apache.commons commons-lang3 junit junit test jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/000077500000000000000000000000001402514743400207405ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/000077500000000000000000000000001402514743400216645ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/000077500000000000000000000000001402514743400226055ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/module-info.java000066400000000000000000000007071402514743400256720ustar00rootroot00000000000000module org.jgrapht.unimi.dsi { exports org.jgrapht.webgraph; exports org.jgrapht.sux4j; requires transitive org.jgrapht.core; requires transitive org.jgrapht.opt; requires transitive it.unimi.dsi.fastutil; requires transitive it.unimi.dsi.webgraph; requires transitive it.unimi.dsi.big.webgraph; requires transitive it.unimi.dsi.dsiutils; requires transitive it.unimi.dsi.sux4j; requires transitive com.google.common; } jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/000077500000000000000000000000001402514743400233745ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/000077500000000000000000000000001402514743400250335ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/000077500000000000000000000000001402514743400261105ustar00rootroot00000000000000AbstractSuccinctDirectedGraph.java000066400000000000000000000152561402514743400345720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import java.util.Arrays; import java.util.NoSuchElementException; import java.util.function.Function; import org.jgrapht.Graph; import org.jgrapht.GraphType; import org.jgrapht.Graphs; import org.jgrapht.graph.DefaultGraphType; import it.unimi.dsi.bits.Fast; import it.unimi.dsi.fastutil.ints.IntArrays; import it.unimi.dsi.fastutil.longs.LongIterator; /** * An abstract base class for all succinct directed implementations. * *

    * Two subclasses, {@link CumulativeSuccessors} and {@link CumulativeDegrees}, generate the monotone * lists that will be encoded using the Elias–Fano representation. * *

    * First, we store the monotone lists of cumulative outdegrees and indegrees. * *

    * Then, we store the outgoing edges x → y as a monotone * sequence using the encoding x2⌈log n + * y. At that point the k-th edge can be obtained by retrieving the * k-th element of the sequence and some bit shifting (the encoding * xn + y would be slightly more compact, but much slower to * decode). Since we know the list of cumulative outdegrees, we know which range of indices * corresponds to the edges outgoing from each vertex. If we need to know whether * x → y is an edge we just look for * x2⌈log n + y in the sequence. * *

    * Finally, we store incoming edges y → x again as a monotone * sequence using the encoding xn + y - e, where * e is the index of the edge in lexicographical order. In this case we just need to be * able to recover the edges associated with a vertex, so we can use a more compact format. * *

    * However, in the case of a {@link SuccinctIntDirectedGraph} after retrieving the source and target * of an incoming edge we need to index it. The slow indexing of the incoming edges is the reason * why a {@link SuccinctIntDirectedGraph} enumerates incoming edges very slowly, whereas a * {@link SuccinctDirectedGraph} does not. * * @param the graph edge type */ public abstract class AbstractSuccinctDirectedGraph extends AbstractSuccinctGraph { private static final long serialVersionUID = 0L; public AbstractSuccinctDirectedGraph(final int n, final int m) { super(n, m); } /** * Turns all edges x → y into a monotone sequence using the * encoding x2⌈log n + y, or the * encoding xn + y - e, where e is the * index of the edge in lexicographical order, depending on the value of the {@code strict} * parameter. * * @param the graph edge type */ protected final static class CumulativeSuccessors implements LongIterator { private final Graph graph; private final long n; private final int sourceShift; private final Function> succ; private final boolean strict; private int x = -1, d, i, e; private long next = -1; private int[] s = IntArrays.EMPTY_ARRAY; protected CumulativeSuccessors( final Graph graph, final Function> succ, final boolean strict) { this.n = graph.iterables().vertexCount(); this.sourceShift = Fast.ceilLog2(n); this.graph = graph; this.succ = succ; this.strict = strict; } @Override public boolean hasNext() { if (next != -1) return true; if (x == n) return false; while (i == d) { if (++x == n) return false; int d = 0; for (final E e : succ.apply(x)) { s = IntArrays.grow(s, d + 1); s[d++] = Graphs.getOppositeVertex(graph, e, x); } Arrays.sort(s, 0, d); this.d = d; i = 0; } // The predecessor list will not be indexed, so we can gain a few bits of space by // subtracting the edge position in the list next = strict ? s[i] + ((long) x << sourceShift) : s[i] + x * n - e++; i++; return true; } @Override public long nextLong() { if (!hasNext()) throw new NoSuchElementException(); final long result = next; next = -1; return result; } } /** * Iterates over the cumulative degrees (starts with a zero). */ protected final static class CumulativeDegrees implements LongIterator { private final Function degreeOf; private final int n; private int i = -1; private long cumul = 0; protected CumulativeDegrees(final int n, final Function degreeOf) { this.n = n; this.degreeOf = degreeOf; } @Override public boolean hasNext() { return i < n; } @Override public long nextLong() { if (!hasNext()) throw new NoSuchElementException(); if (i == -1) return ++i; return cumul += degreeOf.apply(i++); } } @Override public int degreeOf(final Integer vertex) { return inDegreeOf(vertex) + outDegreeOf(vertex); } @Override public GraphType getType() { return new DefaultGraphType.Builder() .directed().weighted(false).modifiable(false).allowMultipleEdges(false) .allowSelfLoops(true).build(); } } jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/AbstractSuccinctGraph.java000066400000000000000000000105521402514743400331770ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import java.io.Serializable; import java.util.Set; import java.util.function.Supplier; import org.jgrapht.graph.AbstractGraph; import it.unimi.dsi.bits.Fast; import it.unimi.dsi.fastutil.ints.IntSets; import it.unimi.dsi.fastutil.objects.ObjectSets; /** * An abstract base class for all succinct implementations. * *

    * This class provides mutators throwing {@link UnsupportedOperationException} and operations * depending only on the number of vertices and edges. * * @param the graph edge type */ public abstract class AbstractSuccinctGraph extends AbstractGraph implements Serializable { private static final long serialVersionUID = 0L; protected static final String UNMODIFIABLE = "this graph is unmodifiable"; /** The number of vertices in the graph. */ protected final int n; /** The number of edges in the graph. */ protected final int m; /** The shift used to read sources in the successor list. */ protected final int sourceShift; /** The mask used to read targets in the successor list (lowest {@link #sourceShift} bits). */ protected final long targetMask; public AbstractSuccinctGraph(final int n, final int m) { super(); this.n = n; this.m = m; sourceShift = Fast.ceilLog2(n); targetMask = (1L << sourceShift) - 1; } @Override public Set vertexSet() { return IntSets.fromTo(0, n); } /** * Ensures that the specified vertex exists in this graph, or else throws exception. * * @param v vertex * @return true if this assertion holds. * @throws IllegalArgumentException if specified vertex does not exist in this graph. */ @Override protected boolean assertVertexExist(final Integer v) { if (v < 0 || v >= n) throw new IllegalArgumentException(); return true; } @Override public boolean containsVertex(final Integer v) { return v >= 0 && v < n; } @Override public Set getAllEdges(final Integer sourceVertex, final Integer targetVertex) { final E edge = getEdge(sourceVertex, targetVertex); return edge == null ? ObjectSets.emptySet() : ObjectSets.singleton(edge); } @Override public Supplier getVertexSupplier() { return null; } @Override public Supplier getEdgeSupplier() { return null; } @Override public E addEdge(final Integer sourceVertex, final Integer targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public boolean addEdge(final Integer sourceVertex, final Integer targetVertex, final E e) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public Integer addVertex() { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public boolean addVertex(final Integer v) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public E removeEdge(final Integer sourceVertex, final Integer targetVertex) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public boolean removeEdge(final E e) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public boolean removeVertex(final Integer v) { throw new UnsupportedOperationException(UNMODIFIABLE); } @Override public double getEdgeWeight(final E e) { return 1.0; } @Override public void setEdgeWeight(final E e, final double weight) { throw new UnsupportedOperationException(UNMODIFIABLE); } } AbstractSuccinctUndirectedGraph.java000066400000000000000000000175041402514743400351330ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import java.util.Arrays; import java.util.NoSuchElementException; import java.util.function.Function; import org.jgrapht.Graph; import org.jgrapht.GraphType; import org.jgrapht.Graphs; import org.jgrapht.graph.DefaultGraphType; import it.unimi.dsi.bits.Fast; import it.unimi.dsi.fastutil.ints.IntArrays; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.sux4j.util.EliasFanoIndexedMonotoneLongBigList; /** * An abstract base class for all succinct undirected implementations. * *

    * Two subclasses, {@link CumulativeSuccessors} and {@link CumulativeDegrees}, generate the monotone * lists that will be encoded using the Elias–Fano representation. * *

    * We use the representation described in {@link AbstractSuccinctDirectedGraph} applied to the * directed graph obtained by choosing the direction x → y for * an edge x — y if x ≤ y * (loops are represented twice). Each edge now appears exactly once in the list of outgoing edges, * and thus can be indexed as in the directed base. * *

    * The set of vertices adjacent to a given vertex can then be retrieved by enumerating both outgoing * and incoming edges, being careful to avoid enumerating twice loops. * *

    * However, in the case of a {@link SuccinctIntUndirectedGraph} after retrieving the source and * target of an incoming edge we need to index it. The slow indexing of the incoming edges is the * reason why a {@link SuccinctIntUndirectedGraph} enumerates edges very slowly, whereas a * {@link SuccinctUndirectedGraph} does not. * * @param the graph edge type */ public abstract class AbstractSuccinctUndirectedGraph extends AbstractSuccinctGraph { private static final long serialVersionUID = 0L; public AbstractSuccinctUndirectedGraph(final int n, final int m) { super(n, m); } /** * Turns all edges x — y, * x ≤ y, into a monotone sequence using the encoding * x2⌈log n + y, or all edges * x — y, x ≥ y, using * the encoding xn + y - e, where e is * the index of the edge in lexicographical order, depending on the value of the {@code sorted} * parameter. * * @param the graph edge type */ protected final static class CumulativeSuccessors implements LongIterator { private final Graph graph; private final long n; private final int sourceShift; private final Function> succ; private final boolean sorted; private int x = -1, d, i, e; private long next = -1; private int[] s = IntArrays.EMPTY_ARRAY; protected CumulativeSuccessors( final Graph graph, final boolean sorted, final Function> succ) { this.n = (int) graph.iterables().vertexCount(); this.sourceShift = Fast.ceilLog2(n); this.graph = graph; this.sorted = sorted; this.succ = succ; } @Override public boolean hasNext() { if (next != -1) return true; if (x == n) return false; while (i == d) { if (++x == n) return false; int d = 0; for (final E e : succ.apply(x)) { final int y = Graphs.getOppositeVertex(graph, e, x); if (sorted) { if (x <= y) { s = IntArrays.grow(s, d + 1); s[d++] = y; } } else { if (x >= y) { s = IntArrays.grow(s, d + 1); s[d++] = y; } } } Arrays.sort(s, 0, d); this.d = d; i = 0; } // The predecessor list will not be indexed, so we can gain a few bits of space by // subtracting the edge position in the list next = sorted ? s[i] + ((long) x << sourceShift) : s[i] + x * n - e++; i++; return true; } @Override public long nextLong() { if (!hasNext()) throw new NoSuchElementException(); final long result = next; next = -1; return result; } } /** * Iterates over the cumulative degrees (starts with a zero). Depending on the value of * {@code sorted}, only edges whose adjacent vertex is greater than or equal to the base vertex * (or vice versa) are included. * * @param the graph edge type */ protected final static class CumulativeDegrees implements LongIterator { private final int n; private int x = -1; private long cumul = 0; private final Function> succ; private final boolean sorted; private final Graph graph; protected CumulativeDegrees( final Graph graph, final boolean sorted, final Function> succ) { this.n = (int) graph.iterables().vertexCount(); this.graph = graph; this.succ = succ; this.sorted = sorted; } @Override public boolean hasNext() { return x < n; } @Override public long nextLong() { if (!hasNext()) throw new NoSuchElementException(); if (x == -1) return ++x; int d = 0; if (sorted) { for (final E e : succ.apply(x)) if (x <= Graphs.getOppositeVertex(graph, e, x)) d++; } else { for (final E e : succ.apply(x)) if (x >= Graphs.getOppositeVertex(graph, e, x)) d++; } x++; return cumul += d; } } @Override public int inDegreeOf(final Integer vertex) { return degreeOf(vertex); } @Override public int outDegreeOf(final Integer vertex) { return degreeOf(vertex); } protected boolean containsEdge( final EliasFanoIndexedMonotoneLongBigList successors, int x, int y) { if (x > y) { final int t = x; x = y; y = t; } return successors.indexOfUnsafe(((long) x << sourceShift) + y) != -1; } @Override public GraphType getType() { return new DefaultGraphType.Builder() .directed().weighted(false).modifiable(false).allowMultipleEdges(false) .allowSelfLoops(true).build(); } } jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctDirectedGraph.java000066400000000000000000000447271402514743400331720ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import java.io.Serializable; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; import org.jgrapht.Graph; import org.jgrapht.GraphIterables; import org.jgrapht.alg.util.Pair; import org.jgrapht.opt.graph.sparse.IncomingEdgesSupport; import org.jgrapht.opt.graph.sparse.SparseIntDirectedGraph; import com.google.common.collect.Iterables; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.longs.LongBigListIterator; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.sux4j.util.EliasFanoIndexedMonotoneLongBigList; import it.unimi.dsi.sux4j.util.EliasFanoIndexedMonotoneLongBigList.EliasFanoIndexedMonotoneLongBigListIterator; import it.unimi.dsi.sux4j.util.EliasFanoMonotoneLongBigList; /** * An immutable directed graph with {@link IntIntPair} edges represented using quasi-succinct data * structures. * *

    * The graph representation of this implementation uses the {@linkplain EliasFanoMonotoneLongBigList * Elias–Fano representation of monotone sequences} to represent the positions of ones in the * (linearized) adjacency matrix of the graph. Edges are represented by instances of * {@link IntIntPair}. Instances are serializable and thread safe. * *

    * If the vertex set is compact (i.e., vertices are numbered from 0 consecutively), space usage will * be close to twice the information-theoretical lower bound (typically, a few times smaller than a * {@link SparseIntDirectedGraph}). If you {@link #SuccinctDirectedGraph(Graph, boolean) drop * support for incoming edges} the space will close to the information-theoretical lower bound . * *

    * All accessors are very fast. {@linkplain org.jgrapht.Graph#containsEdge(Object) Adjacency tests} * are very fast and happen in almost constant time. * *

    * {@link SuccinctIntDirectedGraph} is a much slower implementation with a similar footprint using * {@link Integer} as edge type. Please read the {@linkplain org.jgrapht.sux4j class documentation} * for more information. * *

    * For convenience, and as a compromise with the approach of {@link SuccinctIntDirectedGraph}, this * class provides methods {@link org.jgrapht.sux4j.SuccinctDirectedGraph#getEdgeFromIndex(long) * getEdgeFromIndex()} and * {@link org.jgrapht.sux4j.SuccinctDirectedGraph#getIndexFromEdge(it.unimi.dsi.fastutil.ints.IntIntPair) * getIndexFromEdge()} that map bijectively the edge set into a contiguous set of longs. * * @author Sebastiano Vigna * @see SuccinctIntDirectedGraph */ public class SuccinctDirectedGraph extends AbstractSuccinctDirectedGraph implements Serializable { private static final long serialVersionUID = 0L; /** The cumulative list of outdegrees. */ private final EliasFanoIndexedMonotoneLongBigList cumulativeOutdegrees; /** The cumulative list of indegrees. */ private final EliasFanoMonotoneLongBigList cumulativeIndegrees; /** The cumulative list of successor lists. */ private final EliasFanoIndexedMonotoneLongBigList successors; /** The cumulative list of predecessor lists. */ private final EliasFanoMonotoneLongBigList predecessors; /** * Creates a new immutable succinct directed graph from a given directed graph, choosing whether * to support incoming edges. * * @param graph a directed graph: for good results, vertices should be numbered consecutively * starting from 0. * @param incomingEdgesSupport whether to support incoming edges or not. * @param the graph edge type */ public < E> SuccinctDirectedGraph(final Graph graph, final boolean incomingEdgesSupport) { super((int) graph.iterables().vertexCount(), (int) graph.iterables().edgeCount()); if (graph.getType().isUndirected()) throw new IllegalArgumentException("This class supports directed graphs only"); assert graph.getType().isDirected(); final GraphIterables iterables = graph.iterables(); if (iterables.vertexCount() > Integer.MAX_VALUE) throw new IllegalArgumentException( "The number of nodes (" + iterables.vertexCount() + ") is greater than " + Integer.MAX_VALUE); if (iterables.edgeCount() > Integer.MAX_VALUE) throw new IllegalArgumentException( "The number of edges (" + iterables.edgeCount() + ") is greater than " + Integer.MAX_VALUE); cumulativeOutdegrees = new EliasFanoIndexedMonotoneLongBigList( n + 1, m, new CumulativeDegrees(n, graph::outDegreeOf)); assert cumulativeOutdegrees.getLong(cumulativeOutdegrees.size64() - 1) == m; successors = new EliasFanoIndexedMonotoneLongBigList( m, (long) n << sourceShift, new CumulativeSuccessors<>(graph, iterables::outgoingEdgesOf, true)); if (incomingEdgesSupport) { cumulativeIndegrees = new EliasFanoMonotoneLongBigList( n + 1, m, new CumulativeDegrees(n, graph::inDegreeOf)); assert cumulativeIndegrees.getLong(cumulativeIndegrees.size64() - 1) == m; predecessors = new EliasFanoIndexedMonotoneLongBigList( m, (long) n * n - m, new CumulativeSuccessors<>(graph, iterables::incomingEdgesOf, false)); } else { cumulativeIndegrees = predecessors = null; } } /** * Creates a new immutable succinct directed graph from a given directed graph, supporting both * outgoing and incoming edges. * * @param graph a directed graph: for good results, vertices should be numbered consecutively * starting from 0. * @param the graph edge type */ public SuccinctDirectedGraph(final Graph graph) { this(graph, true); } /** * Creates a new immutable succinct directed graph from an edge list, choosing whether to * support incoming edges. * *

    * This constructor just builds a {@link SparseIntDirectedGraph} and delegates to the * {@linkplain #SuccinctDirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param edges the edge list. * @param incomingEdgesSupport whether to support incoming edges or not. * @see #SuccinctDirectedGraph(Graph) */ public SuccinctDirectedGraph( final int numVertices, final List> edges, final boolean incomingEdgesSupport) { this(new SparseIntDirectedGraph(numVertices, edges, incomingEdgesSupport ? IncomingEdgesSupport.FULL_INCOMING_EDGES : IncomingEdgesSupport.NO_INCOMING_EDGES)); } /** * Creates a new immutable succinct directed graph from an edge list, supporting both outgoing * and incoming edges. *

    * This constructor just builds a {@link SparseIntDirectedGraph} and delegates to the * {@linkplain #SuccinctDirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param edges the edge list. * @see #SuccinctDirectedGraph(Graph) */ public SuccinctDirectedGraph(final int numVertices, final List> edges) { this(numVertices, edges, true); } /** * Creates a new immutable succinct directed graph from a supplier of streams of edges, choosing * whether to support incoming edges. * *

    * This constructor just builds a {@link SparseIntDirectedGraph} and delegates to the * {@linkplain #SuccinctDirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param numEdges the number of edges. * @param edges a supplier of streams of edges. * @param incomingEdgesSupport whether to support incoming edges or not. * @see #SuccinctDirectedGraph(Graph) */ public SuccinctDirectedGraph( final int numVertices, final int numEdges, final Supplier>> edges, final boolean incomingEdgesSupport) { this( new SparseIntDirectedGraph( numVertices, numEdges, edges, incomingEdgesSupport ? IncomingEdgesSupport.FULL_INCOMING_EDGES : IncomingEdgesSupport.NO_INCOMING_EDGES)); } /** * Creates a new immutable succinct directed graph from a supplier of streams of edges, * supporting both outgoing and incoming edges. * *

    * This constructor just builds a {@link SparseIntDirectedGraph} and delegates to the * {@linkplain #SuccinctDirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param numEdges the number of edges. * @param edges a supplier of streams of edges. * @see #SuccinctDirectedGraph(Graph) */ public SuccinctDirectedGraph( final int numVertices, final int numEdges, final Supplier>> edges) { this(numVertices, numEdges, edges, true); } @Override public boolean containsEdge(final IntIntPair e) { return successors.indexOfUnsafe(((long) e.firstInt() << sourceShift) + e.secondInt()) != -1; } @Override public Set edgeSet() { return new ObjectOpenHashSet<>(iterables().edges().iterator()); } @Override public Set edgesOf(final Integer vertex) { final Set result = outgoingEdgesOf(vertex); result.addAll(incomingEdgesOf(vertex)); return result; } @Override public int inDegreeOf(final Integer vertex) { assertVertexExist(vertex); return (int) cumulativeIndegrees.getDelta(vertex); } @Override public Set incomingEdgesOf(final Integer target) { assertVertexExist(target); final int t = target; final long[] result = new long[2]; cumulativeIndegrees.get(t, result); final int d = (int) (result[1] - result[0]); final LongBigListIterator iterator = predecessors.listIterator(result[0]); final ObjectOpenHashSet s = new ObjectOpenHashSet<>(); long base = (long) n * t - result[0]; for (int i = d; i-- != 0;) { final long source = iterator.nextLong() - base--; s.add(IntIntPair.of((int) source, t)); } return s; } @Override public int outDegreeOf(final Integer vertex) { assertVertexExist(vertex); return (int) cumulativeOutdegrees.getDelta(vertex); } @Override public Set outgoingEdgesOf(final Integer vertex) { assertVertexExist(vertex); final int x = vertex; final long[] result = new long[2]; cumulativeOutdegrees.get(x, result); final ObjectOpenHashSet s = new ObjectOpenHashSet<>(); final LongBigListIterator iterator = successors.listIterator(result[0]); final long base = (long) x << sourceShift; for (int d = (int) (result[1] - result[0]); d-- != 0;) s.add(IntIntPair.of(x, (int) (iterator.nextLong() - base))); return s; } @Override public Integer getEdgeSource(final IntIntPair e) { return e.firstInt(); } @Override public Integer getEdgeTarget(final IntIntPair e) { return e.secondInt(); } /** * Returns the index associated with the given edge. * * @param e an edge of the graph. * @return the index associated with the edge, or −1 if the edge is not part of the graph. * @see #getEdgeFromIndex(long) */ public long getIndexFromEdge(final IntIntPair e) { final int source = e.firstInt(); final int target = e.secondInt(); if (source < 0 || source >= n || target < 0 || target >= n) throw new IllegalArgumentException(); return successors.indexOfUnsafe(((long) source << sourceShift) + target); } /** * Returns the edge with given index. * * @param i an index between 0 (included) and the number of edges (excluded). * @return the pair with index {@code i}. * @see #getIndexFromEdge(IntIntPair) */ public IntIntPair getEdgeFromIndex(final long i) { if (i < 0 || i >= m) throw new IllegalArgumentException(); final long t = successors.getLong(i); return IntIntPair.of((int) (t >>> sourceShift), (int) (t & targetMask)); } @Override public IntIntPair getEdge(final Integer sourceVertex, final Integer targetVertex) { final long index = successors.indexOfUnsafe(((long) sourceVertex << sourceShift) + targetVertex); return index != -1 ? IntIntPair.of(sourceVertex, targetVertex) : null; } @Override public boolean containsEdge(final Integer sourceVertex, final Integer targetVertex) { return successors.indexOfUnsafe(((long) sourceVertex << sourceShift) + targetVertex) != -1; } private final static class SuccinctGraphIterables implements GraphIterables, Serializable { private static final long serialVersionUID = 0L; private final SuccinctDirectedGraph graph; private SuccinctGraphIterables() { graph = null; } private SuccinctGraphIterables(final SuccinctDirectedGraph graph) { this.graph = graph; } @Override public Graph getGraph() { return graph; } @Override public long vertexCount() { return graph.n; } @Override public long edgeCount() { return graph.m; } @Override public Iterable edges() { final int sourceShift = graph.sourceShift; final long targetMask = graph.targetMask; return () -> new Iterator<>() { private final EliasFanoIndexedMonotoneLongBigListIterator iterator = graph.successors.iterator(); private final int n = graph.n; @Override public boolean hasNext() { return iterator.hasNext(); } @Override public IntIntPair next() { final long t = iterator.nextLong(); return IntIntPair.of((int) (t >>> sourceShift), (int) (t & targetMask)); } }; } @Override public Iterable edgesOf(final Integer source) { return Iterables.concat(outgoingEdgesOf(source), incomingEdgesOf(source, true)); } private Iterable incomingEdgesOf(final int target, final boolean skipLoops) { final SuccinctDirectedGraph graph = this.graph; final long[] result = new long[2]; graph.cumulativeIndegrees.get(target, result); final int d = (int) (result[1] - result[0]); final EliasFanoIndexedMonotoneLongBigList successors = graph.successors; final LongBigListIterator iterator = graph.predecessors.listIterator(result[0]); return () -> new Iterator<>() { int i = d; IntIntPair edge = null; long n = graph.n; long base = target * n - result[0]; @Override public boolean hasNext() { if (edge == null && i > 0) { i--; final long source = iterator.nextLong() - base--; if (skipLoops && source == target && i-- != 0) return false; edge = IntIntPair.of((int) source, target); } return edge != null; } @Override public IntIntPair next() { if (!hasNext()) throw new NoSuchElementException(); final IntIntPair result = edge; edge = null; return result; } }; } @Override public Iterable outgoingEdgesOf(final Integer vertex) { final int sourceShift = graph.sourceShift; final long targetMask = graph.targetMask; graph.assertVertexExist(vertex); final int x = vertex; final long[] result = new long[2]; graph.cumulativeOutdegrees.get(x, result); final LongBigListIterator iterator = graph.successors.listIterator(result[0]); final long base = (long) x << sourceShift; return () -> new Iterator<>() { private int d = (int) (result[1] - result[0]); @Override public boolean hasNext() { return d != 0; } @Override public IntIntPair next() { if (d == 0) throw new NoSuchElementException(); d--; return IntIntPair.of(x, (int) (iterator.nextLong() - base)); } }; } @Override public Iterable incomingEdgesOf(final Integer vertex) { graph.assertVertexExist(vertex); return incomingEdgesOf(vertex, false); } } private final GraphIterables ITERABLES = new SuccinctGraphIterables(this); @Override public GraphIterables iterables() { return ITERABLES; } } SuccinctIntDirectedGraph.java000066400000000000000000000376661402514743400335720ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import java.io.Serializable; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Stream; import org.jgrapht.Graph; import org.jgrapht.GraphIterables; import org.jgrapht.alg.util.Pair; import org.jgrapht.opt.graph.sparse.IncomingEdgesSupport; import org.jgrapht.opt.graph.sparse.SparseIntDirectedGraph; import com.google.common.collect.Iterables; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSets; import it.unimi.dsi.fastutil.longs.LongBigListIterator; import it.unimi.dsi.sux4j.util.EliasFanoIndexedMonotoneLongBigList; import it.unimi.dsi.sux4j.util.EliasFanoMonotoneLongBigList; /** * An immutable directed graph with {@link Integer} edges represented using quasi-succinct data * structures. * *

    * The graph representation of this implementation is similar to that of * {@link SparseIntDirectedGraph}: nodes and edges are initial intervals of the natural numbers. * Under the hood, however, this class uses the {@linkplain EliasFanoMonotoneLongBigList * Elias–Fano representation of monotone sequences} to represent the positions of ones in the * (linearized) adjacency matrix of the graph. Instances are serializable and thread safe. * *

    * If the vertex set is compact (i.e., vertices are numbered from 0 consecutively), space usage will * be close to twice the information-theoretical lower bound (typically, a few times smaller than a * {@link SparseIntDirectedGraph}). If you {@link #SuccinctIntDirectedGraph(Graph, boolean) drop * support for incoming edges} the space will close to the information-theoretical lower bound . * *

    * {@linkplain org.jgrapht.GraphIterables#outgoingEdgesOf(Object) Enumeration of outgoing edges} is * quite fast, but {@linkplain org.jgrapht.GraphIterables#incomingEdgesOf(Object) enumeration of * incoming edges} is very slow. {@linkplain org.jgrapht.Graph#containsEdge(Object) Adjacency tests} * are very fast and happen in almost constant time. * *

    * {@link SuccinctDirectedGraph} is a much faster implementation with a similar footprint using * {@link IntIntPair} as edge type. Please read the {@linkplain org.jgrapht.sux4j class * documentation} for more information. * * @author Sebastiano Vigna * @see SuccinctDirectedGraph */ public class SuccinctIntDirectedGraph extends AbstractSuccinctDirectedGraph implements Serializable { private static final long serialVersionUID = 0L; /** The cumulative list of outdegrees. */ private final EliasFanoIndexedMonotoneLongBigList cumulativeOutdegrees; /** The cumulative list of indegrees. */ private final EliasFanoMonotoneLongBigList cumulativeIndegrees; /** The cumulative list of successor lists. */ private final EliasFanoIndexedMonotoneLongBigList successors; /** The cumulative list of predecessor lists. */ private final EliasFanoMonotoneLongBigList predecessors; /** * Creates a new immutable succinct directed graph from a given directed graph, choosing whether * to support incoming edges. * * @param graph a directed graph: for good results, vertices should be numbered consecutively * starting from 0. * @param incomingEdgesSupport whether to support incoming edges or not. * @param the graph edge type */ public SuccinctIntDirectedGraph( final Graph graph, final boolean incomingEdgesSupport) { super((int) graph.iterables().vertexCount(), (int) graph.iterables().edgeCount()); if (graph.getType().isUndirected()) throw new IllegalArgumentException("This class supports directed graphs only"); assert graph.getType().isDirected(); final GraphIterables iterables = graph.iterables(); if (iterables.vertexCount() > Integer.MAX_VALUE) throw new IllegalArgumentException( "The number of nodes (" + iterables.vertexCount() + ") is greater than " + Integer.MAX_VALUE); if (iterables.edgeCount() > Integer.MAX_VALUE) throw new IllegalArgumentException( "The number of edges (" + iterables.edgeCount() + ") is greater than " + Integer.MAX_VALUE); cumulativeOutdegrees = new EliasFanoIndexedMonotoneLongBigList( n + 1, m, new CumulativeDegrees(n, graph::outDegreeOf)); assert cumulativeOutdegrees.getLong(cumulativeOutdegrees.size64() - 1) == m; successors = new EliasFanoIndexedMonotoneLongBigList( m, (long) n << sourceShift, new CumulativeSuccessors<>(graph, iterables::outgoingEdgesOf, true)); if (incomingEdgesSupport) { cumulativeIndegrees = new EliasFanoMonotoneLongBigList( n + 1, m, new CumulativeDegrees(n, graph::inDegreeOf)); assert cumulativeIndegrees.getLong(cumulativeIndegrees.size64() - 1) == m; predecessors = new EliasFanoIndexedMonotoneLongBigList( m, (long) n * n - m, new CumulativeSuccessors<>(graph, iterables::incomingEdgesOf, false)); } else { cumulativeIndegrees = predecessors = null; } } /** * Creates a new immutable succinct directed graph from a given directed graph, supporting both * outgoing and incoming edges. * * @param graph a directed graph: for good results, vertices should be numbered consecutively * starting from 0. * @param the graph edge type */ public SuccinctIntDirectedGraph(final Graph graph) { this(graph, true); } /** * Creates a new immutable succinct directed graph from an edge list, choosing whether to * support incoming edges. * *

    * This constructor just builds a {@link SparseIntDirectedGraph} and delegates to the * {@linkplain #SuccinctIntDirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param edges the edge list. * @param incomingEdgesSupport whether to support incoming edges or not. * @see #SuccinctIntDirectedGraph(Graph) */ public SuccinctIntDirectedGraph( final int numVertices, final List> edges, final boolean incomingEdgesSupport) { this( new SparseIntDirectedGraph( numVertices, edges, incomingEdgesSupport ? IncomingEdgesSupport.FULL_INCOMING_EDGES : IncomingEdgesSupport.NO_INCOMING_EDGES)); } /** * Creates a new immutable succinct directed graph from an edge list, supporting both outgoing * and incoming edges. *

    * This constructor just builds a {@link SparseIntDirectedGraph} and delegates to the * {@linkplain #SuccinctIntDirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param edges the edge list. * @see #SuccinctIntDirectedGraph(Graph) */ public SuccinctIntDirectedGraph(final int numVertices, final List> edges) { this(numVertices, edges, true); } /** * Creates a new immutable succinct directed graph from a supplier of streams of edges, choosing * whether to support incoming edges. * *

    * This constructor just builds a {@link SparseIntDirectedGraph} and delegates to the * {@linkplain #SuccinctIntDirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param numEdges the number of edges. * @param edges a supplier of streams of edges. * @param incomingEdgesSupport whether to support incoming edges or not. * @see #SuccinctIntDirectedGraph(Graph) */ public SuccinctIntDirectedGraph( final int numVertices, final int numEdges, final Supplier>> edges, final boolean incomingEdgesSupport) { this( new SparseIntDirectedGraph( numVertices, numEdges, edges, incomingEdgesSupport ? IncomingEdgesSupport.FULL_INCOMING_EDGES : IncomingEdgesSupport.NO_INCOMING_EDGES)); } /** * Creates a new immutable succinct directed graph from a supplier of streams of edges, * supporting both outgoing and incoming edges. * *

    * This constructor just builds a {@link SparseIntDirectedGraph} and delegates to the * {@linkplain #SuccinctIntDirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param numEdges the number of edges. * @param edges a supplier of streams of edges. * @see #SuccinctIntDirectedGraph(Graph) */ public SuccinctIntDirectedGraph( final int numVertices, final int numEdges, final Supplier>> edges) { this(numVertices, numEdges, edges, true); } @Override public boolean containsEdge(final Integer e) { return e >= 0 && e < m; } @Override public Set edgeSet() { return IntSets.fromTo(0, m); } @Override public IntSet edgesOf(final Integer vertex) { final IntSet result = outgoingEdgesOf(vertex); result.addAll(incomingEdgesOf(vertex)); return result; } @Override public int inDegreeOf(final Integer vertex) { assertVertexExist(vertex); return (int) cumulativeIndegrees.getDelta(vertex); } @Override public IntSet incomingEdgesOf(final Integer target) { assertVertexExist(target); final int t = target; final long[] result = new long[2]; cumulativeIndegrees.get(t, result); final int d = (int) (result[1] - result[0]); final LongBigListIterator iterator = predecessors.listIterator(result[0]); final IntOpenHashSet s = new IntOpenHashSet(); long base = (long) n * t - result[0]; for (int i = d; i-- != 0;) { final long source = iterator.nextLong() - base--; final int e = (int) (successors.successorIndexUnsafe((source << sourceShift) + t)); assert getEdgeSource(e).longValue() == source; assert getEdgeTarget(e).longValue() == target; s.add(e); } return s; } @Override public int outDegreeOf(final Integer vertex) { assertVertexExist(vertex); return (int) cumulativeOutdegrees.getDelta(vertex); } @Override public IntSet outgoingEdgesOf(final Integer vertex) { assertVertexExist(vertex); final long[] result = new long[2]; cumulativeOutdegrees.get(vertex, result); return IntSets.fromTo((int) result[0], (int) result[1]); } @Override public Integer getEdgeSource(final Integer e) { assertEdgeExist(e); return (int) (successors.getLong(e) >>> sourceShift); } @Override public Integer getEdgeTarget(final Integer e) { assertEdgeExist(e); return (int) (successors.getLong(e) & targetMask); } @Override public Integer getEdge(final Integer sourceVertex, final Integer targetVertex) { final long index = successors.indexOfUnsafe(((long) sourceVertex << sourceShift) + targetVertex); return index != -1 ? (int) index : null; } @Override public boolean containsEdge(final Integer sourceVertex, final Integer targetVertex) { return successors.indexOfUnsafe(((long) sourceVertex << sourceShift) + targetVertex) != -1; } /** * Ensures that the specified edge exists in this graph, or else throws exception. * * @param e edge * @return true if this assertion holds. * @throws IllegalArgumentException if specified edge does not exist in this graph. */ protected boolean assertEdgeExist(final Integer e) { if (e < 0 || e >= m) throw new IllegalArgumentException(); return true; } private final static class SuccinctGraphIterables implements GraphIterables, Serializable { private static final long serialVersionUID = 0L; private final SuccinctIntDirectedGraph graph; private SuccinctGraphIterables() { graph = null; } private SuccinctGraphIterables(final SuccinctIntDirectedGraph graph) { this.graph = graph; } @Override public Graph getGraph() { return graph; } @Override public long vertexCount() { return graph.n; } @Override public long edgeCount() { return graph.m; } @Override public Iterable edgesOf(final Integer source) { return Iterables.concat(outgoingEdgesOf(source), incomingEdgesOf(source, true)); } private Iterable incomingEdgesOf(final int target, final boolean skipLoops) { final SuccinctIntDirectedGraph graph = this.graph; final long[] result = new long[2]; graph.cumulativeIndegrees.get(target, result); final int d = (int) (result[1] - result[0]); final EliasFanoIndexedMonotoneLongBigList successors = graph.successors; final LongBigListIterator iterator = graph.predecessors.listIterator(result[0]); final int sourceShift = graph.sourceShift; return () -> new IntIterator() { int i = d; int edge = -1; long n = graph.n; long base = target * n - result[0]; @Override public boolean hasNext() { if (edge == -1 && i > 0) { i--; final long source = iterator.nextLong() - base--; if (skipLoops && source == target && i-- != 0) return false; final long v = (source << sourceShift) + target; assert v == successors.successor(v) : v + " != " + successors.successor(v); edge = (int) successors.successorIndexUnsafe(v); assert graph.getEdgeSource(edge).longValue() == source; assert graph.getEdgeTarget(edge).longValue() == target; } return edge != -1; } @Override public int nextInt() { if (!hasNext()) throw new NoSuchElementException(); final int result = edge; edge = -1; return result; } }; } @Override public Iterable incomingEdgesOf(final Integer vertex) { return incomingEdgesOf(vertex, false); } } private final GraphIterables ITERABLES = new SuccinctGraphIterables(this); @Override public GraphIterables iterables() { return ITERABLES; } } SuccinctIntUndirectedGraph.java000066400000000000000000000276231402514743400341250ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import java.io.Serializable; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.GraphIterables; import org.jgrapht.alg.util.Pair; import org.jgrapht.opt.graph.sparse.SparseIntDirectedGraph; import org.jgrapht.opt.graph.sparse.SparseIntUndirectedGraph; import com.google.common.collect.Iterables; import it.unimi.dsi.fastutil.ints.IntIntSortedPair; import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.ints.IntSets; import it.unimi.dsi.fastutil.longs.LongBigListIterator; import it.unimi.dsi.sux4j.util.EliasFanoIndexedMonotoneLongBigList; import it.unimi.dsi.sux4j.util.EliasFanoMonotoneLongBigList; /** * An immutable undirected graph with {@link Integer} edges represented using quasi-succinct data * structures. * *

    * The graph representation of this implementation is similar to that of * {@link SparseIntDirectedGraph}: nodes and edges are initial intervals of the natural numbers. * Under the hood, however, this class uses the {@linkplain EliasFanoMonotoneLongBigList * Elias–Fano representation of monotone sequences} to represent the positions of ones in the * (linearized) adjacency matrix of the graph. Instances are serializable and thread safe. * *

    * If the vertex set is compact (i.e., vertices are numbered from 0 consecutively), space usage will * be close to the information-theoretical lower bound (typically, a few times smaller than a * {@link SparseIntUndirectedGraph}). * *

    * {@linkplain org.jgrapht.GraphIterables#outgoingEdgesOf(Object) Enumeration of edges} is very * slow. {@linkplain org.jgrapht.Graph#containsEdge(Object) Adjacency tests} are very fast and * happen in almost constant time. * *

    * {@link SuccinctUndirectedGraph} is a much faster implementation with a similar footprint using * {@link IntIntSortedPair} as edge type. Please read the {@linkplain org.jgrapht.sux4j class * documentation} for more information. * * @author Sebastiano Vigna * @see SuccinctUndirectedGraph */ public class SuccinctIntUndirectedGraph extends AbstractSuccinctUndirectedGraph implements Serializable { private static final long serialVersionUID = 0L; /** The cumulative list of outdegrees (number of edges in sorted order, including loops). */ private final EliasFanoIndexedMonotoneLongBigList cumulativeOutdegrees; /** The cumulative list of indegrees (number of edges in reversed order, including loops). */ private final EliasFanoMonotoneLongBigList cumulativeIndegrees; /** The cumulative list of successor (edges in sorted order, including loops) lists. */ private final EliasFanoIndexedMonotoneLongBigList successors; /** The cumulative list of predecessor (edges in reversed order, including loops) lists. */ private final EliasFanoMonotoneLongBigList predecessors; /** * Creates a new immutable succinct undirected graph from a given undirected graph. * * @param graph an undirected graph: for good results, vertices should be numbered consecutively * starting from 0. * @param the graph edge type */ public SuccinctIntUndirectedGraph(final Graph graph) { super((int) graph.iterables().vertexCount(), (int) graph.iterables().edgeCount()); if (graph.getType().isDirected()) throw new IllegalArgumentException("This class supports directed graphs only"); assert graph.getType().isUndirected(); final GraphIterables iterables = graph.iterables(); if (iterables.vertexCount() > Integer.MAX_VALUE) throw new IllegalArgumentException( "The number of nodes (" + iterables.vertexCount() + ") is greater than " + Integer.MAX_VALUE); if (iterables.edgeCount() > Integer.MAX_VALUE) throw new IllegalArgumentException( "The number of edges (" + iterables.edgeCount() + ") is greater than " + Integer.MAX_VALUE); cumulativeOutdegrees = new EliasFanoIndexedMonotoneLongBigList( n + 1, m, new CumulativeDegrees<>(graph, true, iterables::edgesOf)); cumulativeIndegrees = new EliasFanoMonotoneLongBigList( n + 1, m, new CumulativeDegrees<>(graph, false, iterables::edgesOf)); assert cumulativeOutdegrees.getLong(cumulativeOutdegrees.size64() - 1) == m; assert cumulativeIndegrees.getLong(cumulativeIndegrees.size64() - 1) == m; successors = new EliasFanoIndexedMonotoneLongBigList( m, (long) n << sourceShift, new CumulativeSuccessors<>(graph, true, iterables::outgoingEdgesOf)); predecessors = new EliasFanoIndexedMonotoneLongBigList( m, (long) n * n - m, new CumulativeSuccessors<>(graph, false, iterables::incomingEdgesOf)); } /** * Creates a new immutable succinct undirected graph from an edge list. * *

    * This constructor just builds a {@link SparseIntUndirectedGraph} and delegates to the * {@linkplain #SuccinctIntUndirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param edges the edge list. * @see #SuccinctIntUndirectedGraph(Graph) */ public SuccinctIntUndirectedGraph( final int numVertices, final List> edges) { this(new SparseIntUndirectedGraph(numVertices, edges)); } @Override public boolean containsEdge(final Integer e) { return e >= 0 && e < m; } @Override public Set edgeSet() { return IntSets.fromTo(0, m); } @Override public int degreeOf(final Integer vertex) { return (int) cumulativeIndegrees.getDelta(vertex) + (int) cumulativeOutdegrees.getDelta(vertex); } @Override public IntSet edgesOf(final Integer vertex) { final long[] result = new long[2]; cumulativeOutdegrees.get(vertex, result); final IntSet s = new IntOpenHashSet(IntSets.fromTo((int) result[0], (int) result[1])); for (final int e : ITERABLES.reverseSortedEdgesOfNoLoops(vertex)) s.add(e); return s; } @Override public IntSet incomingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public IntSet outgoingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public Integer getEdgeSource(final Integer e) { assertEdgeExist(e); return (int) (successors.getLong(e) >>> sourceShift); } @Override public Integer getEdgeTarget(final Integer e) { assertEdgeExist(e); return (int) (successors.getLong(e) & targetMask); } @Override public Integer getEdge(final Integer sourceVertex, final Integer targetVertex) { int x = sourceVertex; int y = targetVertex; if (x > y) { final int t = x; x = y; y = t; } final long index = successors.indexOfUnsafe(((long) x << sourceShift) + y); return index != -1 ? (int) index : null; } @Override public boolean containsEdge(final Integer sourceVertex, final Integer targetVertex) { return containsEdge(successors, sourceVertex, targetVertex); } /** * Ensures that the specified edge exists in this graph, or else throws exception. * * @param e edge * @return true if this assertion holds. * @throws IllegalArgumentException if specified edge does not exist in this graph. */ protected boolean assertEdgeExist(final Integer e) { if (e < 0 || e >= m) throw new IllegalArgumentException(); return true; } private final static class SuccinctGraphIterables implements GraphIterables, Serializable { private static final long serialVersionUID = 0L; private final SuccinctIntUndirectedGraph graph; private SuccinctGraphIterables() { graph = null; } private SuccinctGraphIterables(final SuccinctIntUndirectedGraph graph) { this.graph = graph; } @Override public Graph getGraph() { return graph; } @Override public long vertexCount() { return graph.n; } @Override public long edgeCount() { return graph.m; } @Override public Iterable edgesOf(final Integer source) { final long[] result = new long[2]; graph.cumulativeOutdegrees.get(source, result); return Iterables .concat( IntSets.fromTo((int) result[0], (int) result[1]), reverseSortedEdgesOfNoLoops(source)); } private Iterable reverseSortedEdgesOfNoLoops(final int target) { final long[] result = new long[2]; graph.cumulativeIndegrees.get(target, result); final int d = (int) (result[1] - result[0]); final int sourceShift = graph.sourceShift; final EliasFanoIndexedMonotoneLongBigList successors = graph.successors; final LongBigListIterator iterator = graph.predecessors.listIterator(result[0]); return () -> new IntIterator() { int i = d; int edge = -1; long n = graph.n; long base = n * target - result[0]; @Override public boolean hasNext() { if (edge == -1 && i > 0) { i--; final long source = iterator.nextLong() - base--; if (source == target && i-- == 0) return false; final long v = (source << sourceShift) + target; assert v == successors.successor(v) : v + " != " + successors.successor(v); edge = (int) successors.successorIndexUnsafe(v); assert graph.getEdgeSource(edge).longValue() == source; assert graph.getEdgeTarget(edge).longValue() == target; } return edge != -1; } @Override public int nextInt() { if (!hasNext()) throw new NoSuchElementException(); final int result = edge; edge = -1; return result; } }; } @Override public Iterable incomingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public Iterable outgoingEdgesOf(final Integer vertex) { return edgesOf(vertex); } } private final SuccinctGraphIterables ITERABLES = new SuccinctGraphIterables(this); @Override public GraphIterables iterables() { return ITERABLES; } } jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/SuccinctUndirectedGraph.java000066400000000000000000000347631402514743400335340ustar00rootroot00000000000000/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import java.io.Serializable; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; import org.jgrapht.Graph; import org.jgrapht.GraphIterables; import org.jgrapht.alg.util.Pair; import org.jgrapht.opt.graph.sparse.SparseIntUndirectedGraph; import com.google.common.collect.Iterables; import it.unimi.dsi.fastutil.ints.IntIntSortedPair; import it.unimi.dsi.fastutil.longs.LongBigListIterator; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.sux4j.util.EliasFanoIndexedMonotoneLongBigList; import it.unimi.dsi.sux4j.util.EliasFanoIndexedMonotoneLongBigList.EliasFanoIndexedMonotoneLongBigListIterator; import it.unimi.dsi.sux4j.util.EliasFanoMonotoneLongBigList; /** * An immutable undirected graph with {@link IntIntSortedPair} edges represented using * quasi-succinct data structures. * *

    * The graph representation of this implementation uses the {@linkplain EliasFanoMonotoneLongBigList * Elias–Fano representation of monotone sequences} to represent the positions of ones in the * (linearized) adjacency matrix of the graph. Edges are represented by instances of * {@link IntIntSortedPair}. Instances are serializable and thread safe. * *

    * If the vertex set is compact (i.e., vertices are numbered from 0 consecutively), space usage will * be close to the information-theoretical lower bound (typically, a few times smaller than a * {@link SparseIntUndirectedGraph}). * *

    * All accessors are very fast. {@linkplain org.jgrapht.Graph#containsEdge(Object) Adjacency tests} * are very fast and happen in almost constant time. * *

    * {@link SuccinctIntUndirectedGraph} is a much slower implementation with a similar footprint using * {@link Integer} as edge type. Please read the {@linkplain org.jgrapht.sux4j class documentation} * for more information. * *

    * For convenience, and as a compromise with the approach of {@link SuccinctIntUndirectedGraph}, * this class provides methods {@link org.jgrapht.sux4j.SuccinctDirectedGraph#getEdgeFromIndex(long) * getEdgeFromIndex()} and * {@link org.jgrapht.sux4j.SuccinctDirectedGraph#getIndexFromEdge(it.unimi.dsi.fastutil.ints.IntIntPair) * getIndexFromEdge()} that map bijectively the edge set into a contiguous set of longs. * * @author Sebastiano Vigna * @see SuccinctIntUndirectedGraph */ public class SuccinctUndirectedGraph extends AbstractSuccinctUndirectedGraph implements Serializable { private static final long serialVersionUID = 0L; /** The cumulative list of outdegrees (number of edges in sorted order, including loops). */ private final EliasFanoIndexedMonotoneLongBigList cumulativeOutdegrees; /** The cumulative list of indegrees (number of edges in reversed order, including loops). */ private final EliasFanoMonotoneLongBigList cumulativeIndegrees; /** The cumulative list of successor (edges in sorted order, including loops) lists. */ private final EliasFanoIndexedMonotoneLongBigList successors; /** The cumulative list of predecessor (edges in reversed order, including loops) lists. */ private final EliasFanoMonotoneLongBigList predecessors; /** * Creates a new immutable succinct undirected graph from a given undirected graph. * * @param graph an undirected graph: for good results, vertices should be numbered consecutively * starting from 0. * @param the graph edge type */ public SuccinctUndirectedGraph(final Graph graph) { super((int) graph.iterables().vertexCount(), (int) graph.iterables().edgeCount()); if (graph.getType().isDirected()) throw new IllegalArgumentException("This class supports directed graphs only"); assert graph.getType().isUndirected(); final GraphIterables iterables = graph.iterables(); if (iterables.vertexCount() > Integer.MAX_VALUE) throw new IllegalArgumentException( "The number of nodes (" + iterables.vertexCount() + ") is greater than " + Integer.MAX_VALUE); if (iterables.edgeCount() > Integer.MAX_VALUE) throw new IllegalArgumentException( "The number of edges (" + iterables.edgeCount() + ") is greater than " + Integer.MAX_VALUE); cumulativeOutdegrees = new EliasFanoIndexedMonotoneLongBigList( n + 1, m, new CumulativeDegrees<>(graph, true, iterables::edgesOf)); cumulativeIndegrees = new EliasFanoMonotoneLongBigList( n + 1, m, new CumulativeDegrees<>(graph, false, iterables::edgesOf)); assert cumulativeOutdegrees.getLong(cumulativeOutdegrees.size64() - 1) == m; assert cumulativeIndegrees.getLong(cumulativeIndegrees.size64() - 1) == m; successors = new EliasFanoIndexedMonotoneLongBigList( m, (long) n << sourceShift, new CumulativeSuccessors<>(graph, true, iterables::outgoingEdgesOf)); predecessors = new EliasFanoIndexedMonotoneLongBigList( m, (long) n * n - m, new CumulativeSuccessors<>(graph, false, iterables::incomingEdgesOf)); } /** * Creates a new immutable succinct undirected graph from an edge list. * *

    * This constructor just builds a {@link SparseIntUndirectedGraph} and delegates to the * {@linkplain #SuccinctUndirectedGraph(Graph) main constructor}. * * @param numVertices the number of vertices. * @param edges the edge list. * @see #SuccinctUndirectedGraph(Graph) */ public SuccinctUndirectedGraph( final int numVertices, final List> edges) { this(new SparseIntUndirectedGraph(numVertices, edges)); } @Override public boolean containsEdge(final IntIntSortedPair e) { return successors.indexOfUnsafe(((long) e.firstInt() << sourceShift) + e.secondInt()) != -1; } @Override public Set edgeSet() { return new ObjectOpenHashSet<>(iterables().edges().iterator()); } @Override public int degreeOf(final Integer vertex) { return (int) cumulativeIndegrees.getDelta(vertex) + (int) cumulativeOutdegrees.getDelta(vertex); } @Override public Set edgesOf(final Integer vertex) { assertVertexExist(vertex); final int x = vertex; final long[] result = new long[2]; cumulativeOutdegrees.get(x, result); final Set s = new ObjectOpenHashSet<>(); final LongBigListIterator iterator = successors.listIterator(result[0]); final long base = (long) x << sourceShift; for (int d = (int) (result[1] - result[0]); d-- != 0;) s.add(IntIntSortedPair.of(x, (int) (iterator.nextLong() - base))); for (final IntIntSortedPair e : ITERABLES.reverseSortedEdgesOfNoLoops(x)) s.add(e); return s; } @Override public Set incomingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public Set outgoingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public Integer getEdgeSource(final IntIntSortedPair e) { return e.firstInt(); } @Override public Integer getEdgeTarget(final IntIntSortedPair e) { return e.secondInt(); } /** * Returns the index associated with the given edge. * * @param e an edge of the graph. * @return the index associated with the edge, or −1 if the edge is not part of the graph. * @see #getEdgeFromIndex(long) */ public long getIndexFromEdge(final IntIntSortedPair e) { final int source = e.firstInt(); final int target = e.secondInt(); if (source < 0 || source >= n || target < 0 || target >= n) throw new IllegalArgumentException(); return successors.indexOfUnsafe(((long) source << sourceShift) + target); } /** * Returns the edge with given index. * * @param i an index between 0 (included) and the number of edges (excluded). * @return the pair with index {@code i}. * @see #getIndexFromEdge(IntIntSortedPair) */ public IntIntSortedPair getEdgeFromIndex(final long i) { if (i < 0 || i >= m) throw new IllegalArgumentException(); final long t = successors.getLong(i); return IntIntSortedPair.of((int) (t >>> sourceShift), (int) (t & targetMask)); } @Override public IntIntSortedPair getEdge(final Integer sourceVertex, final Integer targetVertex) { int x = sourceVertex; int y = targetVertex; if (x > y) { final int t = x; x = y; y = t; } final long index = successors.indexOfUnsafe(((long) x << sourceShift) + y); return index != -1 ? IntIntSortedPair.of(x, y) : null; } @Override public boolean containsEdge(final Integer sourceVertex, final Integer targetVertex) { return containsEdge(successors, sourceVertex, targetVertex); } private final static class SuccinctGraphIterables implements GraphIterables, Serializable { private static final long serialVersionUID = 0L; private final SuccinctUndirectedGraph graph; private SuccinctGraphIterables() { graph = null; } private SuccinctGraphIterables(final SuccinctUndirectedGraph graph) { this.graph = graph; } @Override public Graph getGraph() { return graph; } @Override public long vertexCount() { return graph.n; } @Override public long edgeCount() { return graph.m; } @Override public Iterable edges() { final int sourceShift = graph.sourceShift; final long targetMask = graph.targetMask; return () -> new Iterator<>() { private final EliasFanoIndexedMonotoneLongBigListIterator iterator = graph.successors.iterator(); private final int n = graph.n; @Override public boolean hasNext() { return iterator.hasNext(); } @Override public IntIntSortedPair next() { final long t = iterator.nextLong(); return IntIntSortedPair.of((int) (t >>> sourceShift), (int) (t & targetMask)); } }; } @Override public Iterable edgesOf(final Integer source) { return Iterables.concat(sortedEdges(source), reverseSortedEdgesOfNoLoops(source)); } private Iterable sortedEdges(final int source) { final int sourceShift = graph.sourceShift; final long targetMask = graph.targetMask; final long[] result = new long[2]; graph.cumulativeOutdegrees.get(source, result); final var iterator = graph.successors.listIterator(result[0]); final long base = (long) source << sourceShift; return () -> new Iterator<>() { private int d = (int) (result[1] - result[0]); @Override public boolean hasNext() { return d != 0; } @Override public IntIntSortedPair next() { if (d == 0) throw new NoSuchElementException(); d--; return IntIntSortedPair.of(source, (int) (iterator.nextLong() - base)); } }; } private Iterable reverseSortedEdgesOfNoLoops(final int target) { final long[] result = new long[2]; graph.cumulativeIndegrees.get(target, result); final int d = (int) (result[1] - result[0]); final LongBigListIterator iterator = graph.predecessors.listIterator(result[0]); return () -> new Iterator<>() { int i = d; IntIntSortedPair edge = null; long n = graph.n; long base = n * target - result[0]; @Override public boolean hasNext() { if (edge == null && i > 0) { i--; final long source = iterator.nextLong() - base--; if (source == target && i-- == 0) return false; edge = IntIntSortedPair.of((int) source, target); } return edge != null; } @Override public IntIntSortedPair next() { if (!hasNext()) throw new NoSuchElementException(); final IntIntSortedPair result = edge; edge = null; return result; } }; } @Override public Iterable incomingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public Iterable outgoingEdgesOf(final Integer vertex) { return edgesOf(vertex); } } private final SuccinctGraphIterables ITERABLES = new SuccinctGraphIterables(this); @Override public GraphIterables iterables() { return ITERABLES; } } jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/sux4j/package-info.java000066400000000000000000000135631402514743400313070ustar00rootroot00000000000000/** * Immutable graphs stored using Sux4J's quasi-succinct data * structures. * *

    * This package contains implementations of immutable graphs based on the * {@linkplain it.unimi.dsi.sux4j.util.EliasFanoIndexedMonotoneLongBigList Elias–Fano * quasi-succinct representation of monotone sequences}. The positions of the nonzero entries of the * adjacency matrix of a graph are represented as a monotone sequence of natural numbers. * *

    * The memory footprint of these implementation is close to the information-theoretical lower bound * in the undirected case, and close to twice the information-theoretical lower bound in the * directed case, because the transposed graph must be stored separately, but in the latter case you * have the choice to not support incoming edges and obtain, again, footprint close to the * information-theoretical lower bound. The actual space used can be easily measured as all * implementations are serializable, and their in-memory footprint is very close to the on-disk * footprint. Usually the size is a few times smaller than that of a * {@link org.jgrapht.opt.graph.sparse.SparseIntDirectedGraph * SparseIntDirectedGraph}/{@link org.jgrapht.opt.graph.sparse.SparseIntUndirectedGraph * SparseIntUndirectedGraph}. * *

    * We provide two classes mimicking {@link org.jgrapht.opt.graph.sparse.SparseIntDirectedGraph * SparseIntDirectedGraph} and {@link org.jgrapht.opt.graph.sparse.SparseIntUndirectedGraph * SparseIntUndirectedGraph}, in the sense that both vertices and edges are integers (and they are * numbered contiguously). Thus, by definition these classes cannot represent graphs with more than * {@link Integer#MAX_VALUE} edges. * *

      *
    • {@link org.jgrapht.sux4j.SuccinctIntDirectedGraph} is an implementation for directed graphs. * {@linkplain org.jgrapht.GraphIterables#outgoingEdgesOf(Object) Enumeration of outgoing edges} is * quite fast, but {@linkplain org.jgrapht.GraphIterables#incomingEdgesOf(Object) enumeration of * incoming edges} is very slow. {@linkplain org.jgrapht.Graph#containsEdge(Object) Adjacency tests} * are very fast and happen in almost constant time. *
    • {@link org.jgrapht.sux4j.SuccinctIntUndirectedGraph} is an implementation for undirected * graphs. {@linkplain org.jgrapht.GraphIterables#edgesOf(Object) Enumeration of edges} is very * slow. {@linkplain org.jgrapht.Graph#containsEdge(Object) Adjacency tests} are very fast and * happen in almost constant time. *
    * *

    * The sometimes slow behavior of the previous classes is due to a clash between JGraphT's design * and the need of representing an edge with an {@link java.lang.Integer Integer}, which cannot be * extended: there is no information that can be carried by the object representing the edge. This * limitation forces the two classes above to compute two expensive functions that are one the * inverse of the other. * *

    * As an alternative, we provide classes {@link org.jgrapht.sux4j.SuccinctDirectedGraph * SuccinctDirectedGraph} and {@link org.jgrapht.sux4j.SuccinctUndirectedGraph * SuccinctUndirectedGraph} using the same amount of space, but having edges represented by pairs of * integers stored in an {@link it.unimi.dsi.fastutil.ints.IntIntPair IntIntPair} (for directed * graphs) or an {@link it.unimi.dsi.fastutil.ints.IntIntSortedPair IntIntSortedPair} (for * undirected graphs). Storing the edges explicitly avoids the cumbersome back-and-forth * computations of the previous classes. All accessors are extremely fast. There is no limitation on * the number of edges. * *

    * Both classes provide methods * {@link org.jgrapht.sux4j.SuccinctDirectedGraph#getEdgeFromIndex(long) getEdgeFromIndex()} and * {@link org.jgrapht.sux4j.SuccinctDirectedGraph#getIndexFromEdge(it.unimi.dsi.fastutil.ints.IntIntPair) * getIndexFromEdge()} that map bijectively the edge set into a contiguous set of longs. In this way * the user can choose when and how to use the feature (e.g., to store compactly data associated to * edges). * *

    * Finally, note that the best performance and compression can be obtained by representing the graph * using WebGraph's {@link it.unimi.dsi.webgraph.EFGraph * EFGraph} format and then accessing the graph using the suitable {@linkplain org.jgrapht.webgraph * adapter}; in particular, one can represent graphs with more than {@link Integer#MAX_VALUE} * vertices. However, the adapters do not provide methods mapping bijectively edges into a * contiguous set of integers. * *

    Building and serializing with limited memory

    * *

    * All implementations provide a copy constructor taking a {@link org.jgrapht.Graph Graph} and a * constructor accepting a list of edges; the latter just builds a sparse graph and delegates to the * copy constructor. Both methods can be inconvenient if the graph to be represented is large, as * the list of edges might have too large a footprint. * *

    * There is however a simple strategy that makes it possible to build succinct representations using * a relatively small amount of additional memory with respect to the representation itself: *

      *
    1. {@linkplain it.unimi.dsi.webgraph convert your graph to a WebGraph format} such as * {@link it.unimi.dsi.big.webgraph.BVGraph BVGraph} or {@link it.unimi.dsi.webgraph.EFGraph * EFGraph}; *
    2. if your graph is directed, use {@link it.unimi.dsi.webgraph.Transform Transform} to store the * transpose of your graph in the same way; *
    3. use a {@linkplain org.jgrapht.webgraph suitable adapter} to get a {@link org.jgrapht.Graph * Graph} representing your graph, taking care of loading the WebGraph representations using * {@link it.unimi.dsi.webgraph.ImmutableGraph#loadMapped() ImmutableGraph.loadMapped()}; *
    4. use the copy constructor to obtain a quasi-succinct representation. *
    */ package org.jgrapht.sux4j; jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/000077500000000000000000000000001402514743400266325ustar00rootroot00000000000000AbstractImmutableBigGraphAdapter.java000066400000000000000000000130441402514743400357300ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import java.util.Collections; import java.util.Set; import java.util.function.Supplier; import org.jgrapht.graph.AbstractGraph; import it.unimi.dsi.big.webgraph.ImmutableGraph; import it.unimi.dsi.big.webgraph.LazyLongIterator; import it.unimi.dsi.big.webgraph.LazyLongSkippableIterator; import it.unimi.dsi.fastutil.longs.LongLongPair; import it.unimi.dsi.fastutil.longs.LongSets; /** * An abstract base class for adapters using WebGraph * (big)'s {@link ImmutableGraph}. Nodes are instances of {@link Long} corresponding to the * index of a node in WebGraph. * * @param the type of an edge. * @author Sebastiano Vigna */ public abstract class AbstractImmutableBigGraphAdapter extends AbstractGraph { /** The underlying graph. */ protected final ImmutableGraph immutableGraph; /** The number of nodes of {@link #immutableGraph}. */ protected final long n; /** * The number of edges, cached, or -1 if it still unknown. This will have to be computed by * enumeration for undirected graphs, as we do not know how many loops are present, and for * graphs which do not support {@link ImmutableGraph#numArcs()}. */ protected long m = -1; protected AbstractImmutableBigGraphAdapter(final ImmutableGraph immutableGraph) { this.immutableGraph = immutableGraph; this.n = immutableGraph.numNodes(); } @Override public Set getAllEdges(final Long sourceVertex, final Long targetVertex) { if (sourceVertex == null || targetVertex == null) return null; final long x = sourceVertex; final long y = targetVertex; if (x < 0 || x >= n || y < 0 || y >= n) return null; return containsEdgeFast(x, y) ? Collections.singleton(makeEdge(x, y)) : Collections.emptySet(); } protected abstract E makeEdge(long x, long y); @Override public E getEdge(final Long sourceVertex, final Long targetVertex) { if (sourceVertex == null || targetVertex == null) return null; final long x = sourceVertex; final long y = targetVertex; return containsEdgeFast(x, y) ? makeEdge(x, y) : null; } @Override public Supplier getVertexSupplier() { return null; } @Override public Supplier getEdgeSupplier() { return null; } @Override public E addEdge(final Long sourceVertex, final Long targetVertex) { throw new UnsupportedOperationException(); } @Override public boolean addEdge(final Long sourceVertex, final Long targetVertex, final E e) { throw new UnsupportedOperationException(); } @Override public Long addVertex() { throw new UnsupportedOperationException(); } @Override public boolean addVertex(final Long v) { throw new UnsupportedOperationException(); } @Override public boolean containsEdge(final Long sourceVertex, final Long targetVertex) { if (sourceVertex == null || targetVertex == null) return false; return containsEdgeFast(sourceVertex, targetVertex); } protected boolean containsEdgeFast(final long x, final long y) { if (x < 0 || x >= n || y < 0 || y >= n) return false; final LazyLongIterator successors = immutableGraph.successors(x); if (successors instanceof LazyLongSkippableIterator) { // Fast skipping available return y == ((LazyLongSkippableIterator) successors).skipTo(y); } else for (long target; (target = successors.nextLong()) != -1;) if (target == y) return true; return false; } @Override public boolean containsVertex(final Long v) { if (v == null) return false; final long x = v; return x >= 0 && x < n; } @Override public E removeEdge(final Long sourceVertex, final Long targetVertex) { throw new UnsupportedOperationException(); } @Override public boolean removeEdge(final E e) { throw new UnsupportedOperationException(); } @Override public boolean removeVertex(final Long v) { throw new UnsupportedOperationException(); } @Override public Set vertexSet() { return LongSets.fromTo(0, n); } @Override public Long getEdgeSource(final E e) { return e.leftLong(); } @Override public Long getEdgeTarget(final E e) { return e.rightLong(); } @Override public double getEdgeWeight(final E e) { return DEFAULT_EDGE_WEIGHT; } @Override public void setEdgeWeight(final E e, final double weight) { if (weight != 1) throw new UnsupportedOperationException(); } } AbstractImmutableGraphAdapter.java000066400000000000000000000130641402514743400353100ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import java.util.Collections; import java.util.Set; import java.util.function.Supplier; import org.jgrapht.graph.AbstractGraph; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.ints.IntSets; import it.unimi.dsi.webgraph.ImmutableGraph; import it.unimi.dsi.webgraph.LazyIntIterator; import it.unimi.dsi.webgraph.LazyIntSkippableIterator; /** * An abstract base class for adapters using WebGraph's * {@link ImmutableGraph}. Nodes are instances of {@link Integer} corresponding to the index of a * node in WebGraph. * * @param the type of an edge. * @author Sebastiano Vigna */ public abstract class AbstractImmutableGraphAdapter extends AbstractGraph { /** The underlying graph. */ protected final ImmutableGraph immutableGraph; /** The number of nodes of {@link #immutableGraph}. */ protected final int n; /** * The number of edges, cached, or -1 if it still unknown. This will have to be computed by * enumeration for undirected graphs, as we do not know how many loops are present, and for * graphs which do not support {@link ImmutableGraph#numArcs()}. */ protected long m = -1; protected AbstractImmutableGraphAdapter(final ImmutableGraph immutableGraph) { this.immutableGraph = immutableGraph; this.n = immutableGraph.numNodes(); } @Override public Set getAllEdges(final Integer sourceVertex, final Integer targetVertex) { if (sourceVertex == null || targetVertex == null) return null; final int x = sourceVertex; final int y = targetVertex; if (x < 0 || x >= n || y < 0 || y >= n) return null; return containsEdgeFast(x, y) ? Collections.singleton(makeEdge(x, y)) : Collections.emptySet(); } protected abstract E makeEdge(int x, int y); @Override public E getEdge(final Integer sourceVertex, final Integer targetVertex) { if (sourceVertex == null || targetVertex == null) return null; final int x = sourceVertex; final int y = targetVertex; return containsEdgeFast(x, y) ? makeEdge(x, y) : null; } @Override public Supplier getVertexSupplier() { return null; } @Override public Supplier getEdgeSupplier() { return null; } @Override public E addEdge(final Integer sourceVertex, final Integer targetVertex) { throw new UnsupportedOperationException(); } @Override public boolean addEdge(final Integer sourceVertex, final Integer targetVertex, final E e) { throw new UnsupportedOperationException(); } @Override public Integer addVertex() { throw new UnsupportedOperationException(); } @Override public boolean addVertex(final Integer v) { throw new UnsupportedOperationException(); } @Override public boolean containsEdge(final Integer sourceVertex, final Integer targetVertex) { if (sourceVertex == null || targetVertex == null) return false; return containsEdgeFast(sourceVertex, targetVertex); } protected boolean containsEdgeFast(final int x, final int y) { if (x < 0 || x >= n || y < 0 || y >= n) return false; final LazyIntIterator successors = immutableGraph.successors(x); if (successors instanceof LazyIntSkippableIterator) { // Fast skipping available return y == ((LazyIntSkippableIterator) successors).skipTo(y); } else for (int target; (target = successors.nextInt()) != -1;) if (target == y) return true; return false; } @Override public boolean containsVertex(final Integer v) { if (v == null) return false; final int x = v; return x >= 0 && x < n; } @Override public E removeEdge(final Integer sourceVertex, final Integer targetVertex) { throw new UnsupportedOperationException(); } @Override public boolean removeEdge(final E e) { throw new UnsupportedOperationException(); } @Override public boolean removeVertex(final Integer v) { throw new UnsupportedOperationException(); } @Override public Set vertexSet() { return IntSets.fromTo(0, n); } @Override public Integer getEdgeSource(final E e) { return e.leftInt(); } @Override public Integer getEdgeTarget(final E e) { return e.rightInt(); } @Override public double getEdgeWeight(final E e) { return DEFAULT_EDGE_WEIGHT; } @Override public void setEdgeWeight(final E e, final double weight) { if (weight != 1) throw new UnsupportedOperationException(); } } ImmutableDirectedBigGraphAdapter.java000066400000000000000000000270261402514743400357150ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import org.jgrapht.GraphIterables; import org.jgrapht.GraphType; import org.jgrapht.graph.DefaultGraphType; import com.google.common.collect.Iterables; import it.unimi.dsi.big.webgraph.ImmutableGraph; import it.unimi.dsi.big.webgraph.LazyLongIterator; import it.unimi.dsi.big.webgraph.LazyLongIterators; import it.unimi.dsi.big.webgraph.NodeIterator; import it.unimi.dsi.fastutil.longs.LongLongPair; import it.unimi.dsi.fastutil.longs.LongLongSortedPair; import it.unimi.dsi.fastutil.objects.ObjectIterables; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashBigSet; import it.unimi.dsi.lang.FlyweightPrototype; /** * An adapter class for directed graphs using WebGraph * (big)'s {@link ImmutableGraph}. * *

    * This class is equivalent to {@link ImmutableDirectedGraphAdapter}, except that nodes are * instances of {@link Long}, and edges are instances of {@link LongLongPair}. * *

    * If necessary, you can adapt a {@linkplain it.unimi.dsi.webgraph.ImmutableGraph standard WebGraph * graph} using the suitable {@linkplain ImmutableGraph#wrap(it.unimi.dsi.webgraph.ImmutableGraph) * wrapper}. * * @see ImmutableDirectedGraphAdapter * @author Sebastiano Vigna */ public class ImmutableDirectedBigGraphAdapter extends AbstractImmutableBigGraphAdapter implements FlyweightPrototype { private final ImmutableGraph immutableTranspose; /** * Creates an adapter for a directed big immutable graph. * *

    * It is responsibility of the caller that the two provided graphs are one the transpose of the * other (for each arc x → y in a graph there must be an * arc y → x in the other). If this property is not true, * results will be unpredictable. * * @param immutableGraph a big immutable graph. * @param immutableTranspose its transpose. */ public ImmutableDirectedBigGraphAdapter( final ImmutableGraph immutableGraph, final ImmutableGraph immutableTranspose) { super(immutableGraph); this.immutableTranspose = immutableTranspose; if (immutableTranspose != null && n != immutableTranspose.numNodes()) throw new IllegalArgumentException( "The graph has " + n + " nodes, but the transpose has " + immutableTranspose.numNodes()); } /** * Creates an adapter for a directed big immutable graph implementing only methods based on * outgoing edges. * * @param immutableGraph a big immutable graph. */ public ImmutableDirectedBigGraphAdapter(final ImmutableGraph immutableGraph) { this(immutableGraph, null); } @Override protected LongLongPair makeEdge(final long x, final long y) { return LongLongPair.of(x, y); } @Override public boolean containsEdge(final LongLongPair e) { if (e == null) return false; if (e instanceof LongLongSortedPair) return false; return containsEdgeFast(e.leftLong(), e.rightLong()); } @Override public Set edgeSet() { final NodeIterator nodeIterator = immutableGraph.nodeIterator(); final long m = iterables().edgeCount(); final ObjectOpenHashBigSet edges = new ObjectOpenHashBigSet<>(m); for (long i = 0; i < n; i++) { final long x = nodeIterator.nextLong(); final LazyLongIterator successors = nodeIterator.successors(); for (long y; (y = successors.nextLong()) != -1;) edges.add(LongLongPair.of(x, y)); } return edges; } @Override public int degreeOf(final Long vertex) { final long d = inDegreeOf(vertex) + outDegreeOf(vertex); if (d > Integer.MAX_VALUE) throw new ArithmeticException(); return (int) d; } @Override public Set edgesOf(final Long vertex) { final ObjectLinkedOpenHashSet set = new ObjectLinkedOpenHashSet<>(); final long source = vertex; final LazyLongIterator successors = immutableGraph.successors(source); for (long target; (target = successors.nextLong()) != -1;) set.add(LongLongPair.of(source, target)); final LazyLongIterator predecessors = immutableTranspose.successors(source); for (long target; (target = predecessors.nextLong()) != -1;) if (source != target) set.add(LongLongPair.of(target, source)); return set; } @Override public int inDegreeOf(final Long vertex) { final long d = immutableTranspose.outdegree(vertex); if (d > Integer.MAX_VALUE) throw new ArithmeticException(); return (int) d; } @Override public Set incomingEdgesOf(final Long vertex) { final ObjectLinkedOpenHashSet set = new ObjectLinkedOpenHashSet<>(); final long source = vertex; final LazyLongIterator predecessors = immutableTranspose.successors(source); for (long target; (target = predecessors.nextLong()) != -1;) set.add(LongLongPair.of(target, source)); return set; } @Override public int outDegreeOf(final Long vertex) { final long d = immutableGraph.outdegree(vertex); if (d > Integer.MAX_VALUE) throw new ArithmeticException(); return (int) d; } @Override public Set outgoingEdgesOf(final Long vertex) { final ObjectLinkedOpenHashSet set = new ObjectLinkedOpenHashSet<>(); final long source = vertex; final LazyLongIterator successors = immutableGraph.successors(source); for (long target; (target = successors.nextLong()) != -1;) set.add(LongLongPair.of(source, target)); return set; } @Override public GraphType getType() { return new DefaultGraphType.Builder() .weighted(false).modifiable(false).allowMultipleEdges(false).allowSelfLoops(true) .directed().build(); } @Override public ImmutableDirectedBigGraphAdapter copy() { return new ImmutableDirectedBigGraphAdapter( immutableGraph.copy(), immutableTranspose != null ? immutableTranspose.copy() : null); } private final GraphIterables ITERABLES = new GraphIterables<>() { @Override public ImmutableDirectedBigGraphAdapter getGraph() { return ImmutableDirectedBigGraphAdapter.this; } @Override public long vertexCount() { return n; } @Override public long edgeCount() { if (m != -1) return m; try { return m = immutableGraph.numArcs(); } catch (final UnsupportedOperationException e) { } return m = ObjectIterables.size(edges()); } @Override public long degreeOf(final Long vertex) { return inDegreeOf(vertex) + outDegreeOf(vertex); } @Override public Iterable edgesOf(final Long source) { return Iterables.concat(outgoingEdgesOf(source), incomingEdgesOf(source, true)); } @Override public long inDegreeOf(final Long vertex) { return immutableTranspose.outdegree(vertex); } private Iterable incomingEdgesOf(final long x, final boolean skipLoops) { return () -> new Iterator<>() { final LazyLongIterator successors = immutableTranspose.successors(x); long y = -1; @Override public boolean hasNext() { if (y == -1) { y = successors.nextLong(); if (skipLoops && x == y) y = successors.nextLong(); } return y != -1; } @Override public LongLongPair next() { final LongLongPair edge = LongLongPair.of(y, x); y = -1; return edge; } }; } @Override public Iterable incomingEdgesOf(final Long vertex) { return incomingEdgesOf(vertex, false); } @Override public long outDegreeOf(final Long vertex) { return immutableGraph.outdegree(vertex); } @Override public Iterable outgoingEdgesOf(final Long vertex) { return () -> new Iterator<>() { final long x = vertex; final LazyLongIterator successors = immutableGraph.successors(x); long y = -1; @Override public boolean hasNext() { if (y == -1) y = successors.nextLong(); return y != -1; } @Override public LongLongPair next() { final LongLongPair edge = LongLongPair.of(x, y); y = -1; return edge; } }; } @Override public Iterable edges() { return () -> new Iterator<>() { final NodeIterator nodeIterator = immutableGraph.nodeIterator(); LazyLongIterator successors = LazyLongIterators.EMPTY_ITERATOR; long x, y = -1; @Override public boolean hasNext() { if (y != -1) return true; while ((y = successors.nextLong()) == -1) { if (!nodeIterator.hasNext()) return false; x = nodeIterator.nextLong(); successors = nodeIterator.successors(); } return true; } @Override public LongLongPair next() { if (!hasNext()) throw new NoSuchElementException(); final LongLongPair edge = LongLongPair.of(x, y); y = -1; return edge; } }; } }; @Override public GraphIterables iterables() { return ITERABLES; } } ImmutableDirectedGraphAdapter.java000066400000000000000000000346061402514743400352750ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import org.jgrapht.GraphIterables; import org.jgrapht.GraphType; import org.jgrapht.graph.DefaultGraphType; import com.google.common.collect.Iterables; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.ints.IntIntSortedPair; import it.unimi.dsi.fastutil.objects.ObjectIterables; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashBigSet; import it.unimi.dsi.lang.FlyweightPrototype; import it.unimi.dsi.webgraph.EFGraph; import it.unimi.dsi.webgraph.ImmutableGraph; import it.unimi.dsi.webgraph.LazyIntIterator; import it.unimi.dsi.webgraph.LazyIntIterators; import it.unimi.dsi.webgraph.LazyIntSkippableIterator; import it.unimi.dsi.webgraph.NodeIterator; import it.unimi.dsi.webgraph.Transform; /** * An adapter class for directed graphs using WebGraph's * {@link ImmutableGraph}. * *

    * Nodes are instances of {@link Integer} corresponding to the index of a node in WebGraph. Edges * are represented by an {@link IntIntPair}. The left and right element are the source and the * target of the edge. Since the underlying graph is immutable, the resulting graph is unmodifiable. * Edges are immutable and can be tested for equality (e.g., stored in a dictionary). * *

    * WebGraph provides methods for successors only, so to adapt a directed graph you must provide both * a graph and its transpose (methods to compute the transpose are available in {@link Transform}). * * You need to load an {@link ImmutableGraph} and its transpose using one of the available load * methods, and then build an adapter: * *

     * immutableGraph = ImmutableGraph.loadMapped("mygraph");
     * immutableTranspose = ImmutableGraph.loadMapped("mygraph-t");
     * adapter = new ImmutableDirectedGraphAdapter(immutableGraph, immutableTranspose);
     * 
    * *

    * The first graph will be used to implement {@link #outgoingEdgesOf(Integer)}, and the second graph * to implement {@link #incomingEdgesOf(Integer)}. It is your responsibility that the two provided * graphs are one the transpose of the other (for each arc * x → y in a graph there must be an arc * y → x in the other, and vice versa). No check will be * performed. Note that {@linkplain GraphIterables#edgeCount() computing the number of edges of a * graph} requires a full scan of the edge set if {@link ImmutableGraph#numArcs()} is not supported * (the first time—then it will be cached). * *

    * If you use a load method that does not provide random access, most methods will throw an * {@link UnsupportedOperationException}. * *

    * If you know that you will never used methods based on incoming edges * ({@link #incomingEdgesOf(Integer)}, {@link #inDegreeOf(Integer)}, {@link #edgesOf(Integer)}, * {@link #degreeOf(Integer)}), you can also use the constructor using just a graph, but all such * methods will throw a {@link NullPointerException}: * *

     * immutableGraph = ImmutableGraph.loadMapped("mygraph");
     * adapter = new ImmutableDirectedGraphAdapter(immutableGraph);
     * 
    * *

    * If necessary, you can adapt a {@linkplain it.unimi.dsi.big.webgraph.ImmutableGraph big WebGraph * graph} with at most {@link Integer#MAX_VALUE} vertices using the suitable * {@linkplain it.unimi.dsi.big.webgraph.ImmutableGraph#wrap(ImmutableGraph) wrapper}. * *

    Thread safety

    * *

    * This class is not thread safe: following the {@link FlyweightPrototype} pattern, users can access * concurrently the graph {@linkplain #copy() by getting lightweight copies}. * *

    Fast adjacency check

    * *

    * As it happens for the sparse representation of JGraphT, usually a WebGraph compressed * representation requires scanning the adjacency list of a node to * {@linkplain #getEdge(Integer, Integer) test whether a specific arc exists}. However, if you adapt * a WebGraph class (such as {@link EFGraph}) which provides {@linkplain LazyIntSkippableIterator * skippable iterators} with fast skipping, adjacency can be tested more quickly (e.g., essentially * in constant time in the case of {@link EFGraph}). * * @see AbstractImmutableBigGraphAdapter * @author Sebastiano Vigna */ public class ImmutableDirectedGraphAdapter extends AbstractImmutableGraphAdapter implements FlyweightPrototype { /** * The transpose of {@link #immutableGraph}, for a directed graph with full support; * {@code null}, for a directed graph with access to outgoing edges, only. */ private final ImmutableGraph immutableTranspose; /** * Creates an adapter for a directed immutable graph. * *

    * It is responsibility of the caller that the two provided graphs are one the transpose of the * other (for each arc x → y in a graph there must be an * arc y → x in the other). If this property is not true, * results will be unpredictable. * * @param immutableGraph an immutable graph. * @param immutableTranspose its transpose. */ public ImmutableDirectedGraphAdapter( final ImmutableGraph immutableGraph, final ImmutableGraph immutableTranspose) { super(immutableGraph); this.immutableTranspose = immutableTranspose; if (immutableTranspose != null && n != immutableTranspose.numNodes()) throw new IllegalArgumentException( "The graph has " + n + " nodes, but the transpose has " + immutableTranspose.numNodes()); } /** * Creates an adapter for a directed immutable graph implementing only methods based on outgoing * edges. * * @param immutableGraph an immutable graph. */ public ImmutableDirectedGraphAdapter(final ImmutableGraph immutableGraph) { this(immutableGraph, null); } @Override protected IntIntPair makeEdge(final int x, final int y) { return IntIntPair.of(x, y); } @Override public boolean containsEdge(final IntIntPair e) { if (e == null) return false; if (e instanceof IntIntSortedPair) return false; return containsEdgeFast(e.leftInt(), e.rightInt()); } @Override public Set edgeSet() { final NodeIterator nodeIterator = immutableGraph.nodeIterator(); final long m = iterables().edgeCount(); final ObjectOpenHashBigSet edges = new ObjectOpenHashBigSet<>(m); for (int i = 0; i < n; i++) { final int x = nodeIterator.nextInt(); final LazyIntIterator successors = nodeIterator.successors(); for (int y; (y = successors.nextInt()) != -1;) edges.add(IntIntPair.of(x, y)); } return edges; } @Override public int degreeOf(final Integer vertex) { final long d = (long) inDegreeOf(vertex) + outDegreeOf(vertex); if (d > Integer.MAX_VALUE) throw new ArithmeticException(); return (int) d; } @Override public Set edgesOf(final Integer vertex) { final ObjectLinkedOpenHashSet set = new ObjectLinkedOpenHashSet<>(); final int source = vertex; final LazyIntIterator successors = immutableGraph.successors(source); for (int target; (target = successors.nextInt()) != -1;) set.add(IntIntPair.of(source, target)); final LazyIntIterator predecessors = immutableTranspose.successors(source); for (int target; (target = predecessors.nextInt()) != -1;) if (source != target) set.add(IntIntPair.of(target, source)); return set; } @Override public int inDegreeOf(final Integer vertex) { return immutableTranspose.outdegree(vertex); } @Override public Set incomingEdgesOf(final Integer vertex) { final ObjectLinkedOpenHashSet set = new ObjectLinkedOpenHashSet<>(); final int source = vertex; final LazyIntIterator predecessors = immutableTranspose.successors(source); for (int target; (target = predecessors.nextInt()) != -1;) set.add(IntIntPair.of(target, source)); return set; } @Override public int outDegreeOf(final Integer vertex) { return immutableGraph.outdegree(vertex); } @Override public Set outgoingEdgesOf(final Integer vertex) { final ObjectLinkedOpenHashSet set = new ObjectLinkedOpenHashSet<>(); final int source = vertex; final LazyIntIterator successors = immutableGraph.successors(source); for (int target; (target = successors.nextInt()) != -1;) set.add(IntIntPair.of(source, target)); return set; } @Override public GraphType getType() { return new DefaultGraphType.Builder() .weighted(false).modifiable(false).allowMultipleEdges(false).allowSelfLoops(true) .directed().build(); } @Override public ImmutableDirectedGraphAdapter copy() { return new ImmutableDirectedGraphAdapter( immutableGraph.copy(), immutableTranspose != null ? immutableTranspose.copy() : null); } private final GraphIterables ITERABLES = new GraphIterables<>() { @Override public ImmutableDirectedGraphAdapter getGraph() { return ImmutableDirectedGraphAdapter.this; } @Override public long vertexCount() { return n; } @Override public long edgeCount() { if (m != -1) return m; try { return m = immutableGraph.numArcs(); } catch (final UnsupportedOperationException e) { } return m = ObjectIterables.size(edges()); } @Override public long degreeOf(final Integer vertex) { return inDegreeOf(vertex) + outDegreeOf(vertex); } @Override public Iterable edgesOf(final Integer source) { return Iterables.concat(outgoingEdgesOf(source), incomingEdgesOf(source, true)); } @Override public long inDegreeOf(final Integer vertex) { return immutableTranspose.outdegree(vertex); } private Iterable incomingEdgesOf(final int x, final boolean skipLoops) { return () -> new Iterator<>() { final LazyIntIterator successors = immutableTranspose.successors(x); int y = successors.nextInt(); @Override public boolean hasNext() { if (y == -1) { y = successors.nextInt(); if (skipLoops && x == y) y = successors.nextInt(); } return y != -1; } @Override public IntIntPair next() { final IntIntPair edge = IntIntPair.of(y, x); y = -1; return edge; } }; } @Override public Iterable incomingEdgesOf(final Integer vertex) { return incomingEdgesOf(vertex, false); } @Override public long outDegreeOf(final Integer vertex) { return immutableGraph.outdegree(vertex); } @Override public Iterable outgoingEdgesOf(final Integer vertex) { return () -> new Iterator<>() { final int x = vertex; final LazyIntIterator successors = immutableGraph.successors(x); int y = successors.nextInt(); @Override public boolean hasNext() { if (y == -1) y = successors.nextInt(); return y != -1; } @Override public IntIntPair next() { final IntIntPair edge = IntIntPair.of(x, y); y = -1; return edge; } }; } @Override public Iterable edges() { return () -> new Iterator<>() { final NodeIterator nodeIterator = immutableGraph.nodeIterator(); LazyIntIterator successors = LazyIntIterators.EMPTY_ITERATOR; int x, y = -1; @Override public boolean hasNext() { if (y != -1) return true; while ((y = successors.nextInt()) == -1) { if (!nodeIterator.hasNext()) return false; x = nodeIterator.nextInt(); successors = nodeIterator.successors(); } return true; } @Override public IntIntPair next() { if (!hasNext()) throw new NoSuchElementException(); final IntIntPair edge = IntIntPair.of(x, y); y = -1; return edge; } }; } }; @Override public GraphIterables iterables() { return ITERABLES; } } ImmutableUndirectedBigGraphAdapter.java000066400000000000000000000237011402514743400362540ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import org.jgrapht.GraphIterables; import org.jgrapht.GraphType; import org.jgrapht.graph.DefaultGraphType; import it.unimi.dsi.big.webgraph.ImmutableGraph; import it.unimi.dsi.big.webgraph.LazyLongIterator; import it.unimi.dsi.big.webgraph.LazyLongIterators; import it.unimi.dsi.big.webgraph.NodeIterator; import it.unimi.dsi.fastutil.longs.LongLongSortedPair; import it.unimi.dsi.fastutil.longs.LongSets; import it.unimi.dsi.fastutil.objects.ObjectIterables; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashBigSet; import it.unimi.dsi.lang.FlyweightPrototype; /** * An adapter class for undirected graphs using WebGraph * (big)'s {@link ImmutableGraph}. * *

    * This class is equivalent to {@link ImmutableUndirectedGraphAdapter}, except that nodes are * instances of {@link Long}, and edges are instances of {@link LongLongSortedPair}. * *

    * If necessary, you can adapt a {@linkplain it.unimi.dsi.webgraph.ImmutableGraph standard WebGraph * graph} using the suitable {@linkplain ImmutableGraph#wrap(it.unimi.dsi.webgraph.ImmutableGraph) * wrapper}. * * @see ImmutableUndirectedGraphAdapter * @author Sebastiano Vigna */ public class ImmutableUndirectedBigGraphAdapter extends AbstractImmutableBigGraphAdapter implements FlyweightPrototype { /** * Creates an adapter for an undirected (i.e., symmetric) big immutable graph. * *

    * It is responsibility of the caller that the provided graph has is symmetric (for each arc * x → y there is an arc y → * x). If this property is not true, results will be unpredictable. * * @param immutableGraph a symmetric big immutable graph. */ public ImmutableUndirectedBigGraphAdapter(final ImmutableGraph immutableGraph) { super(immutableGraph); } @Override protected LongLongSortedPair makeEdge(final long x, final long y) { return LongLongSortedPair.of(x, y); } @Override public boolean containsEdge(final LongLongSortedPair e) { if (e == null) return false; return containsEdgeFast(e.leftLong(), e.rightLong()); } @Override public Set edgeSet() { final NodeIterator nodeIterator = immutableGraph.nodeIterator(); final long m = iterables().edgeCount(); final ObjectOpenHashBigSet edges = new ObjectOpenHashBigSet<>(m); for (long i = 0; i < n; i++) { final long x = nodeIterator.nextLong(); final LazyLongIterator successors = nodeIterator.successors(); for (long y; (y = successors.nextLong()) != -1;) if (x <= y) edges.add(LongLongSortedPair.of(x, y)); } return edges; } @Override public int degreeOf(final Long vertex) { final long d = inDegreeOf(vertex) + (containsEdgeFast(vertex, vertex) ? 1L : 0L); if (d > Integer.MAX_VALUE) throw new ArithmeticException(); return (int) d; } @Override public Set edgesOf(final Long vertex) { final ObjectLinkedOpenHashSet set = new ObjectLinkedOpenHashSet<>(); final long source = vertex; final LazyLongIterator predecessors = immutableGraph.successors(source); for (long target; (target = predecessors.nextLong()) != -1;) set.add(LongLongSortedPair.of(source, target)); return set; } @Override public int inDegreeOf(final Long vertex) { final long d = immutableGraph.outdegree(vertex); if (d > Integer.MAX_VALUE) throw new ArithmeticException(); return (int) d; } @Override public Set incomingEdgesOf(final Long vertex) { return edgesOf(vertex); } @Override public int outDegreeOf(final Long vertex) { final long d = immutableGraph.outdegree(vertex); if (d > Integer.MAX_VALUE) throw new ArithmeticException(); return (int) d; } @Override public Set outgoingEdgesOf(final Long vertex) { return edgesOf(vertex); } @Override public LongLongSortedPair removeEdge(final Long sourceVertex, final Long targetVertex) { throw new UnsupportedOperationException(); } @Override public boolean removeEdge(final LongLongSortedPair e) { throw new UnsupportedOperationException(); } @Override public boolean removeVertex(final Long v) { throw new UnsupportedOperationException(); } @Override public Set vertexSet() { return LongSets.fromTo(0, n); } @Override public Long getEdgeSource(final LongLongSortedPair e) { return e.leftLong(); } @Override public Long getEdgeTarget(final LongLongSortedPair e) { return e.rightLong(); } @Override public double getEdgeWeight(final LongLongSortedPair e) { return DEFAULT_EDGE_WEIGHT; } @Override public void setEdgeWeight(final LongLongSortedPair e, final double weight) { if (weight != 1) throw new UnsupportedOperationException(); } @Override public GraphType getType() { return new DefaultGraphType.Builder() .weighted(false).modifiable(false).allowMultipleEdges(false).allowSelfLoops(true) .undirected().build(); } @Override public ImmutableUndirectedBigGraphAdapter copy() { return new ImmutableUndirectedBigGraphAdapter(immutableGraph.copy()); } private final GraphIterables ITERABLES = new GraphIterables<>() { @Override public ImmutableUndirectedBigGraphAdapter getGraph() { return ImmutableUndirectedBigGraphAdapter.this; } @Override public long vertexCount() { return n; } @Override public long edgeCount() { if (m != -1) return m; return m = ObjectIterables.size(edges()); } @Override public long degreeOf(final Long vertex) { return inDegreeOf(vertex) + (containsEdgeFast(vertex, vertex) ? 1 : 0); } @Override public Iterable edgesOf(final Long vertex) { final long x = vertex; return () -> new Iterator<>() { final LazyLongIterator successors = immutableGraph.successors(x); long y = -1; @Override public boolean hasNext() { if (y == -1) y = successors.nextLong(); return y != -1; } @Override public LongLongSortedPair next() { final LongLongSortedPair edge = LongLongSortedPair.of(y, x); y = -1; return edge; } }; } @Override public long inDegreeOf(final Long vertex) { return immutableGraph.outdegree(vertex); } public Iterable incomingEdgesOf(final Long vertex) { return edgesOf(vertex); } @Override public long outDegreeOf(final Long vertex) { return immutableGraph.outdegree(vertex); } @Override public Iterable outgoingEdgesOf(final Long vertex) { return edgesOf(vertex); } @Override public Iterable edges() { return () -> new Iterator<>() { final NodeIterator nodeIterator = immutableGraph.nodeIterator(); LazyLongIterator successors = LazyLongIterators.EMPTY_ITERATOR; long x, y = -1; @Override public boolean hasNext() { if (y != -1) return true; do { while ((y = successors.nextLong()) == -1) { if (!nodeIterator.hasNext()) return false; x = nodeIterator.nextLong(); successors = nodeIterator.successors(); } } while (y < x); return true; } @Override public LongLongSortedPair next() { if (!hasNext()) throw new NoSuchElementException(); final LongLongSortedPair edge = LongLongSortedPair.of(x, y); y = -1; return edge; } }; } }; @Override public GraphIterables iterables() { return ITERABLES; } } ImmutableUndirectedGraphAdapter.java000066400000000000000000000253111402514743400356310ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import org.jgrapht.GraphIterables; import org.jgrapht.GraphType; import org.jgrapht.graph.DefaultGraphType; import it.unimi.dsi.fastutil.ints.IntIntSortedPair; import it.unimi.dsi.fastutil.objects.ObjectIterables; import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashBigSet; import it.unimi.dsi.lang.FlyweightPrototype; import it.unimi.dsi.webgraph.Check; import it.unimi.dsi.webgraph.EFGraph; import it.unimi.dsi.webgraph.ImmutableGraph; import it.unimi.dsi.webgraph.LazyIntIterator; import it.unimi.dsi.webgraph.LazyIntIterators; import it.unimi.dsi.webgraph.LazyIntSkippableIterator; import it.unimi.dsi.webgraph.NodeIterator; /** * An adapter class for undirected graphs using * WebGraph's {@link ImmutableGraph}. * *

    * Nodes are instances of {@link Integer} corresponding to the index of a node in WebGraph. Edges * are represented by an {@link IntIntSortedPair}. Edges are canonicalized so that the left element * is always smaller than or equal to the right element. Since the underlying graph is immutable, * the resulting graph is unmodifiable. Edges are immutable and can be tested for equality (e.g., * stored in a dictionary). * *

    * You need to load a symmetric {@link ImmutableGraph} using one of the available load methods * available, and then build an adapter: * *

     * immutableGraph = ImmutableGraph.loadMapped("mygraph");
     * adapter = new ImmutableUndirectedGraphAdapter(immutableGraph);
     * 
    * *

    * It is your responsibility that the provided graph is symmetric (for each arc * x → y there is an arc y → * x). No check will be performed, but you can use the {@link Check} class to this * purpose. Note that {@linkplain GraphIterables#edgeCount() computing the number of edges of a * graph} requires a full scan of the edge set if {@link ImmutableGraph#numArcs()} is not supported * (the first time—then it will be cached). * *

    * If you use a load method that does not provide random access, most methods will throw an * {@link UnsupportedOperationException}. * *

    * If necessary, you can adapt a {@linkplain it.unimi.dsi.big.webgraph.ImmutableGraph big WebGraph * graph} with at most {@link Integer#MAX_VALUE} vertices using the suitable * {@linkplain it.unimi.dsi.big.webgraph.ImmutableGraph#wrap(ImmutableGraph) wrapper}. * *

    Thread safety

    * *

    * This class is not thread safe: following the {@link FlyweightPrototype} pattern, users can access * concurrently the graph {@linkplain #copy() by getting lightweight copies}. * *

    Fast adjacency check

    * *

    * As it happens for the sparse representation of JGraphT, usually a WebGraph compressed * representation requires scanning the adjacency list of a node to * {@linkplain #getEdge(Integer, Integer) test whether a specific arc exists}. However, if you adapt * a WebGraph class (such as {@link EFGraph}) which provides {@linkplain LazyIntSkippableIterator * skippable iterators} with fast skipping, adjacency can be tested more quickly (e.g., essentially * in constant time in the case of {@link EFGraph}). * * @see AbstractImmutableBigGraphAdapter * @author Sebastiano Vigna */ public class ImmutableUndirectedGraphAdapter extends AbstractImmutableGraphAdapter implements FlyweightPrototype { /** * Creates an adapter for an undirected (i.e., symmetric) immutable graph. * *

    * It is responsibility of the caller that the provided graph has is symmetric (for each arc * x → y there is an arc y → * x). If this property is not true, results will be unpredictable. * * @param immutableGraph a symmetric immutable graph. */ public ImmutableUndirectedGraphAdapter(final ImmutableGraph immutableGraph) { super(immutableGraph); } @Override protected IntIntSortedPair makeEdge(final int x, final int y) { return IntIntSortedPair.of(x, y); } @Override public boolean containsEdge(final IntIntSortedPair e) { if (e == null) return false; return containsEdgeFast(e.leftInt(), e.rightInt()); } @Override public Set edgeSet() { final NodeIterator nodeIterator = immutableGraph.nodeIterator(); final long m = iterables().edgeCount(); final ObjectOpenHashBigSet edges = new ObjectOpenHashBigSet<>(m); for (int i = 0; i < n; i++) { final int x = nodeIterator.nextInt(); final LazyIntIterator successors = nodeIterator.successors(); for (int y; (y = successors.nextInt()) != -1;) if (x <= y) edges.add(IntIntSortedPair.of(x, y)); } return edges; } @Override public int degreeOf(final Integer vertex) { final long d = inDegreeOf(vertex) + (containsEdgeFast(vertex, vertex) ? 1L : 0L); if (d > Integer.MAX_VALUE) throw new ArithmeticException(); return (int) d; } @Override public Set edgesOf(final Integer vertex) { final ObjectLinkedOpenHashSet set = new ObjectLinkedOpenHashSet<>(); final int source = vertex; final LazyIntIterator successors = immutableGraph.successors(source); for (int target; (target = successors.nextInt()) != -1;) set.add(IntIntSortedPair.of(source, target)); return set; } @Override public int inDegreeOf(final Integer vertex) { return immutableGraph.outdegree(vertex); } @Override public Set incomingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public int outDegreeOf(final Integer vertex) { return immutableGraph.outdegree(vertex); } @Override public Set outgoingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public GraphType getType() { return new DefaultGraphType.Builder() .weighted(false).modifiable(false).allowMultipleEdges(false).allowSelfLoops(true) .undirected().build(); } @Override public ImmutableUndirectedGraphAdapter copy() { return new ImmutableUndirectedGraphAdapter(immutableGraph.copy()); } private final GraphIterables ITERABLES = new GraphIterables<>() { @Override public ImmutableUndirectedGraphAdapter getGraph() { return ImmutableUndirectedGraphAdapter.this; } @Override public long vertexCount() { return n; } @Override public long edgeCount() { if (m != -1) return m; return m = ObjectIterables.size(edges()); } @Override public long degreeOf(final Integer vertex) { return inDegreeOf(vertex) + (containsEdgeFast(vertex, vertex) ? 1 : 0); } @Override public Iterable edgesOf(final Integer vertex) { final int x = vertex; return () -> new Iterator<>() { final LazyIntIterator successors = immutableGraph.successors(x); int y = successors.nextInt(); @Override public boolean hasNext() { if (y != -1) return true; return (y = successors.nextInt()) != -1; } @Override public IntIntSortedPair next() { final IntIntSortedPair edge = IntIntSortedPair.of(y, x); y = -1; return edge; } }; } @Override public long inDegreeOf(final Integer vertex) { return immutableGraph.outdegree(vertex); } @Override public Iterable incomingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public long outDegreeOf(final Integer vertex) { return immutableGraph.outdegree(vertex); } @Override public Iterable outgoingEdgesOf(final Integer vertex) { return edgesOf(vertex); } @Override public Iterable edges() { return () -> new Iterator<>() { final NodeIterator nodeIterator = immutableGraph.nodeIterator(); LazyIntIterator successors = LazyIntIterators.EMPTY_ITERATOR; int x, y = -1; @Override public boolean hasNext() { if (y != -1) return true; do { while ((y = successors.nextInt()) == -1) { if (!nodeIterator.hasNext()) return false; x = nodeIterator.nextInt(); successors = nodeIterator.successors(); } } while (y < x); return true; } @Override public IntIntSortedPair next() { if (!hasNext()) throw new NoSuchElementException(); final IntIntSortedPair edge = IntIntSortedPair.of(x, y); y = -1; return edge; } }; } }; @Override public GraphIterables iterables() { return ITERABLES; } } jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/main/java/org/jgrapht/webgraph/package-info.java000066400000000000000000000002471402514743400320240ustar00rootroot00000000000000/** * Adapters for graphs stored using * WebGraph's compressed and succinct * formats. */ package org.jgrapht.webgraph; jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/000077500000000000000000000000001402514743400217175ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/000077500000000000000000000000001402514743400226405ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/000077500000000000000000000000001402514743400234275ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/000077500000000000000000000000001402514743400250665ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/000077500000000000000000000000001402514743400261435ustar00rootroot00000000000000SuccinctDirectedGraphTest.java000066400000000000000000000244431402514743400337770ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.Iterator; import java.util.Set; import java.util.function.Supplier; import org.jgrapht.generate.GnpRandomGraphGenerator; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.util.SupplierUtil; import org.junit.Test; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.util.XoRoShiRo128PlusPlusRandomGenerator; public class SuccinctDirectedGraphTest { @Test public void testDirected() { final DefaultDirectedGraph d = new DefaultDirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); for (int i = 0; i < 5; i++) d.addVertex(i); d.addEdge(0, 1); d.addEdge(0, 2); d.addEdge(1, 2); d.addEdge(2, 1); d.addEdge(2, 3); d.addEdge(3, 0); d.addEdge(3, 3); d.addEdge(3, 4); d.addEdge(4, 1); final SuccinctDirectedGraph s = new SuccinctDirectedGraph(d); assertEquals(2, s.outDegreeOf(0)); assertEquals(1, s.outDegreeOf(1)); assertEquals(2, s.outDegreeOf(2)); assertEquals(3, s.outDegreeOf(3)); assertEquals(1, s.outDegreeOf(4)); assertEquals(1, s.inDegreeOf(0)); assertEquals(3, s.inDegreeOf(1)); assertEquals(2, s.inDegreeOf(2)); assertEquals(2, s.inDegreeOf(3)); assertEquals(1, s.inDegreeOf(4)); assertEquals(IntIntPair.of(0, 1), s.getEdge(0, 1)); assertEquals(IntIntPair.of(0, 2), s.getEdge(0, 2)); assertEquals(IntIntPair.of(1, 2), s.getEdge(1, 2)); assertEquals(IntIntPair.of(2, 1), s.getEdge(2, 1)); assertEquals(IntIntPair.of(2, 3), s.getEdge(2, 3)); assertEquals(IntIntPair.of(3, 0), s.getEdge(3, 0)); assertEquals(IntIntPair.of(3, 3), s.getEdge(3, 3)); assertEquals(IntIntPair.of(3, 4), s.getEdge(3, 4)); assertEquals(IntIntPair.of(4, 1), s.getEdge(4, 1)); assertNull(s.getEdge(0, 0)); assertNull(s.getEdge(0, 3)); assertNull(s.getEdge(0, 4)); assertNull(s.getEdge(1, 0)); assertNull(s.getEdge(1, 1)); assertNull(s.getEdge(1, 3)); assertNull(s.getEdge(1, 4)); assertNull(s.getEdge(2, 0)); assertNull(s.getEdge(2, 2)); assertNull(s.getEdge(2, 4)); assertNull(s.getEdge(3, 1)); assertNull(s.getEdge(3, 2)); assertNull(s.getEdge(4, 0)); assertNull(s.getEdge(4, 4)); assertNull(s.getEdge(4, 2)); assertNull(s.getEdge(4, 3)); assertTrue(s.containsEdge(0, 1)); assertTrue(s.containsEdge(0, 2)); assertTrue(s.containsEdge(1, 2)); assertTrue(s.containsEdge(2, 1)); assertTrue(s.containsEdge(2, 3)); assertTrue(s.containsEdge(3, 0)); assertTrue(s.containsEdge(3, 3)); assertTrue(s.containsEdge(3, 4)); assertTrue(s.containsEdge(4, 1)); assertFalse(s.containsEdge(0, 0)); assertFalse(s.containsEdge(0, 3)); assertFalse(s.containsEdge(0, 4)); assertFalse(s.containsEdge(1, 0)); assertFalse(s.containsEdge(1, 1)); assertFalse(s.containsEdge(1, 3)); assertFalse(s.containsEdge(1, 4)); assertFalse(s.containsEdge(2, 0)); assertFalse(s.containsEdge(2, 2)); assertFalse(s.containsEdge(2, 4)); assertFalse(s.containsEdge(3, 1)); assertFalse(s.containsEdge(3, 2)); assertFalse(s.containsEdge(4, 0)); assertFalse(s.containsEdge(4, 4)); assertFalse(s.containsEdge(4, 2)); assertFalse(s.containsEdge(4, 3)); assertEquals(Set.of(IntIntPair.of(0, 1), IntIntPair.of(0, 2)), s.outgoingEdgesOf(0)); assertEquals(Set.of(IntIntPair.of(1, 2)), s.outgoingEdgesOf(1)); assertEquals(Set.of(IntIntPair.of(2, 1), IntIntPair.of(2, 3)), s.outgoingEdgesOf(2)); assertEquals( Set.of(IntIntPair.of(3, 0), IntIntPair.of(3, 3), IntIntPair.of(3, 4)), s.outgoingEdgesOf(3)); assertEquals(Set.of(IntIntPair.of(4, 1)), s.outgoingEdgesOf(4)); assertEquals(Set.of(IntIntPair.of(3, 0)), s.incomingEdgesOf(0)); assertEquals( Set.of(IntIntPair.of(0, 1), IntIntPair.of(2, 1), IntIntPair.of(4, 1)), s.incomingEdgesOf(1)); assertEquals(Set.of(IntIntPair.of(0, 2), IntIntPair.of(1, 2)), s.incomingEdgesOf(2)); assertEquals(Set.of(IntIntPair.of(2, 3), IntIntPair.of(3, 3)), s.incomingEdgesOf(3)); assertEquals(Set.of(IntIntPair.of(3, 4)), s.incomingEdgesOf(4)); assertEquals( Set.of(IntIntPair.of(3, 0)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(0).iterator())); assertEquals( Set.of(IntIntPair.of(0, 1), IntIntPair.of(2, 1), IntIntPair.of(4, 1)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(1).iterator())); assertEquals( Set.of(IntIntPair.of(0, 2), IntIntPair.of(1, 2)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(2).iterator())); assertEquals( Set.of(IntIntPair.of(2, 3), IntIntPair.of(3, 3)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(3).iterator())); assertEquals( Set.of(IntIntPair.of(3, 4)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(4).iterator())); Iterator iterator = s.iterables().edgesOf(0).iterator(); while (iterator.hasNext()) iterator.next(); assertFalse(iterator.hasNext()); assertFalse(iterator.hasNext()); iterator = s.iterables().outgoingEdgesOf(0).iterator(); while (iterator.hasNext()) iterator.next(); assertFalse(iterator.hasNext()); assertFalse(iterator.hasNext()); iterator = s.iterables().incomingEdgesOf(0).iterator(); while (iterator.hasNext()) iterator.next(); assertFalse(iterator.hasNext()); assertFalse(iterator.hasNext()); } @Test public void testSink() { final DefaultDirectedGraph d = new DefaultDirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); for (int i = 0; i < 3; i++) d.addVertex(i); d.addEdge(0, 1); d.addEdge(2, 0); final SuccinctDirectedGraph s = new SuccinctDirectedGraph(d); assertEquals(IntIntPair.of(0, 1), s.getEdge(0, 1)); assertEquals(IntIntPair.of(2, 0), s.getEdge(2, 0)); } @Test public void testRandomDense() { final GnpRandomGraphGenerator r = new GnpRandomGraphGenerator<>(1000, .1, 0, false); final DefaultDirectedGraph s = new DefaultDirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); r.generateGraph(s); final SuccinctDirectedGraph t = new SuccinctDirectedGraph(s); for (final IntIntPair e : t.edgeSet()) assertTrue(e.toString(), s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e))); for (final DefaultEdge e : s.edgeSet()) assertTrue(e.toString(), t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e))); final XoRoShiRo128PlusPlusRandomGenerator random = new XoRoShiRo128PlusPlusRandomGenerator(); final int n = (int) s.iterables().vertexCount(); for (int i = 0; i < 10000; i++) { final int x = random.nextInt(n); final int y = random.nextInt(n); assertEquals(s.containsEdge(x, y), t.containsEdge(x, y)); } } @Test public void testRandomSparse() { final GnpRandomGraphGenerator r = new GnpRandomGraphGenerator<>(1000, .001, 0, false); final DefaultDirectedGraph s = new DefaultDirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); r.generateGraph(s); final SuccinctDirectedGraph t = new SuccinctDirectedGraph(s); for (final IntIntPair e : t.edgeSet()) assertTrue(e.toString(), s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e))); for (final DefaultEdge e : s.edgeSet()) assertTrue(e.toString(), t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e))); final XoRoShiRo128PlusPlusRandomGenerator random = new XoRoShiRo128PlusPlusRandomGenerator(); final int n = (int) s.iterables().vertexCount(); for (int i = 0; i < 10000; i++) { final int x = random.nextInt(n); final int y = random.nextInt(n); assertEquals(s.containsEdge(x, y), t.containsEdge(x, y)); } } } SuccinctIntDirectedGraphTest.java000066400000000000000000000253341402514743400344520ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.Iterator; import java.util.function.Supplier; import org.jgrapht.generate.GnpRandomGraphGenerator; import org.jgrapht.graph.DefaultDirectedGraph; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.util.SupplierUtil; import org.junit.Test; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSets; import it.unimi.dsi.util.XoRoShiRo128PlusPlusRandomGenerator; public class SuccinctIntDirectedGraphTest { @Test public void testDirected() { final DefaultDirectedGraph d = new DefaultDirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); for (int i = 0; i < 5; i++) d.addVertex(i); d.addEdge(0, 1); d.addEdge(0, 2); d.addEdge(1, 2); d.addEdge(2, 1); d.addEdge(2, 3); d.addEdge(3, 0); d.addEdge(3, 3); d.addEdge(3, 4); d.addEdge(4, 1); final SuccinctIntDirectedGraph s = new SuccinctIntDirectedGraph(d); assertEquals(2, s.outDegreeOf(0)); assertEquals(1, s.outDegreeOf(1)); assertEquals(2, s.outDegreeOf(2)); assertEquals(3, s.outDegreeOf(3)); assertEquals(1, s.outDegreeOf(4)); assertEquals(1, s.inDegreeOf(0)); assertEquals(3, s.inDegreeOf(1)); assertEquals(2, s.inDegreeOf(2)); assertEquals(2, s.inDegreeOf(3)); assertEquals(1, s.inDegreeOf(4)); assertEquals(0, s.getEdge(0, 1).intValue()); assertEquals(1, s.getEdge(0, 2).intValue()); assertEquals(2, s.getEdge(1, 2).intValue()); assertEquals(3, s.getEdge(2, 1).intValue()); assertEquals(4, s.getEdge(2, 3).intValue()); assertEquals(5, s.getEdge(3, 0).intValue()); assertEquals(6, s.getEdge(3, 3).intValue()); assertEquals(7, s.getEdge(3, 4).intValue()); assertEquals(8, s.getEdge(4, 1).intValue()); assertNull(s.getEdge(0, 0)); assertNull(s.getEdge(0, 3)); assertNull(s.getEdge(0, 4)); assertNull(s.getEdge(1, 0)); assertNull(s.getEdge(1, 1)); assertNull(s.getEdge(1, 3)); assertNull(s.getEdge(1, 4)); assertNull(s.getEdge(2, 0)); assertNull(s.getEdge(2, 2)); assertNull(s.getEdge(2, 4)); assertNull(s.getEdge(3, 1)); assertNull(s.getEdge(3, 2)); assertNull(s.getEdge(4, 0)); assertNull(s.getEdge(4, 4)); assertNull(s.getEdge(4, 2)); assertNull(s.getEdge(4, 3)); assertTrue(s.containsEdge(0, 1)); assertTrue(s.containsEdge(0, 2)); assertTrue(s.containsEdge(1, 2)); assertTrue(s.containsEdge(2, 1)); assertTrue(s.containsEdge(2, 3)); assertTrue(s.containsEdge(3, 0)); assertTrue(s.containsEdge(3, 3)); assertTrue(s.containsEdge(3, 4)); assertTrue(s.containsEdge(4, 1)); assertFalse(s.containsEdge(0, 0)); assertFalse(s.containsEdge(0, 3)); assertFalse(s.containsEdge(0, 4)); assertFalse(s.containsEdge(1, 0)); assertFalse(s.containsEdge(1, 1)); assertFalse(s.containsEdge(1, 3)); assertFalse(s.containsEdge(1, 4)); assertFalse(s.containsEdge(2, 0)); assertFalse(s.containsEdge(2, 2)); assertFalse(s.containsEdge(2, 4)); assertFalse(s.containsEdge(3, 1)); assertFalse(s.containsEdge(3, 2)); assertFalse(s.containsEdge(4, 0)); assertFalse(s.containsEdge(4, 4)); assertFalse(s.containsEdge(4, 2)); assertFalse(s.containsEdge(4, 3)); assertEquals(0, s.getEdgeSource(0).intValue()); assertEquals(1, s.getEdgeTarget(0).intValue()); assertEquals(0, s.getEdgeSource(1).intValue()); assertEquals(2, s.getEdgeTarget(1).intValue()); assertEquals(1, s.getEdgeSource(2).intValue()); assertEquals(2, s.getEdgeTarget(2).intValue()); assertEquals(2, s.getEdgeSource(3).intValue()); assertEquals(1, s.getEdgeTarget(3).intValue()); assertEquals(2, s.getEdgeSource(4).intValue()); assertEquals(3, s.getEdgeTarget(4).intValue()); assertEquals(IntSets.fromTo(0, 2), s.outgoingEdgesOf(0)); assertEquals(IntSets.fromTo(2, 3), s.outgoingEdgesOf(1)); assertEquals(IntSets.fromTo(3, 5), s.outgoingEdgesOf(2)); assertEquals(IntSets.fromTo(5, 8), s.outgoingEdgesOf(3)); assertEquals(IntSets.fromTo(8, 9), s.outgoingEdgesOf(4)); assertEquals(new IntOpenHashSet(new int[] { 5 }), s.incomingEdgesOf(0)); assertEquals(new IntOpenHashSet(new int[] { 0, 3, 8 }), s.incomingEdgesOf(1)); assertEquals(new IntOpenHashSet(new int[] { 1, 2 }), s.incomingEdgesOf(2)); assertEquals(new IntOpenHashSet(new int[] { 4, 6 }), s.incomingEdgesOf(3)); assertEquals(new IntOpenHashSet(new int[] { 7 }), s.incomingEdgesOf(4)); assertEquals( new IntOpenHashSet(new int[] { 5 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(0).iterator())); assertEquals( new IntOpenHashSet(new int[] { 0, 3, 8 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(1).iterator())); assertEquals( new IntOpenHashSet(new int[] { 1, 2 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(2).iterator())); assertEquals( new IntOpenHashSet(new int[] { 4, 6 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(3).iterator())); assertEquals( new IntOpenHashSet(new int[] { 7 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(4).iterator())); Iterator iterator = s.iterables().edgesOf(0).iterator(); while (iterator.hasNext()) iterator.next(); assertFalse(iterator.hasNext()); assertFalse(iterator.hasNext()); iterator = s.iterables().outgoingEdgesOf(0).iterator(); while (iterator.hasNext()) iterator.next(); assertFalse(iterator.hasNext()); assertFalse(iterator.hasNext()); iterator = s.iterables().incomingEdgesOf(0).iterator(); while (iterator.hasNext()) iterator.next(); assertFalse(iterator.hasNext()); assertFalse(iterator.hasNext()); } @Test public void testSink() { final DefaultDirectedGraph d = new DefaultDirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); for (int i = 0; i < 3; i++) d.addVertex(i); d.addEdge(0, 1); d.addEdge(2, 0); final SuccinctIntDirectedGraph s = new SuccinctIntDirectedGraph(d); assertEquals(0, s.getEdge(0, 1).intValue()); assertEquals(1, s.getEdge(2, 0).intValue()); assertEquals(0, s.getEdgeSource(0).intValue()); assertEquals(1, s.getEdgeTarget(0).intValue()); assertEquals(2, s.getEdgeSource(1).intValue()); assertEquals(0, s.getEdgeTarget(1).intValue()); } @Test public void testRandomDense() { final GnpRandomGraphGenerator r = new GnpRandomGraphGenerator<>(1000, .1, 0, false); final DefaultDirectedGraph s = new DefaultDirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); r.generateGraph(s); final SuccinctIntDirectedGraph t = new SuccinctIntDirectedGraph(s); for (final Integer e : t.edgeSet()) assertTrue(e.toString(), s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e))); for (final DefaultEdge e : s.edgeSet()) assertTrue(e.toString(), t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e))); final XoRoShiRo128PlusPlusRandomGenerator random = new XoRoShiRo128PlusPlusRandomGenerator(); final int n = (int) s.iterables().vertexCount(); for (int i = 0; i < 10000; i++) { final int x = random.nextInt(n); final int y = random.nextInt(n); assertEquals(s.containsEdge(x, y), t.containsEdge(x, y)); } } @Test public void testRandomSparse() { final GnpRandomGraphGenerator r = new GnpRandomGraphGenerator<>(1000, .001, 0, false); final DefaultDirectedGraph s = new DefaultDirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); r.generateGraph(s); final SuccinctIntDirectedGraph t = new SuccinctIntDirectedGraph(s); for (final Integer e : t.edgeSet()) assertTrue(e.toString(), s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e))); for (final DefaultEdge e : s.edgeSet()) assertTrue(e.toString(), t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e))); final XoRoShiRo128PlusPlusRandomGenerator random = new XoRoShiRo128PlusPlusRandomGenerator(); final int n = (int) s.iterables().vertexCount(); for (int i = 0; i < 10000; i++) { final int x = random.nextInt(n); final int y = random.nextInt(n); assertEquals(s.containsEdge(x, y), t.containsEdge(x, y)); } } } SuccinctIntUndirectedGraphTest.java000066400000000000000000000250471402514743400350160ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.Iterator; import java.util.function.Supplier; import org.jgrapht.generate.GnpRandomGraphGenerator; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DefaultUndirectedGraph; import org.jgrapht.util.SupplierUtil; import org.junit.Test; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSets; import it.unimi.dsi.util.XoRoShiRo128PlusPlusRandomGenerator; public class SuccinctIntUndirectedGraphTest { @Test public void testUndirected() { final DefaultUndirectedGraph d = new DefaultUndirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); for (int i = 0; i < 5; i++) d.addVertex(i); d.addEdge(0, 1); d.addEdge(1, 2); d.addEdge(2, 3); d.addEdge(3, 0); d.addEdge(3, 3); d.addEdge(3, 4); d.addEdge(4, 1); final SuccinctIntUndirectedGraph s = new SuccinctIntUndirectedGraph(d); assertEquals(5, d.iterables().vertexCount()); assertEquals(7, d.iterables().edgeCount()); assertEquals(2, s.outDegreeOf(0)); assertEquals(3, s.outDegreeOf(1)); assertEquals(2, s.outDegreeOf(2)); assertEquals(5, s.outDegreeOf(3)); assertEquals(2, s.outDegreeOf(4)); assertEquals(0, s.getEdge(0, 1).intValue()); assertEquals(1, s.getEdge(3, 0).intValue()); assertEquals(2, s.getEdge(1, 2).intValue()); assertEquals(3, s.getEdge(4, 1).intValue()); assertEquals(4, s.getEdge(2, 3).intValue()); assertEquals(5, s.getEdge(3, 3).intValue()); assertEquals(6, s.getEdge(3, 4).intValue()); assertNull(s.getEdge(0, 0)); assertNull(s.getEdge(0, 4)); assertNull(s.getEdge(1, 1)); assertNull(s.getEdge(1, 3)); assertNull(s.getEdge(2, 0)); assertNull(s.getEdge(2, 2)); assertNull(s.getEdge(2, 4)); assertNull(s.getEdge(3, 1)); assertNull(s.getEdge(4, 0)); assertNull(s.getEdge(4, 4)); assertNull(s.getEdge(4, 2)); assertTrue(s.containsEdge(0, 1)); assertTrue(s.containsEdge(1, 0)); assertTrue(s.containsEdge(1, 2)); assertTrue(s.containsEdge(2, 1)); assertTrue(s.containsEdge(2, 3)); assertTrue(s.containsEdge(3, 2)); assertTrue(s.containsEdge(3, 0)); assertTrue(s.containsEdge(0, 3)); assertTrue(s.containsEdge(3, 3)); assertTrue(s.containsEdge(3, 4)); assertTrue(s.containsEdge(4, 3)); assertTrue(s.containsEdge(4, 1)); assertTrue(s.containsEdge(1, 4)); assertFalse(s.containsEdge(0, 0)); assertFalse(s.containsEdge(0, 4)); assertFalse(s.containsEdge(1, 1)); assertFalse(s.containsEdge(1, 3)); assertFalse(s.containsEdge(2, 0)); assertFalse(s.containsEdge(2, 2)); assertFalse(s.containsEdge(2, 4)); assertFalse(s.containsEdge(3, 1)); assertFalse(s.containsEdge(4, 0)); assertFalse(s.containsEdge(4, 4)); assertFalse(s.containsEdge(4, 2)); assertEquals(0, s.getEdgeSource(0).intValue()); assertEquals(1, s.getEdgeTarget(0).intValue()); assertEquals(0, s.getEdgeSource(1).intValue()); assertEquals(3, s.getEdgeTarget(1).intValue()); assertEquals(1, s.getEdgeSource(2).intValue()); assertEquals(2, s.getEdgeTarget(2).intValue()); assertEquals(1, s.getEdgeSource(3).intValue()); assertEquals(4, s.getEdgeTarget(3).intValue()); assertEquals(2, s.getEdgeSource(4).intValue()); assertEquals(3, s.getEdgeTarget(4).intValue()); assertEquals(3, s.getEdgeSource(5).intValue()); assertEquals(3, s.getEdgeTarget(5).intValue()); assertEquals(3, s.getEdgeSource(6).intValue()); assertEquals(4, s.getEdgeTarget(6).intValue()); assertEquals(IntSets.fromTo(0, 2), s.edgesOf(0)); assertEquals(new IntOpenHashSet(new int[] { 0, 2, 3 }), s.edgesOf(1)); assertEquals(new IntOpenHashSet(new int[] { 2, 4 }), s.edgesOf(2)); assertEquals(new IntOpenHashSet(new int[] { 1, 4, 5, 6 }), s.edgesOf(3)); assertEquals(new IntOpenHashSet(new int[] { 3, 6 }), s.edgesOf(4)); assertEquals(IntSets.fromTo(0, 2), s.outgoingEdgesOf(0)); assertEquals(new IntOpenHashSet(new int[] { 0, 2, 3 }), s.outgoingEdgesOf(1)); assertEquals(new IntOpenHashSet(new int[] { 2, 4 }), s.outgoingEdgesOf(2)); assertEquals(new IntOpenHashSet(new int[] { 1, 4, 5, 6 }), s.outgoingEdgesOf(3)); assertEquals(new IntOpenHashSet(new int[] { 3, 6 }), s.outgoingEdgesOf(4)); assertEquals(IntSets.fromTo(0, 2), s.incomingEdgesOf(0)); assertEquals(new IntOpenHashSet(new int[] { 0, 2, 3 }), s.incomingEdgesOf(1)); assertEquals(new IntOpenHashSet(new int[] { 2, 4 }), s.incomingEdgesOf(2)); assertEquals(new IntOpenHashSet(new int[] { 1, 4, 5, 6 }), s.incomingEdgesOf(3)); assertEquals(new IntOpenHashSet(new int[] { 3, 6 }), s.incomingEdgesOf(4)); assertEquals( new IntOpenHashSet(new int[] { 0, 1 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(0).iterator())); assertEquals( new IntOpenHashSet(new int[] { 0, 2, 3 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(1).iterator())); assertEquals( new IntOpenHashSet(new int[] { 2, 4 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(2).iterator())); assertEquals( new IntOpenHashSet(new int[] { 1, 4, 5, 6 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(3).iterator())); assertEquals( new IntOpenHashSet(new int[] { 6, 3 }), new IntOpenHashSet(s.iterables().incomingEdgesOf(4).iterator())); final Iterator iterator = s.iterables().edgesOf(0).iterator(); while (iterator.hasNext()) iterator.next(); assertFalse(iterator.hasNext()); assertFalse(iterator.hasNext()); } @Test public void testSink() { final DefaultUndirectedGraph d = new DefaultUndirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); for (int i = 0; i < 3; i++) d.addVertex(i); d.addEdge(0, 0); d.addEdge(2, 0); final SuccinctIntUndirectedGraph s = new SuccinctIntUndirectedGraph(d); assertEquals(0, s.getEdge(0, 0).intValue()); assertEquals(1, s.getEdge(2, 0).intValue()); assertEquals(0, s.getEdgeSource(0).intValue()); assertEquals(0, s.getEdgeTarget(0).intValue()); assertEquals(0, s.getEdgeSource(1).intValue()); assertEquals(2, s.getEdgeTarget(1).intValue()); } @Test public void testRandomDense() { final GnpRandomGraphGenerator r = new GnpRandomGraphGenerator<>(1000, .1, 0, false); final DefaultUndirectedGraph s = new DefaultUndirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); r.generateGraph(s); final SuccinctIntUndirectedGraph t = new SuccinctIntUndirectedGraph(s); for (final Integer e : t.edgeSet()) assertTrue(e.toString(), s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e))); for (final DefaultEdge e : s.edgeSet()) assertTrue(e.toString(), t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e))); final XoRoShiRo128PlusPlusRandomGenerator random = new XoRoShiRo128PlusPlusRandomGenerator(); final int n = (int) s.iterables().vertexCount(); for (int i = 0; i < 10000; i++) { final int x = random.nextInt(n); final int y = random.nextInt(n); assertEquals(s.containsEdge(x, y), t.containsEdge(x, y)); } } @Test public void testRandomSparse() { final GnpRandomGraphGenerator r = new GnpRandomGraphGenerator<>(1000, .001, 0, false); final DefaultUndirectedGraph s = new DefaultUndirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); r.generateGraph(s); final SuccinctIntUndirectedGraph t = new SuccinctIntUndirectedGraph(s); for (final Integer e : t.edgeSet()) assertTrue(e.toString(), s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e))); for (final DefaultEdge e : s.edgeSet()) assertTrue(e.toString(), t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e))); final XoRoShiRo128PlusPlusRandomGenerator random = new XoRoShiRo128PlusPlusRandomGenerator(); final int n = (int) s.iterables().vertexCount(); for (int i = 0; i < 10000; i++) { final int x = random.nextInt(n); final int y = random.nextInt(n); assertEquals(s.containsEdge(x, y), t.containsEdge(x, y)); } } } SuccinctUndirectedGraphTest.java000066400000000000000000000256751402514743400343520ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/sux4j/* * (C) Copyright 2020-2021, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.sux4j; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.Iterator; import java.util.Set; import java.util.function.Supplier; import org.jgrapht.generate.GnpRandomGraphGenerator; import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DefaultUndirectedGraph; import org.jgrapht.util.SupplierUtil; import org.junit.Test; import it.unimi.dsi.fastutil.ints.IntIntSortedPair; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.util.XoRoShiRo128PlusPlusRandomGenerator; public class SuccinctUndirectedGraphTest { @Test public void testUndirected() { final DefaultUndirectedGraph d = new DefaultUndirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); for (int i = 0; i < 5; i++) d.addVertex(i); d.addEdge(0, 1); d.addEdge(1, 2); d.addEdge(2, 3); d.addEdge(3, 0); d.addEdge(3, 3); d.addEdge(3, 4); d.addEdge(4, 1); final SuccinctUndirectedGraph s = new SuccinctUndirectedGraph(d); assertEquals(5, d.iterables().vertexCount()); assertEquals(7, d.iterables().edgeCount()); assertEquals(2, s.outDegreeOf(0)); assertEquals(3, s.outDegreeOf(1)); assertEquals(2, s.outDegreeOf(2)); assertEquals(5, s.outDegreeOf(3)); assertEquals(2, s.outDegreeOf(4)); assertEquals(IntIntSortedPair.of(0, 1), s.getEdge(0, 1)); assertEquals(IntIntSortedPair.of(3, 0), s.getEdge(3, 0)); assertEquals(IntIntSortedPair.of(1, 2), s.getEdge(1, 2)); assertEquals(IntIntSortedPair.of(4, 1), s.getEdge(4, 1)); assertEquals(IntIntSortedPair.of(2, 3), s.getEdge(2, 3)); assertEquals(IntIntSortedPair.of(3, 3), s.getEdge(3, 3)); assertEquals(IntIntSortedPair.of(3, 4), s.getEdge(3, 4)); assertNull(s.getEdge(0, 0)); assertNull(s.getEdge(0, 4)); assertNull(s.getEdge(1, 1)); assertNull(s.getEdge(1, 3)); assertNull(s.getEdge(2, 0)); assertNull(s.getEdge(2, 2)); assertNull(s.getEdge(2, 4)); assertNull(s.getEdge(3, 1)); assertNull(s.getEdge(4, 0)); assertNull(s.getEdge(4, 4)); assertNull(s.getEdge(4, 2)); assertTrue(s.containsEdge(0, 1)); assertTrue(s.containsEdge(1, 0)); assertTrue(s.containsEdge(1, 2)); assertTrue(s.containsEdge(2, 1)); assertTrue(s.containsEdge(2, 3)); assertTrue(s.containsEdge(3, 2)); assertTrue(s.containsEdge(3, 0)); assertTrue(s.containsEdge(0, 3)); assertTrue(s.containsEdge(3, 3)); assertTrue(s.containsEdge(3, 4)); assertTrue(s.containsEdge(4, 3)); assertTrue(s.containsEdge(4, 1)); assertTrue(s.containsEdge(1, 4)); assertFalse(s.containsEdge(0, 0)); assertFalse(s.containsEdge(0, 4)); assertFalse(s.containsEdge(1, 1)); assertFalse(s.containsEdge(1, 3)); assertFalse(s.containsEdge(2, 0)); assertFalse(s.containsEdge(2, 2)); assertFalse(s.containsEdge(2, 4)); assertFalse(s.containsEdge(3, 1)); assertFalse(s.containsEdge(4, 0)); assertFalse(s.containsEdge(4, 4)); assertFalse(s.containsEdge(4, 2)); assertEquals(Set.of(IntIntSortedPair.of(0, 1), IntIntSortedPair.of(0, 3)), s.edgesOf(0)); assertEquals( Set.of(IntIntSortedPair.of(0, 1), IntIntSortedPair.of(1, 2), IntIntSortedPair.of(1, 4)), s.edgesOf(1)); assertEquals(Set.of(IntIntSortedPair.of(2, 1), IntIntSortedPair.of(2, 3)), s.edgesOf(2)); assertEquals( Set .of( IntIntSortedPair.of(0, 3), IntIntSortedPair.of(3, 3), IntIntSortedPair.of(3, 4), IntIntSortedPair.of(3, 2)), s.edgesOf(3)); assertEquals(Set.of(IntIntSortedPair.of(4, 1), IntIntSortedPair.of(4, 3)), s.edgesOf(4)); assertEquals( Set.of(IntIntSortedPair.of(0, 1), IntIntSortedPair.of(0, 3)), s.outgoingEdgesOf(0)); assertEquals( Set.of(IntIntSortedPair.of(0, 1), IntIntSortedPair.of(1, 2), IntIntSortedPair.of(1, 4)), s.outgoingEdgesOf(1)); assertEquals( Set.of(IntIntSortedPair.of(2, 1), IntIntSortedPair.of(2, 3)), s.outgoingEdgesOf(2)); assertEquals( Set .of( IntIntSortedPair.of(0, 3), IntIntSortedPair.of(3, 3), IntIntSortedPair.of(3, 4), IntIntSortedPair.of(3, 2)), s.outgoingEdgesOf(3)); assertEquals( Set.of(IntIntSortedPair.of(4, 1), IntIntSortedPair.of(4, 3)), s.outgoingEdgesOf(4)); assertEquals( Set.of(IntIntSortedPair.of(0, 1), IntIntSortedPair.of(0, 3)), s.incomingEdgesOf(0)); assertEquals( Set.of(IntIntSortedPair.of(0, 1), IntIntSortedPair.of(1, 2), IntIntSortedPair.of(1, 4)), s.incomingEdgesOf(1)); assertEquals( Set.of(IntIntSortedPair.of(2, 1), IntIntSortedPair.of(2, 3)), s.incomingEdgesOf(2)); assertEquals( Set .of( IntIntSortedPair.of(0, 3), IntIntSortedPair.of(3, 3), IntIntSortedPair.of(3, 4), IntIntSortedPair.of(3, 2)), s.incomingEdgesOf(3)); assertEquals( Set.of(IntIntSortedPair.of(4, 1), IntIntSortedPair.of(4, 3)), s.incomingEdgesOf(4)); assertEquals( Set.of(IntIntSortedPair.of(0, 1), IntIntSortedPair.of(0, 3)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(0).iterator())); assertEquals( Set.of(IntIntSortedPair.of(0, 1), IntIntSortedPair.of(1, 2), IntIntSortedPair.of(1, 4)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(1).iterator())); assertEquals( Set.of(IntIntSortedPair.of(2, 1), IntIntSortedPair.of(2, 3)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(2).iterator())); assertEquals( Set .of( IntIntSortedPair.of(0, 3), IntIntSortedPair.of(3, 3), IntIntSortedPair.of(3, 4), IntIntSortedPair.of(3, 2)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(3).iterator())); assertEquals( Set.of(IntIntSortedPair.of(4, 1), IntIntSortedPair.of(4, 3)), new ObjectOpenHashSet<>(s.iterables().incomingEdgesOf(4).iterator())); final Iterator iterator = s.iterables().edgesOf(0).iterator(); while (iterator.hasNext()) iterator.next(); assertFalse(iterator.hasNext()); assertFalse(iterator.hasNext()); } @Test public void testSink() { final DefaultUndirectedGraph d = new DefaultUndirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); for (int i = 0; i < 3; i++) d.addVertex(i); d.addEdge(0, 0); d.addEdge(2, 0); final SuccinctUndirectedGraph s = new SuccinctUndirectedGraph(d); assertEquals(IntIntSortedPair.of(0, 0), s.getEdge(0, 0)); assertEquals(IntIntSortedPair.of(2, 0), s.getEdge(2, 0)); } @Test public void testRandomDense() { final GnpRandomGraphGenerator r = new GnpRandomGraphGenerator<>(1000, .1, 0, false); final DefaultUndirectedGraph s = new DefaultUndirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); r.generateGraph(s); final SuccinctUndirectedGraph t = new SuccinctUndirectedGraph(s); for (final IntIntSortedPair e : t.edgeSet()) assertTrue(e.toString(), s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e))); for (final DefaultEdge e : s.edgeSet()) assertTrue(e.toString(), t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e))); final XoRoShiRo128PlusPlusRandomGenerator random = new XoRoShiRo128PlusPlusRandomGenerator(); final int n = (int) s.iterables().vertexCount(); for (int i = 0; i < 10000; i++) { final int x = random.nextInt(n); final int y = random.nextInt(n); assertEquals(s.containsEdge(x, y), t.containsEdge(x, y)); } } @Test public void testRandomSparse() { final GnpRandomGraphGenerator r = new GnpRandomGraphGenerator<>(1000, .001, 0, false); final DefaultUndirectedGraph s = new DefaultUndirectedGraph<>(new Supplier() { private int id = 0; @Override public Integer get() { return id++; } }, SupplierUtil.createDefaultEdgeSupplier(), false); r.generateGraph(s); final SuccinctUndirectedGraph t = new SuccinctUndirectedGraph(s); for (final IntIntSortedPair e : t.edgeSet()) assertTrue(e.toString(), s.containsEdge(t.getEdgeSource(e), t.getEdgeTarget(e))); for (final DefaultEdge e : s.edgeSet()) assertTrue(e.toString(), t.containsEdge(s.getEdgeSource(e), s.getEdgeTarget(e))); final XoRoShiRo128PlusPlusRandomGenerator random = new XoRoShiRo128PlusPlusRandomGenerator(); final int n = (int) s.iterables().vertexCount(); for (int i = 0; i < 10000; i++) { final int x = random.nextInt(n); final int y = random.nextInt(n); assertEquals(s.containsEdge(x, y), t.containsEdge(x, y)); } } } jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/000077500000000000000000000000001402514743400266655ustar00rootroot00000000000000ImmutableDirectedBigGraphAdapterTest.java000066400000000000000000000272061402514743400366100ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Set; import org.junit.Test; import com.google.common.collect.Iterables; import it.unimi.dsi.big.webgraph.EFGraph; import it.unimi.dsi.big.webgraph.ImmutableGraph; import it.unimi.dsi.big.webgraph.LazyLongIterator; import it.unimi.dsi.fastutil.longs.LongLongPair; import it.unimi.dsi.fastutil.longs.LongLongSortedPair; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.webgraph.ArrayListMutableGraph; import it.unimi.dsi.webgraph.Transform; import it.unimi.dsi.webgraph.examples.ErdosRenyiGraph; public class ImmutableDirectedBigGraphAdapterTest { @Test public void testSmallRandom() { for (final int size : new int[] { 10, 100, 500 }) { final it.unimi.dsi.webgraph.ImmutableGraph mg = new ArrayListMutableGraph(new ErdosRenyiGraph(size, .1, 0L, true)).immutableView(); final ImmutableGraph g = ImmutableGraph.wrap(mg); final ImmutableDirectedBigGraphAdapter a = new ImmutableDirectedBigGraphAdapter( g, ImmutableGraph.wrap(Transform.transpose(mg))); assertEquals(g.numNodes(), a.vertexSet().size()); assertEquals(g.numNodes(), a.iterables().vertexCount()); assertEquals(g.numArcs(), a.iterables().edgeCount()); // Test cached value assertEquals(g.numArcs(), a.iterables().edgeCount()); for (long x = 0L; x < size; x++) { final LazyLongIterator successors = g.successors(x); for (long y; (y = successors.nextLong()) != -1L;) assertTrue(a.containsEdge(x, y)); } assertNull(a.getAllEdges(0L, -1L)); assertNull(a.getAllEdges(-1L, -1L)); assertNull(a.getAllEdges(-1L, 0L)); assertNull(a.getAllEdges(0L, null)); assertNull(a.getAllEdges(null, 0L)); assertNull(a.getAllEdges(null, null)); } } public static File storeTempGraph(final ImmutableGraph g) throws IOException, IllegalArgumentException, SecurityException { final File basename = File .createTempFile(ImmutableDirectedBigGraphAdapterTest.class.getSimpleName(), "test"); EFGraph.store(g, basename.toString()); basename.deleteOnExit(); new File(basename + EFGraph.GRAPH_EXTENSION).deleteOnExit(); new File(basename + EFGraph.PROPERTIES_EXTENSION).deleteOnExit(); new File(basename + EFGraph.OFFSETS_EXTENSION).deleteOnExit(); return basename; } @Test public void testSmall() throws IllegalArgumentException, SecurityException, IOException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(3); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 2); m.addArc(2, 2); final it.unimi.dsi.webgraph.ImmutableGraph mg = m.immutableView(); final ImmutableGraph g = ImmutableGraph.wrap(mg); final ImmutableGraph t = ImmutableGraph.wrap(Transform.transpose(mg)); final File basename = storeTempGraph(g); final EFGraph ef = EFGraph.load(basename.toString()); final ImmutableDirectedBigGraphAdapter a = new ImmutableDirectedBigGraphAdapter(g, t); final ImmutableDirectedBigGraphAdapter b = new ImmutableDirectedBigGraphAdapter( ef, ImmutableGraph.wrap(Transform.transpose(mg))); assertEquals(g.numNodes(), a.vertexSet().size()); for (long x = 0; x < g.numNodes(); x++) { final LazyLongIterator successors = g.successors(x); for (long y; (y = successors.nextLong()) != -1L;) assertTrue(a.containsEdge(x, y)); } assertNull(a.getVertexSupplier()); assertNull(a.getEdgeSupplier()); assertFalse(a.containsVertex(null)); assertNull(a.getEdge(0L, -1L)); assertNull(a.getEdge(-1L, -1L)); assertNull(a.getEdge(-1L, 0L)); assertNull(a.getEdge(0L, g.numNodes())); assertNull(a.getEdge(g.numNodes(), g.numNodes())); assertNull(a.getEdge(g.numNodes(), 0L)); assertNull(a.getEdge(0L, null)); assertNull(a.getEdge(null, 0L)); assertNull(a.getEdge(null, null)); assertNull(a.getEdge(1L, 0L)); assertEquals(LongLongPair.of(0L, 1L), a.getEdge(0L, 1L)); assertNull(a.getAllEdges(0L, -1L)); assertNull(a.getAllEdges(-1L, -1L)); assertNull(a.getAllEdges(-1L, 0L)); assertNull(a.getAllEdges(0L, null)); assertNull(a.getAllEdges(null, 0L)); assertNull(a.getAllEdges(null, null)); assertEquals(Collections.emptySet(), a.getAllEdges(1L, 0L)); assertEquals(Collections.singleton(LongLongPair.of(0L, 1L)), a.getAllEdges(0L, 1L)); assertEquals( Collections.singleton(LongLongPair.of(0L, 1L)), new ObjectOpenHashSet<>(a.iterables().allEdges(0L, 1L).iterator())); assertFalse(a.containsEdge(0L, null)); assertFalse(a.containsEdge(null, 0L)); assertTrue(a.containsEdge(0L, 1L)); assertTrue(b.containsVertex(0L)); assertFalse(b.containsVertex(3L)); assertFalse(b.containsVertex(-1L)); assertTrue(b.containsEdge(LongLongPair.of(0L, 2L))); assertTrue(b.containsEdge(LongLongPair.of(1L, 2L))); assertFalse(b.containsEdge(LongLongPair.of(2L, 1L))); assertFalse(b.containsEdge(null)); assertFalse(a.containsEdge(LongLongSortedPair.of(0L, 2L))); assertEquals(2, a.degreeOf(1L)); assertEquals(4, a.degreeOf(2L)); assertEquals(1, a.inDegreeOf(1L)); assertEquals(1, a.outDegreeOf(1L)); assertEquals(2, a.iterables().degreeOf(1L)); assertEquals(1, a.iterables().inDegreeOf(1L)); assertEquals(1, a.iterables().outDegreeOf(1L)); assertEquals( new ObjectOpenHashSet<>( new LongLongPair[] { LongLongPair.of(0L, 1L), LongLongPair.of(0L, 2L), LongLongPair.of(1L, 2L), LongLongPair.of(2L, 2L) }), new ObjectOpenHashSet<>(a.edgeSet())); assertEquals( new ObjectOpenHashSet<>( new LongLongPair[] { LongLongPair.of(0L, 1L), LongLongPair.of(0L, 2L), LongLongPair.of(1L, 2L), LongLongPair.of(2L, 2L) }), new ObjectOpenHashSet<>(a.iterables().edges().iterator())); assertEquals( new ObjectOpenHashSet<>( new LongLongPair[] { LongLongPair.of(0L, 1L), LongLongPair.of(1L, 2L) }), a.edgesOf(1L)); assertEquals( new ObjectOpenHashSet<>( new LongLongPair[] { LongLongPair.of(0L, 1L), LongLongPair.of(1L, 2L) }), new ObjectOpenHashSet<>(a.iterables().edgesOf(1L).iterator())); assertEquals(3, Iterables.size(a.edgesOf(2L))); assertEquals(3, Iterables.size(a.iterables().edgesOf(2L))); assertEquals( new ObjectOpenHashSet<>(new LongLongPair[] { LongLongPair.of(0L, 1L) }), a.incomingEdgesOf(1L)); assertEquals( new ObjectOpenHashSet<>(new LongLongPair[] { LongLongPair.of(0L, 1L) }), new ObjectOpenHashSet<>(a.iterables().incomingEdgesOf(1L).iterator())); assertEquals( new ObjectOpenHashSet<>(new LongLongPair[] { LongLongPair.of(1L, 2L) }), a.outgoingEdgesOf(1L)); assertEquals( new ObjectOpenHashSet<>(new LongLongPair[] { LongLongPair.of(1L, 2L) }), new ObjectOpenHashSet<>(a.iterables().outgoingEdgesOf(1L).iterator())); final Set v = a.vertexSet(); assertTrue(v.contains(0L)); assertFalse(v.contains(-1L)); assertFalse(v.contains(3L)); assertEquals( new LongOpenHashSet(v.iterator()), new LongOpenHashSet(new long[] { 0L, 1L, 2L })); assertEquals(1, a.getEdgeSource(LongLongPair.of(1L, 2L)).longValue()); assertEquals(2, a.getEdgeTarget(LongLongPair.of(1L, 2L)).longValue()); assertEquals(1, a.getEdgeWeight(LongLongPair.of(1L, 2L)), 0); a.setEdgeWeight(LongLongPair.of(0L, 1L), 1); } @Test public void testCopy() throws IllegalArgumentException, SecurityException, IOException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); final it.unimi.dsi.webgraph.ImmutableGraph v = m.immutableView(); final ImmutableDirectedBigGraphAdapter a = new ImmutableDirectedBigGraphAdapter( ImmutableGraph.wrap(v), ImmutableGraph.wrap(Transform.transpose(v))); assertEquals(a, a.copy()); } @Test public void testType() throws IllegalArgumentException, SecurityException, IOException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); final it.unimi.dsi.webgraph.ImmutableGraph v = m.immutableView(); final ImmutableDirectedBigGraphAdapter a = new ImmutableDirectedBigGraphAdapter( ImmutableGraph.wrap(v), ImmutableGraph.wrap(Transform.transpose(v))); assertTrue(a.getType().isDirected()); assertFalse(a.getType().isUndirected()); assertEquals( new ObjectOpenHashSet<>( new LongLongPair[] { LongLongPair.of(0L, 2L), LongLongPair.of(0L, 1L), LongLongPair.of(1L, 3L), LongLongPair.of(2L, 3L) }), a.edgeSet()); assertEquals( new ObjectOpenHashSet<>( new LongLongPair[] { LongLongPair.of(0L, 2L), LongLongPair.of(0L, 1L), LongLongPair.of(1L, 3L), LongLongPair.of(2L, 3L) }), new ObjectOpenHashSet<>(a.edgeSet().iterator())); } @Test public void testAdjacencyCheck() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(100); for (int i = 0; i < 30; i++) m.addArc(0, i); final it.unimi.dsi.webgraph.ImmutableGraph v = m.immutableView(); ImmutableDirectedBigGraphAdapter a = new ImmutableDirectedBigGraphAdapter( ImmutableGraph.wrap(v), ImmutableGraph.wrap(Transform.transpose(v))); assertEquals(LongLongPair.of(0L, 1L), a.getEdge(0L, 1L)); assertEquals(null, a.getEdge(1L, 0L)); assertEquals(null, a.getEdge(0L, 50L)); a = new ImmutableDirectedBigGraphAdapter(ImmutableGraph.wrap(v)); assertEquals(LongLongPair.of(0L, 1L), a.getEdge(0L, 1l)); assertEquals(null, a.getEdge(1L, 0L)); assertEquals(null, a.getEdge(0L, 50L)); } } ImmutableDirectedGraphAdapterTest.java000066400000000000000000000255251402514743400361700ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.Collections; import java.util.Set; import org.junit.Test; import com.google.common.collect.Iterables; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.ints.IntIntSortedPair; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.webgraph.ArrayListMutableGraph; import it.unimi.dsi.webgraph.EFGraph; import it.unimi.dsi.webgraph.ImmutableGraph; import it.unimi.dsi.webgraph.LazyIntIterator; import it.unimi.dsi.webgraph.Transform; import it.unimi.dsi.webgraph.examples.ErdosRenyiGraph; public class ImmutableDirectedGraphAdapterTest { @Test public void testSmallRandom() { for (final int size : new int[] { 10, 100, 500 }) { final ImmutableGraph g = new ArrayListMutableGraph(new ErdosRenyiGraph(size, .1, 0, true)).immutableView(); final ImmutableDirectedGraphAdapter a = new ImmutableDirectedGraphAdapter(g, Transform.transpose(g)); assertEquals(g.numNodes(), a.vertexSet().size()); assertEquals(g.numNodes(), a.iterables().vertexCount()); assertEquals(g.numArcs(), a.iterables().edgeCount()); // Test cached value assertEquals(g.numArcs(), a.iterables().edgeCount()); for (int x = 0; x < size; x++) { final LazyIntIterator successors = g.successors(x); for (int y; (y = successors.nextInt()) != -1;) assertTrue(a.containsEdge(x, y)); } assertNull(a.getAllEdges(0, -1)); assertNull(a.getAllEdges(-1, -1)); assertNull(a.getAllEdges(-1, 0)); assertNull(a.getAllEdges(0, null)); assertNull(a.getAllEdges(null, 0)); assertNull(a.getAllEdges(null, null)); } } public static File storeTempGraph(final ImmutableGraph g) throws IOException, IllegalArgumentException, SecurityException { final File basename = File .createTempFile(ImmutableDirectedGraphAdapterTest.class.getSimpleName(), "test"); EFGraph.store(g, basename.toString()); basename.deleteOnExit(); new File(basename + EFGraph.GRAPH_EXTENSION).deleteOnExit(); new File(basename + EFGraph.PROPERTIES_EXTENSION).deleteOnExit(); new File(basename + EFGraph.OFFSETS_EXTENSION).deleteOnExit(); return basename; } @Test public void testSmall() throws IllegalArgumentException, SecurityException, IOException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(3); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 2); m.addArc(2, 2); final ImmutableGraph g = m.immutableView(); final ImmutableGraph t = Transform.transpose(g); final File basename = storeTempGraph(g); final EFGraph ef = EFGraph.load(basename.toString()); final ImmutableDirectedGraphAdapter a = new ImmutableDirectedGraphAdapter(g, t); final ImmutableDirectedGraphAdapter b = new ImmutableDirectedGraphAdapter(ef, Transform.transpose(ef)); assertEquals(g.numNodes(), a.vertexSet().size()); for (int x = 0; x < g.numNodes(); x++) { final LazyIntIterator successors = g.successors(x); for (int y; (y = successors.nextInt()) != -1;) assertTrue(a.containsEdge(x, y)); } assertNull(a.getVertexSupplier()); assertNull(a.getEdgeSupplier()); assertFalse(a.containsVertex(null)); assertNull(a.getEdge(0, -1)); assertNull(a.getEdge(-1, -1)); assertNull(a.getEdge(-1, 0)); assertNull(a.getEdge(0, g.numNodes())); assertNull(a.getEdge(g.numNodes(), g.numNodes())); assertNull(a.getEdge(g.numNodes(), 0)); assertNull(a.getEdge(0, null)); assertNull(a.getEdge(null, 0)); assertNull(a.getEdge(null, null)); assertNull(a.getEdge(1, 0)); assertEquals(IntIntPair.of(0, 1), a.getEdge(0, 1)); assertNull(a.getAllEdges(0, -1)); assertNull(a.getAllEdges(-1, -1)); assertNull(a.getAllEdges(-1, 0)); assertNull(a.getAllEdges(0, null)); assertNull(a.getAllEdges(null, 0)); assertNull(a.getAllEdges(null, null)); assertEquals(Collections.emptySet(), a.getAllEdges(1, 0)); assertEquals(Collections.singleton(IntIntPair.of(0, 1)), a.getAllEdges(0, 1)); assertEquals( Collections.singleton(IntIntPair.of(0, 1)), new ObjectOpenHashSet<>(a.iterables().allEdges(0, 1).iterator())); assertFalse(a.containsEdge(0, null)); assertFalse(a.containsEdge(null, 0)); assertTrue(a.containsEdge(0, 1)); assertTrue(b.containsVertex(0)); assertFalse(b.containsVertex(3)); assertFalse(b.containsVertex(-1)); assertTrue(b.containsEdge(IntIntPair.of(0, 2))); assertTrue(b.containsEdge(IntIntPair.of(1, 2))); assertFalse(b.containsEdge(IntIntPair.of(2, 1))); assertFalse(b.containsEdge(null)); assertFalse(b.containsEdge(IntIntSortedPair.of(0, 2))); assertEquals(2, a.degreeOf(1)); assertEquals(4, a.degreeOf(2)); assertEquals(1, a.inDegreeOf(1)); assertEquals(1, a.outDegreeOf(1)); assertEquals(2, a.iterables().degreeOf(1)); assertEquals(1, a.iterables().inDegreeOf(1)); assertEquals(1, a.iterables().outDegreeOf(1)); assertEquals( new ObjectOpenHashSet<>( new IntIntPair[] { IntIntPair.of(0, 1), IntIntPair.of(0, 2), IntIntPair.of(1, 2), IntIntPair.of(2, 2) }), a.edgeSet()); assertEquals( new ObjectOpenHashSet<>( new IntIntPair[] { IntIntPair.of(0, 1), IntIntPair.of(0, 2), IntIntPair.of(1, 2), IntIntPair.of(2, 2) }), new ObjectOpenHashSet<>(a.iterables().edges().iterator())); assertEquals( new ObjectOpenHashSet<>( new IntIntPair[] { IntIntPair.of(0, 1), IntIntPair.of(1, 2) }), a.edgesOf(1)); assertEquals( new ObjectOpenHashSet<>( new IntIntPair[] { IntIntPair.of(0, 1), IntIntPair.of(1, 2) }), new ObjectOpenHashSet<>(a.iterables().edgesOf(1).iterator())); assertEquals(3, Iterables.size(a.edgesOf(2))); assertEquals(3, Iterables.size(a.iterables().edgesOf(2))); assertEquals( new ObjectOpenHashSet<>(new IntIntPair[] { IntIntPair.of(0, 1) }), a.incomingEdgesOf(1)); assertEquals( new ObjectOpenHashSet<>(new IntIntPair[] { IntIntPair.of(0, 1) }), new ObjectOpenHashSet<>(a.iterables().incomingEdgesOf(1).iterator())); assertEquals( new ObjectOpenHashSet<>(new IntIntPair[] { IntIntPair.of(1, 2) }), a.outgoingEdgesOf(1)); assertEquals( new ObjectOpenHashSet<>(new IntIntPair[] { IntIntPair.of(1, 2) }), new ObjectOpenHashSet<>(a.iterables().outgoingEdgesOf(1).iterator())); final Set v = a.vertexSet(); assertTrue(v.contains(0)); assertFalse(v.contains(-1)); assertFalse(v.contains(3)); assertEquals(new IntOpenHashSet(v.iterator()), new IntOpenHashSet(new int[] { 0, 1, 2 })); assertEquals(1, a.getEdgeSource(IntIntPair.of(1, 2)).longValue()); assertEquals(2, a.getEdgeTarget(IntIntPair.of(1, 2)).longValue()); assertEquals(1.0, a.getEdgeWeight(IntIntPair.of(1, 2)), 0); a.setEdgeWeight(IntIntPair.of(0, 1), 1); } @Test public void testCopy() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); final ImmutableGraph v = m.immutableView(); final ImmutableDirectedGraphAdapter a = new ImmutableDirectedGraphAdapter(v, Transform.transpose(v)); assertEquals(a, a.copy()); } @Test public void testType() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); final ImmutableGraph v = m.immutableView(); final ImmutableDirectedGraphAdapter a = new ImmutableDirectedGraphAdapter(v, Transform.transpose(v)); assertTrue(a.getType().isDirected()); assertFalse(a.getType().isUndirected()); assertEquals( new ObjectOpenHashSet<>( new IntIntPair[] { IntIntPair.of(0, 2), IntIntPair.of(0, 1), IntIntPair.of(1, 3), IntIntPair.of(2, 3) }), a.edgeSet()); assertEquals( new ObjectOpenHashSet<>( new IntIntPair[] { IntIntPair.of(0, 2), IntIntPair.of(0, 1), IntIntPair.of(1, 3), IntIntPair.of(2, 3) }), new ObjectOpenHashSet<>(a.iterables().edges().iterator())); } @Test public void testAdjacencyCheck() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(100); for (int i = 0; i < 30; i++) m.addArc(0, i); final it.unimi.dsi.webgraph.ImmutableGraph v = m.immutableView(); ImmutableDirectedGraphAdapter a = new ImmutableDirectedGraphAdapter(v, Transform.transpose(v)); assertEquals(IntIntPair.of(0, 1), a.getEdge(0, 1)); assertEquals(null, a.getEdge(1, 0)); assertEquals(null, a.getEdge(0, 50)); a = new ImmutableDirectedGraphAdapter(v); assertEquals(IntIntPair.of(0, 1), a.getEdge(0, 1)); assertEquals(null, a.getEdge(1, 0)); assertEquals(null, a.getEdge(0, 50)); } } ImmutableUndirectedBigGraphAdapterTest.java000066400000000000000000000175741402514743400371620ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.Collections; import org.junit.Test; import it.unimi.dsi.big.webgraph.ImmutableGraph; import it.unimi.dsi.big.webgraph.LazyLongIterator; import it.unimi.dsi.fastutil.longs.LongLongPair; import it.unimi.dsi.fastutil.longs.LongLongSortedPair; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.webgraph.ArrayListMutableGraph; import it.unimi.dsi.webgraph.Transform; public class ImmutableUndirectedBigGraphAdapterTest { @Test public void testSmall() throws IllegalArgumentException, SecurityException, IOException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); m.addArc(1, 1); m.addArc(3, 3); final ImmutableGraph g = ImmutableGraph .load( ImmutableDirectedBigGraphAdapterTest .storeTempGraph(ImmutableGraph.wrap(Transform.symmetrize(m.immutableView()))) .toString()); final ImmutableUndirectedBigGraphAdapter a = new ImmutableUndirectedBigGraphAdapter(g); assertEquals(g.numNodes(), a.vertexSet().size()); for (long x = 0; x < g.numNodes(); x++) { final LazyLongIterator successors = g.successors(x); for (long y; (y = successors.nextLong()) != -1;) assertTrue(a.containsEdge(x, y)); } assertFalse(a.containsEdge(null)); assertEquals(6, a.iterables().edgeCount()); assertEquals(6, a.edgeSet().size()); assertNull(a.getEdge(2L, 2L)); assertEquals(LongLongSortedPair.of(0L, 1L), a.getEdge(0L, 1L)); assertTrue( a.getEdgeSource(a.getEdge(0L, 1L)) == 0 && a.getEdgeTarget(a.getEdge(0L, 1L)) == 1 || a.getEdgeSource(a.getEdge(0L, 1L)) == 1 && a.getEdgeTarget(a.getEdge(0L, 1L)) == 0); final ObjectOpenHashSet edgesOf2 = new ObjectOpenHashSet<>( new LongLongSortedPair[] { LongLongSortedPair.of(2, 0), LongLongSortedPair.of(2, 3) }); assertEquals(edgesOf2, a.edgesOf(2L)); assertEquals(edgesOf2, a.incomingEdgesOf(2L)); assertEquals(edgesOf2, a.outgoingEdgesOf(2L)); assertEquals( edgesOf2, new ObjectOpenHashSet<>(a.iterables().incomingEdgesOf(2L).iterator())); assertEquals( edgesOf2, new ObjectOpenHashSet<>(a.iterables().outgoingEdgesOf(2L).iterator())); final ObjectOpenHashSet edgesOf1 = new ObjectOpenHashSet<>( new LongLongSortedPair[] { LongLongSortedPair.of(1, 0), LongLongSortedPair.of(1, 3), LongLongSortedPair.of(1, 1) }); assertEquals(edgesOf1, a.edgesOf(1L)); assertEquals(edgesOf1, a.incomingEdgesOf(1L)); assertEquals(edgesOf1, a.outgoingEdgesOf(1L)); assertEquals(edgesOf1, new ObjectOpenHashSet<>(a.iterables().edgesOf(1L).iterator())); assertEquals( edgesOf1, new ObjectOpenHashSet<>(a.iterables().incomingEdgesOf(1L).iterator())); assertEquals( edgesOf1, new ObjectOpenHashSet<>(a.iterables().outgoingEdgesOf(1L).iterator())); assertEquals(Collections.singleton(LongLongSortedPair.of(0L, 1L)), a.getAllEdges(0L, 1L)); assertEquals( Collections.singleton(LongLongSortedPair.of(0L, 1L)), new ObjectOpenHashSet<>(a.iterables().allEdges(0L, 1L).iterator())); assertEquals(4, a.degreeOf(1L)); assertEquals(3, a.inDegreeOf(1L)); assertEquals(3, a.outDegreeOf(1L)); assertEquals(4, a.iterables().degreeOf(1L)); assertEquals(3, a.iterables().inDegreeOf(1L)); assertEquals(3, a.iterables().outDegreeOf(1L)); assertEquals(2, a.degreeOf(2L)); assertEquals(2, a.inDegreeOf(2L)); assertEquals(2, a.outDegreeOf(2L)); assertEquals(2, a.iterables().degreeOf(2L)); assertEquals(2, a.iterables().inDegreeOf(2L)); assertEquals(2, a.iterables().outDegreeOf(2L)); } @Test public void testCopy() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); final it.unimi.dsi.webgraph.ImmutableGraph v = m.immutableView(); final it.unimi.dsi.webgraph.ImmutableGraph g = Transform.symmetrize(v); final ImmutableUndirectedBigGraphAdapter a = new ImmutableUndirectedBigGraphAdapter(ImmutableGraph.wrap(g)); assertEquals(a, a.copy()); } @Test public void testType() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); final it.unimi.dsi.webgraph.ImmutableGraph v = m.immutableView(); final it.unimi.dsi.webgraph.ImmutableGraph g = Transform.symmetrize(v); final ImmutableUndirectedBigGraphAdapter a = new ImmutableUndirectedBigGraphAdapter(ImmutableGraph.wrap(g)); assertTrue(a.getType().isUndirected()); assertFalse(a.getType().isDirected()); assertEquals( new ObjectOpenHashSet<>( new LongLongPair[] { LongLongSortedPair.of(0L, 2L), LongLongSortedPair.of(0L, 1L), LongLongSortedPair.of(1L, 3L), LongLongSortedPair.of(2L, 3L) }), a.edgeSet()); assertEquals( new ObjectOpenHashSet<>( new LongLongPair[] { LongLongSortedPair.of(0L, 2L), LongLongSortedPair.of(0L, 1L), LongLongSortedPair.of(1L, 3L), LongLongSortedPair.of(2L, 3L) }), new ObjectOpenHashSet<>(a.iterables().edges().iterator())); } @Test public void testAdjacencyCheck() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(100); for (int i = 0; i < 30; i++) m.addArc(0, i); final it.unimi.dsi.webgraph.ImmutableGraph v = m.immutableView(); final ImmutableUndirectedBigGraphAdapter a = new ImmutableUndirectedBigGraphAdapter( ImmutableGraph.wrap(Transform.symmetrize(v))); assertEquals(LongLongPair.of(0L, 1L), a.getEdge(0L, 1L)); assertEquals(LongLongPair.of(0L, 1L), a.getEdge(1L, 0L)); assertEquals(null, a.getEdge(0L, 50L)); } @Test public void testEdgeCoherence() { final ImmutableGraph m = ImmutableGraph .wrap( new ArrayListMutableGraph(2, new int[][] { new int[] { 0, 1 }, new int[] { 1, 0 } }) .immutableView()); final ImmutableUndirectedBigGraphAdapter a = new ImmutableUndirectedBigGraphAdapter(m); assertEquals(a.getEdgeSource(a.getEdge(0L, 1L)), a.getEdgeSource(a.getEdge(1L, 0L))); } } ImmutableUndirectedGraphAdapterTest.java000066400000000000000000000170551402514743400365320ustar00rootroot00000000000000jgrapht-jgrapht-1.5.1/jgrapht-unimi-dsi/src/test/java/org/jgrapht/webgraph/* * (C) Copyright 2020-2020, by Sebastiano Vigna and Contributors. * * JGraphT : a free Java graph-theory library * * See the CONTRIBUTORS.md file distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * http://www.eclipse.org/legal/epl-2.0, or the * GNU Lesser General Public License v2.1 or later * which is available at * http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html. * * SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later */ package org.jgrapht.webgraph; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.Collections; import org.junit.Test; import it.unimi.dsi.fastutil.ints.IntIntPair; import it.unimi.dsi.fastutil.ints.IntIntSortedPair; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.webgraph.ArrayListMutableGraph; import it.unimi.dsi.webgraph.ImmutableGraph; import it.unimi.dsi.webgraph.LazyIntIterator; import it.unimi.dsi.webgraph.Transform; public class ImmutableUndirectedGraphAdapterTest { @Test public void testSmall() throws IllegalArgumentException, SecurityException, IOException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); m.addArc(1, 1); m.addArc(3, 3); final ImmutableGraph g = ImmutableGraph .load( ImmutableDirectedGraphAdapterTest .storeTempGraph(Transform.symmetrize(m.immutableView())).toString()); final ImmutableUndirectedGraphAdapter a = new ImmutableUndirectedGraphAdapter(g); assertEquals(g.numNodes(), a.vertexSet().size()); for (int x = 0; x < g.numNodes(); x++) { final LazyIntIterator successors = g.successors(x); for (int y; (y = successors.nextInt()) != -1;) assertTrue(a.containsEdge(x, y)); } assertFalse(a.containsEdge(null)); assertEquals(6, a.iterables().edgeCount()); assertEquals(6, a.edgeSet().size()); assertNull(a.getEdge(2, 2)); assertEquals(IntIntPair.of(0, 1), a.getEdge(0, 1)); assertTrue( a.getEdgeSource(a.getEdge(0, 1)) == 0 && a.getEdgeTarget(a.getEdge(0, 1)) == 1 || a.getEdgeSource(a.getEdge(0, 1)) == 1 && a.getEdgeTarget(a.getEdge(0, 1)) == 0); final ObjectOpenHashSet edgesOf2 = new ObjectOpenHashSet<>( new IntIntSortedPair[] { IntIntSortedPair.of(2, 0), IntIntSortedPair.of(2, 3) }); assertEquals(edgesOf2, a.edgesOf(2)); assertEquals(edgesOf2, a.incomingEdgesOf(2)); assertEquals(edgesOf2, a.outgoingEdgesOf(2)); assertEquals(edgesOf2, new ObjectOpenHashSet<>(a.iterables().edgesOf(2).iterator())); assertEquals( edgesOf2, new ObjectOpenHashSet<>(a.iterables().incomingEdgesOf(2).iterator())); assertEquals( edgesOf2, new ObjectOpenHashSet<>(a.iterables().outgoingEdgesOf(2).iterator())); final ObjectOpenHashSet edgesOf1 = new ObjectOpenHashSet<>( new IntIntSortedPair[] { IntIntSortedPair.of(1, 0), IntIntSortedPair.of(1, 3), IntIntSortedPair.of(1, 1) }); assertEquals(edgesOf1, a.edgesOf(1)); assertEquals(edgesOf1, a.incomingEdgesOf(1)); assertEquals(edgesOf1, a.outgoingEdgesOf(1)); assertEquals(edgesOf1, new ObjectOpenHashSet<>(a.iterables().edgesOf(1).iterator())); assertEquals( edgesOf1, new ObjectOpenHashSet<>(a.iterables().incomingEdgesOf(1).iterator())); assertEquals( edgesOf1, new ObjectOpenHashSet<>(a.iterables().outgoingEdgesOf(1).iterator())); assertEquals(Collections.singleton(IntIntSortedPair.of(0, 1)), a.getAllEdges(0, 1)); assertEquals( Collections.singleton(IntIntSortedPair.of(0, 1)), new ObjectOpenHashSet<>(a.iterables().allEdges(0, 1).iterator())); assertEquals(4, a.degreeOf(1)); assertEquals(3, a.inDegreeOf(1)); assertEquals(3, a.outDegreeOf(1)); assertEquals(4, a.iterables().degreeOf(1)); assertEquals(3, a.iterables().inDegreeOf(1)); assertEquals(3, a.iterables().outDegreeOf(1)); assertEquals(2, a.degreeOf(2)); assertEquals(2, a.inDegreeOf(2)); assertEquals(2, a.outDegreeOf(2)); assertEquals(2, a.iterables().degreeOf(2)); assertEquals(2, a.iterables().inDegreeOf(2)); assertEquals(2, a.iterables().outDegreeOf(2)); } @Test public void testCopy() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); final ImmutableGraph v = m.immutableView(); final ImmutableGraph g = Transform.symmetrize(v); final ImmutableUndirectedGraphAdapter a = new ImmutableUndirectedGraphAdapter(g); assertEquals(a, a.copy()); } @Test public void testType() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(4); m.addArc(0, 1); m.addArc(0, 2); m.addArc(1, 3); m.addArc(2, 3); final ImmutableGraph v = m.immutableView(); final ImmutableGraph g = Transform.symmetrize(v); final ImmutableUndirectedGraphAdapter a = new ImmutableUndirectedGraphAdapter(g); assertTrue(a.getType().isUndirected()); assertFalse(a.getType().isDirected()); assertEquals( new ObjectOpenHashSet<>( new IntIntSortedPair[] { IntIntSortedPair.of(0, 2), IntIntSortedPair.of(0, 1), IntIntSortedPair.of(1, 3), IntIntSortedPair.of(2, 3) }), a.edgeSet()); assertEquals( new ObjectOpenHashSet<>( new IntIntSortedPair[] { IntIntSortedPair.of(0, 2), IntIntSortedPair.of(0, 1), IntIntSortedPair.of(1, 3), IntIntSortedPair.of(2, 3) }), new ObjectOpenHashSet<>(a.iterables().edges().iterator())); } @Test public void testAdjacencyCheck() throws IllegalArgumentException, SecurityException { final ArrayListMutableGraph m = new ArrayListMutableGraph(); m.addNodes(100); for (int i = 0; i < 30; i++) m.addArc(0, i); final it.unimi.dsi.webgraph.ImmutableGraph v = m.immutableView(); final ImmutableUndirectedGraphAdapter a = new ImmutableUndirectedGraphAdapter(Transform.symmetrize(v)); assertEquals(IntIntPair.of(0, 1), a.getEdge(0, 1)); assertEquals(IntIntPair.of(0, 1), a.getEdge(1, 0)); assertEquals(null, a.getEdge(0, 50)); } @Test public void testEdgeCoherence() { final ImmutableGraph m = new ArrayListMutableGraph(2, new int[][] { new int[] { 0, 1 }, new int[] { 1, 0 } }) .immutableView(); final ImmutableUndirectedGraphAdapter a = new ImmutableUndirectedGraphAdapter(m); assertEquals(a.getEdgeSource(a.getEdge(0, 1)), a.getEdgeSource(a.getEdge(1, 0))); } } jgrapht-jgrapht-1.5.1/license-EPL.txt000066400000000000000000000335661402514743400174340ustar00rootroot00000000000000Eclipse Public License - v 2.0 THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and b) in the case of each subsequent Contributor: i) changes to the Program, and ii) additions to the Program; where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution "originates" from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. "Contributor" means any person or entity that Distributes the Program. "Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. "Program" means the Contributions Distributed in accordance with this Agreement. "Recipient" means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. "Derivative Works" shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. "Modified Works" shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. "Distribute" means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. "Source Code" means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. "Secondary License" means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. 2. GRANT OF RIGHTS a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). 3. REQUIREMENTS 3.1 If a Contributor Distributes the Program in any form, then: a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. 3.2 When the Program is Distributed as Source Code: a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and b) a copy of this Agreement must be included with each copy of the Program. 3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability ("notices") contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. 4. COMMERCIAL DISTRIBUTION Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 5. NO WARRANTY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. Exhibit A - Form of Secondary Licenses Notice "This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}." Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership.jgrapht-jgrapht-1.5.1/license-LGPL.txt000066400000000000000000000635061402514743400175470ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! jgrapht-jgrapht-1.5.1/pom.xml000066400000000000000000000420061402514743400161350ustar00rootroot00000000000000 4.0.0 org.jgrapht jgrapht pom JGraphT - Parent 1.5.1 A Java class library for graph-theory data structures and algorithms. http://www.jgrapht.org org.sonatype.oss oss-parent 7 GNU Lesser General Public License Version 2.1, February 1999 http://jgrapht.sourceforge.net/LGPL.html repo Eclipse Public License (EPL) 2.0 http://www.eclipse.org/legal/epl-v20.html repo https://github.com/jgrapht/jgrapht.git scm:git:git://github.com/jgrapht/jgrapht.git scm:git:git@github.com:jgrapht/jgrapht.git jgrapht-1.5.1 https://github.com/jgrapht/jgrapht/issues jgrapht-users http://lists.sourceforge.net/lists/listinfo/jgrapht-users jgrapht-announce http://lists.sourceforge.net/lists/listinfo/jgrapht-announce jsichi John V. Sichi jsichi@gmail.com jkinable Joris Kinable j.kinable@gmail.com d-michail Dimitrios Michail dimitrios.michail@gmail.com UTF-8 ${project.basedir} ${project.groupId} jgrapht-core ${project.version} ${project.groupId} jgrapht-io ${project.version} ${project.groupId} jgrapht-ext ${project.version} ${project.groupId} jgrapht-guava ${project.version} ${project.groupId} jgrapht-opt ${project.version} ${project.groupId} jgrapht-unimi-dsi ${project.version} ${project.groupId} jgrapht-demo ${project.version} org.xmlunit xmlunit-core 2.7.0 test junit junit 4.13.1 test com.googlecode.junit-toolbox junit-toolbox 2.4 test org.apache.maven.plugins maven-compiler-plugin 3.8.1 11 --add-modules java.xml default-compile true -Xlint:all,-deprecation org.apache.maven.plugins maven-surefire-plugin 3.0.0-M5 false false maven-failsafe-plugin 3.0.0-M5 org.apache.maven.surefire surefire-junit47 3.0.0-M5 integration-test integration-test verify org.apache.maven.plugins maven-jar-plugin 3.2.0 org.apache.felix maven-bundle-plugin 5.1.1 *;-noimport:=true process-classes manifest org.apache.maven.plugins maven-assembly-plugin 3.3.0 org.apache.maven.plugins maven-shade-plugin 3.2.4 org.apache.maven.plugins maven-resources-plugin 3.1.0 org.apache.maven.plugins maven-release-plugin 3.0.0-M1 forked-path org.apache.maven.plugins maven-deploy-plugin 3.0.0-M1 org.apache.maven.plugins maven-source-plugin attach-sources jar-no-fork org.apache.maven.plugins maven-javadoc-plugin protected JGraphT : a free Java graph library --allow-script-in-comments none 11

    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML,https://jgrapht.org/mathjax/mathjaxConfig.js"></script> <img src="https://github.com/jgrapht/jgrapht/blob/master/etc/logo/jgrapht-logo-transparent-cropped-javadocheader.png?raw=true" style="height:55px;">
    ${project.name} ${project.version} API <img src="https://github.com/jgrapht/jgrapht/blob/master/etc/logo/jgrapht-logo-transparent-cropped.png?raw=true" style="height:100px;float:right" > false attach-javadocs jar org.eclipse.m2e lifecycle-mapping 1.0.0 org.apache.felix maven-bundle-plugin [3.0.1,) manifest org.apache.maven.plugins maven-javadoc-plugin 3.2.0 org.apache.maven.plugins maven-source-plugin 3.2.1 jgrapht-core jgrapht-io jgrapht-opt jgrapht-ext jgrapht-guava jgrapht-unimi-dsi jgrapht-demo jgrapht-dist checkstyle org.apache.maven.plugins maven-checkstyle-plugin 3.1.1 com.puppycrawl.tools checkstyle 8.33 checkstyle validate check etc/jgrapht_checks.xml false ${project.basedir} src/main/**/*.java,src/test/**/*.java,**/*.xml **/target/** true release-sign-artifacts performRelease true org.apache.maven.plugins maven-gpg-plugin 1.6 sign-artifacts verify sign